Spring学习-Spring核心技术(二)


读Spring框架官方文档记录。由于注解比较常用且比较杂,单拎出来

1. 注解简介

  • 通过在相关类、方法或字段声明上使用注解,将配置转移到组件类本身而不是在XML文件中。
  • 注解 vs XML配置
    注解:配置更短更简洁但是变得分散难以控制;
    XML:在擅长在不改动源码或者重新编译的情况下连接组件,相对复杂。
  • 注解注入在XML注入之前执行,因此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"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>

< context:annotation-config/>向Spring容器中注册 AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor, RequiredAnnotationBeanPostProcessor这4个BeanPostProcessor。注册这4个BeanPostProcessor的作用,就是为了你的系统能够识别相应的注解。

2. 注入相关注解

(1) @Required

表示必须通过bean定义中的显式属性值或自动装配来填充受影响的bean属性。如果未填充受影响的属性,容器将会抛出异常应用于bean属性的setter方法,举例如下:

public class SimpleMovieLister {
    private MovieFinder movieFinder;
    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
    // ...
}

注意:从Spring Framework 5.1开始,@Required注释正式被弃用,支持使用构造函数注入来进行必需的设置(或者使用InitializingBean.afterPropertiesSet()的自定义实现以及bean属性setter方法)。

(2) @Autowired

1) 将@Autowired应用于构造函数、setter方法、具有任意名及参数的方法、属性上
public class MovieRecommender {
    private final CustomerPreferenceDao customerPreferenceDao;
    private MovieCatalog movieCatalog;
    private CustomerPreferenceDao customerPreferenceDao;
    //应用在构造函数上,从Spring 4.3开始,如果只有一个构造函数,则不需要在构造函数上加@Autowired,如果有多个构造函数且没有默认构造函数,就需要在其中一个构造函数上使用@Autowired来指定容器使用那个构造函数。
    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }
    //应用在setter方法上
	@Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
    //应用在任意名的函数上
    @Autowired
    public void prepare(MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }
    //应用在属性上(这里举例,代码不规范)
    @Autowired
    private MovieCatalog movieCatalog;
    // ...
}
2) @Autowired用在数组以及Set、Map集合上
  • 对于数组、Set以及Map至少需要匹配上一个元素,否则会注入失败。
  • 如果是数组,则获取数组元素类型,查找匹配该类型的所有bean,返回一个这些bean的数组;如果该类可赋给Collection,并且是一个接口,则获取集合元素类型,查找匹配该类型的所有bean,返回一个这些bean的集合;如果该类型是Map(注意是type == Map.class),且key是String类型,则获取Map的value的类型,查找匹配该类型的所有bean,这是一个key为bean name、value为bean实例的一个Map,返回这个Map。
  • 如果想让生成的集合或者数组中的元素按照一定的顺序排序,则可以让bean实现Ordered接口或者使用@Order/@Priority注解来实现,如果不设置顺序,会按照bean注册的顺序排列。
  • @Order可以和@Bean一起使用,其值影响注入顺序,但是不影响单例启动顺序(由依赖关系决定)。@Priority不可和@Bean,因为它不能在方法上使用,一起使用。
    使用举例如下:
public class MovieRecommender {
	//应用于数组
    @Autowired
    private MovieCatalog[] movieCatalogsArray;
    //应用于Set
    private Set<MovieCatalog> movieCatalogsSet;
    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogsSet = movieCatalogs;
    }
    //应用于Map
    private Map<String, MovieCatalog> movieCatalogsMap;
    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogsMap = movieCatalogs;
    }
    // ...
}

设置当依赖项不可用的时候,容器跳过该依赖,而不是报错,设置Autowired中的required属性为false。

public class SimpleMovieLister {

