六、Spring 的 Bean 注入与装配
Spring 的 Bean 注入与装配的方式有很多种,可以通过 xml、get set 方式、构造函数或者注解等。简单易用的方式就是使用 Spring 的注解,Spring 提供了大量的注解方式,如 @Autowired、@Qualifier 等。Spring 还支持多种 BeanFactory 和 ApplicationContext,它们提供了不同的方式来实现 Bean 的注入与装配。
Spring 的 Bean 注入与装配的方式主要有以下几种:
- 使用 XML 配置文件进行显式配置:
-
- 在 XML 文件中,可以通过<bean>标签来定义 Bean,并使用<property>标签或<constructor-arg>标签进行属性注入或构造器注入。例如:
<bean id="studentDao" class="com.it.dao.impl.StudentDaoImpl"/>
<bean id="studentService" class="com.it.service.impl.StudentServiceImpl">
<constructor-arg ref="studentDao"/>
</bean>
- 在 Java 代码中进行显式配置:
-
- 使用@Configuration注解标记配置类,通过@Bean注解定义 Bean。例如:
@Configuration
@ComponentScan
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean
public Gson gson() {
return new Gson();
}
}
- 自动化装配:
-
- 通过扫描包中的注解(如@Component)生成 Bean,使用注解@Autowired自动满足 Bean 之间的依赖。例如:
@Service
public class StudentServiceImpl implements StudentService {
private StudentDao studentDao;
@Override
public void add() {
studentDao.add();
}
@Override
public void del() {
studentDao.del();
}
}
- 自动装配可以使用@ComponentScan注解进行组件扫描,也可以在 XML 配置文件中使用<context:component-scan>元素进行扫描。
Bean 注入的方式主要有以下几种:
- 属性注入,也就是 setter 注入:
-
- 需要生成属性的 set 方法,在 XML 配置文件中使用<property>标签进行属性赋值。例如:
<bean id="student" class="com.it.entity.Student">
<property name="id" value="1001"/>
<property name="name" value="sam"/>
<property name="age" value="25"/>
</bean>
- 构造器注入:
-
- 在类的构造函数中接收依赖对象,在 XML 配置文件中使用<constructor-arg>标签进行构造器参数的注入。例如:
<bean id="school" class="com.it.entity.School">
<constructor-arg ref="student"/>
</bean>
- 工厂方法注入:包括静态工厂和实例工厂。
-
- 静态工厂:通过调用静态工厂的方法来获取对象,在 XML 配置文件中使用<bean>标签的factory-method属性指定静态工厂方法。例如:
<bean name="staticFactoryDao" class="com.bless.springdemo.factory.DaoFactory" factory-method="getStaticFactoryDaoImpl"/>
- 实例工厂:先创建工厂类的实例,再调用普通的实例方法获取对象。在 XML 配置文件中,首先定义工厂类的 Bean,然后在需要注入的 Bean 中使用<property>标签或<constructor-arg>标签引用工厂类的 Bean,并指定工厂方法。例如:
<bean name="daoFactory" class="com.bless.springdemo.factory.DaoFactory"/>
<bean name="springAction" class="com.bless.springdemo.action.SpringAction">
<property name="factoryDao" ref="daoFactory" factory-method="getFactoryDaoImpl"/>
</bean>
Spring 还提供了注解方式进行 Bean 注入,主要有以下几种:
- @Autowired注解:
-
- 自动装配对象,在 Spring 容器中查找相应的对象并将其注入到需要使用的地方。可以用于构造函数、属性、Setter 方法和方法参数上。
-
- 默认优先按照类型取 IOC 容器中寻找对应的组件,如果有多个相同类型的组件,再将属性的名称作为组件的 id 去容器中查找。
-
- 可以配合@Primary使用,当使用@Autowired自动装配时,默认优先选择被注解@Primary标注的组件;也可以配合@Qualifier使用,使用注解@Qualifier可以指定需要装配组件的 id。
- @Qualifier注解:
-
- 用于解决依赖注入中的歧义问题。当 Spring 容器中存在多个相同类型的 Bean 时,它允许你指定具体要注入哪一个 Bean。
-
- 可以与@Autowired注解一起使用,以实现更精确的依赖注入。
Spring 支持多种 BeanFactory 和 ApplicationContext,它们也提供了不同的方式来实现 Bean 的注入与装配。例如:
- BeanFactory:
-
- 是 Spring bean 容器的根接口,提供获取 bean、判断 bean 是否存在、判断 bean 是否为单例或原型等方法。
-
- 按需加载 bean,是轻量级的容器。
- ApplicationContext:
-
- 是 BeanFactory 的子接口,提供了更完整的功能,如对国际化的支持、统一的资源文件访问方式、提供在监听器中注册 bean 的事件等。
-
- 在启动时加载所有 bean,启动过程可能比较慢,但后续的执行比较快。
七、Spring 的创建 Bean 的流程
Spring 创建一个 Bean 的流程包括根据类的构造方法实例化得到一个对象、进行依赖注入、Aware 回调、初始化前、初始化和初始化后等步骤。如果需要进行 AOP,则会进行动态代理并生成一个代理对象做为 Bean。
一、实例化对象
在 Spring 中,Bean 的创建顺序是由 Bean 的依赖关系决定的。当容器启动时,它会根据 Bean 的定义和依赖关系创建和初始化 Bean。如果一个 Bean 依赖于其他 Bean,那么它会在依赖的 Bean 之后被创建。
Spring 会通过反射方式拿到 Bean 的构造方法,再通过构造方法去创建一个普通对象。具体通过哪一个构造器去创建对象分以下情况:
- 类中只有一个构造器,就使用这个构造器。
- 类中有多个构造器:
-
- 优先选择被@Autowried指定的构造器。
-
- 否则,Spring 默认使用无参构造器。
-
- 若没有无参构造器,使用类中唯一的构造器。如果既没有为 Spring 指定需要使用的构造器,也没有无参构造器且有多个构造器,程序将报错。
二、依赖注入
依赖注入是一种软件设计模式,用于解耦组件之间的依赖关系。在 Spring 中,依赖注入可以理解为对上个阶段实例化出来的对象填充属性。被@Autowired修饰的属性,会从 Spring 容器中获取对应的值进行注入。如果找不到对应的 Bean,程序会报错。一旦注入成功,普通对象就变成了一个完整的 Bean。
三、Aware 回调
对于 bean 中非依赖注入的属性,Spring 中提供了 Aware 回调接口,用于在 bean 生命周期中依赖注入之后设置其他属性值。例如BeanNameAware接口,实现该接口的 Bean 可以获取到 Spring 容器中自己的 Bean 名称。在 Spring 框架中,Bean 之间的依赖关系通常是通过依赖注入(DI)来实现的。即在 Bean 定义中指定依赖的 Bean 名称或类型,然后由 Spring 容器自动将对应的 Bean 注入到当前 Bean 中。但是,在某些情况下,Bean 需要获取 Spring 容器的一些其他资源或上下文信息,例如获取ApplicationContext对象、获取BeanFactory对象等。这时候,就可以使用 Aware 接口回调来实现。
四、初始化前
在这个阶段,Spring 会判断 Bean 是否实现了一些特定的接口,如BeanPostProcessorAware接口等。如果实现了这些接口,Spring 会执行相应的回调方法,为 Bean 提供一些额外的功能或信息。
五、初始化
初始化就是对 Bean 中属性的进一步加工处理。主要包括以下几个方面:
- 调用各种 Aware 接口的回调方法,如BeanNameAware、BeanFactoryAware等,让 Bean 获取到 Spring 容器的相关信息。
- 调用BeanPostProcessor的postProcessBeforeInitialization方法,这个方法可以在 Bean 初始化之前对 Bean 进行一些处理。
- 调用初始化方法,主要包括InitializingBean的afterPropertiesSet和用户自定义的初始化方法。这些初始化方法可以对 Bean 的属性进行进一步的设置或执行一些特定的逻辑。
六、初始化后(AOP)
如果需要进行 AOP,则会在这个阶段进行动态代理并生成一个代理对象做为 Bean。Spring 在创建一个 Bean 的过程中,最后一步会判断当前正在创建的这个 Bean 是不是需要进行 AOP。如果需要,则会进行以下步骤:
- 找出所有的切面 Bean。
- 遍历切面中的每个方法,看是否写了@Before、@After等注解。
- 如果写了,则判断所对应的Pointcut是否和当前 Bean 对象的类是否匹配。
- 如果匹配则表示当前 Bean 对象有匹配的的Pointcut,表示需要进行 AOP。
利用 CGLIB 进行 AOP 的大致流程:以UserService作为被代理类举例说明。
- 生成代理类UserServiceProxy,代理类继承UserService。
- 代理类中重写了父类的方法,比如UserService中的test()方法。
- 代理类中还会有一个target属性,该属性的值为被代理对象(也就是通过UserService类推断构造方法实例化出来的对象,进行了依赖注入、初始化等步骤的对象)。
- 代理类中的test()方法被执行时的逻辑如下:
-
- 执行切面逻辑(@Before)。
-
- 调用target.test()。
当我们从 Spring 容器得到UserService的 Bean 对象时,拿到的就是UserServiceProxy所生成的对象,也就是代理对象。当执行test方法时,实际上执行的是代理对象的test方法。UserService代理对象.test() -> 执行切面逻辑 -> target.test(),注意target对象不是代理对象,而是被代理对象。
八、Spring 的自定义标签和扩展功能
Spring 支持自定义标签,可以通过定义 XSD 文件、实现 BeanDefinitionParser 接口、创建 NamespaceHandler 文件等方式来实现自定义标签的解析和注册。Spring 还提供了扩展功能,如通过继承 ClassPathXmlApplicationContext 来实现自定义的初始化要求,加载 BeanFactory、注册监听器等。
一、自定义标签的实现方式
1. 定义 XSD 文件
XSD(XML Schema Definition)文件用于描述自定义标签的结构和属性。通过定义 XSD 文件,可以为自定义标签提供明确的规范,使得 Spring 在解析 XML 配置文件时能够正确识别和处理自定义标签。
例如,可以创建一个名为mycustom.xsd的文件,定义一个名为mycustomtag的元素,具有attr1和attr2两个属性:
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.example.com/schema/mycustom"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.example.com/schema/mycustom"
elementFormDefault="qualified">
<xsd:element name="mycustomtag">
<xsd:complexType>
<xsd:attribute name="attr1" type="xsd:string"/>
<xsd:attribute name="attr2" type="xsd:int"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
2. 实现 BeanDefinitionParser 接口
BeanDefinitionParser 接口用于解析自定义标签,并将其转换为 Spring 的 BeanDefinition 对象。通过实现这个接口,可以自定义标签的解析逻辑,将标签中的属性和子元素映射到 BeanDefinition 中。
例如,可以创建一个名为MyCustomTagParser的类,实现 BeanDefinitionParser 接口:
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
public class MyCustomTagParser extends AbstractSingleBeanDefinitionParser {
@Override
protected Class<?> getBeanClass(Element element) {
// 返回自定义标签对应的Bean类
return MyCustomBean.class;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String attr1 = element.getAttribute("attr1");
int attr2 = Integer.parseInt(element.getAttribute("attr2"));
if (StringUtils.hasText(attr1)) {
builder.addPropertyValue("attr1", attr1);
}
builder.addPropertyValue("attr2", attr2);
}
}
3. 创建 NamespaceHandler 文件
NamespaceHandler 文件用于注册自定义标签的解析器。通过创建 NamespaceHandler 文件,可以将自定义标签与对应的解析器关联起来,使得 Spring 在遇到自定义标签时能够调用正确的解析器进行处理。
例如,可以创建一个名为MyCustomNamespaceHandler的类,继承NamespaceHandlerSupport类:
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class MyCustomNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("mycustomtag", new MyCustomTagParser());
}
}
4. 修改 spring.handlers 和 spring.schemas 文件
spring.handlers 文件用于将命名空间与 NamespaceHandler 关联起来,spring.schemas 文件用于指定命名空间对应的 XSD 文件路径。通过修改这两个文件,可以让 Spring 在解析 XML 配置文件时能够找到自定义标签的解析器和 XSD 文件。
例如,可以在META-INF目录下创建spring.handlers和spring.schemas文件:
spring.handlers文件内容:
http://www.example.com/schema/mycustom = com.example.MyCustomNamespaceHandler
spring.schemas文件内容:
http://www.example.com/schema/mycustom/mycustom.xsd = META-INF/mycustom.xsd
二、扩展功能的实现方式
Spring 提供了多种扩展功能,如通过继承 ClassPathXmlApplicationContext 来实现自定义的初始化要求,加载 BeanFactory、注册监听器等。
1. 继承 ClassPathXmlApplicationContext
可以通过继承ClassPathXmlApplicationContext类来实现自定义的初始化要求。在子类中,可以重写postProcessBeanFactory方法,在该方法中进行自定义的初始化操作。
例如,可以创建一个名为MyCustomApplicationContext的类,继承ClassPathXmlApplicationContext类:
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyCustomApplicationContext extends ClassPathXmlApplicationContext {
public MyCustomApplicationContext(String... configLocations) {
super(configLocations);
}
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 在这里进行自定义的初始化操作
}
}
2. 加载 BeanFactory
可以在自定义的ApplicationContext子类中,通过调用loadBeanDefinitions方法来加载 BeanFactory。在加载 BeanFactory 的过程中,可以使用自定义的BeanDefinitionReader来解析 XML 配置文件中的自定义标签。
例如,可以在MyCustomApplicationContext类的构造方法中加载 BeanFactory:
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyCustomApplicationContext extends ClassPathXmlApplicationContext {
public MyCustomApplicationContext(String... configLocations) {
super(configLocations);
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(getBeanFactory());
reader.loadBeanDefinitions(configLocations);
}
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 在这里进行自定义的初始化操作
}
}
3. 注册监听器
可以在自定义的ApplicationContext子类中,通过调用addApplicationListener方法来注册监听器。监听器可以在应用程序的不同阶段接收事件通知,并进行相应的处理。
例如,可以在MyCustomApplicationContext类的构造方法中注册监听器:
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyCustomApplicationContext extends ClassPathXmlApplicationContext {
public MyCustomApplicationContext(String... configLocations) {
super(configLocations);
addApplicationListener(new ApplicationListener<ApplicationEvent>() {
@Override
public void onApplicationEvent(ApplicationEvent event) {
// 在这里处理应用程序事件
}
});
}
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 在这里进行自定义的初始化操作
}
}
通过以上方式,可以实现 Spring 的自定义标签和扩展功能,使得 Spring 在应用程序开发中更加灵活和可扩展。