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