具体文档可以看这里:Spring Framework Documentation

简介

Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring的核心是控制反转(IoC)和面向切面(AOP)。
简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架。

Spring IoC

Quick Start

  1. Maven pom.xml依赖配置如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<packaging>jar</packaging>
<dependencies>
<!--导入Spring相关jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<!--导入测试相关jar包-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
  1. 创建实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.ray.pojo;

public class User {
String name;
Integer id;
public User(String name, Integer id) {
System.out.println("调用有参构造");
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public void setId(Integer id) {
System.out.println("调用setId");
this.id = id;
}
public void setName(String name) {
System.out.println("调用setName");
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}
public Integer getId() {
return id;
}
public User() {
System.out.println("调用无参构造");
}
}

  1. Resource目录下创建配置文件 bean.xml
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="User" class="com.ray.pojo.User">
<property name="name" value="Ray"/>
<property name="id" value="123"/>
</bean>
</beans>

需要注意的是property设置时对应类中的Setter方法,若无对应Setter方法则会发生报错

  1. 测试类调用
1
2
3
4
5
6
7
8
9
10
11
12
import com.ray.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
//User user = (User)applicationContext.getBean("user");
User user = applicationContext.getBean("User", User.class);
System.out.println(user);
}
}

结果:

1
2
3
4
5
6
7
/Users/ray/Library/Java/JavaVirtualMachines/openjdk-15.0.2/Contents/Home/bin/java -javaagent:/Users/ray/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/211.7142.45/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=58760:/Users/ray/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/211.7142.45/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/ray/Downloads/Spring/target/test-classes:/Users/ray/Downloads/Spring/target/classes:/Users/ray/.m2/repository/org/springframework/spring-context/5.2.12.RELEASE/spring-context-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-aop/5.2.12.RELEASE/spring-aop-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-beans/5.2.12.RELEASE/spring-beans-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-core/5.2.12.RELEASE/spring-core-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-jcl/5.2.12.RELEASE/spring-jcl-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-expression/5.2.12.RELEASE/spring-expression-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/junit/junit/4.12/junit-4.12.jar:/Users/ray/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar Test
调用无参构造
调用setName
调用setId
User{name='Ray', id=123}

Process finished with exit code 0

IoC获取bean方式

  1. 根据 id 获取
    根据bean.xml中配置的bean的唯一标识id进行查找
1
User user = (User)applicationContext.getBean("user")
  1. 通过 bean 类型获取
    如果同一个类型的bean在xml文件中配置了多个,则获取时会抛出异常,所以同一个类型的bean在容器中必须是唯一的
1
User user = applicationContext.getBean(User.class);
  1. 指定bean的 id 值和类型
1
User user = applicationContext.getBean("User", User.class);

BeanFactory和ApplicationContext区别

  • BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口, 包含 BeanFactory 的所有特性,它的主要功能是支持大型的业务应用的创建。
  • BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
  • ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化;(饿汉式加载?)
  • 相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
  • BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
  • BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor(后置处理器)的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。

