Mybatis官网中,有这么一节专门介绍如何注入一个mapper
对于单个mapper,有两种方式可以注入,分别是xml和注解
其中,xml这种方式耐人寻味。
<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.java
/** * 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.java
@Override public <T> T getMapper(Class<T> type) { return configuration.getMapper(type, this); } 复制代码
Configuration.java
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } 复制代码
MapperProxyFactory.java
@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接口就可以了? 先来一个接口
public interface IHello { void sayHello(); } 复制代码
被代理对象
public class RealSubject implements IHello { @Override public void sayHello() { System.out.println("我是被逼说hello的"); } } 复制代码
InvocationHandler增强日志
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方法!我不管,我要为自己的牛逼点个赞:+1:
好了,不要脸半天了,收! 我们现在可以通过一个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 { } 复制代码
大功告成,撒花!