Spring依赖注入

此教程的目标是提供关于Spring依赖注入基于注解配置和基于xml文件配置的例子的详细信息。我还会为应用程序提供JUnit测试用例,因为易测试是依赖注入的主要优点之一。

Spring Dependency Injection – Maven Dependencies

首先在pom.xml添加Spring和JUnit maven依赖代码。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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框架的稳定版本是4.00RELESE(注:目前已发布了支持响应式编程的5.x版本),JUnit当前版本是4.8.1(最新版本是支持Java8及以上的JUnit5.2.0版本),如果你使用了其他版本那么这个项目可能会需要一些小的修改。如果你创建了项目,你会注意到由于传递依赖性一些jars包被添加到maven依赖。

Spring Dependency Injection – Service Classes

如果我们想要发送邮件消息和微信消息给用户。根据依赖注入,我们需要有一个服务基类。因此我创建了一个拥有发送消息的方法声明的MessageService接口。

public interface MessageService {

	boolean sendMessage(String msg, String rec);
}

现在我们要有实现类来发送邮件和微信消息。

public class EmailService implements MessageService {

	public boolean sendMessage(String msg, String rec) {
		System.out.println("Email Sent to "+rec+ " with Message="+msg);
		return true;
	}

}

public class WechatService implements MessageService {

	public boolean sendMessage(String msg, String rec) {
		System.out.println("Wechat message Sent to "+rec+ " with Message="+msg);
		return true;
	}

}

现在,我们的服务类准备好了,我们可以开始创建消费服务的组件类。

Spring Dependency Injection – Component Classes

让我们为上面的服务写一个消费者类。我们有两个消费者类。一个是带有autowiring的Spring注解还有另外一个带有xml文件配置的方式。

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知道自动装配是必需的。 这可以应用于字段,构造函数和方法。 这个注解允许我们在组件中实现基于构造函数,基于字段或基于方法的依赖注入。
  • 对于我们的示例,我使用基于方法的依赖注入。你可以取消注释构造函数方法以切换到基于构造函数的依赖注入。

现在让我们来写一个相似的不带注解的类。

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);
	}
}

一个简单的应用程序类消费服务。 对于基于XML的配置,我们可以使用实现基于构造函数的Spring依赖注入或基于方法的Spring依赖注入。 请注意,基于方法和基于setter的注入方法是相同的,只是有些人更喜欢将其称为基于setter的方法,而有些则将其称为基于方法的方法.

Spring Dependency Injection Configuration with Annotations

对于基于注解的配置,我们需要编写一个配置器类,用于将实际实现的bean注入到组件属性中。

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();
	}
}

关于上面类的一下重要点:

  • @Configuration注解用于让Spring知道它是一个配置类。
  • @ComponentScan注解与@Configuration注解一起使用来指定要查找组件类的包。
  • @Bean注解用于让Spring框架知道应该使用这个方法来获取要在Component类中注入的bean实现。

让我们写一个简单的程序来测试具有Spring依赖注入的注解的例子吧。

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是AbstractApplicationContext抽象类的实现,当使用注解时,它用于将组件自动装配到组件。
AnnotationConfigApplicationContext构造函数将Class用作参数,该参数将用于获取要在组件类中注入的Bean实现。
getBean(Class)方法返回Component对象并使用配置来自动装配对象。Context对象是资源密集型的,所以当我们完成它时,我们应该关闭它们。当我们运行程序上面时,我们得到下面的输出。

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

我们将根据下面的数据创建Spring配置文件,文件名可以随意取。
applicationContext.xml 代码:

<?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-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>

请注意,上面的XML同时包含基于构造函数和基于setter的Spring依赖注入的配置。 由于MyXMLApplication使用setter方法进行注入,所以bean配置包含用于注入的属性元素。 对于基于构造函数的注入,我们必须使用constructor-arg元素。
配置XML文件放置在源目录中,因此它将在构建之后位于classes目录中。让我们通过一个简单的程序看下怎样使用基于xml配置。

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用于通过提供配置文件位置来获取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 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 Dependency Injection JUnit Test Case

Spring依赖注入的一个主要好处是可以轻松地模拟服务类而不是使用实际的服务。 所以我已经将上面的所有学习结合起来,并在一个JUnit4测试类中用Spring依赖注入写了所有代码。

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类,因此我使用AnnotationConfigApplicationContext并在setUp()方法中创建它的对象。 Context对象在tearDown()方法中关闭。 test()方法代码只是从context获取组件对象并对其进行测试。

你是否想知道Spring框架如何进行自动装配并调用Spring框架未知的方法。这是通过大量使用Java反射来完成的,我们可以使用它来在运行时分析和修改类的行为。

原文链接:Spring依赖注入教程

posted @ 2019-02-21 21:31  universal  阅读(243)  评论(0编辑  收藏  举报