spring 2.5 中除了提供 @Component 注释外,还定义了几个拥有特殊语义的注释,它们分别是:@Repository、@Service 和 @Controller。
在目前的 Spring 版本中,这 3 个注释和 @Component 是等效的,但是从注释类的命名上,很容易看出这 3 个注释分别和持久层、业务层和控制层(Web 层)相对应。
虽然目前这3 个注释和 @Component 相比没有什么新意,但 Spring 将在以后的版本中为它们添加特殊的功能。
所以,如果 Web 应用程序采用了经典的三层分层结构的话,最好在持久层、业务层和控制层分别采用上述注解对分层中的类进行注释。
@Controller用于标注控制层组件(如struts中的action,SpringMVC中的controller)
@Service用于标注业务或者对外提供Service服务API的组件
@Repository用于标注数据访问组件,即DAO组件
@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
@Service
public class VentorServiceImpl implementsiVentorService {
}
@Repository
public class VentorDaoImpl implementsiVentorDao {
}
在一个稍大的项目中,如果组件采用xml的bean定义来配置,显然会增加配置文件的体积,查找以及维护起来也不太方便。
Spring2.5为我们引入了组件自动扫描机制,他在类路径下寻找标注了上述注解的类,并把这些类纳入进spring容器中管理。
它的作用和在xml文件中使用bean节点配置组件时一样的。要使用自动扫描机制,我们需要打开以下配置信息:
代码
<?xml version="1.0"encoding="UTF-8" ?>
<beansxmlns="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-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package=”com.eric.spring”>
</beans>
1.component-scan标签默认情况下自动扫描指定路径下的包(含所有子包),将带有@Component、@Repository、@Service、@Controller标签的类自动注册到spring容器。对标记了 Spring's @Required、@Autowired、JSR250's @PostConstruct、@PreDestroy、@Resource、JAX-WS's@WebServiceRef、EJB3's @EJB、JPA's @PersistenceContext、@PersistenceUnit等注解的类进行对应的操作使注解生效(包含了annotation-config标签的作用)。
getBean的默认名称是类名(头字母小写),如果想自定义,可以@Service(“aaaaa”)这样来指定。
这种bean默认是“singleton”的,如果想改变,可以使用@Scope(“prototype”)来改变。
可以使用以下方式指定初始化方法和销毁方法:
@PostConstruct
public void init() {
}
@PreDestroy
public void destory() {
}
注入方式:
把DAO实现类注入到action的service接口(注意不要是service的实现类)中,注入时不要new 这个注入的类,因为spring会自动注入,如果手动再new的话会出现错误,
然后属性加上@Autowired后不需要getter()和setter()方法,Spring也会自动注入。
在接口前面标上@Autowired注释使得接口可以被容器注入,如:
@Autowired
@Qualifier("chinese")
private Man man;
当接口存在两个实现类的时候必须使用@Qualifier指定注入哪个实现类,否则可以省略,只写@Autowired。
spring从2.5版本开始支持注解注入,注解注入可以省去很多的xml配置工作。由于注解是写入java代码中的,所以注解注入会失去一定的灵活性,我们要根据需要来选择是否启用注解注入。
我们首先看一个注解注入的实际例子,然后再详细介绍context:component-scan的使用。
如果你已经在用spring mvc的注解配置,那么你一定已经在使用注解注入了,本文不会涉及到spring mvc,我们用一个简单的例子来说明问题。
本例中我们会定义如下类:
1. PersonService类,给上层提供Person相关操作
2. PersonDao类,给PersonService类提供DAO方法
3. Person类,定义Person相关属性,是一个POJO
4. App类,入口类,调用注解注入的PersonService类
PersonService类实现如下:
package cn.outofmemory.spring;
importorg.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class PersonService {
@Autowired
private PersonDao personDao;
public Person getPerson(int id) {
return personDao.selectPersonById(id);
}
}
在Service类上使用了@Service注解修饰,在它的私有字段PersonDao上面有@Autowired注解修饰。@Service告诉spring容器,这是一个Service类,默认情况会自动加载它到spring容器里。而@Autowired注解告诉spring,这个字段是需要自动注入的。
PersonDao类:
package cn.outofmemory.spring;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Repository;
@Scope("singleton")
@Repository
public class PersonDao {
public Person selectPersonById(int id) {
Person p = new Person();
p.setId(id);
p.setName("Person name");
return p;
}
}
在PersonDao类上面有两个注解,分别为@Scope和@Repository,前者指定此spring bean的scope是单例,你也可以根据需要将此bean指定为prototype,@Repository注解指定此类是一个容器类,是DA层类的实现。这个类我们只是简单的定义了一个selectPersonById方法,该方法的实现也是一个假的实现,只是声明了一个Person的新实例,然后设置了属性,返回他,在实际应用中DA层的类肯定是要从数据库或者其他存储中取数据的。
Person类:
package cn.outofmemory.spring;
public class Person {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Person类是一个POJO。
App类:
package cn.outofmemory.spring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Hellospring! from outofmemory.cn
*
*/
public class App
{
public static void main( String[] args )
{
ApplicationContext appContext = new ClassPathXmlApplicationContext("/spring.xml");
PersonService service = appContext.getBean(PersonService.class);
Person p = service.getPerson(1);
System.out.println(p.getName());
}
}
在App类的main方法中,我们初始化了ApplicationContext,然后从中得到我们注解注入的PersonService类,然后调用此对象的getPerson方法,并输出返回结果的name属性。
注解注入也必须在spring的配置文件中做配置,我们看下spring.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-3.0.xsd">
<context:component-scan base-package="cn.outofmemory.spring" use-default-filters="false">
<context:include-filter type="regex" expression="cn\.outofmemory\.spring\.[^.]+(Dao|Service)"/>
</context:component-scan>
</beans>
这个配置文件中必须声明xmlns:context 这个xml命名空间,在schemaLocation中需要指定schema:
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
这个文件中beans根节点下只有一个context:component-scan节点,此节点有两个属性base-package属性告诉spring要扫描的包,use-default-filters="false"表示不要使用默认的过滤器,此处的默认过滤器,会扫描包含Service,Component,Repository,Controller注解修饰的类,而此处我们处于示例的目的,故意将use-default-filters属性设置成了false。
context:component-scan节点允许有两个子节点<context:include-filter>和<context:exclude-filter>。filter标签的type和表达式说明如下:
Filter Type
Examples Expression
Description
annotation
org.example.SomeAnnotation
符合SomeAnnoation的target class
assignable
org.example.SomeClass
指定class或interface的全名
aspectj
org.example..*Service+
AspectJ語法
regex
org\.example\.Default.*
Regelar Expression
custom
org.example.MyTypeFilter
Spring3新增自訂Type,實作org.springframework.core.type.TypeFilter
在我们的示例中,将filter的type设置成了正则表达式,regex,注意在正则里面.表示所有字符,而\.才表示真正的.字符。我们的正则表示以Dao或者Service结束的类。
我们也可以使用annotaion来限定,如下:
默认情况下,<context:component-scan>查找使用构造型(stereotype)注解所标注的类,如@Component(组件),@Service(服务),@Controller(控制器),@Repository(数据仓库)
我们具体看下<context:component-scan>的一些属性,以下是一个比较具体的<context:component-scan>配置
<context:component-scan base-package="com.wjx.betalot" <!-- 扫描的基本包路径 --> annotation-config="true" <!-- 是否激活属性注入注解 --> name-generator="org.springframework.context.annotation.AnnotationBeanNameGenerator" <!-- Bean的ID策略生成器 --> resource-pattern="**/*.class" <!-- 对资源进行筛选的正则表达式,这边是个大的范畴,具体细分在include-filter与exclude-filter中进行 --> scope-resolver="org.springframework.context.annotation.AnnotationScopeMetadataResolver" <!-- scope解析器 ,与scoped-proxy只能同时配置一个 --> scoped-proxy="no" <!-- scope代理,与scope-resolver只能同时配置一个 --> use-default-filters="false" <!-- 是否使用默认的过滤器,默认值true --> > <!-- 注意:若使用include-filter去定制扫描内容,要在use-default-filters="false"的情况下,不然会“失效”,被默认的过滤机制所覆盖 --> <!-- annotation是对注解进行扫描 --> <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/> <!-- assignable是对类或接口进行扫描 --> <context:include-filter type="assignable" expression="com.wjx.betalot.performer.Performer"/> <context:include-filter type="assignable" expression="com.wjx.betalot.performer.impl.Sonnet"/> <!-- 注意:在use-default-filters="false"的情况下,exclude-filter是针对include-filter里的内容进行排除 --> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:exclude-filter type="assignable" expression="com.wjx.betalot.performer.impl.RainPoem"/> <context:exclude-filter type="regex" expression=".service.*"/> </context:component-scan>以上配置注释已经很详细了,当然因为这些注释,你若是想复制去验证,你得删掉注释。我们具体再说明一下这些注释:
back-package:标识了<context:component-scan>元素所扫描的包,可以使用一些通配符进行配置
annotation-config:<context:component-scan>元素也完成了<context:annotation-config>元素的工作,开关就是这个属性,false则关闭属性注入注解功能
name-generator:这个属性指定你的构造型注解,注册为Bean的ID生成策略,这个生成器基于接口BeanNameGenerator实现generateBeanName方法,你可以自己写个类去自定义策略。这边,我们可不显示配置,它是默认使用org.springframework.context.annotation.AnnotationBeanNameGenerator生成器,也就是类名首字符小写的策略,如Performer类,它注册的Bean的ID为performer.并且可以自定义ID,如@Component("Joy").这边简单贴出这个默认生成器的实现。
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) { if (definition instanceof AnnotatedBeanDefinition) { String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition); if (StringUtils.hasText(beanName)) { // Explicit bean name found. return beanName; } } // Fallback: generate a unique default bean name. return buildDefaultBeanName(definition, registry); }
Spring除了实现了AnnotationBeanNameGenerator生成器外,还有个org.springframework.beans.factory.support.DefaultBeanNameGenerator生成器,它为了防止Bean的ID重复,它的生成策略是类路径+分隔符+序号
,如com.wjx.betalot.performer.impl.Sonnet注册为Bean的ID是com.wjx.betalot.performer.impl.Sonnet#0,这个生成器不支持自定义ID,否则抛出异常。同样贴出代码,有兴趣的可以去看下。
public static String generateBeanName( BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean) throws BeanDefinitionStoreException { String generatedBeanName = definition.getBeanClassName(); if (generatedBeanName == null) { if (definition.getParentName() != null) { generatedBeanName = definition.getParentName() + "$child"; } else if (definition.getFactoryBeanName() != null) { generatedBeanName = definition.getFactoryBeanName() + "$created"; } } if (!StringUtils.hasText(generatedBeanName)) { throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither " + "'class' nor 'parent' nor 'factory-bean' - can't generate bean name"); } String id = generatedBeanName; if (isInnerBean) { // Inner bean: generate identity hashcode suffix. id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition); } else { // Top-level bean: use plain class name. // Increase counter until the id is unique. int counter = -1; while (counter == -1 || registry.containsBeanDefinition(id)) { counter++; id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter; } } return id; }
resource-pattern:对资源进行筛选的正则表达式,这边是个大的范畴,具体细分在include-filter与exclude-filter中进行。
scoped-proxy: scope代理,有三个值选项,no(默认值),interfaces(接口代理),targetClass(类代理),那什么时候需要用到scope代理呢,举个例子,我们知道Bean的作用域scope有singleton,prototype,request,session,那有这么一种情况,当你把一个session或者request的Bean注入到singleton的Bean中时,因为singleton的Bean在容器启动时就会创建A,而session的Bean在用户访问时才会创建B,那么当A中要注入B时,有可能B还未创建,这个时候就会出问题,那么代理的时候来了,B如果是个接口,就用interfaces代理,是个类则用targetClass代理。这个例子出处:http://www.bubuko.com/infodetail-1434289.html。
scope-resolver:这个属性跟name-generator有点类似,它是基于接口ScopeMetadataResolver的,实现resolveScopeMetadata方法,目的是为了将@Scope(value="",proxyMode=ScopedProxyMode.NO,scopeName="")的配置解析成为一个ScopeMetadata对象,Spring这里也提供了两个实现,我们一起看下。首先是org.springframework.context.annotation.AnnotationScopeMetadataResolver中,
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) { ScopeMetadata metadata = new ScopeMetadata(); if (definition instanceof AnnotatedBeanDefinition) { AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition; AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(annDef.getMetadata(), this.scopeAnnotationType); if (attributes != null) { metadata.setScopeName(attributes.getAliasedString("value", this.scopeAnnotationType, definition.getSource())); ScopedProxyMode proxyMode = attributes.getEnum("proxyMode"); if (proxyMode == null || proxyMode == ScopedProxyMode.DEFAULT) { proxyMode = this.defaultProxyMode; } metadata.setScopedProxyMode(proxyMode); } } return metadata; }
对比一下org.springframework.context.annotation.Jsr330ScopeMetadataResolver中的实现:
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) { ScopeMetadata metadata = new ScopeMetadata(); metadata.setScopeName(BeanDefinition.SCOPE_PROTOTYPE); if (definition instanceof AnnotatedBeanDefinition) { AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition; Set<String> annTypes = annDef.getMetadata().getAnnotationTypes(); String found = null; for (String annType : annTypes) { Set<String> metaAnns = annDef.getMetadata().getMetaAnnotationTypes(annType); if (metaAnns.contains("javax.inject.Scope")) { if (found != null) { throw new IllegalStateException("Found ambiguous scope annotations on bean class [" + definition.getBeanClassName() + "]: " + found + ", " + annType); } found = annType; String scopeName = resolveScopeName(annType); if (scopeName == null) { throw new IllegalStateException( "Unsupported scope annotation - not mapped onto Spring scope name: " + annType); } metadata.setScopeName(scopeName); } } } return metadata; }
ps:scope-resolver与scoped-proxy只能配置一个,配置了scope-resolver后你要使用代理,可以配置@Scope总的proxyMode属性项
use-default-filters:是否使用默认的扫描过滤。
<context:include-filter> :用来告知哪些类需要注册成Spring Bean,使用type和expression属性一起协作来定义组件扫描策略。type有以下5种
过滤器类型 描述 annotation 过滤器扫描使用注解所标注的那些类,通过expression属性指定要扫描的注释 assignable 过滤器扫描派生于expression属性所指定类型的那些类 aspectj 过滤器扫描与expression属性所指定的AspectJ表达式所匹配的那些类 custom 使用自定义的org.springframework.core.type.TypeFliter实现类,该类由expression属性指定 regex 过滤器扫描类的名称与expression属性所指定正则表示式所匹配的那些类
要注意的是:若使用include-filter去定制扫描内容,要在use-default-filters="false"的情况下,不然会“失效”,被默认的过滤机制所覆盖
<context:exclude-filter>:与<context:include-filter> 相反,用来告知哪些类不需要注册成Spring Bean,同样注意的是:在use-default-filters="false"的情况下,exclude-filter是针对include-filter里的内容进行排除。