IoC创建对象方式

  1. 默认使用无参构造,然后根据bean中设置的property属性调用对应的Setter方法。亦可通过p命名空间实现

  2. 可以采用constructor-arg进行下标赋值亦可通过c命名空间实现

    • 下标赋值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="User" class="com.ray.pojo.User">
    <constructor-arg index="0" value="Ray"/>
    <constructor-arg index="1" value="123"/>
    </bean>
    </beans>

    运行结果:

    1
    2
    3
    4
    5
    /Users/ray/Library/Java/JavaVirtualMachines/openjdk-15.0.2/Contents/Home/bin/java -javaagent:/Users/ray/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/211.7142.45/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=61025:/Users/ray/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/211.7142.45/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/ray/Downloads/Spring/target/test-classes:/Users/ray/Downloads/Spring/target/classes:/Users/ray/.m2/repository/org/springframework/spring-context/5.2.12.RELEASE/spring-context-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-aop/5.2.12.RELEASE/spring-aop-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-beans/5.2.12.RELEASE/spring-beans-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-core/5.2.12.RELEASE/spring-core-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-jcl/5.2.12.RELEASE/spring-jcl-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-expression/5.2.12.RELEASE/spring-expression-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/junit/junit/4.12/junit-4.12.jar:/Users/ray/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar Test
    调用有参构造
    User{name='Ray', id=123}

    Process finished with exit code 0
    • 类型赋值
      根据args类型进行赋值(不建议使用)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="User" class="com.ray.pojo.User">
    <constructor-arg type="java.lang.String" value="Ray"/>
    <constructor-arg type="java.lang.Integer" value="123"/>
    </bean>
    </beans>

    运行结果:

    1
    2
    3
    4
    5
    /Users/ray/Library/Java/JavaVirtualMachines/openjdk-15.0.2/Contents/Home/bin/java -javaagent:/Users/ray/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/211.7142.45/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=51746:/Users/ray/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/211.7142.45/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/ray/Downloads/Spring/target/test-classes:/Users/ray/Downloads/Spring/target/classes:/Users/ray/.m2/repository/org/springframework/spring-context/5.2.12.RELEASE/spring-context-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-aop/5.2.12.RELEASE/spring-aop-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-beans/5.2.12.RELEASE/spring-beans-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-core/5.2.12.RELEASE/spring-core-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-jcl/5.2.12.RELEASE/spring-jcl-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-expression/5.2.12.RELEASE/spring-expression-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/junit/junit/4.12/junit-4.12.jar:/Users/ray/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar Test
    调用有参构造
    User{name='Ray', id=123}

    Process finished with exit code 0
    • 参数名赋值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="User" class="com.ray.pojo.User">
    <constructor-arg name="name" value="Ray"/>
    <constructor-arg name="id" value="123"/>
    </bean>
    </beans>

    运行结果:

    1
    2
    3
    4
    5
    /Users/ray/Library/Java/JavaVirtualMachines/openjdk-15.0.2/Contents/Home/bin/java -javaagent:/Users/ray/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/211.7142.45/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=53590:/Users/ray/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/211.7142.45/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/ray/Downloads/Spring/target/test-classes:/Users/ray/Downloads/Spring/target/classes:/Users/ray/.m2/repository/org/springframework/spring-context/5.2.12.RELEASE/spring-context-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-aop/5.2.12.RELEASE/spring-aop-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-beans/5.2.12.RELEASE/spring-beans-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-core/5.2.12.RELEASE/spring-core-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-jcl/5.2.12.RELEASE/spring-jcl-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/org/springframework/spring-expression/5.2.12.RELEASE/spring-expression-5.2.12.RELEASE.jar:/Users/ray/.m2/repository/junit/junit/4.12/junit-4.12.jar:/Users/ray/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar Test
    调用有参构造
    User{name='Ray', id=123}

    Process finished with exit code 0

Bean的作用域

  1. singleton
    在SpringIOC容器中仅存在唯一一个Bean实例,Bean以单实例的方式存在
  2. prototype
    每次调用getBean()是都会返回一个新的实例
  3. request(创建一个新的bean)
    每次HTTP请求都会创建一个新的Bean,作用域仅适用于WebApplicationContext环境
  4. session(一个会话中共享bean)
    同一个HTTP Session 共享一个Bean,不同的HTTP Session 使用不同的Bean。该作用域适用于WebApplicationContext环境
    示例:
1
<bean id="User" class="com.ray.pojo.User" scope="singleton">

Bean的生命周期

四个阶段

  1. 实例化 Instantiation
  2. 属性赋值 Populate
  3. 初始化 Initialization
  4. 销毁 Destruction
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName);
}

if (instanceWrapper == null) {
instanceWrapper = this.createBeanInstance(beanName, mbd, args);
}

Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}

synchronized(mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
this.applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
} catch (Throwable var17) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", var17);
}

mbd.postProcessed = true;
}
}

boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
if (earlySingletonExposure) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
}

this.addSingletonFactory(beanName, () -> {
return this.getEarlyBeanReference(beanName, mbd, bean);
});
}

Object exposedObject = bean;

try {
this.populateBean(beanName, mbd, instanceWrapper);
exposedObject = this.initializeBean(beanName, exposedObject, mbd);
} catch (Throwable var18) {
if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) {
throw (BeanCreationException)var18;
}

throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18);
}

if (earlySingletonExposure) {
Object earlySingletonReference = this.getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
} else if (!this.allowRawInjectionDespiteWrapping && this.hasDependentBean(beanName)) {
String[] dependentBeans = this.getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet(dependentBeans.length);
String[] var12 = dependentBeans;
int var13 = dependentBeans.length;

for(int var14 = 0; var14 < var13; ++var14) {
String dependentBean = var12[var14];
if (!this.removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}

if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] 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 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}

