众所周知:mybatis整合spring,大家使用这个框架的时候都知道mapper层都是接口类型的,而接口类型是不会实现功能的,但是mybatis整合spring之后却能实现,证明确实存在一个实现方法,但我们看不见。实际上在整合的时候mybatis使用了代理把接口转换成了一个类并注入到了容器当中。
首先考虑一下有哪些方式:
1、我们要把一个接口使用代理成为一个类。那么就需要使用动态代理。
2、将类注册到容器当中,而且是手动注入进去的。那么我们需要获取到bd的注册器(BeanDefinitionRegistry)才能够去注册。
那么需要哪些扩展来获取到呢?
1、beanPostProsessor :这是bean的实例化过程用到的,此时的bd已经固定好了,没有办法去改变。
2、BeanFactoryPostProcessor:里面提供了一个beanFactory,由于没有注册器,不能添加bd。
3、BeanDefinitionRegistryPostProcessor:增加了一个接口。接口提供了bd注册器,可以动态的向bd中添加bean。
4、ImportSelector:这个是根据提供的字符来构建bd,如果使用这个接口,字符串到bd的过程无法参与,那么我们动态代理的类必须在项目当中,不然我们提供的字符就不能构建出bd。
5、ImportBeanDefinitionRegistrar:里面提供了注册器,可以动态的向bd中添加bean。
仔细思考就第三和第五种符合要求。我们以第5种为例(因为MapperScan用的这种)。
先建立一个mapper:
package test.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Select;
import test.dao.User;
public interface UserMapper {
@Select("select * from user")
List<User> getAll();
}
首先按照一贯的想法:
package test.util;
import java.lang.reflect.Method;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.Proxy;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import test.mapper.UserMapper;
public class MyImport implements ImportBeanDefinitionRegistrar,InvocationHandler {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//得到代理对象
UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] {UserMapper.class}, this);
System.out.println("userMapper="+userMapper.getAll());
//构建bd
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(userMapper.getClass());
//得到bd
GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionBuilder.getBeanDefinition();
//注册bd
registry.registerBeanDefinition("userMapper", beanDefinition);
}
@Override
public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
System.out.println("代理");
return null;
}
}
建立一个测试类:
package test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import test.mapper.UserMapper;
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Appconfig.class);
UserMapper bean = (UserMapper) ac.getBean("userMapper");
bean.getAll();
}
}
配置类:
package test;
import test.util.MyProxy;
@MyProxy
public class Appconfig {
}
我们看MyImport这个类。
先运行,结果如下:
14:17:13.885 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@311d617d
14:17:13.904 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
代理
userMapper=null
14:17:14.018 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
14:17:14.021 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
14:17:14.022 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
14:17:14.024 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
14:17:14.033 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appconfig'
14:17:14.039 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'userMapper'
14:17:14.044 [main] DEBUG org.springframework.core.LocalVariableTableParameterNameDiscoverer - Cannot find '.class' file for class [class org.springframework.cglib.proxy.Proxy$ProxyImpl$$EnhancerByCGLIB$$5ecfa6bd] - unable to determine constructor/method parameter names
14:17:14.054 [main] WARN org.springframework.context.annotation.AnnotationConfigApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userMapper': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.cglib.proxy.InvocationHandler' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userMapper': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.cglib.proxy.InvocationHandler' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:798)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:228)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1358)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1204)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:879)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:89)
at test.App.main(App.java:11)
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.cglib.proxy.InvocationHandler' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1695)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1253)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1207)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:885)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:789)
... 14 more
可以发现代理的对象无法注册到容器当中。这也很好理解,虽然我们将接口代理成了一个对象,但是它并不符合spring生产bean的流程,因此在注册的时候就不满足bean的条件,导致没有办法注册。
那么怎么解决呢?
我们现在需要的是一个完整的bean,需要经过spring的流程,这里自然而然就想到了FactoryBean了,因为它最大的特殊点在于它能够生产一个bean对象出来。
同时因为我们为了动态的构造,需要提供构造方法去手动传一个class进去。
改造后如下:
package test.util;
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;
import test.mapper.UserMapper;
public class MyImport implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//得到代理对象
//UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] {UserMapper.class}, this);
//System.out.println("userMapper="+userMapper.getAll());
//构建bd
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(UserMapper.class);
//得到bd
GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionBuilder.getBeanDefinition();
//设置构造方法
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClass());
//设置bd
beanDefinition.setBeanClass(MyFactoryBean.class);
//注册bd
registry.registerBeanDefinition("userMapper", beanDefinition);
}
}
package test.util;
import java.lang.reflect.Method;
import org.apache.ibatis.annotations.Select;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.Proxy;
import test.mapper.UserMapper;
public class MyFactoryBean implements FactoryBean,InvocationHandler{
Class clazz;
public MyFactoryBean(Class clazz) {
this.clazz=clazz;
}
@Override
public Object getObject() throws Exception {
System.out.println("getObject");
Object object = Proxy.newProxyInstance(this.getClass().getClassLoader(),
new Class[] {clazz}, this);
return object;
}
@Override
public Class getObjectType() {
return clazz;
}
@Override
public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
//arg0:代理对象
//arg1:对象上的方法
//arg2:参数
System.out.println("代理");
Method method = arg0.getClass().getInterfaces()[0].getMethod(arg1.getName());
Select select = method.getDeclaredAnnotation(Select.class);
System.out.println("select="+select.value()[0]);
return null;
}
}
最后我们模拟拿到注解的值。具体操作可以自行模拟了,因为到了这一步,我们已经把接口做成了对象,再想想以前做的项目,自动注入应该就能懂了。
BeanDefinitionRegistryPostProcessor也能实现,只要上面的做出来了这个就很简单了。这里就不写怎么实现出来的了。
结果展示: