Spring中的注解使用总结

在实际使用中,Spring 的注解使用还是非常常见的,毕竟省去了那么多罗嗦的配置,前面在看它们整合 SSH 框架的时候栗子都是用注解,忽然发现我并没有学习过 Spring 的注解….
这不就马上来补上

准备工作

使用注解是在 Spring2.5(2.0以后陆续加入) 以后新加的功能,所以至少要保证版本是 2.5+,并且引入了注解的包
然后需要在 Spring 的配置文件中告诉它你使用了注解,最简单的一种方式就是加入下面的一句:
<context:component-scan base-package="com.xxx" />
它的意思就是开启自动扫描,会自动扫描你设置的包路径下的所有类,如果有注解就进行解析
这就是所谓的用注解来构造 IoC 容器;base-package 是可以指定多个包的,用逗号分割

@Autowired

顾名思义,就是自动装配,其作用是为了消除 Java 代码里面的 getter/setter 与 bean 属性中的 property。
当然,getter 看个人需求,如果私有属性需要对外提供的话,应当予以保留。
@Autowired 默认按类型匹配的方式,在容器查找匹配的 Bean,当有且仅有一个匹配的 Bean 时,Spring 将其注入 @Autowired 标注的变量中。
下面就搞个栗子看看吧,便于理解,首先在 Spring 的配置文件中设置好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
">

<context:component-scan base-package="com.spring" />

<bean id="zoo" class="com.spring.model.Zoo" />
<bean id="tiger" class="com.spring.model.Tiger" />
<bean id="monkey" class="com.spring.model.Monkey" />

</beans>

配置中我们开启了自动扫描,并且配了三个 bean,但是没有给这些 bean 配置 property 属性,因为我们用注解的话是不需要的
下面在我们的 zoo.java 文件中这样写:

1
2
3
4
5
6
7
8
9
10
11
public class Zoo {
@Autowired
private Tiger tiger;

@Autowired
private Monkey monkey;

public String toString(){
return tiger + "\n" + monkey;
}
}

在这个 bean 中使用了 @Autowired 注解,并且没有设置 getter/setter 方法;因为开启了自动扫描,当 Spring 发现 @Autowired 注解时,将自动在代码上下文中找到和其匹配(默认是类型匹配)的 Bean,并自动注入到相应的地方去。
所以,类中的 tiger 和 monkey 都已经注入成功了

这里有个细节,就是如果在配置文件中配置了 property 属性,又使用了注解,会怎么样?
Spring 会按照 xml 优先的原则去 Zoo.java 中寻找这两个属性的 getter/setter,如果连 getter/setter 方法也没有那就只能抛异常了

匹配不到 bean 的情况下,如果不想让 Spring 抛异常而是将属性设置位 null,那么可以将 @Autowired 注解的 required 属性设置为 false 即可

@Qualifier

这个注解是用来指定注入 Bean 的名称 ;也就是说如果容器中有一个以上匹配的 Bean,则可以通过 @Qualifier 注解限定 Bean 的名称,否则调用的时候会抛异常
比如:某个 bean 中引用了一个接口,实现这个接口的 bean 有多个,Spring 在注入的时候就不知道注入那一个了,这样要么删除其他的 bean 要么就是使用 @Qualifier 注解

1
2
3
@Autowired
@Qualifier("bmwCar")
private ICar car;

这样通过 id 就指明了注入的是那个 bean

@Resource

@Resource 注解与 @Autowired 注解作用非常相似,是通过 name/type 进行注入的

1
2
3
4
5
6
7
8
9
10
11
12
public class Zoo1 {

@Resource(name="tiger")
private Tiger tiger;

@Resource(type=Monkey.class)
private Monkey monkey;

public String toString(){
return tiger + "\n" + monkey;
}
}

下面就来看下它的装配顺序和与 @Autowired 的区别

@Resource的装配顺序

  1. @Resource 如果后面没有任何内容,默认通过 name 属性去匹配 bean,找不到再按 type 去匹配
  2. 指定了 name 或者 type 则根据指定的类型去匹配 bean
  3. 指定了 name 和 type 则根据指定的 name 和 type 去匹配 bean,任何一个不匹配都将报错

