这是了解Spring代理机制的第一篇,尝试了解Spring如何实现Bean的注册和代理。这篇文章会抛出问题:Spring注册Bean,都会用Jdk代理或cglib创建代理对象吗?
1 项目准备
1.1 创建 Spring Boot 项目
创建一个使用 jpa 访问数据库的 Spring Boot 项目。
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
主程序
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 监控 Spring 注册的 Bean
为了方便了解 Spring 启动过程,先创建一个类用于在日志中输出生成的 Bean。可以使用 BeanPostProcessor 接口。它设计的作用,如果需要在 Spring 容器完成 Bean 的实例化、配置和其他的初始化前后添加一些自己的逻辑处理,就可以定义一个或者多个 BeanPostProcessor 接口的实现,然后注册到容器中。
我们实现一个接口,只是打印一下注册的 Bean 信息,代码如下:
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; /** * 创建一个 BeanPostProcessor , 为了方便查看Spring 注册的 Bean * * @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("完成 初始化Bean {} : {}", beanName, bean.getClass().getName()); return bean; } }
启动项目,在控制台就可以看到一些日志输出(对输出做了一些调整):
完成 初始化Bean dataSource : com.zaxxer.hikari.HikariDataSource 完成 初始化Bean entityManagerFactoryBuilder : org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder 完成 初始化Bean researchSpringBootApplication : tech.codestory.research.boot.ResearchSpringBootApplication$$EnhancerBySpringCGLIB$$e4d04c1b 完成 初始化Bean transactionManager : org.springframework.orm.jpa.JpaTransactionManager 完成 初始化Bean jdbcTemplate : org.springframework.jdbc.core.JdbcTemplate 完成 初始化Bean namedParameterJdbcTemplate : org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
注意看bean:researchSpringBootApplication
,实例类名中有字符串$$EnhancerBySpringCGLIB$$
,这表示 Spring 使用 cglib 实现其代理类。
2 创建一个基本的 Service
在代码中创建一个 service,观察 Spring 注册 Bean 的信息。
2.1 数据对象 model
package tech.codestory.research.boot.model; import lombok.Data; /** * 用户实体 * * @author javacodestory@gmail.com */ @Data public class UserInfo { /** * 账号 */ private String account; /** * 密码 */ private String password; /** * 姓名 */ private String name; }
2.2 service 接口
package tech.codestory.research.boot.service; import tech.codestory.research.boot.model.UserInfo; /** * 定义 Service 接口 * * @author javacodestory@gmail.com */ public interface UserInfoFirstService { /** * 获取一个用户信息 * * @param account * @return */ UserInfo getUserInfo(String account); }
2.3 无其他注解的 service 实现
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; /** * 没有添加其他注解的实现类 * * @author javacodestory@gmail.com */ @Service public class UserInfoFirstServiceImpl implements UserInfoFirstService { /** * 获取一个用户信息 * * @param account * @return */ @Override public UserInfo getUserInfo(String account) { return null; } }
2.4 查看 Bean 注册信息
重新启动项目,再日志中查看 Bean 注册信息,可以看到注册的 beanName 是 userInfoFirstServiceImpl
完成 初始化Bean userInfoFirstServiceImpl : tech.codestory.research.boot.service.impl.UserInfoFirstServiceImpl
2.5 测试 Bean 引用
2.5.1 测试类 UserInfoFirstServiceTest
注意,我在测试代码中同时注入了 UserInfoFirstService 和 UserInfoFirstServiceImpl
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; /** * 测试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; // 是同一个实例 log.info("firstService 和 firstServiceImpl 是同一个Bean = {}", firstService == firstServiceImpl); assert firstService == firstServiceImpl; } }
2.5.2 测试结果
在项目目录执行 maven 命令
mvn clean test
关键测试输出
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 和 firstServiceImpl 是同一个Bean = true [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.01 s - in tech.codestory.research.boot.service.UserInfoFirstServiceTest
从测试结果看,两个依赖注入都正常引用了同一个对象。
2.6 问题来了
2.6.1 问题 1
通常我们理解Spring 注册Bean,会使用JDK代理或cglib。但在本例中,注册UserInfoFirstServiceImpl 时,为什么没有创建代理对象?
2.6.2 问题 2
注册 beanName 是userInfoFirstServiceImpl
,为什么用接口和实现类定义变量却都能正常注入?
【未完待续】续篇不知要到猴年马月
敬请关注公众号 《程序猿讲故事》 codestory