Source code analysis | handwritten core functions of mybait spring

Time:2021-1-13

Source code analysis | handwritten core functions of mybait spring

Author: Xiao Fu Ge
Blog: https://bugstack.cn –Collect series of original articles

Precipitation, sharing, growth, so that they and others can gain!

1、 Introduction to foreword

The learning process of a knowledge point is basically divided into: running hellowworld, skilled use of API, source code analysis, core experts. Before analyzing mybaits and mybatis spring source code, I just use it simply because it is easy to use. But how he did it was mostly based on his own experience to analyze, but he always felt that this feeling lacked something. After several sleeps, he decided to make a thorough study, and then he wrote a version of the core functions. In turn to complete their own technology stack vacancy. Now, technological knowledge is exploding, and most of us are busy with business development. Just like an old driver who can’t repair a car, he can only run with one foot of accelerator and one foot of brake. It’s very fast, but it can’t bear to break down. I feel tired. Good! In order to solve this problem, but also for money Cheng Sijin (to describe the money like home cotton), efforts!

Before starting, I’d like to celebrate that my iPhone 4S is alive again. It’s still so easy to use(Yeah! It’s a bit of a card);

Source code analysis | handwritten core functions of mybait spring

2、 Previous chapters

The chapter summary of mybaits & Spring source code analysis and demo function can be learned systematically through the following contents. At the same time, some of the following chapters will involve mybaits of demo version;

  • Source code analysis | mybatis interface is not implemented, why can the class be added, deleted, modified and checked
  • Source code analysis | analyze how spring initializes XML and registers beans like tomb raiding
  • Source code analysis | implementation of a demo version of mybatis based on JDBC

3、 A dish of vegetables agent

Often from the simplest content to grasp. Let’s first look at the use of an interface to an implementation class, and then convert this part of the content into a proxy class.

1. Define an iuserdao interface and implement the interface class

public interface IUserDao {

    String queryUserInfo();

}

public class UserDao implements IUserDao {

    @Override
    public String queryUserInfo() {
        Return "implementation class";
    }

}

2. Instantiation of new() method

IUserDao userDao = new UserDao();
userDao.queryUserInfo();

This is the simplest and most commonly used way to use new objects.

3. Proxy mode instantiation

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?>[] classes = {IUserDao.class};
Invocationhandler = (proxy, method, args) - > "you are represented"+ method.getName ();

IUserDao userDao = (IUserDao) Proxy.newProxyInstance(classLoader, classes, handler);

String res = userDao.queryUserInfo();
logger.info ("test result: {}", RES));
  • Proxy.newProxyInstance The instantiation method of the proxy class corresponds to the parameters of the incoming class
  • Classloader is the class loader. We can get the class loader of the current thread
  • Invocationhandler is the content executed by the actual operation method after proxy. Here, you can add the logic required by your own business scenario. Here, we only return the method name

Test results:

23:20:18.841 [main] INFO   org.itstack.demo . test.ApiTest  -Test result: you are represented by queryuserinfo

Process finished with exit code 0

4、 The feast comes from bean factory

When using spring, we will register or configure our classes to spring for management. For example;

<bean id="userDao" class="org.itstack.demo.UserDao" scope="singleton"/>

Userdao is the implementation class of iuserdao interface. Through the above configuration, we can instantiate a class for our use. However, if iuserdao does not have an implementation class or we want to dynamically change its implementation class, such as mounting it to other places (like mybaits) and managed by the spring bean factory, what should we do?

1. Use of factorybean

Factorybean plays a second leading role in spring. It has nearly 70 children (implementing its interface definition), so it has three methods;

  • T getobject() throws exception; returns the bean instance object
  • Class <? > getobjecttype(); returns the instance class type
  • Boolean issingleton(); judge whether to singleton, and the singleton will be put into the singleton cache pool in the spring container

So we’ll use it nowproxy classGive it to spring’s factorybean for management. The code is as follows:;

ProxyBeanFactory.java &Bean factory implementation class

public class ProxyBeanFactory implements FactoryBean<IUserDao> {

    @Override
    public IUserDao getObject() throws Exception {

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Class<?>[] classes = {IUserDao.class};
        Invocationhandler = (proxy, method, args) - > "you are represented"+ method.getName ();

        return (IUserDao) Proxy.newProxyInstance(classLoader, classes, handler);
    }

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