try {
this.registerDisposableBeanIfNecessary(beanName, bean, mbd);
return exposedObject;
} catch (BeanDefinitionValidationException var16) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", var16);
}
}

doCreateBean()方法中调用了以下三个方法

  • createBeanInstance() -> 实例化
  • populateBean() -> 属性赋值
  • initializeBean() -> 初始化

销毁是在ConfigurableApplicationContext#close()中使用的,是在容器关闭时调用的

拓展点


后置处理器:InstantiationAwareBeanPostProcessor作用于实例化阶段的前后,BeanPostProcessor作用于初始化阶段的前后。正好和第一、第三个生命周期阶段对应。

InstantiationAwareBeanPostProcessor实际上继承了BeanPostProcessor接口,严格意义上来看他们不是两兄弟,而是两父子。但是从生命周期角度我们重点关注其特有的对实例化阶段的影响

Bean自动装配

装配模式介绍

Spring 容器可以在不使用 元素的情况下自动装配相互协作的 bean 之间的关系,这有助于减少编写一个大的基于 Spring 的应用程序的 XML 配置的数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface AutowireCapableBeanFactory{
//无需自动装配
int AUTOWIRE_NO = 0;
//按名称自动装配bean属性
int AUTOWIRE_BY_NAME = 1;
//按类型自动装配bean属性
int AUTOWIRE_BY_TYPE = 2;
//按构造器自动装配
int AUTOWIRE_CONSTRUCTOR = 3;
//过时方法,Spring3.0之后不再支持
@Deprecated
int AUTOWIRE_AUTODETECT = 4;
}

下列自动装配模式,它们可用于指示 Spring 容器为来使用自动装配进行依赖注入。你可以使用元素的 autowire 属性为一个 bean 定义指定自动装配模式。

  1. no 这是默认的设置,它意味着没有自动装配,你应该使用显式的bean引用来连线。你不用为了连线做特殊的事。
  2. byName 由属性名自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byName。然后尝试匹配,并且将它的属性与在配置文件中被定义为相同名称的 beans 的属性进行连接。
  3. byType 由属性数据类型自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byType。然后如果它的类型匹配配置文件中的一个确切的 bean 名称,它将尝试匹配和连接属性的类型。如果存在不止一个这样的 bean,则一个致命的异常将会被抛出。
  4. constructor 类似于 byType,但该类型适用于构造函数参数类型。如果在容器中没有一个构造函数参数类型的 bean,则一个致命错误将会发生。
  5. autodetect(3.0版本不支持deprecated)Spring首先尝试通过 constructor 使用自动装配来连接,如果它不执行,Spring 尝试通过 byType 来自动装配。

示例

byName
1
2
3
4
5
6
7
package com.ray.pojo;
public class Role {
public User user;
public void setUser(User user) {
this.user = user;
}
}
1
2
3
4
5
6
7
package com.ray.pojo;
public class Role {
public User user;
public void setUser(User user) {
this.user = user;
}
}
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.ray.pojo.User">
<property name="name" value="Ray"/>
<property name="id" value="123"/>
</bean>
<bean id="role" class="com.ray.pojo.Role" autowire="byName"/>
</beans>

结果:

1
2
3
4
5
6
调用无参构造
调用setName
调用setId
User{name='Ray', id=123}

Process finished with exit code 0

值得注意的是自动装配使用byName时,其id值为"user"Role中的属性名称"user"相对应,若不对应则无法将对应参数进行填充。参数填充时需要与setter方法配合。

byType

类似的:

1
2
3
4
5
6
7
package com.ray.pojo;
public class Role {
public User user;
public void setUser(User user) {
this.user = user;
}
}
1
2
3
4
5
6
7
package com.ray.pojo;
public class Role {
public User user;
public void setUser(User user) {
this.user = user;
}
}
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.ray.pojo.User">
<property name="name" value="Ray"/>
<property name="id" value="123"/>
</bean>
<bean id="role" class="com.ray.pojo.Role" autowire="byType"/>
</beans>

结果:

1
2
3
4
5
6
调用无参构造
调用setName
调用setId
User{name='Ray', id=123}

Process finished with exit code 0