    private MovieFinder movieFinder;
    @Autowired(required = false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
    // ...
}

注意:构造函数上@Autowired有不同的含义,表示该构造函数事容器可能选择的一个候选构造函数。只有一个构造函数能够被声明@Autowired(required=true),作为自动装配的默认构造函数。只有一个构造函数能被声明为@Autowired(required=true),如果有多个构造函数声明@Autowired,那么必须所有的的required都设置为false,容器将选择满足依赖关系最多的构造函数。如果没有候选的构造函数满足,则容器会选择默认的构造函数。如果一个类有多个构造函数,但是都没有声明@Autowired,容器也会选择默认的构造函数。如果只有一个构造函数,即使没有声明@Autowired,这个构造函数也会被选择。
也可以通过Optional表示非required性质,使用举例如下:

public class SimpleMovieLister {
    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}

Spring框架5.0下,使用@Nullable注解,表示非空的特性

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}
3) @Autowired可以用在接口上

如ApplicationContext,BeanFactory,Environment,ResourceLoader,ApplicationEventPublisher及MessageSource。他们的扩展接口如ConfigurableApplicationContext or ResourcePatternResolver也可以使用。

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;
    public MovieRecommender() {}
    // ...
}

(3) @Primary

由于根据类型进行自动装配经常会有很多个满足条件的候选bean,可以通过在某个bean上指定@Primary,当多个候选bean出现时,该bean被选择。

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }
    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}
public class MovieRecommender {
	//firstMovieCatalog被选择
    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}

对应的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"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog" primary="true">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>

(4) @Qualifier

  • 可以将@Qualifier与特定的参数相连,缩小类型匹配的候选集合
public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog1;
    // ...
    //在构造函数或者方法参数上使用
    private CustomerPreferenceDao customerPreferenceDao;
     @Autowired
    public void prepare(@Qualifier("main") MovieCatalog movieCatalog2,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }
}

对应的bean定义

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="main"/> <!--注入的时候,会注入到movieCatalog1-->
        <!-- inject any dependencies required by this bean -->
    </bean>
    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/> <!--注入的时候,会注入到movieCatalog2-->

        <!-- inject any dependencies required by this bean -->
    </bean>
    <bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
  • @Qualifier可以用在集合上,如将@Qualifier(“main”)用于多个bean定义,则多个bean定义可作为一个集合注入到Set< type>中。
    如果想通过名称匹配来进行注入,则不要使用@Autowired,而是使用@Resource,就能通过名称进行匹配,但是@Resource只支持在域或者有单个参数的setter方法上使用。
  • 可以自己定义@Qualifier注解
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
    String value();
}

定义完成后可使用

public class MovieRecommender {

    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;
    private MovieCatalog comedyCatalog;

    @Autowired
    public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
        this.comedyCatalog = comedyCatalog;
    }
    // ...
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="Genre" value="Action"/><!--type后面跟的是自定义的注解的类名,如果类名不存在重名的情况下-->
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="example.Genre" value="Comedy"/><!--type后面跟的是自定义的注解的全限定类名-->
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

使用@Qualifier时,可以包含一个参数如上,也可以不包含参数或包含多个参数
不包括参数(注解用于更通用的目的)

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {

}
//使用上面的定义
public class MovieRecommender {

    @Autowired
    @Offline 
    private MovieCatalog offlineCatalog;

    // ...
}
<bean class="example.SimpleMovieCatalog">
    <qualifier type="Offline"/> <!--这里不需要value属性-->
    <!-- inject any dependencies required by this bean -->
</bean>

多个参数

//定义
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
    String genre();
    Format format();
}
public enum Format {
    VHS, DVD, BLURAY
}
//使用
public class MovieRecommender {

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Action")
    private MovieCatalog actionVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Comedy")
    private MovieCatalog comedyVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.DVD, genre="Action")
    private MovieCatalog actionDvdCatalog;

    @Autowired
    @MovieQualifier(format=Format.BLURAY, genre="Comedy")
    private MovieCatalog comedyBluRayCatalog;

    // ...
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Action"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Comedy"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>
    <!--如果找不到<qualifier选项,就会找meta选项-->
    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="DVD"/>
        <meta key="genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="BLURAY"/>
        <meta key="genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>
</beans>

(5) 使用泛型作为自动装配中的@Qualifier

@Configuration
public class MyConfiguration {
    @Bean
    public StringStore stringStore() {
        return new StringStore();
    }

    @Bean
    public IntegerStore integerStore() {
        return new IntegerStore();
    }
}

//在使用中(Store是通用的接口,定义为Store<T>)
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean
@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
//用于数组或者List、Map也是可以自动装配的
@Autowired
private List<Store<Integer>> s;

