Agent mechanism of spring boot project [1]

Keywords: Java Spring Maven Lombok JDBC

This is the first article to understand the Spring proxy mechanism, trying to understand how Spring implements Bean registration and proxy. This article will raise the question: will Spring register beans and create proxy objects with Jdk proxy or cglib?

1 project preparation

1.1 create a Spring Boot project

Create a Spring Boot project that uses jpa to access the database.

1.1.1 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>tech.codestory.research</groupId>
    <artifactId>research-spring-boot</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>research-spring-boot</name>

    <properties>
        <java.version>1.8</java.version>
        <fastjson.version>1.2.62</fastjson.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>  

1.1.2 application.yml

src/main/resources/application.yml

server:
  port: 9080
spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:h2test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    username: sa
    password:
    platform: h2
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        show_sql: true
        use_sql_comments: tru
  h2:
    console:
      enabled: true
      path: /console
      settings:
        trace: false
        web-allow-others: false
logging:
  level:
      root: INFO

1.1.3 ResearchSpringBootApplication.java

main program

package tech.codestory.research.boot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Research Spring Boot Demo Application
 *
 * @author javacodestory@gmail.com
 */
@SpringBootApplication
public class ResearchSpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(ResearchSpringBootApplication.class, args);
    }
} 

1.2 monitoring Spring registered beans

For the convenience of understanding the Spring startup process, first create a class to output the generated Bean in the log. You can use the BeanPostProcessor interface. Its design function is to add some logic processing before and after the Spring container completes the instantiation, configuration and other initialization of the Bean, then you can define the implementation of one or more BeanPostProcessor interfaces and register them in the container.

We implement an interface to print the registered Bean information. The code is as follows:

package tech.codestory.research.boot.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

/**
 * Create a BeanPostProcessor to facilitate viewing the Spring registered beans
 *
 * @author javacodestory@gmail.com
 */
@Component
@Slf4j
public class SpringBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        log.info("Initialization complete Bean {} :  {}", beanName, bean.getClass().getName());
        return bean;
    }
}

Start the project, and you can see some log output on the console (make some adjustments to the output):

Finish initializing Bean dataSource: com.zaxxer.hikari.HikariDataSource
Finish initializing Bean entityManagerFactoryBuilder: org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder
Finish initializing Bean researchSpringBootApplication: tech.codestore.research.boot.researchspringbootapplication $$enhancerbyspringcglib $$e4d04c1b
Finish initializing Bean transactionManager: org.springframework.orm.jpa.jpartransactionmanager
Finish initializing bean JDBC template: org.springframework.jdbc.core.JdbcTemplate
Finish initializing Bean namedParameterJdbcTemplate: org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate

Note that the bean:researchSpringBootApplication. The instance class name contains the string $$EnhancerBySpringCGLIB $$, which means that Spring uses cglib to implement its proxy class.

2 create a basic Service

Create a service in the code and observe the information of Spring registering the Bean.

2.1 data object model

package tech.codestory.research.boot.model;

import lombok.Data;

/**
 * User entity
 *
 * @author javacodestory@gmail.com
 */
@Data
public class UserInfo {
    /**
     * Account number
     */
    private String account;
    /**
     * Password
     */
    private String password;
    /**
     * Full name
     */
    private String name;
}  

2.2 service interface

package tech.codestory.research.boot.service;

import tech.codestory.research.boot.model.UserInfo;

/**
 * Define Service interface
 *
 * @author javacodestory@gmail.com
 */
public interface UserInfoFirstService {
    /**
     * Get a user information
     *
     * @param account
     * @return
     */
    UserInfo getUserInfo(String account);
}

2.3 service implementation without other annotations

package tech.codestory.research.boot.service.impl;

import org.springframework.stereotype.Service;
import tech.codestory.research.boot.model.UserInfo;
import tech.codestory.research.boot.service.UserInfoFirstService;

/**
 * Implementation classes without additional annotations
 *
 * @author javacodestory@gmail.com
 */
@Service
public class UserInfoFirstServiceImpl implements UserInfoFirstService {
    /**
     * Get a user information
     *
     * @param account
     * @return
     */
    @Override
    public UserInfo getUserInfo(String account) {
        return null;
    }
}

2.4 viewing Bean registration information

Restart the project and check the Bean registration information in the log. You can see that the registered Bean name is userInfoFirstServiceImpl

Finish initializing Bean userInfoFirstServiceImpl: tech.codestore.research.boot.service.impl.userinfofirstserviceimpl

2.5 test Bean references

2.5.1 test class UserInfoFirstServiceTest

Note that I injected both UserInfoFirstService and UserInfoFirstServiceImpl into the test code

package tech.codestory.research.boot.service;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import tech.codestory.research.boot.service.impl.UserInfoFirstServiceImpl;

/**
 * Test UserInfoFirstService
 *
 * @author javacodestory@gmail.com
 */
@SpringBootTest
@Slf4j
public class UserInfoFirstServiceTest {
    @Autowired
    UserInfoFirstService firstService;
    @Autowired
    UserInfoFirstServiceImpl firstServiceImpl;

    @Test
    public void testServiceInstances() {
        log.info("firstService = {}", firstService);
        assert firstService != null;
        log.info("firstServiceImpl = {}", firstServiceImpl);
        assert firstServiceImpl != null;

        // Is the same instance
        log.info("firstService and firstServiceImpl It's the same. Bean  = {}", firstService == firstServiceImpl);
        assert firstService == firstServiceImpl;
    }
}

2.5.2 test results

Execute maven command in project directory

mvn clean test

Key test output

t.c.r.b.s.UserInfoFirstServiceTest       : firstService = tech.codestory.research.boot.service.impl.UserInfoFirstServiceImpl@4899799b
t.c.r.b.s.UserInfoFirstServiceTest       : firstServiceImpl = tech.codestory.research.boot.service.impl.UserInfoFirstServiceImpl@4899799b
t.c.r.b.s.UserInfoFirstServiceTest       : firstService and firstServiceImpl It's the same. Bean  = true
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.01 s - in tech.codestory.research.boot.service.UserInfoFirstServiceTest

From the test results, both dependency injection references the same object normally.

2.6 here comes the question

2.6.1 question 1

In general, we understand that Spring registers beans and uses JDK proxy or cglib. But in this case, when registering UserInfoFirstServiceImpl, why didn't you create a proxy object?

2.6.2 question 2

The registered bean name is userInfoFirstServiceImpl. Why can variables defined by interface and implementation class be injected normally?

[to be continued] I don't know if it's going to be the year of the monkey, the year of the horse and the moon

Please pay attention to the public number "program ape telling story" codestory

Posted by mrfritz379 on Tue, 07 Jan 2020 03:22:55 -0800