欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring源码深度解析(四)——模拟mybatis和原理分析

程序员文章站 2024-02-27 12:10:27
...

说到Mybatis就顺理成章的想起了@Mapper,@MapperScan,@Select,@Update等等注解,下面就来模拟一下它的过程。
用到的知识点:自定义注解,FactoryBean,Spring的构造方法装配,ImportBeanDefinitionRegistrar,@Import,JDK动态代理

需要去模拟mybatis需要想清楚的是目标是什么?

  1. 目标就是将抽象接口进行注入值,也就是将我们实现的daoImpl注入到dao
  2. 将@Select,@Update中的东西拿出来
    第一个目标怎么去实现,很明显这是需要代理的,因为dao和daoImpl根本就不是同一个类,因此我们就顺理成章的想到了使用FactoryBean构造一个自定义返回的Bean,然后里面通过动态代理完成对daoImol的改变(改变其内部的BeanDefinition)
    第二个目标很简单,那就是得到类,然后得到类里方法,并对方法上面的注解的值进行解析

下面就来具体的实现一下
创建IndexDao和IndexDaoImpl

package mybatis.dao;
/*
 * @Author  Wrial
 * @Date Created in 14:39 2020/2/25
 * @Description
 */

import mybatis.anno.Select;

import java.util.List;
import java.util.Map;

public interface IndexDao {

	@Select("select * from student")
	List<Map<Integer,String>> list(String string);
}

package mybatis.dao;
/*
 * @Author  Wrial
 * @Date Created in 20:01 2020/2/25
 * @Description
 */

import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;

@Component
public class IndexDaoImpl implements IndexDao {

	@Override
	public List<Map<Integer, String>> list(String string) {
		System.out.println("IndexDaoImpl");
		return null;
	}
}

自定义@Select注解

package mybatis.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/*
 * @Author  Wrial
 * @Date Created in 20:17 2020/2/25
 * @Description
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
	String value();
}

创建IndexService,并@AutowireIndexDao

package mybatis.config;
/*
 * @Author  Wrial
 * @Date Created in 14:45 2020/2/25
 * @Description
 */

import mybatis.anno.Select;
import mybatis.dao.IndexDao;
import org.springframework.beans.factory.FactoryBean;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


public class MyFactoryBean implements FactoryBean<Object>, InvocationHandler {

	// 保证通用性,根据传入的class(接口)进行代理
	Class<?> clazz;
	// 提供一个构造方法,会在MyImportDefinitionRegistrar进行构造填充
	public MyFactoryBean(Class<?> clazz){
		this.clazz = clazz;
	}

	@Override
	public Object getObject(){

		Class<?>[] classes = new Class<?>[]{clazz};
		IndexDao object = (IndexDao)Proxy.newProxyInstance(clazz.getClassLoader(),classes,this);
		// 产生的是一个代理对象(经过ImportDefinitionRegistrar对BD处理过)
		return object;
	}

	@Override
	public Class<?> getObjectType() {
		return clazz;
	}

	/**
	 * 动态代理需要实现的方法
	 * @param proxy
	 * @param method
	 * @param args
	 * @return
	 * @throws Throwable
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("Proxy————————————————————————————————————————");
		// 拿到方法
		Method method1 = proxy.getClass().getInterfaces()[0].getMethod(method.getName(), String.class);
		// 拿出我们自定义的Select注解并打印
		Select select = method1.getDeclaredAnnotation(Select.class);
		System.out.println(select.value());
		return null;
	}
}

这个是我们流程实现的核心步骤,它可以对BeanDefinition进行修改,也可以给它的构造方法中添加值,这块下面会结合构造方法构造Bean的源码中解疑惑

package mybatis.config;
/*
 * @Author  Wrial
 * @Date Created in 14:57 2020/2/25
 * @Description
 */

import lifecycle.Test;
import mybatis.dao.IndexDao;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;



public class MyImportDefinitionRegistrar implements ImportBeanDefinitionRegistrar {


	//修改FactoryBean的定义
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

		BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(IndexDao.class);
		GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionBuilder.getBeanDefinition();
		System.out.println(beanDefinition.getBeanClassName());
		//传入beanDefinition.getBeanClassName()
		// 给构造方法中添加一个参数
		beanDefinition.getConstructorArgumentValues().addGenericArgumentValue("mybatis.dao.IndexDao");
		beanDefinition.setBeanClass(MyFactoryBean.class);
		registry.registerBeanDefinition("indexDao",beanDefinition);

	}
}

接下来是编写配置类

package mybatis.config;
/*
 * @Author  Wrial
 * @Date Created in 14:42 2020/2/25
 * @Description
 */