(6) CustomAutowireConfigurer

是一个BeanFactoryPostProcessor,允许方便的注册自定义Autowire限定符类型,即使没有@Qualifier注解。

<bean id="customAutowireConfigurer"
        class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>
//定义注解
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
                                 //不再使用@Qualifier自定义修饰注解CustomQualifier
public @interface CustomQualifier{ ... }

AutowireCandidateResolver通过以下方式决定自动装载的候选集:

  • bean定义中的autowire-candicate值
  • < bean/>中的default-autowire-candicate值
  • @Qualifier注解以及通过CustomAutowireConfigurer进行注册的自定义注解
    在有多个bean满足条件时,如果有bean的primary属性被设置为true,则选择该bean。

(7) @Resource

在属性或者setter方法上的注入可使用@Resource(这里注意与@Autowire的区别,@Autowire能注入的类型更多,且根据类型进行注入)。@Resource是根据name进行注入的,@Resource有name属性,默认情况下,Spring将name属性的值与bean的name进行匹配。

public class SimpleMovieLister {

    private MovieFinder movieFinder;
    @Resource(name="myMovieFinder") 
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

如果没有指定指定name,则name的值来源于属性名或者setter方法。如果是属性,则name值就是属性名;如果是Setter方法,使用Setter方法参数名作name值。

public class SimpleMovieLister {

    private MovieFinder movieFinder;
    @Resource<!--名为movieFinder的bean会被注入setter方法-->
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

注意:在@Resource没有显式指定name属性的情况下,@Resource首先会根据属性名或者方法名来查找指定的bean,如果没有查找到,则认就会按照类型来查找满足条件的一个bean。

(8) @Value

主要用于用于从外部(如文件中)进行属性注入。

@Component
public class MovieRecommender {

    private final String catalog;
    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}
//想上面的@Value能够起作用则要进行如下配置,其中application.properties是一个文件,文件中配置了catalog.name=xxxx。或者在xml中进行<context:property-placeholder location="classpath:application.properties"/>的配置
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }

注意:如果Spring解析不到属性的值,如${catalog.name},属性名将会被注入为属性值。如果在解析失败的时候不将属性名注入为属性值,则需要将bean申明为PropertySourcesPlaceholderConfigurer bean。

@Configuration
public class AppConfig {
	//此处的方法必须为static
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
          return new PropertySourcesPlaceholderConfigurer();
    }
}

这样配置解析失败后,Spring就会初始化失败。可以通过setPlaceholderPrefix, setPlaceholderSuffix, or setValueSeparator自定义解析失败后的默认值。也可以通过如下方式提供默认值:

@Component
public class MovieRecommender {
    private final String catalog;
    public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
        this.catalog = catalog;
    }
}

配置文件中配置的属性值一般为字符串,如何将字符串转化为想要的类型(如Int及自定义的类型),Spring中支持简单的转化,如转int,如果想转化自己定义的类型,可创建自己ConversionService bean实例来进行处理

@Configuration
public class AppConfig {

    @Bean
    public ConversionService conversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        conversionService.addConverter(new MyCustomConverter());
        return conversionService;
    }
}

当@Value包含SpEL表达式(后面会学习)时,能够在运行时自动转换。举例如下

@Component
public class MovieRecommender {

    private final String catalog;
    public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
        this.catalog = catalog;
    }
	
	//更加复杂的配置
	private final Map<String, Integer> countOfMoviesPerCatalog;
    public MovieRecommender(
            @Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
        this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
    }
}

(9) @PostConstruct及@PreDestroy

用于生命周期回调中的初始化回调和销毁回调。举例如下:

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // populates the movie cache upon initialization...
    }

    @PreDestroy
    public void clearMovieCache() {
        // clears the movie cache upon destruction...
    }
}

3. bean定义相关注解

(1) @Component,@Repository,@Service及@Controller

Spring提供了构造性注解:@Component,@Repository,@Service及@Controller。其中@Component是所有Spring管理的组件的通用构造性注解。@Repository,@Service及@Controller的用法更加具体化,@Repository用于持久层,@Service用于服务层,@Controller用于表示层。组件类可以通过@Component来注解,但是通过@Repository,@Service及@Controller来注解更加专业化,使得组件更适合通过工具进行处理,比如这些组件更适合作为切入点。