    @Override
    public boolean isSingleton() {
        return true;
    }

}

spring- config.xml &Configure bean class information

<bean id="userDao" class="org.itstack.demo.bean.ProxyBeanFactory"/>

ApiTest.test_ Iuserdao () & unit test

@Test
public void test_IUserDao() {
    BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");
    IUserDao userDao = beanFactory.getBean("userDao", IUserDao.class);
    String res = userDao.queryUserInfo();
    logger.info ("test result: {}", RES));
}

Test results:

January 20, 2020 23:43:35 am org.springframework.beans . factory.xml.XmlBeanDefinitionReader  loadBeanDefinitions
Information: loading xml bean definitions from class path resource [spring]- config.xml ]
23:43:35.440 [main] INFO   org.itstack.demo . test.ApiTest  -Test result: you are represented by queryuserinfo

Process finished with exit code 0

howMagic no! Your interfaces don’t need to implement classes, so they are arranged explicitly. Remember this method factorybean and dynamic proxy.

2. Beandefinitionregistrypostprocessor class registration

Have you ever doubted where your daughter-in-law has put your money after confiscating it? Why do you spend so much effort every time you get it? It’s like garbage collection, and it’s impossible to reach it.

OK, daughter-in-law, don’t think about it. Just study where your beans are registered. In spring bean management, all beans will eventually be registered in the class defaultlistablebeanfactory. Next, we will take the initiative to register a bean that we have represented.

RegisterBeanFactory.java &Registration bean implementation class

public class RegisterBeanFactory implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(ProxyBeanFactory.class);

        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, "userDao");
        registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // left intentionally blank
    }

}
  • There are four main contents in this paper;

    • Implementation of beandefinitionr egistryPostProcessor.postProcessBeanDefinitionRegistry Method to get the bean registration object
    • Define bean, generic bean definition. Here we mainly set our proxy class factory. We’ve tested him to get a proxy class
    • Create bean definition processing class, bean definition holder, the main parameters needed here; define bean and bean name
    • Finally, we register our own beans in the spring container, registry.registerBeanDefinition ()

spring- config.xml &Configure bean class information

<bean id="userDao" class="org.itstack.demo.bean.RegisterBeanFactory"/>

ApiTest.test_ Iuserdao () & unit test

@Test
public void test_IUserDao() {
    BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");
    IUserDao userDao = beanFactory.getBean("userDao", IUserDao.class);
    String res = userDao.queryUserInfo();
    logger.info ("test result: {}", RES));
}

Test results:

Information: loading xml bean definitions from class path resource [spring]- config.xml ]
January 20, 2020 23:42:29 am org.springframework.beans . factory.support.DefaultListableBeanFactory  registerBeanDefinition
Info: overriding bean definition for bean 'userdao' with a different definition: replacing [generic bean: Class[ org.itstack.demo . bean.RegisterBeanFactory ]; scope=; abstract=false; lazyInit=false; autowireMode=1; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null;  destroyMethodName=null; defined in class path resource [spring- config.xml ]] with [Generic bean: class [ org.itstack.demo . bean.ProxyBeanFactory ]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null]
23:42:29.754 [main] INFO   org.itstack.demo . test.ApiTest  -Test result: you are represented by queryuserinfo

Process finished with exit code 0

what? Is there a kind of brain is full of Sao operation feeling, their registered beans know where, what’s going on.

5、 My kids spring

If through the above knowledge points, such as proxy class, bean factory and bean registration, we can clearly arrange an interface that does not implement a class, and let it execute whatever it executes, can you think that this interface that does not implement a class can call our mybaits through our tossing and turning!

As shown in the figure below, through the configuration used by mybatis, we can see that the data source is handed over to sqlsessionfactorybean, the sqlsessionfactory instantiated by sqlsessionfactorybean is handed over to mapperscannerconfigurer. What we want to implement is mapperscannerconfigurer;

Source code analysis | handwritten core functions of mybait spring

1. What core classes need to be implemented