如果bean的类型匹配配置文件中的一个确切的 bean 名称,它将尝试匹配和连接属性的类型。例如通过Roles类中的com.ray.pojo.User匹配到bean.xml中的user bean。参数填充时需要与setter方法配合。

constructor
1
2
3
4
5
6
7
8
9
10
11
12
package com.ray.pojo;
public class Role {
public User user;
public void setUser(User user) {
this.user = user;
}
public Role() {
}
public Role(User user) {
this.user = user;
}
}
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.ray.pojo.User">
<property name="name" value="Ray"/>
<property name="id" value="123"/>
</bean>
<bean id="role" class="com.ray.pojo.Role" autowire="constructor"/>
</beans>

类似byType,按照类型进行匹配,但是不完全一样
填充属性时,调用构造contructor方法,因此bean.xml中必须有与constructor方法参数中对应的已注册的bean,否则会发生错误

default-autowire

默认情况下,default-autowire属性被设置为none,标示所有的Bean都不使用自动装配,除非Bean上配置了autowire属性。 如果你需要为所有的Bean配置相同的autowire属性,有个办法可以简化这一操作。
在配置文件中加入<beans default-autowire="byType">

自动装配中的循环依赖

依赖注入稍不注意就会出现循环依赖:
Bean之间的依赖顺序: BeanA -> BeanB -> BeanA
即BeanA对象中引用了BeanB对象,而BeanB对象中又引用了BeanA

解决方法:递归,三级缓存
类似于执行图的深拷贝等类似的过程,无向图中我们使用Map<Node, Node>的方式,在遍历未完成节点的同时将已经完成拷贝的节点用Map的形式标记出来,下次再连接时直接从Map中取即可(可以认为是一级缓存?)

下面是未解决循环依赖的常规步骤:

  • 实例化 A,此时 A 还未完成属性填充和初始化方法(@PostConstruct)的执行。
  • A 对象发现需要注入 B 对象,但是容器中并没有 B 对象(如果对象创建完成并且属性注入完成和执行完初始化方法就会放入容器中)。
  • 实例化 B,此时 B 还未完成属性填充和初始化方法(@PostConstruct)的执行。
  • B 对象发现需要注入 A 对象,但是容器中并没有 A 对象。
  • 重复步骤 1。

我们很容易的能够看出常规的解决循环依赖的步骤在这里并不起作用

从而引入三级缓存的概念

三级缓存

Spring解决循环依赖的核心思想在于提前曝光

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Names of beans that are currently in creation. */
// 这个缓存也十分重要:它表示bean创建过程中都会在里面呆着~
// 它在Bean开始创建时放值,创建完成时会将其移出~
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
/** Names of beans that have already been created at least once. */
// 当这个Bean被创建完成后,会标记为这个 注意:这里是set集合 不会重复
// 至少被创建了一次的 都会放进这里~~~~
private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));

以下为三级缓存的对应作用

  • singletonObjects 一级缓存,存放完整的 Bean。
  • earlySingletonObjects 二级缓存,存放提前暴露的Bean,Bean 是不完整的,未完成属性注入和执行 init 方法。用以解决循环依赖。
  • singletonFactories 三级缓存,存放的是 Bean 工厂,主要是生产 Bean,存放到二级缓存中。用以解决循环依赖。

获取单例Bean的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
...
@Override
@Nullable
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
...
public boolean isSingletonCurrentlyInCreation(String beanName) {
return this.singletonsCurrentlyInCreation.contains(beanName);
}
protected boolean isActuallyInCreation(String beanName) {
return isSingletonCurrentlyInCreation(beanName);
}
...
}
  • A半成品加入第三级缓存
  • A填充属性注入B -> 创建B对象 -> B半成品加入第三级缓存
  • B填充属性注入A -> 创建A代理对象,从第三级缓存移除A对象,A代理对象加入第二级缓存(此时A还是半成品,B注入的是A代理对象)
  • 创建B代理对象(此时B是完成品) -> 从第三级缓存移除B对象,B代理对象加入第一级缓存
  • A半成品注入B代理对象
  • 从第二级缓存移除A代理对象,A代理对象加入第一级缓存

PS: 加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决

附上一张偷来的图:

是否可以去掉第二级缓存?