(2) 元注解及组合注解

Spring提供了很多可以直接使用的注解,元注解就是可以应用于其它注解的胡姐。比如@Service由元注解@Component组成。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component 
public @interface Service {
    // ...
}

可以组合元注解区创建组合注解,比如@RestController注解是由@Controller及@ResponseBody组成。组合注解可以选择性的声明元注解的属性以实现定制化,当只想使用元注解的部分属性时就可以这样做。如@SessionScope注解硬编码属性name为session,但是属性proxyMode可以定制化,如下所示:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {

    /**
     * Alias for {@link Scope#proxyMode}.
     * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
     */
    @AliasFor(annotation = Scope.class)
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
//可以进行如下使用
@Service
@SessionScope
public class SessionScopedService {
    // ...
}
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
    // ...
}

(3) 自动检测类并注册bean定义

Spring容器能够自动检测构造性类并进行对应的bean定义实例注册。如下所示:

@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

为了实现自动检测,需要在加了@Configuration的配置类上面增加@ComponentScan,其basePackages属性应该是两个类的通用父包,或者也可以使用逗号,分号或者空格将不同类的父包隔开。举例如下:

@Configuration
@ComponentScan(basePackages = "org.example")//也可将basePackages简化不要,@ComponentScan("org.example")
public class AppConfig  {
    // ...
}

对应的等价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"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example"/><!--隐含了<context:annotation-config>,因此不用再配置这项。同时也隐含了AutowiredAnnotationBeanPostProcessor以及CommonAnnotationBeanPostProcessor,当然可以通过配置annotation-config为false将这两个功能禁用-->

</beans>

(4) 使用过滤器进行定制化扫描

默认由@Component, @Repository, @Service, @Controller, @Configuration及包含@Component的自定义注解注解的类是Spring的候选检测组件。但是可以通过自定义过滤器进行修改或者扩展。如下所示:

// 排除@Repository注解的类,增加名字匹配.*Stub.*Repository的类
@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    ...
}

等价的xml配置如下:

<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex"
                expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>

如上面例子所示,完成自定义过滤器可设置@ComponentScan的includeFilters或excludeFilters属性或者使用<context:component-scan>标签的
<context:include-filter />或<context:exclude-filter />的子标签来实现,其中使用子标签需要设置type及expression两个属性。下表描述了过滤器的以下选择:

过滤器类型expression举例相关说明
annotation(默认)org.example.SomeAnnotation排除或者增加的注解
assignableorg.example.SomeClass排除或者增加的类或接口
aspectjorg.example…*Service+排除或者增加的匹配AspectJ表达式的类
regexorg.example.Default.*排除或者增加的匹配正则表达式的类
customorg.example.MyTypeFilter排除或者增加的实现TypeFilter接口

也可通过设置<component-scan/>标签的use-default-filters="false"禁用所有默认的过滤器。

(5) 在Component内部的bean定义

@Component
public class FactoryMethodComponent {

    @Bean //表示工厂方法和其它bean定义属性,除了@Qualifier,还可以添加@Lazy(延迟注入)等。
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    public void doWork() {
        // Component method implementation omitted
    }
    // use of a custom qualifier and autowiring of method parameters
    @Bean
    protected TestBean protectedInstance(
            @Qualifier("public") TestBean spouse,
            @Value("#{privateInstance.age}") String country) {//@Value将privateInstance的age属性值注入给country
        TestBean tb = new TestBean("protectedInstance", 1);
        tb.setSpouse(spouse);
        tb.setCountry(country);
        return tb;
    }
    @Bean
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }

    @Bean
    @RequestScope
    public TestBean requestScopedInstance() {
        return new TestBean("requestScopedInstance", 3);
    }
}

从Spring 框架4.3开始,可以将上述工厂方法的参数声明为InjectionPoint类型,以访问触发当前bean创建的请求注入点。举下面的例子以理解这句话的意义:

