《Spring5学习》 01 装配Bean之自动化装配

    Spring的自动化装配就便利性方面远远优于其余装配方法,这也是业界目前主要采用的Bean装配机制。Spring基于组建扫描和自动装配实现自动化装配,能将用户的显示配置降到最低。如下经过一段代码了解自动装配的基本使用(楼主关于Spring5讲解的全部的代码示例均是在一个Spring Boot项目)。java

1.建立组件Beanspring

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自动装配maven

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会在上下文容器中找到该类的实例并将它注入进去。ide

 

1、基于注解为Spring Bean命名

    Spring上下问容器的全部Bean都会有一个ID,若是没有设置,默认是将类名的第一个字符替换为小写以后的字符串做为ID,若是想本身命名Bean ID,能够在Component注解中指定memcached

@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上下文容器都以相同的方式存在。函数

2、设置组件扫描的基础包或类

    在上述例子中咱们经过在配置类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 {
}

 

3、自动化装配注解

    Spring5实现自动化装配的注解有三种,他们是Autowired,Inject,Namedui

1 - 使用@Autowired注解实现自动化装配

    @Autowired是Spring自带的注解,下面是Spring5中该注解的源码this

@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相似,它也能够做用在构造函数、方法和成员变量,这里就不对它的全部使用场景多作讲述了。

4、条件化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的规则

5、处理自动装配的歧义性

    在使用@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咱们能够在很是复杂的业务场景下使用。

 

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

}
相关文章
相关标签/搜索