Spring管理的组件和Classpath的扫描

在前文描述中使用到的Spring中的Bean的定义,都是通过指定的XML来配置的。而前文中描述的注解的解析则是在源代码级别来提供配置元数据的。在那些例子中,Bean的定义还是在XML中明确的定义的,注解的作用只不过是提供依赖注入。本文将描述Spring通过扫描classpath来自动注册组件。候选的组建就是指一些类能够匹配一些过滤的标准,然后自动注册成为Spring容器之中的Bean。这种方式可以不用在XML中指定Bean的注册配置,而是由开发者使用一些注解(比如@Component),AspectJ类型表达式,或者开发者自定义的过滤标准来将类注册为容器的Bean。

从Spring 3.0开始,Spring的基于JavaConfig的配置提供的特性就属于Spring框架了。开发者可以通过这些特性来使用Java来配置Bean,而不再使用XML文件。可以参考@COnfiguration, @Bean, @Import以及@DependsOn这些注解来了解如何使用这些特性。

@Component和相关的注解

任何的类配置了@Repository注解,都表示这个类属于仓库类(通常就是DAO对象)。

Spring提供了很多类似的注解,包括@Component, @Service以及@Controller等。@Component是一个泛型,代表任何由Spring所管理的组件。而@Repository,@Service以及@Controller都是组件的具体化的一种形式,比如,在持久化上,就会使用@Repository。因此,开发者可以将任何的组件类都声明为@Component,但是如果将其声明为@Repository@Service@Controller可以携带更多的额外的含义。因此,如果开发者选在在服务层可以使用@Service或者@Compponent的话,那么@Service则是更好的选择。

元注解

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组合而成的。

而且,复合注解可以选择性的重新声明注解的属性,开发者可以通过这一特性来进行自定义。这一点在开发者想使用一个元注解某一个属性的子集的时候,尤其高效。比如,Spring的@SessionScope注解就将作用域硬编码为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; }

SessionScope完全可以通过如下代码来使用:

@Service
@SessionScope
public class SessionScopedService {
// ...
}

或者配合使用proxyMode,如下:

@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}

自动注册

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 {
...
}

如果简单的配置的话,可以使用注解的value属性,比如:ComponentScan("org.example")

下面是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>标签了。

classpath的包路径的扫描是于目录结构相关的。当开发者通过Ant来构建Jar包的时候,请确保没有激活JAR任务中的files-only开关。而且,classpath的目录有可能因为安全的策略,在一些环境无法获取。相关问题可以参考:stackoverflow

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

开发者也可以手动取消上面提到的Bean的注册,在包含注解的属性中配置为false即可。

使用过滤器来自定义扫描

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

过滤类型 样例表达式 描述
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。

通过组件定义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等都可以指定。

@Lazy注解出了用在组件的初始化上面,也可以用在注入的地方,比如标记了@Autowired或者Inject的地方。它能够延迟依赖注入的解析。

自动装载实例变量和方法在前文已经有过描述,自动装载也支持识别装载了@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处理或者其他的约束。

开发者可能会将注解了@Bean的方法配置为静态的,来让其他的调用无需创建对象即可执行。者在定义后置处理Bean的时候尤其正常,比如,BeanFactoryPostProcessor或者BeanPostProcessor,因为这一类Bean再容器的生命周期中初始化很早,应该避免触发其他部分的配置。

需要注意的是,调用静态的注解了@Bean的方法在容器中是不会被拦截的,就算是注解了@Configuration的类也是一样。这取决于一些技术的限制,CGLIB自雷只能重写非静态的方法,因此,直接调用令一个注解了@Bean的方法只能有标准的Java语义,也就是直接返回由之调用而产生的对象。

Java语言并不能立刻在Spring容器中看到Bean实例的。开发者也可以在非@Configuration的类中声明静态方法。然而,常规的由@Bean注解的方法是需要被覆盖的,所以不能声明为private或者final

@Bean注解的方法也能够从组件或者配置的积累来获取,在Java 8中,接口注解了@Bean的方法,也是可以的。所以在Java 8中,使用Spring 4.2可以令配置十分灵活。

最后,需要注意的是,一个类可以为同一个Bean来提供多个不同的方法,这样可以在运行时来选择可以使用的Bean。

命名自动注册的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>

关于命名,普遍的原则是,如果其他组件需要明确的指向一个组件的话,最好特指其名字。另一方面来说,自动生成的名字通常就足够容器来装载了。

定义自动注册Bean的作用域

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

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

组件的作用信息可以参考前文Spring中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>

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

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

XML代码如下:

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

通过注解提供限定符

@Qualifier注解在前文Spring自动装载的注解中有所描述。前文中所举的例子是通过使用限定符来控制自动装载的依赖。因为那些例子是基于XML的,限定符元数据等信息是在Bean中使用qualifier或者meta子标签来实现的。当在基于classpath的扫描和自动装载组件的过程中,开发者可以通过在候选的类上来提供限定符元数据。如以下的三个例子:

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