//有一个类Test
@Component
public class Test{
	private TestBean testBean;
	@Autowired
	public Test(TestBean testBean){
		this.testBean=testBean;
	}
	public int getMember(){
		//……
	}
}

@Component
public class FactoryMethodComponent {

    @Bean @Scope("prototype")
    public TestBean prototypeInstance(InjectionPoint injectionPoint) {
        return new TestBean("prototypeInstance for " + injectionPoint.getMember());
    }
}

上述Test类在创建时,会自动注入TestBean,这时,TestBean会知道是由Test完成注入的,即注入点在Test。可以调用
注意:这样的应用只有实际在创建bean实例的时候有用,而不是在注入已经存在的实例时有用,因此,只对原型作用域的的bean有用。
注意:

  • @Bean用在Spring组件和其用在@Configuration类中的处理方法是不同的。Spring没有通过CGLIB来增强@Component类以拦截方法和子字段的调用。CGLIB代理即为通过调用@Configuration类中@bean方法中的方法或者域来创建协作对象的bean元数据引用。为了来提供Spring bean的常见生命周期管理和代理,这些方法不是通过普通的Java调用的而是通过容器,即使在通过编程调用@Bean方法引用其他bean时也是如此。相反,在普通的@Component类中调用@Bean方法中的方法或字段则通过普通的Java方式,没有特殊的CGLIB处理或应用其他约束。
  • 可以将@bean的方法声明为static,这样就可以在不创建配置类的实例的情况下调用。这在定义后处理器bean(如:BeanFactoryPostProcessor或BeanPostProcessor)时特别有用,因为这样bean就会在容器生命周期的早期被初始化,此时应该避免配置类的其它部分被初始化。
  • 调用静态的@Bean方法不会被容器拦击,即使是在@Configuration类中,因为CGLIB子类只能覆盖非静态方法。
  • @Bean方法的Java语言可见性不会对Spring容器中生成的bean定义产生直接影响。可以在非@Configuration类中自由声明工厂方法,也可以在任何地方声明静态方法。然而,@Configuration类中的常规@Bean方法需要是可覆盖的,也就是说,它们不能被声明为private或final。
  • @Bean方法可以在给定组件类或者配置类的基类中使用。
  • 一个类中可有多个@Bean方法,会在运行时进行选择。

(6) 命名自动扫描的组件

当一个组件被扫描过程自动扫描到,它的bean名字一般由BeanNameGenerator策略产生。默认情况下,任何包含value值的原生注解(@Component, @Repository, @Service, and @Controller)都应该将该值提供给bean定义使用。如果没有value值,默认的bean名称生成器返回非限定的类型,且第一个首字母小写。

//生成bean名称为myMovieLister
@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}
//生成的名称为movieFinderImpl
@Repository 
public class MovieFinderImpl implements MovieFinder {
    // ...
}

如果不想使用默认的bean命名策略,可以提供自定义的bean命名celue。需要实现BeanNameGenerator接口,需包含默认的无参构造,然后在配置扫描器的时候提供完全限定的类名,如下所示:

//配置扫描器
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
    // ...
}

等价的xml配置

<beans>
    <context:component-scan base-package="org.example"
        name-generator="org.example.MyNameGenerator" />
</beans>

(7) 给自动检测的组件提供一个作用域

默认是单例,如果想更改可使用@Scope。

@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

注意:@Scope注解只在具体的bean类(带注解的组件)或是工厂方法(@Bean方法)上使用。
要实现自定义策略的作用域解析,可以实现ScopeMetadataResolver接口,并确定包含默认的无参构造,然后在配置扫描器时提供全限定的类名即可。

//配置扫描器
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
    // ...
}

等价的xml配置

<beans>
    <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>

当使用某些非单例作用域时,可能需要为作用域对象产生代理。为此,component-scan属性上可使用scopedProxy属性。该属性有三个值:no, interfaces, and targetClass。

@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
    // ...
}

等价的xml配置

<beans>
    <context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>

(8) 为注解提供限定元数据

举例如下:

@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
//利用@Qualifier自定义注解,可参考2(4)
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
//利用@Qualifier自定义无参数注解,可参考2(4)
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
    // ...
}

(9) 生成候选组件的索引

