Spring的自动化装配就便利性方面远远优于其他装配方法,这也是业界目前主要采用的Bean装配机制。Spring基于组建扫描和自动装配实现自动化装配,能将用户的显示配置降到最低。以下通过一段代码了解自动装配的基本使用(楼主关于Spring5讲解的所有的代码示例均是在一个Spring Boot项目)。
1.创建组件Bean
package com.example.demo.service;
/**
* @author zhangyanqing
* @desc
* @date 2018/08/05
*/
public interface MessageService {
void printMessage();
}
package com.example.demo.service;
import org.springframework.stereotype.Component;
/**
* @author zhangyanqing
* @desc
* @date 2018/08/05
*/
@Component
public class MessageServiceImpl implements MessageService {
@Override
public void printMessage() {
System.out.println("Method printMessage Invoke!");
}
}
2.创建配置类启动组件扫描
package com.example.demo.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author zhangyanqing
* @desc
* @date 2018/08/05
*/
@Configuration
@ComponentScan(basePackages = {"com.example.demo.service"})
public class MessageConfig {
}
3.创建Junit测试类测试bean自动装配
package com.example.demo.test;
import com.example.demo.config.MessageConfig;
import com.example.demo.service.MessageService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author zhangyanqing
* @desc
* @date 2018/08/05
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MessageConfig.class)
public class MessageServicecTest {
@Autowired
private MessageService messageServiceImpl;
@Test
public void printMessage(){
messageServiceImpl.printMessage();
}
}
这个Bean自动装配的例子里Spring用到了Component注解表明该类会作为组件类,在Spring进行组件扫描时,它会去扫描配置类MessageConfig的@ComponentScan注解中定义的组建扫描包,扫描包下所有带有Component注解的类,在Spring容器中自动为该类创建一个实例。在测试类中messageServiceImpl被自动装配注解AutoWired修饰,Spring会在上下文容器中找到该类的实例并将它注入进去。
一、基于注解为Spring Bean命名
Spring上下问容器的所有Bean都会有一个ID,如果没有设置,默认是将类名的第一个字符替换为小写之后的字符串作为ID,如果想自己命名Bean ID,可以在Component注解中指定
@Component("myMessageService")
public class MessageServiceImpl implements MessageService {
@Override
public void printMessage() {
System.out.println("Method printMessage Invoke!");
}
}
也可以使用java定义的@Named注解为SpringBean命名,使用方式与效果与@Component类似,这里不做讨论。此外Spring基于Bean的不同功能还定义了类似@Service、@Repository、@Controller、@Configuration注解其实质和Component一样,只是为了区别不同Bean的功能起到表意作用而作的区分,他们在Spring上下文容器都以相同的方式存在。
二、设置组件扫描的基础包或类
在上述例子中我们通过在配置类MessageConfig的ComponentScan注解中设置了组件扫描的包限制了Spring组件扫描的范围,那么如果ComponentScan注解没有指定扫描包Spring会如何处理呢,答案是目前Spring会去扫描该本类(MessageConfig)所属包下所有类。
1 - 在ComponentScan注解中指定多个组件扫描包
@Configuration
@ComponentScan(basePackages = {"com.example.demo.service","com.example.demo.dao","com.example.demo.facade"})
public class MessageConfig {
}
2 - 在ComponentScan注解中指定多个注解扫描类
@Configuration
@ComponentScan(basePackageClasses = MessageServiceImpl.class)
public class MessageConfig {
}
三、自动化装配注解
Spring5实现自动化装配的注解有三种,他们是Autowired,Inject,Named
1 - 使用@Autowired注解实现自动化装配
@Autowired是Spring自带的注解,下面是Spring5中该注解的源码
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
通过注解源码我们知道@Autowired可以在构造函数、类的所有方法、成员变量上使用,@Autowired注解的requied属性如果设置为true如果Spring容器中不存在该类的实例会立即抛出异常。下面我们分别为每一种使用场景给出实例,注意我们目前只讨论装配Bean设置为单例的情况。
1)在构造函数中使用Autowired
@Component
public class UserService {
private MessageService messageService;
@Autowired
public UserService(MessageService messageService) {
this.messageService = messageService;
}
public String play(){
return "UserService "+messageService.printMessage();
}
}
UserService类中被Autowired注解修饰的构造函数,当Spring实例化Bean的的时候会通过这个构造器进行实例化,并且在Spring容器中找到一个MessageService实例注入到成员变量messageService中
2)在类其他方法中使用Autowired
@Component
public class UserService {
private MessageService messageService;
@Autowired
public void setMessageService(MessageService messageService){
this.messageService = messageService;
}
public String play(){
String message = "UserService "+messageService.printMessage();
System.out.println(message);
return message;
}
}
本例被Auwired注解修饰的类方法会在Bean默认构造函数(注意调用的是类的默认构造函数如果类中没有定义会默认实现一个无参构造函数)调用完之后被调用,Spring会从容器中找到一个MessageService对象注入到messageService中
3)在类成员变量上使用Autowired
@Component
public class UserService {
@Autowired
private MessageService messageService;
public String play(){
String message = "UserService "+messageService.printMessage();
System.out.println(message);
return message;
}
}
2 - 使用@Inject注解实现自动化装配
@Component
public class UserService {
@Inject
private MessageService messageService;
public String play(){
String message = "UserService "+messageService.printMessage();
System.out.println(message);
return message;
}
}
使用之前需要导入以下maven依赖
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
@Inject注解实现Bean依赖自动装配的使用方法和Autowired类似,它也可以作用在构造函数、方法和成员变量,这里就不对它的所有使用场景多做讲述了。
四、条件化Bean
假如我们有这样的需求,某个Bean只有在满足一定条件的情况下才会被创建,例如必须在某个Bean存在的条件下才能创建,在Spring5中我们可以使用@Conditional注解实现,他可以和Bean注解一起使用,如果满足条件化注解@Conditional上配置的规则那么创建这个Bean否则放弃创建Bean。
例如我们要创建一个类MemcacheService他必须在容器中MemcacheClient实例存在的情况下才能被创建。以这个例子我们来分析下Spring怎么实现条件化Bean
1)创建MemcachedClient和MemcacheService类
public class MemcacheService {
@Autowired
private MemcachedClient client;
public void saveKey(){
client.saveKey();
}
}
@Service
public class MemcachedClient {
public void saveKey(){
System.out.println("key saved succeed!");
}
}
2)创建一个实现Condition的类封装条件化Bean的规则
public class ConditionalOnExistBean implements Condition{
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return conditionContext.getBeanFactory().getBean(MemcachedClient.class) != null;
}
}
当条件化注解@Conditional设置了ConditionalOnExistBean时,他会检查Spring容器中是否存在MemcachedClient类实例,如果不存在拒绝为被条件化注解修饰的类MemcachedService创建实例,这个也是Spring Boot实现依赖自动化配置采用的方式。
3)创建配置类MemcachedConfig
@Configuration
@ComponentScan(basePackageClasses = {MemcachedClient.class})
public class MemcachedConfig {
@Bean
@Conditional(ConditionalOnExistBean.class)
public MemcachedService memcacheService(){
return new MemcachedService();
}
}
4)创建测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MemcachedConfig.class)
public class MemcachedServiceTest {
@Autowired
private MemcachedService memcachedService;
@Test
public void testExistBean(){
memcachedService.saveKey();
Assert.assertTrue(memcachedService != null);
}
}
测试通过,因为我们设计类的时候把MemcachedClient用@Service注解声明为Spring组件类且在配置类MemcachedConfig中设置扫描类中包含了它,因此它会被Spring识别并为他在容器中创建实例。我们试试去除MemcachedClient的@Service注解这时候因为该类无法被识别Spring容器中就不存在该类实例,这时候Spring在创建MemcachedService之前发现容器中不存在MemcachedClient实例也就不会创建MemcachedService实例。
我们可以通过在Spring项目中自己去实现Condition类定制自己条件化Bean的规则
五、处理自动装配的歧义性
在使用@Autowired注解实现自动装配的过程中如果Spring容器中仅有一个Bean匹配结果时不会有问题,但是如果被@Autowired注解修饰的是一个接口且该接口有多个实现类时Spring在试图自动装配时就无法做出选择,此时Spring会直接抛出一个NoUniqueBeanDefinitionException异常。这解决这种歧义性的时候Spring提供了如下方案。
1 - @Primary注解标识首选Bean
当有多个可选Bean时,通过在bean声明中将其中一个可选Bean设置为首选Bean,这样Spring在自动装配遇到歧义性的时候会首先使用首选Bean。设置Bean为首选Bean的方式包含以下三种
1)在组件扫描的Bean上组合使用@Primary注解与@Component
@Component
@Primary
public class PrimaryMessageService implements MessageService{
@Override
public String printMessage() {
System.out.println("method PrimaryMessageService.printMessage invoke!");
return "PrimaryMessage";
}
}
2)在Java配置类中@Bean与@Primary组合使用
@Configuration
@ComponentScan(basePackageClasses = {MemcachedClient.class})
public class MemcachedConfig {
@Bean
@Primary
public MemcachedService memcacheService(){
return new MemcachedService();
}
}
3)使用XML配置Bean,将bean元素节点的primary属性设置为true
<bean id="messageService" class="com.demo.service.MessageServiceImpl" primary="true" />
2 - 使用限定符限定自动装配
当为我们在Spring项目中错误地为一个接口创建了多个实现类,且将两个以上的实现类声明为首选Bean。此时Spring还是无法做出选择会抛出之前类似的异常。这时候可以通过使用限定符去解决这类歧义性。
Spring中使用限定符的主要方式是@Qualifier,他可以与@Autowired和@Inject注解协同使用,在@Qualifier注解中指定Bean的ID。
@Component
public class UserService {
@Autowired
@Qualifier("primaryMessageService")
private MessageService primaryMessageService;
public String play(){
String message = "UserService "+primaryMessageService.printMessage();
System.out.println(message);
return message;
}
}
3 - 为指定Bean设置限定符
我们可以不一定依赖于将BeanID作为限定符,还可以使用系统提供的@Qualifier注解或者自定义注解为Bean设置限定符
1)@Qualifier注解与@Component或者@Bean协同使用实现Bean限定
@Component
@Qualifier("primaryMessageService")
public class PrimaryMessageService implements MessageService{
@Override
public String printMessage() {
System.out.println("method PrimaryMessageService.printMessage invoke!");
return "PrimaryMessage";
}
}
@Configuration
@ComponentScan(basePackageClasses = {MessageServiceImpl.class, PrimaryMessageService.class, UserService.class})
public class MessageConfig {
@Bean
@Qualifier("pPrimaryMessageService")
public MessageService messageService(){
return new PrimaryMessageService();
}
}
2)使用自定义注解
上述为Bean设置限定符的方式要优于直接基于Bean ID限定但是如果有多个Bean具有相同的特性,我们要根据Bean特性去筛选Bean的话我们可能需要多个限定注解但是Java中不允许在一个类中出现相同的多个注解,因此我们只能自定义多个注解并在注解定义时添加@Qualifier注解让它具有@Qualifier注解的特性,下面是使用示例:
首先创建基于@Qualifier注解创建两个限定符注解
@Component
@TypeQualifier("type1")
@SubTypeQualifier("subtype1")
public class MessageServiceImpl implements MessageService {
@Override
public String printMessage() {
System.out.println("Method printMessage Invoke!");
return "Message";
}
}
@Component
@TypeQualifier("type2")
@SubTypeQualifier("subtype2")
public class PrimaryMessageService implements MessageService{
@Override
public String printMessage() {
System.out.println("method PrimaryMessageService.printMessage invoke!");
return "PrimaryMessage";
}
}
创建测试类测试Bean自定义限定符效果
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MessageConfig.class)
public class MessageServicecTest {
@Autowired
@TypeQualifier("type1")
@SubTypeQualifier("subtype1")
private MessageService messageService;
@Test
public void printUserMessage(){
Assert.assertTrue(messageService.printMessage().indexOf("Message") != -1);
}
}
通过使用自定义限定符注解我们可以绕开Java的限制使用多层次限定符使用多个限定符,相对于原始的@Qualifier和基于Bean ID的限定这种方式无疑更加灵活和类型安全。并且结合条件化注解@Conditional我们可以在非常复杂的业务场景下使用。
六、Bean的作用域
在Spring中Bean默认的作用域时singleton(单例)也就是在整个应用中只会为Bean创建一个实例。Spring中定义了多种作用域如列表所示:
作用域 | 说明 |
Singleton(单例) | 在整个应用中,只创建一个Bean实例 |
Prototype(原型) | 每次注入或者通过Spring上下文获取的时候都会创建一个新的Bean实例 |
Session(会话) | 在Web应用中,为每个会话创建一个Bean实例 |
Request(请求) | 在Web应用中,为每个请求创建一个Bean实例 |
在Spring中如果要为Bean指定其他作用域可以使用@Scope与@Bean或@Component组合使用,使用示例如下:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class MemcachedService {
@Autowired
private MemcachedClient client;
public void saveKey(){
client.saveKey();
}
}
@Configuration
@ComponentScan(basePackageClasses = {MemcachedClient.class})
public class MemcachedConfig {
@Bean
@Scope(value= WebApplicationContext.SCOPE_SESSION,proxyMode = ScopedProxyMode.INTERFACES)
public MemcachedService memcacheService(){
return new MemcachedService();
}
}