通过调用对象的无参构造方法创建一个新对象,然后通过依赖注入得到bean对象(默认单例)依赖注入这一步对新对象中添加了 @Autowired 或者@Resource 等注解的属性赋值,得到 Bean 对象,如下是一段伪代码
//1.通过无参构造函数创建一个新对象 userService
UserService userService = new UserService();
//2.遍历对象的属性,给添加了 @Autowired 注解的属性赋值
for(Field field : userService.getClass().getDeclaredFields()) {
if(field.isAnnotationPresent(Autowired.class)) {
field.set(userService, ??)
}
}
... ...
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService1 = (UserService)context.getBean("userService");
UserService userService2 = (UserService)context.getBean("userService");
System.out.println(userService1.equals(userService2)); // true
我们可以发现获取到的两个 UserService 对象是相同的,这说明 spring 有一个类似 Map 的集合来保存单例 Bean,key 值为 bean 的 name 属性,value 的值为 bean 本身,这个保存 bean 的集合就是单例池,它的作用就是用来存储单例 Bean 对象;如果是多例 Bean,就不会被放入到单例池
先看如下代码
class UserService {
private User user;
}
public
我们在 UserService 中定义了一个 User(id, name) 属性,假如有这样的需求:我们需要在使用 userService 这个 Bean 的时候,获取 user 属性的值;
可能很多人觉得,那可以将 user 也定义成一个 Bean,然后注入到 UserService 不就好了么;但是如果这个 user 的信息需要从数据库查询出来并赋值呢?
这样就比较麻烦了;如果在 UserService 初始化成 Bean 的时候,让 User 对象也初始化属性就好了,这个解决办法就是定义一个初始化 User 属性的方法,并添加 @PostConstruct 让 spring 在初始化前去初始化 User 的属性,如下调用对象的无参构造方法 --> UserService 对象 --> 依赖注入 --> 初始化前(调用初始化方法 initUser()) --> 初始化 --> 初始化后 --> 放入单例池(单例 Bean 对象) 所以可改造代码如下
class UserService {
private User user;
private void initUser() {
user = new User();
user.setId(1);
user.setName("dufu");
}
}
public
注意:这个注解在某些 JDK(高版本,例如:1.8)版本没有,这实际上是 JDK 自己的注解!如果找不到可以添加如下依赖
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
除此之外,spring 也提供了一个解决方案,就是实现 InitializingBean 接口并实现 afterPropertiesSet() 方法,所以也可以改造代码如下
class UserService implements InitializingBean {
private User user;
public void afterPropertiesSet() throws Exception {
user = new User();
user.setId(1);
user.setName("dufu");
}
}
public
但要注意 afterPropertiesSet() 方法是在初始化的时候执行,而 @PostConstruct 注解下的方法是在初始化前执行;调用对象的无参构造方法 --> UserService 对象 --> 依赖注入 --> 初始化前(执行 initUser()) --> 初始化(执行 afterPropertiesSet()) --> 初始化后 --> 放入单例池(单例 Bean 对象)
Bean 的实例化:调用对象的无参构造方法 --> 对象
Bean 的初始化:调用对象指定的方法初始化对象,也就是 spring 的 initializeBean() 方法做的事情
class UserService {
private User user;
/**
* 无参构造函数
*/
public UserService() {
System.out.println("调用了无参构造方法");
}
/**
* 有参构造函数1
*/
public UserService(User user) {
System.out.println("调用了有参构造函数1");
}
}
// 调用
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService)context.getBean("userService");
context.close();
public
class UserService {
private User user;
/**
* 有参构造函数1
*/
public UserService(User user) {
System.out.println("调用了有参构造函数1");
}
}
public
3)如果要指定实例化 Bean 的时候定义了多个构造方法中的某一个,需要在该构造方法上添加 @Autowired 注解(此时有没有其他构造方法都没有影响)
class UserService {
private User user;
/**
* 有参构造函数1
*/
public UserService() {
System.out.println("调用了无参构造函数");
}
/**
* 有参构造函数1,添加 @Autowired 注解, 指定调用该构造方法
*/
public UserService(User user) {
System.out.println("调用了有参构造函数1");
}
}
public
先根据 type 获取,然后再根据 name 获取在单例池中,可能会存在 bean 的 name 不一样,但 bean 的类型相同的情况,如下代码
User user1() {
return new User();
}
public User user2() {
return new User();
}
public User user3() {
return new User();
}
// 调用
System.out.println(context.getBean("user1"));
System.out.println(context.getBean("user2"));
System.out.println(context.getBean("user3"));
public
可以看到这是 3 个类型一样但又不相同的 Bean;那么在初始化 Bean 的构造函数中,如下
class UserService {
private User user;
public UserService(User user1) {
System.out.println(user1);
this.user = user1;
}
}
public
在初始化 UserService 并调用 UserService 的构造方法的时候,先根据类型找出 User 类型的 Bean(3个),然后再根据 name(user1) 找出到底使用哪一个进行初始化操作;所以我们将构造函数改为
UserService(User user4) {
System.out.println(user4);
this.user = user4;
}
public
就会报错,No qualifying bean of type [com.spring.demo.entity.User] is defined: expected single matching bean but found 3: user1,user2,user3
因为单例池中虽然有 User 类型的 Bean,但没有 name = user4 的 Bean;但是如果单例池中只有一个 User 类型的 Bean,因为 Spring 根据类型已经能找到 User 类型的 Bean,就不会根据名字再找,那么上面写成 user4 就不会报错
在 Bean 初始化后,如果有 AOP,则通过 AOP 得到一个代理对象,然后把代理对象放入到单例池中(注意不是普通对象),否则放入的是 Bean 实例化后的普通对象,流程如下
调用对象的无参构造方法 --> UserService 对象 --> 依赖注入 --> 初始化前(执行 initUser()) --> 初始化(执行 afterPropertiesSet()) --> 初始化后(AOP) --> 代理对象 --> 放入单例池(单例 Bean 对象)
如下,我们添加了一个 AOP 实现
class SystemAop {
private final String pointCut = "execution(* com.spring.demo.service.*.*(..))";
(pointCut)
public void before(JoinPoint jp) {
System.out.println("Aop Before ... ...");
}
(pointCut)
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object result = pjp.proceed();
System.out.println("Aop Around ... ...");
return result;
}
(pointCut)
public void after() {
System.out.println("Aop After ... ...");
}
}
public
在 UserService 中添加如下代码
class UserService {
private User user;
public void print() {
System.out.println(user);
}
}
public
在调用如下代码的时候
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService)context.getBean("userService");
userService.print(); // 输出:com.spring.demo.entity.User@3d34d211
context.close();
}
在 UserService 中注入了 User 对象;如果在 userService.print(); 这句代码打断点,可以看到,其 user 属性是 Null;这是因为代理对象中没有 user 属性;但最终却能打印出 user 属性的值,这是为什么呢?Spring 的 AOP 是通过 CGLIB 实现的,其实现是基于继承的方式,伪代码如下
public UserServiceProxy extends UserService {
// 实例池中的普通对象
UserService target;
// 重载父类中的 test() 方法
public void test() {
切面逻辑
target.test(); // 此处的 target 就是实例池中的普通对象
切面逻辑
}
}
可以看到,代理对象继承了普通对象,这样就可以在 context.getBean(“userService”) 的时候实现强制转化(UserService userService = (UserService)context.getBean(“userService”));代理对象中的 target 属性的值就是普通对象,这样在调用 test() 方法的时候,就可以通过普通对象去调用;所以在代理对象中,user 属性 = null,但普通对象中 user 有值
(pointCut)
public void before(JoinPoint jp) {
System.out.println("Aop Before ... ...");
System.out.println(jp.getThis().getClass().getName()); // 代理对象
System.out.println(jp.getTarget().getClass().getName()); // 被代理的对象
}
如下我们实现了一个基本的 Spring 的事务示例
("com.spring.demo")
public class AppConfig {
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
public PlatformTransactionManager transactionManager() {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource());
return transactionManager;
}
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl("xxx");
dataSource.setUsername("xxx");
dataSource.setPassword("xxx");
return dataSource;
}
}
class UserService {
private JdbcTemplate jdbdTemplate;
public void insert() {
jdbdTemplate.execute("insert into user(id, name) values(1, 'dufu')");
throw throw new RuntimeException();
}
}
public
注意在 insert() 方法上我们添加了 @Transactional 注解,表示支持事务,在方法中执行了插入操作后,再抛出异常;如果事务生效的话,那么这条记录将不会被插入到数据库中,事实也确实如此;如下是一段 Spring 中实现事务的伪代码
public UserServiceProxy extends UserService {
UserService target;
// 重载父类中的 insert() 方法
public void insert() {
... ...
// 如果方法上面添加了 @Transactional 注解,就处理事务
if(method.isAnnotationPresent(Transactional.class)) {
// 开启事务
try {
// 1.通过事务管理器新建一个 connection;
Connection conn = xxx.getConnection();
// 2.关闭 autocommit 属性
conn.autocommit = false;
// 3.普通对象执行数据操作
target.insert();
... ...
} catch(Exception e) {
// 如果执行异常, 就回滚操作
conn.rollback();
}
}
}
}
注意:如果我们去掉了 AppConfig 配置类上的 @Configuration 注解的话,事务就会失效!这是为什么呢?这是因为在事务管理器中创建 Connection 的时候,是通过 ThreadLocal
JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
public PlatformTransactionManager transactionManager() {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource());
return transactionManager;
}
public
了解了 Spring 的事务实现原理后,再看下面这段代码
class UserService {
private JdbcTemplate jdbdTemplate;
public void insert() {
jdbdTemplate.execute("insert into user(id, name) values(1, 'dufu')");
insert1();
}
// 不提交事务, 也就是说不会执行下面的插入操作
(propagation = Propagation.NEVER)
public void insert() {
jdbdTemplate.execute("insert into user(id, name) values(1, 'dufu')");
}
}
public
按照正常逻辑,只会插入 insert() 方法中的数据,insert1() 方法中的数据是不会插入的,但是最终发现,两条数据都被插入了,也就是说 insert1() 方法的事务失效了,这是什么原因呢?这是因为,在上面伪代码中,target.insert(); 这句代码是普通对象执行的,insert() 方法下的 insert1() 方法也是普通对象执行的,这样的话就 insert1() 方法根本没有进入代理对象执行 insert() 方法的逻辑;而 target.insert() 方法可以,是因为外层包裹了代理对象重载的insert() 方法!
那如何让 insert() 方法下的 insert1() 方法能被事务托管呢?其解决办法就是让 insert1() 方法被代理对象调用即可,所以可修改代码如下
class UserService {
private JdbcTemplate jdbdTemplate;
// Spring 中可以注入自己
private UserService userService;
public void insert() {
jdbdTemplate.execute("insert into user(id, name) values(1, 'dufu')");
userService.insert1();
}
// 不提交事务, 也就是说不会执行下面的插入操作
(propagation = Propagation.NEVER)
public void insert() {
jdbdTemplate.execute("insert into user(id, name) values(2, 'dufu')");
}
}
public
循环依赖就是相互依赖,A 注入 B,B 注入 A 这种情况就是循环依赖的一个场景;在 Spring 中,通过三级缓存解决了循环依赖的问题;三级缓存分别为
单例池:singletonObjects 保存的是经过完整的生命周期后的Bean
第二级缓存:earlySingletonObjects 保存没有经过完整生命周期的单例对象
第三级缓存:singletonFactories 保存根据 Bean 的信息生成的 Lambda 表达式,用于循环依赖时候判断是否需要提前 AOP
A 的创建生命周期如下
B 的创建生命周期如下
- 创建一个 B 普通对象 --> 存储到 singletonFactories
- 填充 A 属性 --> 去单例池中找 A 对象 --> 通过 creatingSet 确定 A 正在创建中(即出现了循环依赖的情况) --> 到 earlySingletonObjects 中寻找 Bean 的信息 lambda 表达式(如果找到了,返回并赋值,没有的话下一步) --> 到 singletonFactories 找,提前 AOP(如果需要 AOP) 得到 A 的代理对象(如果不需要 AOP 得到普通对象) --> 放到 earlySingletonObjects
- 填充其他属性
- 其他操作
- 初始化后(AOP)
- 放入到单例池
只要出现了循环依赖的情况,都会去二级缓存中去找 Bean 对象,三级缓存的目的就是为了怎么生成对应的 Bean 对象(普通对象还是代理对象) 并放入到二级缓存中
class AService {
private BService bService;
public void print() {
System.out.println(bService);
}
}
public
在调用 AService 下的 print 方法的时候会报错
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
AService aService = (AService)context.getBean("AService");
aService.print();
context.close();
}
报错信息:Error creating bean with name ‘AService’: Bean with name ‘AService’ has been injected into other beans [BService] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using ‘getBeanNamesOfType’ with the ‘allowEagerInit’ flag turned off, for example.
这是因为,添加了 @Async 注解后,Spring 会生成 AService 的代理对象,而在循环依赖的情况下,Spring 也创建了一个 AService 的代理对象,这两个代理对象是不一样的,所以会报错;在开发中,应尽量避免这种既有循环依赖又有 @Async 的情况,如果实在没有办法避免,可以在注入的 BService 对象上添加一个 @Lazy 注解即可,如下
class AService {
private BService bService;
public void print() {
System.out.println(bService);
}
}
public
这是因为,添加了 @Lazy 注解后,注入的 BService 是一个通过 @Lazy 注解逻辑直接生成的代理对象(不是从单例池中获取的),在 print() 方法中使用的时候,才会从单例池中去取出真正要使用的 BService 对象
class AService {
private BService bService;
/**
* 构造方法注入 BService
*/
public AService(BService bService) {
this.bService = bService;
}
public void print() {
System.out.println(bService);
}
}
public
解决办法同样是在构造方法上添加 @Lazy 注解
3)在循环依赖的情况下,如果 AService 和 BService 是多例 Bean 的话,也会报错
在 AppConfig 配置类中,添加一个 Bean,指定读取的 properties 文件地址;如果 Spring 的版本在 5 及以上,则可省略该步骤
PreferencesPlaceholderConfigurer configurer() {
PreferencesPlaceholderConfigurer configurer = new PreferencesPlaceholderConfigurer();
configurer.setLocation(new ClassPathResource("/application.properties"));
return configurer;
}
public
在其他类中,就可以通过 @Value(${xxx}) 的方式获取到 properties 配置文件中的值
class AService {
("${appName}")
private String appName;
public void print() {
System.out.println(appName);
}
}
public
如果使用 @Value(#{Bean的name}) 这种方式,可以实现像 @Resource 注解的功能;例如:
class AService {
("#{BService}")
private BService bService;
public void test() {
System.out.println(bService);
}
}
public
假如在 properties 配置文件中有一个很多地方都在使用的配置,如果还是在各个使用的地方添加 @Value(“${keyName}”) 这种方式,假如修改了 keyName 的话,很容易因为漏改而出现问题;有两个简单的解决办法
新建一个类,把常用的值注入到这个类的属性中
新建一个注解,在该注解上添加 @Value(“${keyName}”) 注解,在使用到这个值的地方,使用我们新建的这个注解因为新建的这个注解不需要添加属性,所以不存在修改的问题;如下
({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
(RetentionPolicy.RUNTIME)
(${xxx})
public MyValue {}
该注解可用于在注册 Bean 的时候,根据自定义条件让 Spring 容器注册 Bean;或者对 Bean 做一些操作
public class MyCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
try {
// 读取本地配置文件
Resource resource = context.getResourceLoader().getResource("/application.properties");
Properties properties = new Properties();
properties.load(resource.getInputStream());
BeanFactory beanFactory = context.getBeanFactory();
if(beanFactory.containsBean("BService")) {
// 给 BService 的 name 属性赋值
BService bService = (BService) beanFactory.getBean(BService.class);
bService.setName((String)properties.getProperty("name"));
}
} catch (IOException e) {
return false;
}
return true;
}
}
(MyCondition.class)
public class BService {
private String name;
}
class AService {
private BService bService;
public void print() {
System.out.println(bService.getName());
}
}
public