Spring核心技术(九)——Spring管理的组件和Classpath扫描
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
此外,AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
在使用组件扫描元素的时候,都会被明确包含的。也就是说,这两个组件也会自动检测和装载,而不需要在XML中进行配置。
开发者也可以手动取消上面提到的Bean的注册,在包含注解的属性中配置为false即可。
使用过滤器来自定义扫描
默认情况下,所有的注解了诸如@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。
通过组件定义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扫描的更多相关文章
- spring学习九 spring aop详解
本文来自于:https://www.cnblogs.com/jingzhishen/p/4980551.html AOP(Aspect-Oriented Programming,面向方面编程),可以说 ...
- Spring学习(九)-----Spring bean配置继承
在 Spring,继承是用为支持bean设置一个 bean 来分享共同的值,属性或配置. 一个子 bean 或继承的bean可以继承其父 bean 的配置,属性和一些属性.另外,子 Bean 允许覆盖 ...
- 玩转spring MVC(九)---Spring Data JPA
偷个懒 在网上看有写的比较好的,直接贴个链接吧:http://***/forum/blogPost/list/7000.html 版权声明:本文为博主原创文章,未经博主允许不得转载.
- Spring学习(十一)-----Spring使用@Required注解依赖检查
Spring学习(九)-----Spring依赖检查 bean 配置文件用于确定的特定类型(基本,集合或对象)的所有属性被设置.在大多数情况下,你只需要确保特定属性已经设置但不是所有属性.. 对于这种 ...
- Spring IOC之Classpath扫描和管理的组件
在前面的大部分例子我们使用XML去指明配置数据去定义在Spring容器中的每一个BeanDefinition.上一节我们展示了如何在 代码层注解的方式来提供大量的配置信息.即使在这些例子中,但是,基础 ...
- Spring总结九:事务管理机制
何为事务 事务(Transaction),一般是指要做的或所做的事情.在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit).事务通常由高级数据库操纵语言或编程语言(如SQL ...
- Spring 核心技术与产品理念剖析(上)
IT 技术发展太快了,就像浪潮一样一波接着一波,朝你迎面扑来,稍不留神就会被巨浪卷至海底而不得翻身.我们必须要学会抓住那些不变的本质或规律,只有这样才能屹立潮头而不倒,乘风破浪,做这个巨变时代的弄潮儿 ...
- Spring笔记1——Spring起源及其核心技术
Spring的作用 当我们使用一种技术时,需要思考为什么要使用这门技术.而我们为什么要使用Spring呢?从表面上面SSH这三大框架中,Struts是负责MVC责任的分离,并且提供为Web层提供诸如控 ...
- Spring核心技术(八)——Spring自动装载的注解
本文针对自动装载的一些注解进行描述. 基于注解的容器配置 @Required注解 @Required注解需要应用到Bean的属性的setter方法上面,如下面的例子: public class Sim ...
随机推荐
- 一些CSS的备忘
text-transform 文本转换 属性值是 none表示没有 不转换 同时也是默认的 capitalize 表示首字母大写 uppercase全部转换为大写 lowercase全部转为小写 te ...
- 贪心+拓扑排序 AOJ 2456 Usoperanto
题目传送门 题意:给出一条链,比如x连到y,x一定要在y的左边,且代价是这条链经过的点的权值和,问如何排序使得代价最小 分析:类似拓扑排序,先把入度为0的点入队,把指向该点的所有点按照权值排序,保证这 ...
- imagettftext
ImageTTFText 写 TTF 文字到图中. 语法: array ImageTTFText(int im, int size, int angle, int x, int y, int col, ...
- 在eclipse中配置Tomcat并将项目部署到Tomcat上
参考:http://blog.csdn.net/yerenyuan_pku/article/details/51830104 首先在点击window窗口然后preferences 然后点击Add,选择 ...
- ios微信浏览器click事件不起作用的解决方法
$(document).on( "click", ".weui_cell", function (event) {alert(); }); JS代码是这样的,h ...
- PetStore项目总结
数据库(MySQL): account(用户表:没有外键), profile(用户侧面信息表:有两个外键:catid,username), category(宠物总分类表--鱼:没有外键), prod ...
- viewport实现html页面动态缩放/meta viewport/viewport
页面默认缩放比例为1,最小宽度为375px,在小于375px出现水平滚动条的时候重新计算显示比例缩小界面, <!DOCTYPE html> <html lang="en&q ...
- iOS 应用程序内部国际化,不跟随系统语言
前言:网络上关于iOS国际化的文章很多,但基本上都是基于跟随系统语言的国际化,笔者就不赘述了-0 – 今天要讲的是不跟随系统的切换语言版本方案,即程序内部的切换语言版本方案. 一.总则: 应用内部语言 ...
- idea 下maven 导入本地jar,以及导入之后 java不能引用问题
1.在当前的项目中新建立一个lib文件夹,将需要导入的jar放入其中. 2.配置pom.xml 文件 <!--导入本地jar--> <dependency> <group ...
- [实用技巧] Mac下面如何通过终端快速打开当前文件夹
Mac mac里面其实很简单,直接输入 open .,注意是open + 英文句点. Windows windows里面是start .,注意是start + 英文句点.