Spring source code that interviewers love to ask: Advanced integration of Spring and Mybatis

Before introducing the principle of Spring integration of Mybatis, we must first introduce the working principle of Mybatis.

The basic working principle of Mybatis

In Mybatis, we can use an interface to define the SQL to be executed. The simplified code is as follows:
define an interface, @Select means to execute the query SQL statement.

public interface UserMapper {
  @Select("select * from user where id = #{id}")
  User selectById(Integer id);
}

The following is the execution sql code:

InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();

// 以下使我们需要关注的重点
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Integer id = 1;
User user = mapper.selectById(id);

The purpose of Mybatis is to enable programmers to execute a specified SQL by calling methods, and encapsulate the underlying logic of executing SQL.

Here we focus on the following mapper object. When the getMapper method of SqlSession is called, a proxy object will be generated for the incoming interface , and the proxy object is really used by the program. When the method of the proxy object is called, Mybatis will take it out The SQL statement corresponding to this method is then used to execute the SQL statement using JDBC, and finally the result is obtained.

Analyze the problem to be solved

In Spring and Mybatis, we should focus on this proxy object. Because the purpose of integration is to put the proxy object of a Mapper as a bean in the Spring container, so that the proxy object can be used like an ordinary bean, for example, it can be automatically injected by @Autowire.

For example, when Spring and Mybatis are integrated, we can use the following code to use the proxy object in Mybatis:

@Component
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public User getUserById(Integer id) {
        return userMapper.selectById(id);
    }
}

The userMapper attribute in UserService will be automatically injected as a proxy object in Mybatis. If you debug based on an integrated project, you can find that the type of userMapper is: org.apache.ibatis.binding.MapperProxy@41a0aa7d. The proof is indeed the proxy object in Mybatis.

Okay, then the problem we want to solve now is: how to put the proxy object of Mybatis as a bean into the Spring container?

To solve this, we need to have an understanding of Spring's bean generation process

Bean production process in Spring

During the Spring startup process, beans will be generated roughly through the following steps

  1. Scan the class files in the specified package path
  2. Generate the corresponding BeanDefinition according to the class information
  3. Here, programmers can use certain mechanisms to modify BeanDefinition
  4. Generate bean instance according to BeanDefinition
  5. Put the generated bean instance into the Spring container

Suppose there is a class A, suppose the following code:

A category A:

@Component
public class A {
}

A class B, there is no @Component annotation

public class B {
}

Execute the following code:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("a"));

The output result is: com.luban.util.A@6acdbdf5

The bean object type corresponding to class A is still class A. But this conclusion is uncertain, we can use the BeanFactory post processor to modify the BeanDefinition, we add a BeanFactory post processor:

@Component
public class LubanBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition("a");
        beanDefinition.setBeanClassName(B.class.getName());
    }
}

This will result in that the BeanDefiniton corresponding to the original class A has been modified and changed to the class B, so the type of the bean object that is normally generated subsequently is the class B. At this time, calling the following code will report an error:

context.getBean(A.class);

But calling the following code will not report an error, although there is no @Component annotation on the B class:

context.getBean(B.class);

And, the result returned by the following code is: com.luban.util.B@4b1c1ea0

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("a"));

The reason for talking about this problem is to illustrate a problem: in Spring, the bean object is not directly related to the class, but is directly related to the BeanDefinition.

So back to the problem we want to solve: how can the proxy object of Mybatis be put into the Spring container as a bean?

In Spring, if you want to generate a bean, you must first generate a BeanDefinition , just like you want a new object instance, you must first have a class.

Solve the problem

Continuing to return to our question, we now want to generate a bean by ourselves, so we must first generate a BeanDefinition, as long as there is a BeanDefinition, by setting the type of the bean object in the BeanDefinition, and then adding the BeanDefinition to Spring, Spring will automatically follow the BeanDefinition Help us generate a bean object corresponding to the type.

So, now we have to solve two problems:

  1. What is the type of Mybatis proxy object? Because we want to set it to BeanDefinition
  2. How do we add BeanDefinition to the Spring container?

Note: The BeanFactory post processor we used above can only modify the BeanDefinition, and cannot add a BeanDefinition. We should use Import technology to add a BeanDefinition. Later, if you use Import technology to add a BeanDefinition, you can take a look at the pseudo-code implementation ideas.

