Spring 依赖注入
今天我们将研究 Spring 依赖注入。Spring框架的核心概念是“依赖注入”和“面向方面编程”。我之前写过关于Java 依赖注入的文章,以及如何使用Google Guice框架在我们的应用程序中自动执行此过程。
Spring 依赖注入
本教程旨在提供有关 Spring Dependency Injection 示例的详细信息,包括基于注释的配置和基于 XML 文件的配置。我还将为应用程序提供 JUnit 测试用例示例,因为易于测试是依赖注入的主要优点之一。我创建了spring-dependency-injection maven 项目,其结构如下图所示。让我们逐一查看每个组件。
Spring 依赖注入 - Maven 依赖
我已经在 pom.xml 文件中添加了 Spring 和 JUnit maven 依赖项,最终的 pom.xml 代码如下。
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.journaldev.spring</groupId>
<artifactId>spring-dependency-injection</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Spring Framework 的当前稳定版本是4.0.0.RELEASE,JUnit 当前版本是4.8.1,如果您使用的是其他版本,则项目可能需要进行一些更改。如果您构建项目,您会注意到由于传递依赖关系,一些其他 jar 也被添加到 maven 依赖关系中,就像上图一样。
Spring 依赖注入 - 服务类
假设我们想向用户发送电子邮件消息和推特消息。对于依赖注入,我们需要有一个服务基类。所以我有一个MessageService
带有单一方法声明的接口来发送消息。
package com.journaldev.spring.di.services;
public interface MessageService {
boolean sendMessage(String msg, String rec);
}
现在我们将有实际的实现类来发送电子邮件和推特消息。
package com.journaldev.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.journaldev.spring.di.services;
public class TwitterService implements MessageService {
public boolean sendMessage(String msg, String rec) {
System.out.println("Twitter message Sent to "+rec+ " with Message="+msg);
return true;
}
}
现在我们的服务已经准备就绪,我们可以继续使用该服务的组件类。
Spring 依赖注入 - 组件类
让我们为上述服务编写一个消费者类。我们将有两个消费者类 - 一个带有 Spring 注释以进行自动装配,另一个不带注释,并且将在 XML 配置文件中提供装配配置。
package com.journaldev.spring.di.consumer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
import com.journaldev.spring.di.services.MessageService;
@Component
public class MyApplication {
//field-based dependency injection
//@Autowired
private MessageService service;
// constructor-based 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){
//some magic like validation, logging etc
return this.service.sendMessage(msg, rec);
}
}
关于 MyApplication 类的几个要点:
@Component
注释被添加到类中,这样当 Spring 框架扫描组件时,该类将被视为组件。@Component注释只能应用于类,其保留策略是运行时。如果您不熟悉注释保留策略,我建议您阅读java 注释教程。@Autowired
注释用于让 Spring 知道需要自动装配。这可以应用于字段、构造函数和方法。此注释允许我们在组件中实现基于构造函数、基于字段或基于方法的依赖注入。- For our example, I am using method-based dependency injection. You can uncomment the constructor method to switch to constructor based dependency injection.
Now let’s write similar class without annotations.
package com.journaldev.spring.di.consumer;
import com.journaldev.spring.di.services.MessageService;
public class MyXMLApplication {
private MessageService service;
//constructor-based dependency injection
// public MyXMLApplication(MessageService svc) {
// this.service = svc;
// }
//setter-based dependency injection
public void setService(MessageService svc){
this.service=svc;
}
public boolean processMessage(String msg, String rec) {
// some magic like validation, logging etc
return this.service.sendMessage(msg, rec);
}
}
A simple application class consuming the service. For XML based configuration, we can use implement either constructor-based spring dependency injection or method-based spring dependency injection. Note that method-based and setter-based injection approaches are same, it’s just that some prefer calling it setter-based and some call it method-based.
Spring Dependency Injection Configuration with Annotations
For annotation based configuration, we need to write a Configurator class that will be used to inject the actual implementation bean to the component property.
package com.journaldev.spring.di.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import com.journaldev.spring.di.services.EmailService;
import com.journaldev.spring.di.services.MessageService;
@Configuration
@ComponentScan(value={"com.journaldev.spring.di.consumer"})
public class DIConfiguration {
@Bean
public MessageService getMessageService(){
return new EmailService();
}
}
Some important points related to above class are:
@Configuration
annotation is used to let Spring know that it’s a Configuration class.@ComponentScan
annotation is used with@Configuration
annotation to specify the packages to look for Component classes.@Bean
annotation is used to let Spring framework know that this method should be used to get the bean implementation to inject in Component classes.
Let’s write a simple program to test our annotation based Spring Dependency Injection example.
package com.journaldev.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");
//close the context
context.close();
}
}
AnnotationConfigApplicationContext
is the implementation of AbstractApplicationContext
abstract class and it’s used for autowiring the services to components when annotations are used. AnnotationConfigApplicationContext
constructor takes Class as argument that will be used to get the bean implementation to inject in component classes. getBean(Class) method returns the Component object and uses the configuration for autowiring the objects. Context objects are resource intensive, so we should close them when we are done with it. When we run above program, we get below output.
Dec 16, 2013 11:49:20 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@3067ed13: startup date [Mon Dec 16 23:49:20 PST 2013]; root of context hierarchy
Email Sent to pankaj@abc.com with Message=Hi Pankaj
Dec 16, 2013 11:49:20 PM org.springframework.context.support.AbstractApplicationContext doClose
INFO: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@3067ed13: startup date [Mon Dec 16 23:49:20 PST 2013]; root of context hierarchy
Spring Dependency Injection XML Based Configuration
We will create Spring configuration file with below data, file name can be anything. applicationContext.xml code:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<!--
<bean id="MyXMLApp" class="com.journaldev.spring.di.consumer.MyXMLApplication">
<constructor-arg>
<bean class="com.journaldev.spring.di.services.TwitterService" />
</constructor-arg>
</bean>
-->
<bean id="twitter" class="com.journaldev.spring.di.services.TwitterService"></bean>
<bean id="MyXMLApp" class="com.journaldev.spring.di.consumer.MyXMLApplication">
<property name="service" ref="twitter"></property>
</bean>
</beans>
Notice that above XML contains configuration for both constructor-based and setter-based spring dependency injection. Since MyXMLApplication
is using setter method for injection, the bean configuration contains property element for injection. For constructor based injection, we have to use constructor-arg element. The configuration XML file is placed in the source directory, so it will be in the classes directory after build. Let’s see how to use XML based configuration with a simple program.
package com.journaldev.spring.di.test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.journaldev.spring.di.consumer.MyXMLApplication;
public class ClientXMLApplication {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
MyXMLApplication app = context.getBean(MyXMLApplication.class);
app.processMessage("Hi Pankaj", "pankaj@abc.com");
// close the context
context.close();
}
}
ClassPathXmlApplicationContext
is used to get the ApplicationContext object by providing the configuration files location. It has multiple overloaded constructors and we can provide multiple config files also. Rest of the code is similar to annotation based configuration test program, the only difference is the way we get the ApplicationContext object based on our configuration choice. When we run above program, we get following output.
Dec 17, 2013 12:01:23 AM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4eeaabad: startup date [Tue Dec 17 00:01:23 PST 2013]; root of context hierarchy
Dec 17, 2013 12:01:23 AM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [applicationContext.xml]
Twitter message Sent to pankaj@abc.com with Message=Hi Pankaj
Dec 17, 2013 12:01:23 AM org.springframework.context.support.AbstractApplicationContext doClose
INFO: Closing org.springframework.context.support.ClassPathXmlApplicationContext@4eeaabad: startup date [Tue Dec 17 00:01:23 PST 2013]; root of context hierarchy
请注意,部分输出由 Spring Framework 编写。由于 Spring Framework 使用 log4j 进行日志记录,而我尚未对其进行配置,因此输出将写入控制台。
Spring 依赖注入 JUnit 测试用例
Spring 中依赖注入的主要好处之一是可以轻松拥有模拟服务类,而不是使用实际服务。因此,我结合了上面的所有学习内容,并将所有内容编写在一个 JUnit 4 测试类中,用于 Spring 中的依赖注入。
package com.journaldev.spring.di.test;
import org.junit.Assert;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import com.journaldev.spring.di.consumer.MyApplication;
import com.journaldev.spring.di.services.MessageService;
@Configuration
@ComponentScan(value="com.journaldev.spring.di.consumer")
public class MyApplicationTest {
private AnnotationConfigApplicationContext context = null;
@Bean
public MessageService getMessageService() {
return new MessageService(){
public boolean sendMessage(String msg, String rec) {
System.out.println("Mock Service");
return true;
}
};
}
@Before
public void setUp() throws Exception {
context = new AnnotationConfigApplicationContext(MyApplicationTest.class);
}
@After
public void tearDown() throws Exception {
context.close();
}
@Test
public void test() {
MyApplication app = context.getBean(MyApplication.class);
Assert.assertTrue(app.processMessage("Hi Pankaj", "pankaj@abc.com"));
}
}
该类带有注释,@Configuration
因为@ComponentScan
getMessageService ()方法返回MessageService
模拟实现。这就是getMessageService()带有@Bean
注释的原因。由于我正在测试MyApplication
配置了注释的类,因此我在 setUp() 方法中使用并创建了它的对象。上下文在teadown()方法AnnotationConfigApplicationContext
中关闭。test ()方法代码只是从上下文中获取组件对象并对其进行测试。您是否想知道 Spring Framework 如何自动装配并调用 Spring Framework 未知的方法。这是通过大量使用Java 反射来实现的,我们可以使用它来分析和修改运行时类的行为。
下载 Spring 依赖注入项目
从上面的 URL 下载示例 Spring 依赖注入 (DI) 项目并试用它以了解更多信息。