Mybatis官网中,有这么一节专门介绍如何注入一个mapperhtml
其中,xml这种方式回味无穷。java
<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>
复制代码
不妨咱们今天就手写一个小框架,来实现mapper注入的功能。程序员
咱们都知道,mybatis经过动态代理来实现将interface接口转为具体的类,来执行相应的mapper。具体是怎样作的呢? SqlSession.javaspring
/**
* Retrieves a mapper.
* @param <T> the mapper type
* @param type Mapper interface class
* @return a mapper bound to this SqlSession
*/
<T> T getMapper(Class<T> type);
复制代码
一路追查下去 DefaultSqlSession.javasql
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
复制代码
Configuration.javabash
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
复制代码
MapperProxyFactory.javamybatis
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
复制代码
这时咱们能够看到真身了,mybatis正是使用了JDK的动态代理实现了对mapper的代理。那JDK的动态代理是怎么回事呢? MapperProxy实现了InvocationHandler接口,须要实现invoke方法。这里咱们来探究一下,为何JDK的动态代理,实现InvocationHandler接口就能够了? 先来一个接口app
public interface IHello {
void sayHello();
}
复制代码
被代理对象框架
public class RealSubject implements IHello {
@Override
public void sayHello() {
System.out.println("我是被逼说hello的");
}
}
复制代码
InvocationHandler加强日志dom
public class MyHandler implements InvocationHandler {
private Object target;
public MyHandler(Object subject) {
this.target = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置日志");
Object res = method.invoke(target, args);
System.out.println("后置日志");
return res;
}
}
复制代码
在client中跑一下
public static void main(String[] args) throws IOException {
RealSubject realSubject = new RealSubject();
MyHandler myHandler = new MyHandler(realSubject);
IHello iHello = (IHello) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), new Class[]{IHello.class}, myHandler);
iHello.sayHello();
}
复制代码
获得输出
前置日志
我是被逼说hello的
后置日志
复制代码
Proxy.newProxyInstance能够动态地获取一个代理对象,代理对象调用接口中的方法时,会进入InvokationHandler里的invoke方法,而咱们的实现是,先打印前置日志,再调用被代理对象的方法,最后输出后置日志。这一切是怎么运转起来的呢? 这里咱们须要借助ProxyGenerator,来一窥代理的真容
private static void createProxyClass() throws IOException {
byte[] proxyBytes = ProxyGenerator.generateProxyClass("IHello$Proxy", new Class[]{IHello.class});
Files.write(new File("YOUR_PATH/IHello$Proxy.class").toPath(), proxyBytes);
}
复制代码
这时,咱们能够获得Proxy.class,这个代理对象继承了Proxy并实现了IHello接口,因为Java是单继承且已经继承自Proxy,因此JDK的动态代理是基于接口的
public final class IHello$Proxy extends Proxy implements IHello {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public IHello$Proxy(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void sayHello() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("your.package.IHello").getMethod("sayHello");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
复制代码
咱们看到sayHell()方法,好眼熟啊!这个h是父类Proxy中的protected InvocationHandler h;
。那这个h是怎么传进来的呢?前方高能!前方高能!前方高能!
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
复制代码
这里会拿到代理类的构造方法,这个构造方法的参数是constructorParams,这个constructorParams是什么呢?private static final Class<?>[] constructorParams = { InvocationHandler.class };
那咱们就能够从IHello$Proxy
中找到这个构造函数:
public IHello$Proxy(InvocationHandler var1) throws {
super(var1);
}
复制代码
这个super(var1)就是
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
复制代码
对上了有木有!有木有!!激动不激动!!! 这样,当咱们使用Proxy.newInstance()获取到的代理对象调用相应方法时,就会跑到咱们本身写的InvocationHandler实现类里invoke方法!我无论,我要为本身的牛逼点个赞👍
好了,不要脸半天了,收! 咱们如今能够经过一个UserMapper的接口来建立一个代理对象了,可是咱们怎么把这个对象交给Spring托管呢? 注意,咱们这边是已经new好了对象了,而后交给Spring去管理,而不是提供给Spring一个类,让Spring帮咱们new一个对象是管理,这仍是有本质区别的! Spring会先使用BeanDefinition来保存Bean的信息,其中包含bean的scope、class相关信息、是否懒加载等等内容,当BeanDefinition对象建立好后,会先存入一个map。 为何Spring不直接new呢? 直接new的话可能不符合条件,好比咱们提供的interface;另外,直接new的话,提供给程序员可自由发挥的空间就小了。 这里,咱们要导出Spring的一个后置处理器了
实现BeanFactoryPostProcessor接口,咱们就能够对BeanDefinition进行处理了。 假设有A、B两个类,咱们在postProcessBeanFactory获取到A这个BeanDefinition,而后将其BeanClass设置为B,而后从ApplicationContext中获取A
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
GenericBeanDefinition a = (GenericBeanDefinition) beanFactory.getBeanDefinition("a");
a.setBeanClass(B.class);
}
}
复制代码
Spring会绝不留情地告诉咱们
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'your.package.domain.A' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:350)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:341)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1123)
at com.meituan.Starter.main(Starter.java:22)
复制代码
咱们能够经过这个后置处理器来处理咱们的动态代理类,能够彷佛仍是不够
BeanFactoryPostProcessor的局限是咱们只能从BeanFactory中获取一个BeanDefinition,而后修改它的属性,而不能直接往里面添加一个新的Beand,这时咱们要再挖掘些新玩意儿。
将new出来的对象交给Spring托管有三种经常使用的方法:
这里咱们用第三种方法
public class MyFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
return Proxy.newProxyInstance(CityMapper.class.getClassLoader(), new Class[]{CityMapper.class}, new MyInvocationHandler());
}
@Override
public Class<?> getObjectType() {
return CityMapper.class;
}
}
复制代码
这样能够解决一个mapper的注入问题,能够若是mybatis要提供一个框架,这样写可扩展性很差,这时咱们能够把class做为一个参数注入进来
public class MyFactoryBean implements FactoryBean {
private Class mapperInterface;
public void setMapperInterface(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public Object getObject() throws Exception {
return Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, new MyInvocationHandler());
}
@Override
public Class<?> getObjectType() {
return mapperInterface;
}
}
复制代码
这时咱们回看mybatis官网的那个xml配置,不由恍然大悟!把这里的MyFactoryBean替换成MapperFactoryBean不就是了嘛!
<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>
复制代码
那咱们如何将这个MyFactoryBean交给Spring呢?
这时咱们还须要写一个类,实现ImportBeanDefinitionRegistrar,这个接口能够帮咱们把一个BeanDefinition放入Spring的map
public class MyBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyFactoryBean.class);
GenericBeanDefinition beanDefinition = (GenericBeanDefinition) builder.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue("your.package.dao.CityMapper");
registry.registerBeanDefinition("xxx", beanDefinition);
}
}
复制代码
这个类咱们经过一个注解引入,这时会用到@Import注解
@Retention(RetentionPolicy.RUNTIME)
@Import(MyBeanDefinitionRegistrar.class)
public @interface MyScan {
}
复制代码
将这个注解放到
@ComponentScan("your.package")
@Configuration
@MyScan
public class AppConfig {
}
复制代码
大功告成,撒花!