虽然类路径扫描非常快,但通过在编译时创建一个静态候选列表,可以提高大型应用程序的启动性能。在这种模式下,所有组件扫描的目标模块都必须使用这种机制。当容器检测到候选组件的索引时,就可以自动的使用它而不是去进行类路径扫描。
为了生成这样的索引,需要为每个需要被扫描的目标模块增加下面所示的maven依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>5.3.2</version>
        <optional>true</optional>
    </dependency>
</dependencies>

在Gradle4.5以及更早的例子中,依赖应该声明在compileOnly配置中:

dependencies {
    compileOnly "org.springframework:spring-context-indexer:5.3.2"
}

在Gradle4.6以及后面的例子中,依赖应该声明在annotationProcessor配置中:

dependencies {
    annotationProcessor "org.springframework:spring-context-indexer:{spring-version}"
}

处理过程会在jar文件中产生META-INF/spring.components文件。
注意:在IDE中使用这种模式时,spring-context-indexer必须注册为注解处理器,以确保在候选组件更新时索引是最新的。
注意:当类路径中发现META-INF/spring.components时,索引将自动启用。如果某个索引对于某些库(或用例)是部分可用的,但不能为整个应用程序构建,那么可以通过设置spring.index.ignore为true退回到常规的类路径安排(就好像根本没有索引一样)。

4. JSR 330标准注解

这些注解被扫描的方式与Spring注解相同。要使用这些注解,在类路径中要有相关的jar包,如果使用maven,可在pom.xml中添加如下依赖:

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

(1) 使用@Inject以及@Named进行依赖注入

类似于@Autowired,可以使用@Inject:

import javax.inject.Inject;

public class SimpleMovieLister {
    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    public void listMovies() {
        this.movieFinder.findMovies(...);
        // ...
    }
}

@Inject可用在属性上、方法上以及构造函数上。使用@Inject可以将注入点声明为一个Provider,使得能够通过调用Provider.get()在更小的范围内按需访问beans或延迟调用bean,使用举例如下:

import javax.inject.Inject;
import javax.inject.Provider;

public class SimpleMovieLister {

    private Provider<MovieFinder> movieFinder;

    @Inject
    public void setMovieFinder(Provider<MovieFinder> movieFinder) {
        this.movieFinder = movieFinder;
    }

    public void listMovies() {
        this.movieFinder.get().findMovies(...);
        // ...
    }
}

如果想为要注入的依赖项使用限定名,则可以使用@Named注解,如下所示:

import javax.inject.Inject;
import javax.inject.Named;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

类似于@Autowired可以使用Optional及@Nullable,@Inject也可以使用,由于其没有required属性,这儿应用这两个可能更经常

public class SimpleMovieLister {

    @Inject
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        // ...
    }
}

public class SimpleMovieLister {

    @Inject
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        // ...
    }
}

(2) 使用@Named/@ManagedBean:等价于@Component注解

import javax.inject.Inject;
import javax.inject.Named;

@Named("movieListener")  // @ManagedBean("movieListener")起同样的作用,当然也可以直接@Named/@ManagedBean类似于直接使用@Component
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

当使用@Named/@ManagedBean时,可以和使用@Component同样的方式来扫描组件

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    // ...
}

注意:@Named以及@ManagedBean不可进行组合。

(3) JSR标准注解的局限性

下表时Spring注解和JSR标准注解的对比:

Spring注解名JSR注解名JSR注解的限制
@Autowired@Inject@Inject没有required属性,但是可用Java8中的Option来代替
@Component@Named/@ManagedBean不能进行注解组合来自定义注解
@Scope(‘singleton’)@SingletonJSR330默认的作用域类似于prototype。为了与Spring保持一致,在Spring容器中声明的JSR-330 bean默认是一个singleton作用域
@Qualifier@Qualifier/@NamedJSR330中的@Qualifier仅仅是为了建立自定义注解而存在的,Spring中的@Qualifier与@Named对应
@Value-无对应
@Required-无对应
@Lazy-无对应
ObjectFactoryProviderProvider是ObjectoryFactory的一个代替,拥有更短的get()方法名,它还可以与Spring的@Autowired或与未注解的构造函数和setter方法结合使用。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值