import mybatis.anno.MyMapperScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@ComponentScan("mybatis")
//可以将下面放在注解里
//@Import(MyImportDefinitionRegistrar.class)
@MyMapperScan
public class MyConfig {
}

自定义MapperScan注解

package mybatis.anno;
/*
 * @Author  Wrial
 * @Date Created in 14:34 2020/2/25
 * @Description 自定义的MapperScan
 */

import mybatis.config.MyImportDefinitionRegistrar;
import org.springframework.context.annotation.Import;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyImportDefinitionRegistrar.class)
public @interface MyMapperScan {
}

编写测试类



package mybatis.test;
/*
 * @Author  Wrial
 * @Date Created in 14:32 2020/2/25
 * @Description  模拟Mybatis
 */

import mybatis.config.MyConfig;
import mybatis.dao.IndexDao;
import mybatis.dao.IndexDaoImpl;
import mybatis.service.IndexService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {
	public static void main(String[] args) {
		// 加载自定义的配置类
		AnnotationConfigApplicationContext applicationContext
				= new AnnotationConfigApplicationContext(MyConfig.class);
	/*	IndexDao dao = (IndexDao) applicationContext.getBean("indexDao");
		dao.list();*/
	applicationContext.getBean(IndexService.class).printIndexDao();
	}
}

结果展示:
Spring源码深度解析(四)——模拟mybatis和原理分析
这样很简单的就实现了Mybatis的@MapperScan和抽象类型的动态注入和值的解析!
下面就结合Spring源码来说一说添加的这个ConstructorValue有什么用,怎么去用。
简单的先说一下流程,Spring在构造Bean的时候回将无参的和有参数的分开,在有参数的会对参数进行解析,然后是值的解析,如果内部有@Resource,@Autowire就会通过循环CommonBeanPostProcessor和InstantiationAwareBeanPostProcessor后置处理器进行处理。

下面先从这段源码开始看起

// 为Bean创建实例
	protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
		// Make sure bean class is actually resolved at this point.
		Class<?> beanClass = resolveBeanClass(mbd, beanName);

		// 检查这个类的访问权限  方法是不是public
		if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
		}

		Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
		if (instanceSupplier != null) {
			return obtainFromSupplier(instanceSupplier, beanName);
		}

		// factoryMethod 可以在xml中配置某个自定义方法,注册的Bean就是方法返回的Bean,而不是当前被factoryMethod修饰的Bean
		// 如果是FactoryMethod 就讲那个方法的返回值放入map
		// 比如在@Bean修饰的方法是静态方法的时候,就会给设置factoryMethod为当前方法  就相当于给Bean配置了一个FactoryMethod
		// 如果@Bean修飾的方法不是静态的,那就是uniqueFactoryMethod,是唯一的(不会每次都去new),static不是唯一的
		// 为什么bean不直接放进去?很显然如果直接放那就没有@Bean这一说法了
		if (mbd.getFactoryMethodName() != null) {

			return instantiateUsingFactoryMethod(beanName, mbd, args);
		}

		// Shortcut(快捷方式) when re-creating the same bean...
		// 当重新创建相同的Bean的时候就不用去做一系列的推断(不用推断去用那种方式去构造这个Bean)
		// 比如多次构造一个原型对象bean就可通过这个shortcut   此处resolved和autowireNecessary会在第一次进行设置
		boolean resolved = false;
		boolean autowireNecessary = false;
		if (args == null) {
			synchronized (mbd.constructorArgumentLock) {
				// 如果是通过构造方法/factoryMethod进行解析的 那就对上面的变量进行设置
				if (mbd.resolvedConstructorOrFactoryMethod != null) {
					resolved = true;
					autowireNecessary = mbd.constructorArgumentsResolved;
				}
			}
		}
		// 如果已经被解析
		if (resolved) {
			//如果是必须自动装配的
			if (autowireNecessary) {
				// 就返回自动装配的BeanWrapper(通过构造方法)
				return autowireConstructor(beanName, mbd, null, null);
			}
			else {
				// 否则就此方法进行初始化
				return instantiateBean(beanName, mbd);
			}
		}

		// Candidate constructors for autowiring?
		// 由后置处理器决定返回那些构造方法(如果是无参构造方法就为null)
		// 默认调用的无参构造方法     也就是说@Component一个类,有两个构造方法
		// 一个是有参的一个是无参的,在扫描实例化后会默认调用无参的
		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
		// 如果是有参构造  或者是自动装配模式是CONSTRUCTOR(默认是no但是采用的是byType的技术,一共有四种)
		// Spring自动装配模型!=自动装配的技术  no==byType的技术
		// 也就是@Autowire 默认mode是no 但是使用byType进行自动装配
		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
			return autowireConstructor(beanName, mbd, ctors, args);
		}

		// Preferred constructors for default construction?
		ctors = mbd.getPreferredConstructors();
		if (ctors != null) {
			return autowireConstructor(beanName, mbd, ctors, null);
		}

		// No special handling: simply use no-arg constructor.
		// 无参构造也就是普通处理类型
		return instantiateBean(beanName, mbd);
	}