To make it easier to understand and compare, we will implement the process core class in mybatis spring, as follows:;

  • Mapperfactorybean {delegate such a class to every interface that does not implement a class to operate the database to execute crud}
  • Mapperscannerconfigurer {scan the interface class under the package, without configuration. This is the core configuration class in the figure above
  • Simplemetadatareader {this class is exactly the same as the class in mybaits spring, in order to parse the class file. If you are curious about class loading, you can read my book “implementing a JVM virtual machine with Java.”
  • Sqlsessionfactorybean {the core content of this class is just one thing, which combines the mybaits of our demo version}

Before the analysis, let’s see how we eat our staple food, as follows:;

<bean id="sqlSessionFactory" class="org.itstack.demo.like.spring.SqlSessionFactoryBean">
    <property name="resource" value="spring/mybatis-config-datasource.xml"/>
</bean>

<bean class="org.itstack.demo.like.spring.MapperScannerConfigurer">
    <! -- inject sqlsessionfactory -- >
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    <! -- gives the Dao interface package that needs to be scanned -- >
    <property name="basePackage" value="org.itstack.demo.dao"/>
</bean>

2. (class introduction) sqlsessionfactorybean

This class itself is relatively simple. It mainly implements factorybean < sqlsessionfactory >. Initializingbean is used to help us handle the loading of mybaits core process class. (for the demo version of mybaits, learning links have been provided above)

SqlSessionFactoryBean.java

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean {

    private String resource;
    private SqlSessionFactory sqlSessionFactory;

    @Override
    public void afterPropertiesSet() throws Exception {
        try (Reader reader = Resources.getResourceAsReader(resource)) {
            this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public SqlSessionFactory getObject() throws Exception {
        return sqlSessionFactory;
    }

    @Override
    public Class<?> getObjectType() {
        return sqlSessionFactory.getClass();
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    public void setResource(String resource) {
        this.resource = resource;
    }

}
  • The implementation of initializingbean is mainly used to load mybatis related content, parse XML, construct sqlsession, link database, etc
  • Factorybean, which we have introduced, has three main methods: GetObject (), getobjecttype (), issingleton ()

3. (class introduction) mapperscannerconfigurer

This kind of content may seem a little more, but the core thing is to scan and register our Dao layer interface

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor {

    private String basePackage;
    private SqlSessionFactory sqlSessionFactory;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        try {
            // classpath*:org/itstack/demo/dao/**/*.class
            String packageSearchPath = "classpath*:" + basePackage.replace('.', '/') + "/**/*.class";

            ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
            Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);

            for (Resource resource : resources) {
                MetadataReader metadataReader = new SimpleMetadataReader(resource, ClassUtils.getDefaultClassLoader());

                ScannedGenericBeanDefinition beanDefinition = new ScannedGenericBeanDefinition(metadataReader);
                String beanName = Introspector.decapitalize(ClassUtils.getShortName(beanDefinition.getBeanClassName()));
                
                beanDefinition.setResource(resource);
                beanDefinition.setSource(resource);
                beanDefinition.setScope("singleton");
                beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
                beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(sqlSessionFactory);
                beanDefinition.setBeanClass(MapperFactoryBean.class);

                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, beanName);
                registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // left intentionally blank
    }

    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }

    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }
}
  • Class scan registration, classpath:org/itstack/demo/dao//. class, parse calss file to get resource information; resource [] resources = res ourcePatternResolver.getResources (packageSearchPath);
  • Traverse the resource, where your class information is used to register beans. ScannedGenericBeanDefinition
  • There is a point here. When defining and setting beans, it is to beanDefinition.setBeanClass ( MapperFactoryBean.class )Set it in. At the same time, we set the construction parameters for him.(savoring)
  • Final registration registry.registerBeanDefinition (beanName, definitionHolder.getBeanDefinition ());

4. (class introduction) mapperfactorybean

This class is very interesting, because all your Dao interface classes are actually his. He is here to help you perform all your SQL operations. In order to be more simple and clear, at present, only the query part is implemented here. In the mybatis spring source code, select, update, insert, delete and others are operated respectively.

public class MapperFactoryBean<T> implements FactoryBean<T> {

    private Class<T> mapperInterface;
    private SqlSessionFactory sqlSessionFactory;

    public MapperFactoryBean(Class<T> mapperInterface, SqlSessionFactory sqlSessionFactory) {
        this.mapperInterface = mapperInterface;
        this.sqlSessionFactory = sqlSessionFactory;
    }

    @Override
    public T getObject() throws Exception {
        InvocationHandler handler = (proxy, method, args) -> {
            System.out.println ("you are represented, execute SQL operation! " +  method.getName ());
            try {
                SqlSession session = sqlSessionFactory.openSession();
                try {
                    return session.selectOne(mapperInterface.getName() + "." + method.getName(), args[0]);
                } finally {
                    session.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            return method.getReturnType().newInstance();
        };
        return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{mapperInterface}, handler);
    }

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

    @Override
    public boolean isSingleton() {
        return true;
    }

}
  • T GetObject () is the implementation of a Java proxy class, which will be linked to your injection. When the method content is really called, it will be executed to the implementation part of the proxy class, that is, “you are represented, execute SQL operation!”
  • Invocationhandler, the implementation part of the proxy class is very simple. It mainly starts sqlsession and uses a fixed key“ org.itstack.demo . dao.IUserDao.queryUserInfoById ”Execute SQL operation;

    session.selectOne(mapperInterface.getName() + “.” + method.getName(), args[0]);

    <mapper namespace="org.itstack.demo.dao.IUserDao">
    
        <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="org.itstack.demo.po.User">
            SELECT id, name, age, createTime, updateTime
            FROM user
            where id = #{id}
        </select>
        
    </mapper>
  • Finally, the execution result is returned. The query result information will be reflected into the object class. You can encounter mybatis in demo version

6AlcoholOne full walk

Good! To all of this, the development content is completed, and the test takes one.

mybatis-config- datasource.xml &Data source configuration

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/itstack_demo_ddd?useUnicode=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/User_Mapper.xml"/>
        <mapper resource="mapper/School_Mapper.xml"/>
    </mappers>

</configuration>

test- config.xml &Configure XML

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
       default-autowire="byName">
    <context:component-scan base-package="org.itstack"/>

    <aop:aspectj-autoproxy/>

    <bean id="sqlSessionFactory" class="org.itstack.demo.like.spring.SqlSessionFactoryBean">
        <property name="resource" value="spring/mybatis-config-datasource.xml"/>
    </bean>

    <bean class="org.itstack.demo.like.spring.MapperScannerConfigurer">
        <! -- inject sqlsessionfactory -- >
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
        <! -- gives the Dao interface package that needs to be scanned -- >
        <property name="basePackage" value="org.itstack.demo.dao"/>
    </bean>

</beans>

SpringTest.java &Unit test

public class SpringTest {

    private Logger logger = LoggerFactory.getLogger(SpringTest.class);

    @Test
    public void test_ClassPathXmlApplicationContext() {
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("test-config.xml");
        IUserDao userDao = beanFactory.getBean("IUserDao", IUserDao.class);
        User user = userDao.queryUserInfoById(1L);
        logger.info (test result: {}), JSON.toJSONString (user));
    }

}

Test results;

January 20, 2020 23:51:43 am org.springframework.context . support.ClassPathXmlApplicationContext  prepareRefresh
Information: refreshing org.springframework.context . support.ClassPathXmlApplicationContext @30b8a058: startup date [Mon Jan 20 23:51:43 CST 2020]; root of context hierarchy
January 20, 2020 23:51:43 am org.springframework.beans . factory.xml.XmlBeanDefinitionReader  loadBeanDefinitions
Information: loading xml bean definitions from class path resource [test]- config.xml ]
You are represented, execute SQL operation! queryUserInfoById
2020-01-20 23:51:45.592 [main] INFO   org.itstack.demo . springtest [26] - test results: {"age": 18, "create time": 1576944000000, "Id": 1, "name": "water", "update time": 15769440000000}

Process finished with exit code 0

Wine dry hot laugh red, spring and autumn years ring, do not ask. Looking back, it’s all spring!Gun! Change your mind! You are represented!

7、 To sum up

  • Through the implementation of these core key classes: sqlsessionfactorybean, mapperscannerconfigurer, sqlsessionfactorybean, we use spring and mybaits together to solve the problem of how to deal with database crud operation without class interface
  • So where can this knowledge be used? Don’t just think about the interview! In our business development, there will not be many other data source operations, such as es, Hadoop, data center, etc., including self built. Then we can make a set of unified data source processing services to optimize the efficiency of service development
  • Since this project is continued to develop in itstack-demo-code-mybatis, if you need to get source code, you can pay attention to the official account: bugstack wormhole stack, reply: source code analysis.

8、 Recommended reading

  • It's not a problem to study so hard and enter a big factory after graduation
  • Work two years, resume writing to teach you optimization
  • Talk about my own learning route, give you some reference
  • Middleware development based on springboot