mybatis-Spring ClassPathMapperScanner源码分析(自定义注解加载Bean)

时间:2022-08-21 05:14:17

摘要:

在使用mybatis时,可以通过mybatis-spring的MapperFactoryBean来配置一系列的DAO接口,MapperFactoryBean的getObject方法能返回DAO接口对应的动态代理类,实际是通过DefaultSqlSession以动态代理的方式生成DAO接口的实现类。而当DAO类很多时,可以通过mybatis-spring的MapperCannerConfigurer类配置要描述的包,来实现生成MapperFactoryBean对象,减少配置。

一、MapperFactoryBean 与MapperCannerConfigurer 配置回顾

MapperFactoryBean 配置要使用的DAO
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">  
<property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
通过上面的配置在 Service层中就可以直接注入 userMapper这个DAO,MapperFactoryBean内部会调用DefaultSqlSession以动态代理的方式生成DAO接口的实现类。

MapperCannerConfigurer配置多个要使用的DAO
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
<property name="basePackage" value="org.mybatis.spring.sample.mapper" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

通过上面的配置在包org.mybaits.spring.sample.mapper中下面对应的DAO接口,便能生成对应的MapperFactoryBean对象,然后被Service层调用。因为在DefaultSqlSession
内部是以动态代理的方式生成DAO接口对应的实现类,这里DAO要声明为接口。 MapperCannerConfigurer是通过实现BeanDefinitionRegistryPostProcessor接口,完成对BeanDefinitionRegistry自定义与修改,postProcessBeanDefinitionRegistry方法中则调用了ClassPathMapperScanner类。该类通过扫描包来指定 要生成的Bean。。。

二、ClassPathMapperScanner代码分析

ClassPathMapperScanner类实现了ClassPathBeanDefinitionScanner并对registerFilters方法与doScan方法进行了重写。 registerFilters方法主要是根据annotationClass或者是markerInterface属性来指定只扫描指定包下面的类带有某个注解的接口或者是为某个接口的子接口的接口。
public void registerFilters() {
boolean acceptAllInterfaces = true;

// if specified, use the given annotation and / or marker interface
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}

// override AssignableTypeFilter to ignore matches on the actual marker interface
if (this.markerInterface != null) {
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
@Override
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}

if (acceptAllInterfaces) {
// default include filter that accepts all classes
addIncludeFilter(new TypeFilter() {
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
}

// exclude package-info.java
addExcludeFilter(new TypeFilter() {
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
}
});
}

doScan方法用来将包下面的接口转化为MapperFactoryBean的类。
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();

if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}

// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class);

definition.getPropertyValues().add("addToConfig", this.addToConfig);

boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}

if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}

if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}

return beanDefinitions;
}

三、通过实现ClassPathBeanDefinitionScanner类自下定义bean注解的扫描

1、定义自己的注解

这里的注解并没有被@Component注解,而通过ClassPathBeanDefinitionScanner 类实现扫描。
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomizeComponent {
String value() default "";
}

2、被注解的类

@CustomizeComponent
public class ScanClass1 {
public void print() {
System.out.println("scanClass1");
}
}

3、自定义的BeanScannerConfigurer类

BeanScannerConfigurer用于嵌入到Spring的加载过程的中,这里用到了BeanFactoryPostProcessor 和 ApplicationContextAware。Spring提供了一些的接口使程序可以嵌入Spring的加载过程。这个类中的继承ApplicationContextAware接口,Spring会读取ApplicationContextAware类型的的JavaBean,并调用setApplicationContext(ApplicationContext applicationContext)传入Spring的applicationContext。
同样继承BeanFactoryPostProcessor接口,Spring会在BeanFactory的相关处理完成后调用postProcessBeanFactory方法,进行定制的功能。
@Component
public static class BeanScannerConfigurer implements BeanFactoryPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
Scanner scanner = new Scanner((BeanDefinitionRegistry) beanFactory);
scanner.setResourceLoader(this.applicationContext);
scanner.scan("org.wcong.test.spring.scan");
}
}

4、ClassPathBeanDefinitionScanner实现类Scanner

Scanner继承的ClassPathBeanDefinitionScanner是Spring内置的Bean定义的扫描器。
includeFilter里定义了类的过滤器,newAnnotationTypeFilter(CustomizeComponent.class)表示只取被CustomizeComponent修饰的类。只扫描我们自已定义的注解。
doScan里扫面了包底下的读取到BeanDefinitionHolder,自定义GenericBeanDefinition相关功能。

public final static class Scanner extends ClassPathBeanDefinitionScanner {
public Scanner(BeanDefinitionRegistry registry) {
super(registry);
}
public void registerDefaultFilters() {
this.addIncludeFilter(new AnnotationTypeFilter(CustomizeComponent.class));
}
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
definition.getPropertyValues().add("innerClassName", definition.getBeanClassName());
definition.setBeanClass(FactoryBeanTest.class);
}
return beanDefinitions;
}
public boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return super.isCandidateComponent(beanDefinition) && beanDefinition.getMetadata()
.hasAnnotation(CustomizeComponent.class.getName());
}
}

5、得到我们Bean

FactoryBean是Spring中比较重要的一个类。它的描述如下

Interface to be implemented by objects used within a BeanFactory which are themselves factories. 
If a bean implements this interface, it is used as a factory for an object to expose, not directly as a bean* instance that will be exposed itself
普通的JavaBean是直接使用类的实例,但是如果一个Bean继承了这个借口,就可以通过getObject()方法来自定义实例的内容,在FactoryBeanTest的getObject()就通过代理了原始类的方法,自定义类的方法。
public static class FactoryBeanTest<T> implements InitializingBean, FactoryBean<T> {
private String innerClassName;
public void setInnerClassName(String innerClassName) {
this.innerClassName = innerClassName;
}
public T getObject() throws Exception {
Class innerClass = Class.forName(innerClassName);
if (innerClass.isInterface()) {
return (T) InterfaceProxy.newInstance(innerClass);
} else {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(innerClass);
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setCallback(new MethodInterceptorImpl());
return (T) enhancer.create();
}
}
public Class<?> getObjectType() {
try {
return Class.forName(innerClassName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
public boolean isSingleton() {
return true;
}
public void afterPropertiesSet() throws Exception {
}
}
public static class InterfaceProxy implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("ObjectProxy execute:" + method.getName());
return method.invoke(proxy, args);
}
public static <T> T newInstance(Class<T> innerInterface) {
ClassLoader classLoader = innerInterface.getClassLoader();
Class[] interfaces = new Class[] { innerInterface };
InterfaceProxy proxy = new InterfaceProxy();
return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
}
}
public static class MethodInterceptorImpl implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("MethodInterceptorImpl:" + method.getName());
return methodProxy.invokeSuper(o, objects);
}
}

6、main函数

@Configuration
public class CustomizeScanTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
annotationConfigApplicationContext.register(CustomizeScanTest.class);
annotationConfigApplicationContext.refresh();
ScanClass1 injectClass = annotationConfigApplicationContext.getBean(ScanClass1.class);
injectClass.print();
}
}
详解代码见https://github.com/wcong/learn-java/blob/master/src/main/java/org/wcong/test/spring/CustomizeScanTest.java