进入autowireConstructor(beanName, mbd, ctors, args)方法


	// 构造方法自动装配  explicitArgs:用过getBean以编程方式传入的参数  其实就是我们设置的那个值
	// Class是无法进行自动装配的,必须是对象
	protected BeanWrapper autowireConstructor(
			String beanName, RootBeanDefinition mbd, @Nullable Constructor<?>[] ctors, @Nullable Object[] explicitArgs) {

		return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs);
	}

进入new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs)

// 使用构造方法自动装配返回一个BeanWrapper
	public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
										   @Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {

		// 实例化一个BeanWrapperImpl  实现BeanWrapper
		BeanWrapperImpl bw = new BeanWrapperImpl();
		this.beanFactory.initBeanWrapper(bw);

		// 用到的构造方法
		Constructor<?> constructorToUse = null;
		// 需要的参数
		ArgumentsHolder argsHolderToUse = null;
		// argsToUse有两种办法进行设置   一种通过BeanDefinition  另一种就是XML
		Object[] argsToUse = null;

		if (explicitArgs != null) {
			argsToUse = explicitArgs;
		} else {
			Object[] argsToResolve = null;
			synchronized (mbd.constructorArgumentLock) {
				// 获取已经解析的构造方法或者是factoryMethod  下次就不需要去解析了,直接用哪个值就行了
				// 一般情况不会有,除非是多个构造方法的
				constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;
				if (constructorToUse != null && mbd.constructorArgumentsResolved) {
					// Found a cached constructor...
					// 如果有缓存到的构造器    然后拿到缓存的参数
					argsToUse = mbd.resolvedConstructorArguments;
					// 如果没有,那就preparedConstructorArguments
					if (argsToUse == null) {
						argsToResolve = mbd.preparedConstructorArguments;
					}
				}
			}
			// 如果argsToResolve等于空说明有缓存就不用执行此处  不为空然后在这进行解析参数
			if (argsToResolve != null) {
				argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve, true);
			}
		}

		// 如果constructorToUse或argsToUse为空 说明没有缓存  下面就进行解析构造方法和参数
		if (constructorToUse == null || argsToUse == null) {
			// Take specified constructors, if any.

			// chosenCtors就是在判断是有参的时候传进来的
			Constructor<?>[] candidates = chosenCtors;
			// 构造方法不为空的话(一般都不为空,不然不会进入这里代码)
			if (candidates == null) {
				Class<?> beanClass = mbd.getBeanClass();
				try {
					// 判断是否允许非public方法构造,如果允许就拿所有,不允许就拿公有
					candidates = (mbd.isNonPublicAccessAllowed() ?
							beanClass.getDeclaredConstructors() : beanClass.getConstructors());
				} catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Resolution of declared constructors on bean Class [" + beanClass.getName() +
									"] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);
				}
			}

			// 针对于一个构造器,explicitArgs(通过getBean以编程方式传入的参数)为null并且在构造器判断参数是否被定义(是否有用的参数)
			if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
				Constructor<?> uniqueCandidate = candidates[0];
				if (uniqueCandidate.getParameterCount() == 0) {
					synchronized (mbd.constructorArgumentLock) {
						mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate;
						mbd.constructorArgumentsResolved = true;
						mbd.resolvedConstructorArguments = EMPTY_ARGS;
					}
					bw.setBeanInstance(instantiate(beanName, mbd, uniqueCandidate, EMPTY_ARGS));
					return bw;
				}
			}

			// Need to resolve the constructor.
			// 判断构造方法是否为null 或AutowireMode是否为构造方法自动装配 满足其一就为true
			boolean autowiring = (chosenCtors != null ||
					mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
			ConstructorArgumentValues resolvedValues = null;

			//最小参数个数
			int minNrOfArgs;
			//通过getBean传进的参数(就说名是具体的,参数个数固定)的个数赋值给minNrOfAges
			if (explicitArgs != null) {
				minNrOfArgs = explicitArgs.length;
			} else {
				// ConstructorArgumentValues是一个存储构造方法值的一个数据结构
				// 先去拿已有的
				// 比如:在模仿mybatis的时候回在bdm中setConstructorArgumentValues(“xxxx”)
				// 就是这个时候取出来的
				ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
				// 在new一个新的  最后应该会被合并
				resolvedValues = new ConstructorArgumentValues();

				// 确定构造方法参数的数量并返回  就是上面至少需要setConstructorArgumentValues的个数
				minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
			}

			// 对构造方法进行排序
			/**
			 * 排序的序列如下 (按照访问权限+参数个数)
			 * public 最多参数的
			 * public  依次到一个参数的
			 * protected 多个参数的
			 * protected 依次到一个参数的
			 */
			AutowireUtils.sortConstructors(candidates);
			// 最小类型的差异权重  差异变量 很重要
			int minTypeDiffWeight = Integer.MAX_VALUE;
			// 存有歧义的构造方法
			Set<Constructor<?>> ambiguousConstructors = null;
			LinkedList<UnsatisfiedDependencyException> causes = null;

			for (Constructor<?> candidate : candidates) {
				Class<?>[] paramTypes = candidate.getParameterTypes();

				// 如果参数个数
				if (constructorToUse != null && argsToUse != null && argsToUse.length > paramTypes.length) {
					// Already found greedy constructor that can be satisfied ->
					// do not look any further, there are only less greedy constructors left.
					break;
				}
				// 如果参数列表的长度小于最小参数长度(那就直接跳过当前这个Constructor<?> candidate)
				if (paramTypes.length < minNrOfArgs) {
					continue;
				}

				ArgumentsHolder argsHolder;
				if (resolvedValues != null) {
					try {
						// 判断是否加了ConstructorProperties注解 如果加了就把值取出来
						// 如@ConstructorProperties(value={“xxx”,“xxxx”})
						String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, paramTypes.length);
						if (paramNames == null) {
							ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
							if (pnd != null) {
								// 获取此构造方法的参数列表
								paramNames = pnd.getParameterNames(candidate);
							}
						}
						argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
								getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
					} catch (UnsatisfiedDependencyException ex) {
						if (logger.isTraceEnabled()) {
							logger.trace("Ignoring constructor [" + candidate + "] of bean '" + beanName + "': " + ex);
						}
						// Swallow and try next constructor.
						if (causes == null) {
							causes = new LinkedList<>();
						}
						causes.add(ex);
						continue;
					}
				} else {
					// Explicit arguments given -> arguments length must match exactly.
					if (paramTypes.length != explicitArgs.length) {
						continue;
					}
					argsHolder = new ArgumentsHolder(explicitArgs);
				}

				int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
						argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
				// Choose this constructor if it represents the closest match.
				// 根据差异值来选择最匹配的构造方法
				/*
				第一遍一定会进,因为minTypeDiffWeight初始化为最大值
				 */
				if (typeDiffWeight < minTypeDiffWeight) {
					constructorToUse = candidate;
					argsHolderToUse = argsHolder;
					argsToUse = argsHolder.arguments;
					minTypeDiffWeight = typeDiffWeight;
					ambiguousConstructors = null;
				} else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
					if (ambiguousConstructors == null) {
						ambiguousConstructors = new LinkedHashSet<>();
						ambiguousConstructors.add(constructorToUse);
					}
					ambiguousConstructors.add(candidate);
				}
			}

			if (constructorToUse == null) {
				if (causes != null) {
					UnsatisfiedDependencyException ex = causes.removeLast();
					for (Exception cause : causes) {
						this.beanFactory.onSuppressedException(cause);
					}
					throw ex;
				}
				throw new BeanCreationException(mbd.getResourceDescription(), beanName,
						"Could not resolve matching constructor " +
								"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)");
			// 如果差异值相同,就是有歧义的构造方法,就抛出异常
			// 为什么不在上面循环中找到差异值相同就直接抛出异常呢?因为还不能确定它就是最优的选择,不过不是最优选择,就算相同(重复)也没有关系
			} else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) {
				throw new BeanCreationException(mbd.getResourceDescription(), beanName,
						"Ambiguous constructor matches found in bean '" + beanName + "' " +
								"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " +
								ambiguousConstructors);
			}

			if (explicitArgs == null && argsHolderToUse != null) {
				// 将构造方法,参数,是否被解析的标志缓存起来
				argsHolderToUse.storeCache(mbd, constructorToUse);
			}
		}

		Assert.state(argsToUse != null, "Unresolved constructor arguments");
		bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
		return bw;
	}

打开调试
Spring源码深度解析(四)——模拟mybatis和原理分析
可以定位到indexDao,可以发现它的构造方法是我们FactoryBean的构造方法,并且是一个有参的构造方法
可以看到执行到这个位置的时候会发现它会进行构造方法解析,并且可以看到类型是IndexDao
Spring源码深度解析(四)——模拟mybatis和原理分析
至此,我们就很清晰它是怎么讲我们添加的构造方法添加进去的,然后是怎么实例化Bean的

实例化Bean是一个很复杂的事情,会在另外一篇博客中细说!

相关标签: Spring5源码分析