与@Autowired注解区别

  1. @Autowired 默认按照 byType 方式进行 bean 匹配,@Resource 默认按照 byName 方式进行 bean 匹配
  2. @Autowired 是 Spring 的注解,@Resource 是 J2EE 的注解,这个看一下导入注解的时候这两个注解的包名就一清二楚了

Spring 属于第三方的,J2EE 是 Java 自己的东西,因此,建议使用 @Resource 注解,以减少代码和 Spring 之间的耦合。

它们都可以使用在“字段”或者 setter 方法上,如果写在了字段上不写 setter 方法也是可以成功注入的

@Service

@Service 对应的是业务层 Bean
上面一开始的例子,还可以继续简化,因为 Spring 的配置文件里面还定义了三个 bean,下一步的简化是把这三个 bean 也给去掉,使得 Spring 配置文件里面只有一个自动扫描的标签,增强 Java 代码的内聚性并进一步减少配置文件。
使用到的当然就是 @Service 注解了,配置文件不用说了,就剩一行扫描了,Java 类要进行稍微的改造了:

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class Zoo {
@Autowired
private Tiger tiger;

@Autowired
private Monkey monkey;

public String toString(){
return tiger + "\n" + monkey;
}
}

确实是没什么大的变化,只是在类上加了一个 @Service 注解,这样,Zoo.java 在 Spring 容器中存在的形式就是 “zoo”,即可以通过 ApplicationContext.getBean("zoo") 来得到 Zoo 对象
@Service 注解,其实做了两件事情:

  1. 声明 Zoo.java 是一个 bean,这点很重要,因为 Zoo.java 是一个 bean,其他的类才可以使用 @Autowired 将 Zoo 作为一个成员变量自动注入。
  2. Zoo.java 在 bean 中的 id 是 “zoo”,即类名且首字母小写。

那么我想指定 id 的名称怎么办?我想设置 bean 为原型怎么办(每次都会 new 一个新的)?

1
2
3
@Service("Zoo")
@Scope("prototype")
public class Zoo{...}

@Scope 注解和 bean 标签例的 Scope 属性是一致的;Spring 默认产生的 bean 是单例,也就是说默认是 “singleton” 即单例,需要每次都 new 一个新的就选择 “prototype”
struts2 是要求每次次访问都对应不同的 Action

@Controller

@Controller 对应表现层的 Bean,也就是 Action,用法上倒是和 @Service 并没有什么区别

1
2
3
@Controller
@Scope("prototype")
public class UserAction {...}

使用 @Controller 注解标识 UserAction 之后,就表示要把 UserAction 交给 Spring 容器管理,在 Spring 容器中会存在一个名字为 “userAction” 的 action,默认的 bean 名字为这个类的类名首字母小写,如果需要单独指定就手动设置下其注解的 value 值

@Repository

@Repository 对应数据访问层 Bean ;和上面类似,本质也是把类交给 Spring 管理而已,只不过对应的不同

1
2
3
4
@Repository(value="userDao")
public class UserDaoImpl extends BaseDaoImpl<User> {
………
}

这样在 Service 层使用 @Resource 进行注入即可

@Repository / @Service / @Controller 这三个没什么功能上的差别,差别只是在语义上,分别代表了特定语义的类(DAO、Service、Web)
这个有点类似于 HTML 5 提出的语义化标签,你说 HTML 5 里面的 “header” 和 “div” 有什么差别呢,其实功能上来说没有,只是语义表达的更清楚

更确切的说,@Service 和 @Controller 功能上和 @Component 是相同的,@Repository 在 2.0 就有了,那时候只能标注在 DAO 层,否则可能抛异常(因为它除了识别 bean 同时它还能将所标注的类中抛出的数据访问异常封装为 Spring 的数据访问异常类型)

@Component

@Component 是所有受 Spring 管理组件的通用形式,@Component 注解可以放在类的头上,@Component 不推荐使用。
标识该类需要 Spring 初始化时自动装配,也许正是因为太通用所以不推荐,实在不好归类的时候再用

