本文内容如有错误、不足之处,欢迎技术爱好者们一同探讨,在本文下面讨论区留言,感谢。欢迎转载,转载请注明出处(https://blog.csdn.net/feng_xiaoshi/article/details/105832004),谢谢。
简介
本文讨论 Setter 注入方式的原理,通过了解 Setter 注入方式的原理,来加深对 Spring IOC 的认识。通过本文将了解如何使用 autowire属性的 byType,byName 执行依赖注入。以及手动模式的 API 配置元信息的案例。
前期工作
引入依赖
pom.xml 中添加 Spring 的库依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
创建依赖类
有一个依赖于实际上处理业务逻辑的服务的应用程序类:
IndexApp.java
public class IndexApp {
private IndexService service;
...
}
IndexService 是一个接口:
IndexService.java
public interface IndexService {
public String serve();
...
}
该接口可以有多种实现。 一个潜在的实现 DefaultIndexServiceImpl:
DefaultIndexServiceImpl.java
public class IndexService implements IService {
@Override
public String serve() {
return "Hello World";
}
}
设计上,IndexApp 是一个高级组件,它依赖于称为 IndexService 的低级组件。
本质上,将 IndexApp 与 IndexService 的具体实现的关联关系实现脱钩,而和 IndexService 的接口进行关联,因为该实现可能会因各种因素而有所不同。
模式
Setter 的实现方式有手动和自动两种。
手动模式
依赖注入是一种通过外部容器提供对象的依赖关系的技术。 因此需要通过外部化配置来进行指定和约束需要装配的内容和规则。
主要有以下三种手动方式进行约束。
- XML 资源配置元信息
- Java 注解配置元信息
- API 配置元信息
XML 资源配置元信息
在 XML 的配置中将依赖项连接在一起:
bean-setter.xml
<bean
id="defaultIndexService"
class="com.feng.di.spring.DefaultIndexServiceImpl" />
<bean
id="indexApp"
class="com.feng.di.spring.IndexApp" >
<property name="service" ref="defaultIndexService" />
</bean>
可以看出,XML 配置创建 DefaultIndexServiceImpl 的实例并为其分配 ID 。默认情况下,bean 是单例。与此同时创建 IndexApp 的实例,在这个 bean 中,使用 setter 方法注入另一个 bean 。
测试代码:
/**
* 基于 XML 资源的依赖 Setter 方法注入示例
*/
public class XmlDependencySetterInjectionDemo {
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(beanFactory);
String xmlResourcePath = "classpath:META-INF/bean-setter.xml";
// 加载 xml 资源,并生成 BeanDefinition 资源
xmlReader.loadBeanDefinitions(xmlResourcePath);
IndexApp indexApp = beanFactory.getBean(IndexApp.class);
}
}
Java 注解配置元信息
从Spring 2.5开始,可以使用注释配置依赖项注入。因此,可以使用相关类,方法或字段声明上的注释,而不是使用XML来描述bean的连接,而是可以将bean配置移入组件类本身。
注释注入在XML注入之前执行。因此,对于通过两种方法连接的属性,后一种配置将覆盖前者。
默认情况下,Spring容器中的注释接线未打开。因此,在使用基于注释的连接之前,需要在Spring配置文件中启用它。
<?xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:context = "http://www.springframework.org/schema/context"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config/>
</beans>
一旦配置了<context:annotation-config />
,就可以开始注释代码,以指示Spring应该自动将值连接到属性,方法和构造函数中。
下面是一些注释列表:
序号 | 注释与说明 |
---|---|
1 | @Required批注适用于bean属性设置器方法。 |
2 | @Autowired批注可以应用于bean属性设置器方法,非设置器方法,构造函数和属性。 |
3 | @Qualifier注释和@Autowired可以通过指定将要连接的确切bean来消除混淆。 |
4 | Spring支持基于JSR-250的注释,包括@ Resource,@ PostConstruct和@PreDestroy注释。 |
Service 类例子
假设 向用户发送电子邮件和短息消息。对于依赖项注入,需要为服务提供一个基类。因此,MessageService 是用于发送消息的单一方法声明的接口。
package com.feng.spring.di.services;
public interface MessageService {
boolean sendMessage(String msg, String rec);
}
现在, 有实际的实现类来发送电子邮件和短信消息。
package com.feng.spring.di.services;
public class EmailService implements MessageService {
public boolean sendMessage(String msg, String rec) {
System.out.println("Email Sent to "+rec+ " with Message="+msg);
return true;
}
}
package com.feng.spring.di.services;
public class SmsService implements MessageService {
public boolean sendMessage(String msg, String rec) {
System.out.println("Sms message Sent to "+rec+ " with Message="+msg);
return true;
}
}
Component 类例子
为上述 Service 编写一个消费者类。
package com.feng.spring.di.consumer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
import com.feng.spring.di.services.MessageService;
@Component
public class MyApplication {
// 基于变量的依赖注入
//@Autowired
private MessageService service;
// 基于构造参数的 dependency injection
// @Autowired
// public MyApplication(MessageService svc){
// this.service=svc;
// }
@Autowired
public void setService(MessageService svc){
this.service=svc;
}
public boolean processMessage(String msg, String rec){
//验证,日志等信息
return this.service.sendMessage(msg, rec);
}
}
关于 MyApplication 类的注意点:
@Component
注释被添加到该类中,以便当 Spring 框架扫描组件时,该类将被视为组件。@Component
批注只能应用于类,其保留策略是运行时。@Autowired
注解用于让 Spring 知道需要自动装配。这可以应用于字段,构造函数和方法。这个注释使我们能够在组件中实现基于构造函数,基于字段或基于方法的依赖注入。
配置类
对于基于注释的配置,我们需要编写一个Configurator类,该类将用于将实际的实现bean注入到组件属性中。
package com.feng.spring.di.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import com.feng.spring.di.services.EmailService;
import com.feng.spring.di.services.MessageService;
@Configuration
@ComponentScan(value={"com.feng.spring.di.consumer"})
public class DIConfiguration {
@Bean
public MessageService getMessageService(){
return new EmailService();
}
}
@Configuration
注解用于让 Spring 知道它是 Configuration 类。@ComponentScan
注解与@Configuration
注解一起使用以指定要查找组件类的包。@Bean
批注用于使 Spring 框架知道该方法应用于使 Bean 实现注入到 Component 类中。
编写一个简单的程序来测试基于注释的 Spring Dependency Injection 示例:
package com.feng.spring.di.test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.journaldev.spring.di.configuration.DIConfiguration;
import com.journaldev.spring.di.consumer.MyApplication;
public class ClientApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DIConfiguration.class);
MyApplication app = context.getBean(MyApplication.class);
app.processMessage("Hi Pankaj", "pankaj@abc.com");
// 关闭上下文
context.close();
}
}
AnnotationConfigApplicationContext 是 AbstractApplicationContext 抽象类的实现,用于在使用注释时将服务自动装配到组件。AnnotationConfigApplicationContext 构造函数将 Class 作为参数,用于获取 Bean 实现以注入组件类。
getBean(Class) 方法返回 Component 对象,并使用配置自动装配对象。上下文对象是资源密集型的,因此在完成操作后应将其关闭。
程序运行输出以下信息:
May 02, 2020 9:14:01 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@17f052a3: startup date [Sat May 02 21:14:01 CST 2020]; root of context hierarchy
Email Sent to pankaj@abc.com with Message=Hi Pankaj
May 02, 2020 9:14:02 PM org.springframework.context.support.AbstractApplicationContext doClose
信息: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@17f052a3: startup date [Sat May 02 21:14:01 CST 2020]; root of context hierarchy
API 配置元信息
API 配置元信息是通过构造 BeanDefinition 来实现的,代码如下:
ApiDependencySetterInjectionDemo.java
package com.feng.spring.di;
import com.feng.spring.di.consumer.MyApplication;
import com.feng.spring.di.services.EmailService;
import com.feng.spring.di.services.MessageService;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
public class ApiDependencySetterInjectionDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApiDependencySetterInjectionDemo.class);
// 生成 MyApplication 的 BeanDefinition
BeanDefinition beanDefinition = createMyApplicationBeanDefinition();
// 注册 MyApplication的Bean
context.registerBeanDefinition("myApplication", beanDefinition);
MyApplication myApplication = context.getBean(MyApplication.class);
myApplication.processMessage("hello bean definition", "abc@outlook.com");
context.close();
}
private static BeanDefinition createMyApplicationBeanDefinition(){
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyApplication.class);
builder.addPropertyReference("service", "messageService");
return builder.getBeanDefinition();
}
@Bean(name = "messageService")
public MessageService getMessageService(){
return new EmailService();
}
}
输出信息如下:
May 02, 2020 10:06:19 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@238e0d81: startup date [Sat May 02 22:06:19 CST 2020]; root of context hierarchy
Email Sent to abc@outlook.com with Message=hello bean definition
May 02, 2020 10:06:19 PM org.springframework.context.support.AbstractApplicationContext doClose
信息: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@238e0d81: startup date [Sat May 02 22:06:19 CST 2020]; root of context hierarchy
自动模式
- byName
- byType
下面通过示例来解释自动模式:
Order.java
package com.feng.spring.di.beans;
public class Order {
private String item;
private String price;
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
}
PaymentGateway.java
package com.feng.spring.di.beans;
public class PaymentGateway {
private Order order;
public void setOrder(Order ord){
this.order = ord;
}
public Order getOrder() {
return order;
}
@Override
public String toString(){
return "ordering "+this.order.getItem()+" | price: "+this.order.getPrice();
}
}
基于xml的配置文件,注入依赖项的常规方法:
spring-autowriting-conventional.xml
<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-3.0.xsd">
<bean id="paymentGateway" class="com.feng.spring.di.beans.PaymentGateway" >
<property name="order" ref="orderBean" />
</bean>
<bean id="orderBean" class="com.feng.spring.di.beans.Order" >
<property name="item" value="Java Book" />
<property name="price" value="RS 225" />
</bean>
</beans>
基于 byType 模式的自动注入:
spring-autowriting-by-type.xml
<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-3.0.xsd">
<bean id="paymentGateway" class="com.feng.spring.di.beans.PaymentGateway"
autowire="byType"/>
<bean id="orderBean" class="com.feng.spring.di.beans.Order" >
<property name="item" value="Java Book" />
<property name="price" value="RS 225" />
</bean>
</beans>
基于 byName 模式的自动注入:
spring-autowriting-by-name.xml
<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-3.0.xsd">
<bean id="paymentGateway" class="com.feng.spring.di.beans.PaymentGateway"
autowire="byName"/>
<bean id="order" class="com.feng.spring.di.beans.Order" >
<property name="item" value="Java Book" />
<property name="price" value="RS 225" />
</bean>
</beans>
测试类
AutoWiringDependencyInjectDemo.java
package com.feng.spring.di;
import com.feng.spring.di.beans.PaymentGateway;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AutoWiringDependencyInjectDemo {
public static void main(String a[]){
String confFile = "spring-autowriting-by-name.xml";
ConfigurableApplicationContext context
= new ClassPathXmlApplicationContext(confFile);
PaymentGateway paymentGateway = (PaymentGateway) context.getBean("paymentGateway");
System.out.println(paymentGateway.toString());
}
}
自动注入通过 byType和byName ,一般在 XML 配置文件中用的较多。
总结
在本文中,举例说明了如何使用 Spring Framework 使用基于 XML 的配置注入依赖项的示例,通过上面的5个案例,分别通过手动模式和自动模式介绍了 Setter 依赖注入的方式,通过对手动模式的 API 配置元信息,能够更多的理解它底层的一个设计初衷,比如通过 BeanDefinitionBuilder#addPropertyReference 来达到引入相关依赖的目的。
参考资料
Spring Setter-based Dependency Injection(基于Spring Setter的依赖注入)
XML-Based Injection in Spring(Spring中基于XML的注入)
Spring Dependency Injection(Spring 依赖注入)
Spring - Dependency Injection example with annotation(Spring-带有注释的依赖注入示例)