Assumption: We have a UserMapper interface, and his proxy object type is UserMapperProxy.
So our idea is like this, the pseudo code is as follows:

BeanDefinitoin bd = new BeanDefinitoin();
bd.setBeanClassName(UserMapperProxy.class.getName());
SpringContainer.addBd(bd);

However, there is a serious problem here, that is, the UserMapperProxy mentioned above is our assumption. It represents a type of proxy class. However, the proxy object in Mybatis is implemented using the dynamic proxy technology of the JDK, which is the proxy of the proxy object. The class is dynamically generated, and we simply cannot be sure what the proxy class of the proxy object is.

So back to our question: What is the type of Mybatis proxy object?

There could have been two answers:

  1. The proxy class corresponding to the proxy object
  2. The interface corresponding to the proxy object

Then answer 1 is equivalent to no, because the proxy class is dynamically generated, then we look at answer 2: the interface corresponding to the proxy object
If we adopt answer 2, then our thinking is:

BeanDefinition bd = new BeanDefinitoin();
// 注意这里,设置的是UserMapper
bd.setBeanClassName(UserMapper.class.getName());
SpringContainer.addBd(bd);

However, actually setting the type corresponding to the BeanDefinition as an interface does not work, because Spring has no way to create an instance of the corresponding type based on this BeanDefinition, and an interface cannot directly create an instance.

So now the problem is here, the problem I want to solve: What is the type of Mybatis proxy object?

Both answers were rejected by us, so this question is unsolvable, so we can no longer think along this line of thinking, but can only return to the original question: how to put the proxy object of Mybatis as a bean In the Spring container?

Summarizing the above reasoning: We want to set the class type of BeanDefinition, and then Spring will automatically help us to generate the corresponding bean, but this road is not feasible.

Ultimate solution

So is there any other way we can generate beans? And the logic of generating beans cannot be done for us by Spring , we have to do it ourselves.

FactoryBean

Yes, that is the FactoryBean in Spring. We can use FactoryBean to customize the bean object we want to generate, such as:

@Component
public class LubanFactoryBean implements FactoryBean {
    @Override
    public Object getObject() throws Exception {
        Object proxyInstance = Proxy.newProxyInstance(LubanFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (Object.class.equals(method.getDeclaringClass())) {
                    return method.invoke(this, args);
                } else {
                    // 执行代理逻辑
                    return null;
                }
            }
        });

        return proxyInstance;
    }

    @Override
    public Class<?> getObjectType() {
        return UserMapper.class;
    }
}

We define a LubanFactoryBean, which implements FactoryBean, and the getObject method is used to customize the logic of generating bean objects.

Execute the following code:

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        System.out.println("lubanFactoryBean: " + context.getBean("lubanFactoryBean"));
        System.out.println("&lubanFactoryBean: " + context.getBean("&lubanFactoryBean"));
        System.out.println("lubanFactoryBean-class: " + context.getBean("lubanFactoryBean").getClass());
    }
}

Will print:

lubanFactoryBean: com.luban.util.LubanFactoryBean$1@4d41cee
&lubanFactoryBean: com.luban.util.LubanFactoryBean@3712b94
lubanFactoryBean-class: class com.sun.proxy.$Proxy20

From the results, we can see that the bean object named "lubanFactoryBean" from the Spring container is the proxy object generated by our custom jdk dynamic proxy.

Therefore, we can add a custom bean object to the Spring container through FactoryBean. The LubanFactoryBean defined above corresponds to UserMapper, which means that we have defined a LubanFactoryBean, which is equivalent to putting the proxy object corresponding to UserMapper as a bean into the container.

But as programmers, it is impossible for us to define a LubanFactoryBean every time we define a Mapper. This is very troublesome. Let's transform the LubanFactoryBean to make it more versatile, such as:

@Component
public class LubanFactoryBean implements FactoryBean {

    // 注意这里
    private Class mapperInterface;
    public LubanFactoryBean(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    @Override
    public Object getObject() throws Exception {
        Object proxyInstance = Proxy.newProxyInstance(LubanFactoryBean.class.getClassLoader(), new Class[]{mapperInterface}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                if (Object.class.equals(method.getDeclaringClass())) {
                    return method.invoke(this, args);
                } else {
                    // 执行代理逻辑
                    return null;
                }
            }
        });

