Writing in the front

Mybatis: How to inject a Mapper

Of these, XML is interesting.

<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>
Copy the code

Let’s write a small framework today to implement mapper injection.

A dynamic proxy

As we all know, Mybatis implements the dynamic proxy to convert the interface interface into a concrete class to execute the corresponding Mapper. How do you do that? 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);
Copy the code

Track down defaultsQLSession.java

  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }
Copy the code

Configuration.java

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
Copy the code

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);
  }
Copy the code

Mybatis is using the JDK dynamic proxy implementation of mapper proxy. What about dynamic proxies in the JDK? MapperProxy implements the InvocationHandler interface and implements the Invoke method. We will explore why JDK dynamic proxy, implementation of the InvocationHandler interface is ok? Let’s start with an interface

public interface IHello {
    void sayHello();
}
Copy the code

Proxied object

public class RealSubject implements IHello {
    @Override
    public void sayHello() {
        System.out.println("I was forced to say hello."); }}Copy the code

InvocationHandler Enhanced logs

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("Pre-log");
        Object res = method.invoke(target, args);
        System.out.println("Post log");
        returnres; }}Copy the code

Let’s do a run in 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();
    }
Copy the code

Get the output

Pre-log The post-log where I was forced to say HelloCopy the code

Proxy.newProxyInstance can dynamically obtain a Proxy object. When the Proxy object calls a method in the interface, it enters the Invoke method in the InvokationHandler. Our implementation prints the pre-log first, then calls the method of the proxied object, and finally outputs the post-log. How does it all work? Here we need to use ProxyGenerator to get a glimpse of the agent

    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);
    }
Copy the code

This Proxy object inherits from Proxy and implements the IHello interface. Since Java is single-inherited and already inherits from Proxy, the JDK’s dynamic Proxy is based on the interface

 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()); }}}Copy the code

We saw the sayHell() method, and it looked familiar! This h is protected InvocationHandler H in the superclass Proxy; . So how does this H get passed in? High energy ahead! High energy ahead! High energy ahead!

@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 Voidrun() {
                        cons.setAccessible(true);
                        returnnull; }}); }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); }}Copy the code

Here you get the constructor for the proxy class, and the constructor takes the parameter constructorParams, what are the constructorParams? private static final Class
[] constructorParams = { InvocationHandler.class }; We can find this constructor in IHello$Proxy:

    public IHello$Proxy(InvocationHandler var1) throws  {
        super(var1);
    }
Copy the code

So this super(var1) is going to be

    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
Copy the code

Yes or no! Have you!! Excited or not!! Thus, when we invoke the corresponding method with the Proxy object obtained by proxy.newinstance (), we run to the invoke method of our own InvocationHandler implementation class! I don’t care, I want to point a praise for their cow force 👍

With Spring integration

All right, shame on you. Take it! We can now create a proxy object through a UserMapper interface, but how do we get this object to Spring for hosting? Note that there is a fundamental difference between providing Spring with a class to manage an object that has already been created. Spring will first use BeanDefinition to store information about the Bean, including the scope of the Bean, class information, lazy loading, etc. When the BeanDefinition object is created, it will first store a map. Why doesn’t Spring just go new? Direct new may not meet the conditions, such as the interface we provide; In addition, direct new provides less room for the programmer to play freely. Here, we’re going to export one of Spring’s post-processors

BeanFactoryPostProcessor

By implementing the BeanFactoryPostProcessor interface, we can process the BeanDefinition. Assuming we have two classes, A and B, we get the BeanDefinition of A at the postProcessBeanFactory, set its BeanClass to B, and get A from the ApplicationContext

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		GenericBeanDefinition a = (GenericBeanDefinition) beanFactory.getBeanDefinition("a"); a.setBeanClass(B.class); }}Copy the code

Spring tells us without mercy

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)
Copy the code

We can use this post-processor to handle our dynamic proxy class, but it still doesn’t seem enough

FactoryBean and ImportBeanDefinitionRegistrar

The limitation of BeanFactoryPostProcessor is that we can only take a BeanDefinition from the BeanFactory and modify its properties, not add a new Beand to it, so we need to find something new.

There are three common ways to hand over new objects to Spring for hosting:

  1. BeanFactory.registerSingleton(String beanName, Object singletonObject);
  2. Create your own new object in the method, based on @bean
  3. FactoryBean!

Let’s do the third way here

public class MyFactoryBean implements FactoryBean {
	@Override
	public Object getObject() throws Exception {
		returnProxy.newProxyInstance(CityMapper.class.getClassLoader(), new Class[]{CityMapper.class}, new MyInvocationHandler()); } @Override public Class<? >getObjectType() {
		returnCityMapper.class; }}Copy the code

If mybatis wants to provide a framework, which is not extensible, then we can inject class as a parameter

public class MyFactoryBean implements FactoryBean {
	private Class mapperInterface;

	public void setMapperInterface(Class mapperInterface) {
		this.mapperInterface = mapperInterface;
	}

	@Override
	public Object getObject() throws Exception {
		returnProxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, new MyInvocationHandler()); } @Override public Class<? >getObjectType() {
		returnmapperInterface; }}Copy the code

At this point we look back at the mybatis official website of the XML configuration, can not help but aha! Just replace MyFactoryBean with 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>
Copy the code

So how do we hand this MyFactoryBean to Spring?

Then we also need to write a class, realize ImportBeanDefinitionRegistrar, this interface can help us put a BeanDefinition in the Spring of the 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); }}Copy the code

This class is introduced with an annotation, which uses the @import annotation

@Retention(RetentionPolicy.RUNTIME)
@Import(MyBeanDefinitionRegistrar.class)
public @interface MyScan {
}
Copy the code

Put this annotation in

@ComponentScan("your.package")
@Configuration
@MyScan
public class AppConfig {
}
Copy the code

You’re done! Scatter the flowers!

Reference documentation

Mybatis.org/spring/mapp…