关键在于AOP
现在有A的field或者setter依赖B的实例对象,同时B的field或者setter依赖了A的实例,A首先开始创建,并将自己暴露到 earlySingletonObjects 中,开始填充属性,此时发现自己依赖B的属性,尝试去get(B),发现B还没有被创建,所以开始创建B,在进行属性填充时初始化A,就从earlySingletonObjects 中获取到了实例化但没有任何属性的A,B拿到A后完成了初始化阶段,将自己放到singletonObjects中,此时返回A,A拿到B的对象继续完成初始化,完成后将自己放到singletonObjects中,由A与B中所表示的A的属性地址是一样的,所以A的属性填充完后,B也获取了A的属性,这样就解决了循环的问题。
似乎完美解决,如果就这么使用的话也没什么问题,但是再加上AOP情况就不同了,被AOP增强的Bean会在初始化后代理成为一个新的对象,也就是说:如果有AOP,A依赖于B,B依赖于A,A实例化完成暴露出去,开始注入属性,发现引用B,B开始实例化,使用A暴露的对象,初始化完成后封装成代理对象,A再将代理后的B注入,再做代理,那么代理A中的B就是代理后的B,但是代理后的B中的A是没用代理的A
显然这是不对的,所以在Spring中存在第三级缓存,在创建对象时判断是否是单例,允许循环依赖,正在创建中,就将其从earlySingletonObjects中移除掉,并在singletonFactories放入新的对象,这样后续再查询beanName时会走到singletonFactory.getObject(),其中就会去调用各个beanPostProcessor的getEarlyBeanReference方法,返回的对象就是代理后的对象。

是否可以去掉第三级缓存?

是否可以:不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入。
每次实例化完 Bean 之后就直接去创建代理对象,并添加到二级缓存中。测试结果是完全正常的,Spring 的初始化时间应该也是不会有太大的影响,因为如果 Bean 本身不需要代理的话,是直接返回原始 Bean 的,并不需要走复杂的创建代理 Bean 的流程。
如果要使用二级缓存解决循环依赖,意味着Bean在构造完后就创建代理对象,这样违背了Spring设计原则。Spring结合AOP跟Bean的生命周期,是在Bean创建完全之后通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。如果出现了循环依赖,那没有办法,只有给Bean先创建代理,但是没有出现循环依赖的情况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。

常用注解

@Configuration注解

该类等价 与XML中配置beans,相当于Ioc容器,它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean,与xml中配置的bean意思一样。

@Configuration注解的类必需使用扫描.
定义一个MainConfig,用@Configuration注解,那MainConfig相当于xml里的beans,里面用@Bean注解的和xml里定义的bean等价,用扫描该类,最终我们可以在程序里用@AutoWired或@Resource注解取得用@Bean注解的bean,和用xml先配置bean然后在程序里自动注入一样。目的是减少xml里配置。

@Value注解

为了简化从properties里取配置,可以使用@Value, 可以在properties文件中的配置值。

在dispatcher-servlet.xml里引入properties文件。
<context:property-placeholder location="classpath:test.properties" />
在程序里使用@Value:

1
2
@Value("${wx_appid}")
public String appid;

即使给变量赋了初值也会以配置文件的值为准。

@Controller,@Service,@Repository,@Component

目前4种注解意思是一样,并没有什么区别,区别只是名字不同。

@PostConstruct 和 @PreDestory

实现初始化和销毁bean之前进行的操作,只能有一个方法可以用此注释进行注释,方法不能有参数,返回值必需是void,方法需要是非静态的。

1
2
3
4
5
6
7
8
9
10
11
public class TestService {
@PostConstruct
public void init(){
System.out.println("初始化");
}

@PreDestroy
public void dostory(){
System.out.println("销毁");
}
}

@Primary

自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常。

@Lazy(true)

用于指定该Bean是否取消预初始化,用于注解类,延迟初始化。

@Autowired

Autowired默认先按byType,如果发现找到多个bean,则,又按照byName方式比对,如果还有多个,则报出异常。
1.可以手动指定按byName方式注入,使用@Qualifier。
//通过此注解完成从spring配置文件中 查找满足Fruit的bean,然后按//@Qualifier指定pean

1
2
3
@Autowired
@Qualifier("pean")
public Fruit fruit;

2.如果要允许null 值,可以设置它的required属性为false,如:

1
2
@Autowired(required=false)
public Fruit fruit;

@Resource