Spring常用注解汇总

  • @Configuration
    把一个类作为一个配置类,它的某个方法头上如果注册了 @Bean,就会作为这个 Spring 容器中的 Bean。
    一个带有 @Bean 的注解方法将返回一个对象,该对象应该被注册为在 Spring 应用程序上下文中的 bean,默认它的 id 就是方法名,此注解包含了 @Component;
    比如,看下面的一段代码:

    1
    2
    3
    4
    5
    6
    7
    @Configuration
    public class HelloWorldConfig {
    @Bean
    public HelloWorld helloWorld(){
    return new HelloWorld();
    }
    }

    上面的代码如果写成 XML 文件就是:

    1
    2
    3
    <beans>
    <bean id="helloWorld" class="com.tutorialspoint.HelloWorld" />
    </beans>

    这样应该就比较好理解了,然后就可以通过 AnnotationConfigApplicationContext 进行获取定义的 Bean:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public static void main(String[] args) {
    ApplicationContext ctx =
    new AnnotationConfigApplicationContext(HelloWorldConfig.class);
    HelloWorld helloWorld = ctx.getBean(HelloWorld.class);
    helloWorld.setMessage("Hello World!");
    helloWorld.getMessage();
    }
    /*************分割线******************/
    public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx =
    new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
    }

    上面两种方式都可以获取,用那种就自己看着办吧….

  • @Scope
    设置 bean 的作用域

  • @Lazy(true)
    表示延迟初始化

  • @Service
    用于标注业务层组件

  • @Controller
    用于标注控制层组件(如 struts 中的 action)

  • @Repository
    用于标注数据访问组件,即 DAO 组件。

  • @Component
    泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。

  • @Scope
    用于指定 scope 作用域的(用在类上)

  • @PostConstruct
    用于指定初始化方法(用在方法上)

  • @PreDestory
    用于指定销毁方法(用在方法上)

  • @DependsOn
    定义 Bean 初始化及销毁时的顺序

  • @Primary
    自动装配时当出现多个 Bean 候选者时,被注解为 @Primary 的 Bean 将作为首选者,否则将抛出异常

  • @Autowired
    默认按类型装配,如果我们想使用按名称装配,可以结合 @Qualifier 注解一起使用。如下:
    @Autowired @Qualifier(“personDaoBean”) 存在多个实例配合使用

  • @Resource
    默认按名称装配,当找不到与名称匹配的 bean 才会按类型装配。

  • @PostConstruct
    初始化注解

  • @PreDestroy
    摧毁注解 默认 单例 启动就加载

  • @Async
    异步方法调用

补充-关于事务

因为上面貌似没有提到过,就单独拿出来补充,除了事务应该还有 AOP,不过那个还没用到以后再说吧
事务方面用的还是比较多的,比如常用的 @Transaction 注解,来说下这个注解
准备工作就不说了,在 SSH 整合中已经说过了,就是要开启事务管理器以及配置下开启注解,然后来看下它的属性吧:

属性名说明
name当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。
propagation事务的传播行为,默认值为 REQUIRED。
isolation事务的隔离度,默认值采用 DEFAULT。
timeout事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
read-only指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
rollback-for用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
no-rollback- for抛出 no-rollback-for 指定的异常类型,不回滚事务。

此注解可以设置在合适的方法上,也可以设置在类上,当把 @Transactional 注解放在类级别时,表示所有该类的公共方法都配置相同的事务属性信息。
当类级别配置了@Transactional,方法级别也配置了@Transactional,应用程序会以方法级别的事务属性信息来管理事务,换言之,方法级别的事务属性信息会覆盖类级别的相关配置信息。

在应用系统调用声明 @Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据 @Transactional 的属性配置信息,这个代理对象决定该声明 @Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截;
在 TransactionInterceptor 拦截时,会在在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器 AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务

propagation属性

