(13)Spring管理的组件和Classpath扫描

1.@Component和相关的注解

Spring提供了很多类似的注解,包括@Component, @Service以及@Controller,@Repository等,@Component是一个泛型,代表任何由Spring所管理的组件

  • @Repository (一般用来表示持久化层)
    +@Service (一般表示业务层)
  • @Controller(一般表示控制层)

2.元注解

Spring提供的很多注解都可以在开发者的代码中作为元注解。元注解,就是可以简单的用到另一个注解之上的注解。比如,@Service注解就是通过@Component构成的元注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // Spring will see this and treat @Service in the same way as @Component
public @interface Service {

    // ....
}

元注解可以组合起来创建复合注解。举例来说,SpringMVC中的@RestController注解就是一个符合的注解,是通过@Controller和@ResponseBody组合而成的。

3.自动注册

Spring可以自动检测模板类,然后将其注册成为Bean,放到ApplicationContext之中,由其管理。比如,如下的代码就符合自动注册的条件。

@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

}
@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}

为了令诸如上面的类能够自动注册成为Bean,你需要在配置有@Configuration的类中增加@ComponentScan注解,在其中配置basePackages属性,也代表将要扫描的包。(当然,开发者也可以根据逗号/分号/空格之类的分隔符来配置多个包)。

@Configuration
@ComponentScan(basePackages = "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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example"/>

</beans>

明确的使用<context:component-scan>会含蓄的包含<context:annotation-config>的功能。所以在使用<context:component-scan>的时候一般就不需要再写<context:annotation-config>标签了。

此外,AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor在使用组件扫描元素的时候,都会被明确包含的。也就是说,这两个组件也会自动检测和装载,而不需要在XML中进行配置。

4.使用过滤器来自定义扫描

默认情况下,所有的注解了诸如@Component,@Repository,@Service,@Controller以及一些自定义的使用@Component元注解的组件,都会被注册到Spring的ApplicationContext之中。然而,开发者可以通过使用过滤器来扩展和修改这个行为。在@ComponentScan注解中增加includeFilters或者excludeFilters(或者是在XML的配置中写入include-filter或者exclude-filter的子标签)。每一个过滤器的元素都要求type和expression两个属性。下表中描述了这些过滤的选项:

过滤类型 样例表达式 描述
annotation(默认) org.example.SomeAnnotation 目标组件在使用的注解类型
assignable org.example.SomeClass 目标组件可以扩展或者实现的类
aspectj org.example..*Service+ 目标组件匹配的AspectJ表达式
regex org.example.Default.* 目标组件匹配的正则表达式
custom org.example.MyTypeFilter 一个自定义的实现了org.springframework.core.type.TypeFilter的类

下面的例子展示了配置会无视@Repository注解,而是通过stub的仓库类代替。

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

开发者可以去使能默认的过滤器,Java Config的话,就配置useDefaultFilters=false,如果是XML的话,就在<component-scan/>标签中配置use-default-filters="false"。这些配置会使配置了@Component,@Repository,@Service,@Controller或者@Configuration的类不会自动注册为bean

5.通过组件定义Bean元数据

Spring组件也能够在Bean的元数据配置上起作用。开发者可以通过在配置了@Configuration的类中使用@Bean注解来定义元数据,代码如下:

@Component
public class FactoryMethodComponent {

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    public void doWork() {
        // Component method implementation omitted
    }

}

上面的类是一个Spring的组件,在容器中有着应用功能的方法doWork(),然而,也提供了一个工厂方法publicInstance()来构造一个Bean实例@Bean注解能够识别工厂方法和其他的Bean属性,比如通过@Qualifier注解来知道限定符的值。其他的方法级别的注解,比如@Scope,@Lazy等都可以指定。

自动装载也支持识别装载了@Bean方法的Bean的:

@Component
public class FactoryMethodComponent {

    private static int i;

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    // use of a custom qualifier and autowiring of method parameters

    @Bean
    protected TestBean protectedInstance(
            @Qualifier("public") TestBean spouse,
            @Value("#{privateInstance.age}") String 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);
    }

}

上面的例子装载了String类型的方法参数到一个名为privateInstance的Bean的Age属性中。Spring表达式语言元素通过#{ <expression> }来定义属性的值。而@Value注解,令表达式的解析器在配置之前就开始查找名字匹配的Bean

在Spring的处理注解了@Bean的方法时,在组件中的方法和在@Configuration类中的方法处理是不同的。不同之处就在于@Component注解的类是没有通过CGLIB增强来拦截方法和实例的调用的,CGLIB代理就是在@Configuration注解的类创建Bean引用和链接对象的方法。这些方法的调用不是通过Java语义调用的,而是通过容器按照顺序提供正常的生命周期管理和代理Spring的Bean注入。相反,在@Component注解的类是通过标准Java语义来注入和引用Bean的,而没有CGLIB处理或者其他的约束

6.bean的自动命名规则

当组件通过扫描的过程来实现自动注册的时候,该Bean的名字是通过BeanNameGenerator来生成的。默认情况下,任何Spring的组件标签(@Component,@Repository,@Service以及@Controller)都是包含一个名字的value的,这个值就是Bean所相关联的名字。

如果注解不包含名字的value的话,默认的名字生成器会返回小写的非限制的类名。比如说,两个组件按照如下代码自动注册的话,名字会分别为myMovieLister以及movieFinderImpl:

@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}

@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

如果开发者不希望名字依赖于默认的Bean名字生成器,开发者也可以自定义一个Bean名字生成的策略。首先,实现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>

8.定义自动注册Bean的作用域

Spring管理的组件,默认是都会被注册为singleton的作用域的。然而,如果开发者需要一个不同的作用域的话,可以通过使用@Scope注解来指定。代码如下:

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

如果不想通过使用注解的方法来修改作用域的话,可以考虑实现ScopeMetadataResolver接口,包含一个默认的无参构造函数,就可以通过类名来配置扫描器了:

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

当使用非单例的作用域时,对于那些Bean是需要使用代理的,scopedProxy属性也是需要在扫描器上使用的。其中包括三个可选的值,分别是no,interfaces以及targetClass。比如说,下列配置会使用JDK动态代理:

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

9.通过注解提供限定符

限定符元数据等信息是在Bean中使用qualifier或者meta子标签来实现的。当在基于classpath的扫描和自动装载组件的过程中,开发者可以通过在候选的类上来提供限定符元数据。如下:

@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}

10.自动生成候选主键的索引

虽然类路径扫描非常快,但是通过在编译时创建一个候选的静态列表,可以提高大型应用程序的启动性能。在这种模式下,应用程序的所有模块都必须使用这种机制,因为当ApplicationContext检测到这样的索引时,它将自动使用它而不是扫描类路径

为了生成索引,每个模块需要一个额外的依赖

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

这个过程会在这个jar中生成一个META-INF/spring.components的文件

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容