Spring 依赖注入
今天我们将研究Spring的依赖注入。Spring框架的核心概念是“依赖注入”和“面向切面编程”。之前我已经写过关于Java依赖注入的文章,以及我们如何使用Google Guice框架来自动化我们应用程序中的这个过程。
Spring的依赖注入
本教程旨在提供有关Spring依赖注入示例的详细信息,包括基于注解的配置和基于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.Olivia.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框架是4.0.0.RELEASE,JUnit的当前版本是4.8.1,如果您使用其他版本,则项目可能需要进行一些更改。如果您构建项目,您会注意到由于传递依赖关系,还会添加一些其他的jar包到maven依赖项中,就像上面的图片一样。
Spring依赖注入-服务类
假设我们想要向用户发送电子邮件和Twitter信息。为了进行依赖注入,我们需要为这些服务创建一个基类。因此,我有一个MessageService接口,其中声明了一个方法用于发送消息。
package com.Olivia.spring.di.services;
public interface MessageService {
boolean sendMessage(String msg, String rec);
}
现在我们将有真正的实现类来发送电子邮件和推特消息。
package com.Olivia.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.Olivia.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.Olivia.spring.di.consumer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
import com.Olivia.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 annotation is added to the class, so that when Spring framework will scan for the components, this class will be treated as component. @Component annotation can be applied only to the class and it’s retention policy is Runtime. If you are not not familiar with Annotations retention policy, I would suggest you to read java annotations tutorial.
- @Autowired annotation is used to let Spring know that autowiring is required. This can be applied to field, constructor and methods. This annotation allows us to implement constructor-based, field-based or method-based dependency injection in our components.
- For our example, I am using method-based dependency injection. You can uncomment the constructor method to switch to constructor based dependency injection.
现在让我们来编写一个没有注释的类。
package com.Olivia.spring.di.consumer;
import com.Olivia.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);
}
}
一个简单的应用程序类使用服务。对于基于XML的配置,我们可以使用基于构造函数的Spring依赖注入或基于方法的Spring依赖注入。请注意,基于方法和基于setter的注入方法是相同的,只是有些人更喜欢称之为基于setter的方式,而有些人称之为基于方法的方式。
使用注解进行Spring依赖注入配置
对于基于注解的配置,我们需要编写一个配置类(Configurator class),用于将实际的实现Bean注入到组件属性中。
package com.Olivia.spring.di.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import com.Olivia.spring.di.services.EmailService;
import com.Olivia.spring.di.services.MessageService;
@Configuration
@ComponentScan(value={"com.Olivia.spring.di.consumer"})
public class DIConfiguration {
@Bean
public MessageService getMessageService(){
return new EmailService();
}
}
关于上述课程的一些重要点有:
- @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.
让我们编写一个简单的程序来测试我们基于注解的Spring依赖注入示例。
package com.Olivia.spring.di.test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.Olivia.spring.di.configuration.DIConfiguration;
import com.Olivia.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", "scdev@abc.com");
//close the context
context.close();
}
}
AnnotationConfigApplicationContext是AbstractApplicationContext抽象类的实现,用于在使用注解时将服务自动注入到组件中。AnnotationConfigApplicationContext构造函数使用Class作为参数,该参数用于获取要注入到组件类中的bean实现。getBean(Class)方法返回Component对象,并使用配置进行对象的自动注入。上下文对象资源密集,所以我们应该在完成后关闭它们。当我们运行以上程序时,我们会得到以下输出。
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 scdev@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
基于XML的Spring依赖注入配置
我们将使用以下数据创建Spring配置文件,文件名可以是任何东西。applicationContext.xml代码:
<?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.Olivia.spring.di.consumer.MyXMLApplication">
<constructor-arg>
<bean class="com.Olivia.spring.di.services.TwitterService" />
</constructor-arg>
</bean>
-->
<bean id="twitter" class="com.Olivia.spring.di.services.TwitterService"></bean>
<bean id="MyXMLApp" class="com.Olivia.spring.di.consumer.MyXMLApplication">
<property name="service" ref="twitter"></property>
</bean>
</beans>
请注意,上述XML文件包含了基于构造函数和基于setter方法的Spring依赖注入的配置。由于MyXMLApplication使用setter方法进行注入,因此bean配置包含了property元素用于注入。对于基于构造函数的注入,我们需要使用constructor-arg元素。配置XML文件放置在源目录中,因此在构建后它将位于类目录中。让我们看看如何使用基于XML的配置来编写一个简单的程序。
package com.Olivia.spring.di.test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.Olivia.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", "scdev@abc.com");
// close the context
context.close();
}
}
通过提供配置文件的位置,可以使用ClassPathXmlApplicationContext获取ApplicationContext对象。它有多个重载的构造函数,我们还可以提供多个配置文件。其余的代码与基于注解的配置测试程序相似,唯一的区别是我们根据配置选择的方式获取ApplicationContext对象。在运行上述程序时,我们得到以下输出。
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 scdev@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框架编写。由于Spring框架使用log4j进行日志记录,并且我尚未配置它,因此输出将被写入控制台。
Spring依赖注入JUnit测试案例
在Spring中采用依赖注入的一个主要优点是可以轻松使用模拟服务类而非实际服务。因此,我将以上所有学习内容结合起来,编写了一个单一的JUnit 4测试类,用于Spring的依赖注入。
package com.Olivia.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.Olivia.spring.di.consumer.MyApplication;
import com.Olivia.spring.di.services.MessageService;
@Configuration
@ComponentScan(value="com.Olivia.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", "scdev@abc.com"));
}
}
该类使用@ Configuration和@ComponentScan注解进行注释,因为getMessageService()方法返回MessageService的模拟实现。这就是为什么getMessageService()被注释为@Bean注解的原因。由于我正在测试使用注解配置的MyApplication类,我在setUp()方法中使用AnnotationConfigApplicationContext并创建它的对象。上下文将在tearDown()方法中关闭。test()方法的代码只是从上下文中获取组件对象并对其进行测试。你是否想知道Spring框架如何进行自动装配和调用Spring框架不了解的方法。这是通过大量使用Java反射来实现的,我们可以使用它来分析和修改类的行为。
下载Spring依赖注入项目
从上述的网址下载样本的Spring依赖注入(DI)项目,然后进行操作以便更多地学习。