事务的传播行为大约有七种,需要注意下面三种 propagation 可以不启动事务。本来期望目标方法进行事务管理,但若是错误的配置这三种 propagation,事务将不会发生回滚。

  1. TransactionDefinition.PROPAGATION_SUPPORTS
    如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  2. TransactionDefinition.PROPAGATION_NOT_SUPPORTED
    以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  3. TransactionDefinition.PROPAGATION_NEVER
    以非事务方式运行,如果当前存在事务,则抛出异常。

其他的一些传播行为有:

  • TransactionDefinition.PROPAGATION_REQUIRED
    如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务(默认)。
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW
    创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_MANDATORY
    如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_NESTED
    如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;
    如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。

isolation属性

就是隔离级别的问题,这个在前面其实已经详细说过了

  • TransactionDefinition.ISOLATION_DEFAULT
    这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED
    该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。
  • TransactionDefinition.ISOLATION_READ_COMMITTED
    该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
  • TransactionDefinition.ISOLATION_REPEATABLE_READ
    该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
  • TransactionDefinition.ISOLATION_SERIALIZABLE
    所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

rollbackFor属性

默认情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常)或者 Error,则 Spring 将回滚事务;除此之外,Spring 不会回滚事务。
如果在事务中抛出其他类型的异常,并期望 Spring 能够回滚事务,可以指定 rollbackFor

只有public的方法才有效

只有 @Transactional 注解应用到 public 方法,才能进行事务管理。
这和使用的 AOP 技术有关,从上面的原理中可以推断出来,详情可见参考

AOP自调用问题

在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。若同一类中的其他没有 @Transactional 注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚。

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class OrderService {
private void insert() {
insertOrder();
}
@Transactional
public void insertOrder() {
//insert log info
//insertOrder
//updateAccount
}
}

insertOrder 尽管有 @Transactional 注解,但它被内部方法 insert 调用,事务被忽略,出现异常事务不会发生回滚。
要解决这个问题需要使用 AspectJ 取代 Spring AOP 代理(具体的配置见参考2)。

补充-Spring中的过滤器

临时看到的就写在这里了,在 Spring 中还自带了一些过滤器,比如最常用的设置编码的,避免中文乱码问题,直接在 web.xml 中配置就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>

</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>

如果查看源码,其实实现比我们的要简单,没有处理 GET 乱码问题,所以,需要 GET 的还是要自己写(或者直接在 Tomcat 中设置死),不过在 Tomcat8.x 默认 Get 编码已改为 utf-8 ,Get 乱码就不用操心了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//........省略..............  
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {
request.setCharacterEncoding(this.encoding);
if (this.forceEncoding) {
response.setCharacterEncoding(this.encoding);
}
}
filterChain.doFilter(request, response);
}
//........省略..............

然后还有一个很常见的问题就是 No Session 的问题,因为 Spring 已提供了过滤器来解决 nosession or session is closed 这个问题,所以我们直接用就行了,使用的也是过滤器来实现的,以此延长 Session 的生命周期(所以有利有弊,对象层级较多的话会拖慢加载速度),在 web.xml 文件中直接配置就行

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--
配置 OpenSessionInViewFilter 解决 nosession 问题
该过滤器必须配置在 struts2 核心控制期之前,否则 action 访问不了
-->
<filter>
<filter-name>openSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate5.support.OpenSessionInViewFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>openSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

也可以配置它的两个参数:singleSession 和 sessionFactoryBeanName
sessionFactoryBeanName 默认值是 sessionFactory
singleSession 默认值是 true

不过在 SSH 的项目中一般都配了事务管理器,并且启用了注解,人嘛,能省事则省事一般都直接加个 @Transaction 注解就不管了,这个也确实能解决问题,至于是不是 openSessionInViewFilter 的思路目前我还不知道,我懒的也没获取下各自的 session 对比下是不是同一个对象…..

参考

http://www.cnblogs.com/xiaoxi/p/5935009.html
https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html
https://www.ibm.com/developerworks/cn/education/opensource/os-cn-spring-trans/index.html

喜欢就请我吃包辣条吧!

评论框加载失败,无法访问 Disqus

你可能需要魔法上网~~