和绝大多数的注解机制类似,注解的元数据是绑定到类的定义上的,而使用XML是允许多个相同类型的Bean来指定限定元数据的,因为那个元数据是基于实例的,而不是基于类的。

Spring核心技术(九)——Spring管理的组件和Classpath扫描的更多相关文章

  1. spring学习九 spring aop详解

    本文来自于:https://www.cnblogs.com/jingzhishen/p/4980551.html AOP(Aspect-Oriented Programming,面向方面编程),可以说 ...

  2. Spring学习(九)-----Spring bean配置继承

    在 Spring,继承是用为支持bean设置一个 bean 来分享共同的值,属性或配置. 一个子 bean 或继承的bean可以继承其父 bean 的配置,属性和一些属性.另外,子 Bean 允许覆盖 ...

  3. 玩转spring MVC(九)---Spring Data JPA

    偷个懒 在网上看有写的比较好的,直接贴个链接吧:http://***/forum/blogPost/list/7000.html 版权声明:本文为博主原创文章,未经博主允许不得转载.

  4. Spring学习(十一)-----Spring使用@Required注解依赖检查

    Spring学习(九)-----Spring依赖检查 bean 配置文件用于确定的特定类型(基本,集合或对象)的所有属性被设置.在大多数情况下,你只需要确保特定属性已经设置但不是所有属性.. 对于这种 ...

  5. Spring IOC之Classpath扫描和管理的组件

    在前面的大部分例子我们使用XML去指明配置数据去定义在Spring容器中的每一个BeanDefinition.上一节我们展示了如何在 代码层注解的方式来提供大量的配置信息.即使在这些例子中,但是,基础 ...

  6. Spring总结九:事务管理机制

    何为事务 事务(Transaction),一般是指要做的或所做的事情.在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit).事务通常由高级数据库操纵语言或编程语言(如SQL ...

  7. Spring 核心技术与产品理念剖析(上)

    IT 技术发展太快了,就像浪潮一样一波接着一波,朝你迎面扑来,稍不留神就会被巨浪卷至海底而不得翻身.我们必须要学会抓住那些不变的本质或规律,只有这样才能屹立潮头而不倒,乘风破浪,做这个巨变时代的弄潮儿 ...

  8. Spring笔记1——Spring起源及其核心技术

    Spring的作用 当我们使用一种技术时,需要思考为什么要使用这门技术.而我们为什么要使用Spring呢?从表面上面SSH这三大框架中,Struts是负责MVC责任的分离,并且提供为Web层提供诸如控 ...

  9. Spring核心技术(八)——Spring自动装载的注解

    本文针对自动装载的一些注解进行描述. 基于注解的容器配置 @Required注解 @Required注解需要应用到Bean的属性的setter方法上面,如下面的例子: public class Sim ...

随机推荐

  1. threading多线程模块

    1 基本实现 Thread(target=函数名,args=(以元组形式传递的实参,要加",")) th = threading.Thread(target=run,args=(i ...

  2. 用eclipse-inst-win64.exe安装eclipse出现Java for Windows Missing 的原因

    Java for Windows Missing 因为jdk的版本没有对,我这里是64位的机器上安了32位的jdk,所以一直报这个. 必须换上相对应版本的jdk,提示页面有链接,直接点击就可以下载. ...

  3. RAID 0、1、5、1+0总结

    RAID(Redundant Array Of Independent Disk,独立磁盘冗余阵列),可以提供比普通磁盘更快的速度.更高的安全性,生产环境中服务器在安装时一般都会做RAID,RAID的 ...

  4. EmitMapper系列之二:EmitMapper的使用小结

    EmitMapper的入门 EmitMapper引用 EmitMapper案例 最近公司开发项目前端使用一个js框架,后端使用ef,js前台读取的json采用实体的dto来进行生成. 在网上看到了Em ...

  5. 基于pymysql模块的增删改查

    上课笔记 重点:(熟练)多表查询创建存储过程原生sql索引原理 pymysql 封装好的客户端cursor 底层就是一个send操作commit 告诉mysql真的要完成修改操作(不然修改不会生效)e ...

  6. poj1724 ROADS

    题意: N个城市,编号1到N.城市间有R条单向道路.每条道路连接两个城市,有长度和过路费两个属性.Bob只有K块钱,他想从城市1走到城市N.问最短共需要走多长的路.如果到不了N,输出-12<=N ...

  7. jQuery选择器之表单元素选择器

    <!DOCTYPE html> <html> <head> <meta http-equiv="Content-type" content ...

  8. IOS问题

    #import "EXFifthViewController.h" @interface EXFifthViewController () @end @implementation ...

  9. Summary of 2016 International Trusted Computing and Cloud Security Summit

    1)      Welcome Remarks 2)      The advancement of Cloud Computing and Tursted Computing national st ...

  10. 读取Chrome书签文件

    使用C#读取Chrome浏览器的本地书签文件,当前文件在C盘下用户文件夹\AppData\Local\Google\Chrome\User Data\Default\下的Bookmarks 打开这个文 ...