        return proxyInstance;
    }

    @Override
    public Class<?> getObjectType() {
        return mapperInterface;
    }
}

After transforming LubanFactoryBean, LubanFactoryBean becomes flexible. When LubanFactoryBean is constructed, different Mapper interfaces can be passed in through construction.

In fact, LubanFactoryBean is also a Bean, we can also generate a LubanFactoryBean by generating a BeanDefinition, and set different values ​​for the parameters of the construction method, for example, the pseudo code is as follows:

BeanDefinition bd = new BeanDefinitoin();
// 注意一:设置的是LubanFactoryBean
bd.setBeanClassName(LubanFactoryBean.class.getName());
// 注意二:表示当前BeanDefinition在生成bean对象时,会通过调用LubanFactoryBean的构造方法来生成,并传入UserMapper
bd.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class.getName())
SpringContainer.addBd(bd);

In particular, note two, which means that when the current BeanDefinition generates a bean object, it will be generated by calling the construction method of LubanFactoryBean and pass in the Class object of UserMapper. Then when LubanFactoryBean is generated, a proxy object corresponding to the UserMapper interface will be generated as a bean.

So far, the problem we want to solve is actually completed: put the proxy object in Mybatis as a bean into the Spring container . It's just that we are here to simulate the proxy object in Mybatis with a simple JDK proxy object. If we have time, we can call the method area provided in Mybatis to generate a proxy object. I won’t take the time to introduce it here.

Import

At this point, we still have one thing that we haven't done, that is, how to really define a BeanDefinition and add it to Spring. As mentioned above, we need to use Import technology. For example, we can achieve this:

Define the following classes:

public class LubanImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        beanDefinition.setBeanClass(LubanFactoryBean.class);
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
        // 添加beanDefinition
        registry.registerBeanDefinition("luban"+UserMapper.class.getSimpleName(), beanDefinition);
    }
}

And add @Import annotation on AppConfig:

@Import(LubanImportBeanDefinitionRegistrar.class)
public class AppConfig {

In this way, a BeanDefinition will be added when Spring is started. The BeanDefinition will generate a LubanFactoryBean object, and pass in the UserMapper.class object when generating the LubanFactoryBean object. Through the logic inside the LubanFactoryBean, it is equivalent to automatically producing a proxy for the UserMapper interface. The object acts as a bean.

to sum up

To sum up, through our analysis, we want to integrate Spring and Mybatis. What we need to do is as follows:

  1. Define a LubanFactoryBean
  2. Define a LubanImportBeanDefinitionRegistrar
  3. Add an annotation @Import(LubanImportBeanDefinitionRegistrar.class) on AppConfig

optimization

In this way, the integration requirements can be basically completed, of course, there are two points that can be optimized

First , define another @LubanScan annotation separately, as follows:

@Retention(RetentionPolicy.RUNTIME)
@Import(LubanImportBeanDefinitionRegistrar.class)
public @interface LubanScan {
}

In this way, @LubanScan can be used directly on AppConfig

Second , in LubanImportBeanDefinitionRegistrar, we can scan the Mapper. In LubanImportBeanDefinitionRegistrar, we can get the corresponding @LubanScan annotation through AnnotationMetadata, so we can set a value on @LubanScan to specify the package path to be scanned. Then get the set package path in LubanImportBeanDefinitionRegistrar, then scan all Mappers under this path, generate BeanDefinition, and put it into the Spring container.

So, so far, the core principle of Spring's integration of Mybatis is over. Let me summarize again:

  1. Define a LubanFactoryBean to generate a bean object from the proxy object of Mybatis
  2. Define a LubanImportBeanDefinitionRegistrar to generate LubanFactoryBean of different Mapper objects
  3. Define a @LubanScan to execute the logic of LubanImportBeanDefinitionRegistrar when starting Spring, and specify the package path

The above three elements are respectively in org.mybatis.spring:
4. MapperFactoryBean
5. MapperScannerRegistrar
6. @MapperScan

Full video explanation address: spring integration Mybatis video

If you have any questions about spring integration, you can also leave a message in the comment area, and the blogger will answer it as soon as possible.

Guess you like

Origin blog.csdn.net/Lubanjava/article/details/106192543