默认按 byName自动注入,如果找不到再按byType找bean,如果还是找不到则抛异常,无论按byName还是byType如果找到多个,则抛异常。
可以手动指定bean,它有2个属性分别是name和type,使用name属性,则使用byName的自动注入,而使用type属性时则使用byType自动注入。
@Resource(name=”bean名字”)

@Resource(type=”bean的class”)
这个注解是属于J2EE的,减少了与spring的耦合。

Spring AOP

基本概念

在 spring 中 Aspect 通过@Aspect@Pointcut和一系列 advice(@Before@after@afterReturning@Around)组成。

  • @Aspect 切面,声明当前 class 为一个切面
  • @Pointcut 切入点,通过 execution 表达式描述切入规则,
  • advice 通知,定义了切面是什么以及何时使用。
  • advice 中还包含了 连接点(joinpoint)
  • weave 织入,将切面应用到目标对象并导致代理对象创建的过程
  • introduction 引入,在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段

advice类型

@Before

前置通知(Before advice):在某连接点(JoinPoint)——被切入代码(类或者方法)之前执行的通知,但这个通知不能阻止连接点前的执行。因为@Before 注解的方法入参不能传 ProceedingJoinPoint,而只能传入 JoinPoint。而从 aop 走到核心代码就是通过调用 ProceedingJionPoint 的 proceed()方法。而 JoinPoint 没有这个方法。
这里牵扯区别这两个类:Proceedingjoinpoint 继承了 JoinPoint 。是在 JoinPoint 的基础上暴露出 proceed 这个方法。proceed 很重要,这个是 aop 代理链执行的方法。暴露出这个方法,就能支持 aop:around 这种切面(而其他的几种切面只需要用到 JoinPoint,这跟切面类型有关), 能决定是否走代理链还是走自己拦截的其他逻辑。

@After

后通知(After advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。

@AfterReturning

返回后通知(After return advice):在某连接点正常完成后执行的通知,不包括抛出异常的情况。

@Around

环绕通知(Around advice):包围一个连接点的通知,类似 Web 中 Servlet 规范中的 Filter 的 doFilter 方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。这时 aop 的最重要的,最常用的注解。用这个注解的方法入参传的是 ProceedingJionPoint pjp,可以决定当前线程能否进入核心方法中——通过调用 pjp.proceed();

@AfterThrowing

抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。

advice 注解的执行先后顺序

单个执行:

多个切面执行:

其实我们可以将aop视作同心圆

spring aop就是一个同心圆,要执行的方法为圆心,最外层的order最小。从最外层按照AOP1、AOP2的顺序依次执行doAround方法,doBefore方法。然后执行method方法,最后按照AOP2、AOP1的顺序依次执行doAfter、doAfterReturn方法。也就是说对多个AOP来说,先before的,一定后after。
如果我们要在同一个方法事务提交后执行自己的AOP,那么把事务的AOP order设置为2,自己的AOP order设置为1,然后在doAfterReturn里边处理自己的业务逻辑。
order越小越是最先执行,但更重要的是最先执行的最后结束。

代码实例

如下为Ascpect类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Aspect
@Component
@Log4j2
public class LogAspect {
@Pointcut("execution(* com.ray.blog.web.*.*(..))")
//表示匹配 所有返回类型(*含义) 下的com.ray.web下面的所有类(.*含义)
//匹配所有方法(.*)任何参数((..)含义)
public void log(){}

@Before("log()")
public void doBefore(JoinPoint joinPoint){
log.info("-----------doBefore-------------");
}
@After("log()")
public void doAfter(){
log.info("-------------doAfter-----------------");
}
@AfterReturning(returning = "result", pointcut = "log()")
public void doAfterReturn(Object result){
log.info("Result: {}", result);
}
}

如下为对应的Servlet Controller类:

1
2
3
4
5
6
7
8
@Controller
public class IndexController {
@GetMapping("/")
public String index(){
System.out.println("-----------index-----------");
return "index";
}
}

访问127.0.0.1:8080得到如下日志:

1
2
3
4
20:00:22.415 [http-nio-8080-exec-1] INFO  com.ray.blog.aspect.LogAspect - -----------doBefore-------------
-----------index-----------
20:00:22.421 [http-nio-8080-exec-1] INFO com.ray.blog.aspect.LogAspect - Result: index
20:00:22.421 [http-nio-8080-exec-1] INFO com.ray.blog.aspect.LogAspect - -------------doAfter-----------------

验证了aop切面的运行顺序