spring用到的另外一项技术就是AOP(Aspect-Oriented Programming,面向切面编程),它是一种新的方法论,是对传统 OOP(Object-Oriented Programming,面向对象编程)的补充。AOP的主要编程对象是切面(aspect),而切面模块化横切关注点。在应用 AOP编程时,仍然需要在定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类.这样一来横切关注点就被模块化到特殊的对象(切面)里。每个事物逻辑位于一个位置,代码不分散,便于维护和升级,业务模块更简洁,只包含核心业务代码。
现实中使用spring最多的就是声明式事务配置功能。下面就来了解其aop在事务上应用。首先要了解的就是AOP中的一些概念:
Aspect(切面):指横切性关注点的抽象即为切面,它与类相似,只是两者的关注点不一样,类是对物体特征的抽象,而切面是横切性关注点的抽象。
joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点,实际上joinpoint还可以是field或类构造器)。
Pointcut(切入点):所谓切入点是指我们要对那些joinpoint进行拦截的定义。
Advice(通知):所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知。
Target(目标对象):代理的目标对象。
Weave(织入):指将aspects应用到target对象并导致proxy对象创建的过程称为织入。
Introduction(引入):在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field。
所谓AOP,我的理解就是应该是这样一个过程,首先需要定义一个切面,这个切面是一个类,里面的方法就是关注点(也是通知),或者说里面的方法就是用来在执行目标对象方法时需要执行的前置通知,后置通知,异常通知,最终通知,环绕通知等等。有了切面和通知,要应用到目标对象,就需要定义这些通知的切入点,换句话说就是需要对哪些方法进行拦截,而这些被拦截的方法就是连接点,所谓连接点也就是在动态执行过程,被织入切面的方法(至少在spring中只能对方法进行拦截)。因此,在动态过程中通知的执行就属于织入过程,而被织入这些通知的对象就是目标对象了。
通常应用中,被织入的都是事务处理,对事务的织入不是普通简单的织入,它有着事务特有的特性——
事务的传播特性:
1.PROPAGATION_REQUIRED: 如果存在一个事务,则支持当前事务。如果没有事务则开启新的事物。
2.PROPAGATION_SUPPORTS: 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。
3.PROPAGATION_MANDATORY: 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
4.PROPAGATION_REQUIRES_NEW: 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。
5.PROPAGATION_NOT_SUPPORTED: 总是非事务地执行,并挂起任何存在的事务。
6.PROPAGATION_NEVER: 总是非事务地执行,如果存在一个活动事务,则抛出异常
7.(spring)PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中.如果没有活动事务,则按TransactionDefinition.PROPAGATION_REQUIRED属性执行。
这些都是事务特有的特性,比如前面分析的,如果两个在代码上不相关的操作,需要放在同一个事务中,这就需要利用到传播特性了,这时后调用的方法的传播特性的值就应该是PROPAGATION_REQUIRED。在spring中只需要进行这样的配置,就实现了生命式的事物处理。
最后一点需要提及的就是Spring事务的隔离级别:
1.ISOLATION_DEFAULT:这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。
2.ISOLATION_READ_UNCOMMITTED:这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。
3.ISOLATION_READ_COMMITTED:保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
4.ISOLATION_REPEATABLE_READ:这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
5.ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。
除了第一个是spring特有的,另外四个与JDBC的隔离级别相对应。第二种隔离级别会产生脏读,不可重复读和幻像读,特别是脏读,一般情况下是不允许的,所以这种隔离级别是很少用到的。大多说数据库的默认格里基本是第三种。它能消除脏读,但是可重复读保证不了。第四种隔离级别也有一些数据库作为默认的隔离级别,比如MySQL。最后一种用的地方不多,除非是多数据访问的要求特别高,否则轻易不要用它,因为它会严重影响数据库的性能
一般大家都对事务的四种隔离模式比较熟悉,从松到严依次是:
- 读取未提交(Read uncommitted):处于此模式下可能会出现脏读、幻象读、不可重复读
- 读取已提交(Read committed):处于此模式下可能会出现幻象读、不可重复读
- 可重复读(Repeatable read):处于此模式下可能会出现幻象读
- 串行(Serialize):不会出现幻象读
那么脏读、幻象行、不可重复读是什么意思呢?
- 脏读:其它的事务(执行单个 select语句也算一个事务)可以读取到某个事务更新(包括插入和删除)了但未提交的数据。脏读是应用中应该避免的,因为读取的是不可靠的数据(我觉得把这个叫做幻象行更形象,实际却不是)。一般数据库不会设定为这个模式,但有时候也会用到。脏读的好处是读取时不会对表或记录加锁,可以绕开写队列的排队,避免了等待。如在一个更新特别频繁的表中要选择表中所有的数据,就可以显示指定隔离级别:
select .... at isolation 0
- 不可重复读:这是描述在同一个事务中两条一模一样的 select语句的执行结果的比较。如果前后执行的结果一样,则是可重复读;如果前后的结果可以不一样,则是不可重复读。这个特性从字面上也能看出来。
不可重复读的模式下首先不会出现脏读,即读取的都是已提交的数据。在一个事务中,读取操作是不会加排他锁的,当下一条一模一样的 select语句的执行时,命中的数据集可能已经被其它事务修改了,这时候,还能读到相同的内容吗?
因此,要达到可重复读的效果,数据库需要做更多的事情,比如,对读取的数据行加共享锁,并保持到事务结束,以禁止其它事务修改它。这样会降低数据库的性能。而隔离级别的串行则比可重复读更严格。一般数据库的的隔离级别只设置到读取已提交。这是兼顾了可靠性和性能的结果。
上面还只提到了对命中的数据行加锁,以防止其它事务修改它。但没有提到,如果其它事务增加了符合条件的数据行怎么办?有些数据库对这种情况新定义了两个级别:读取稳定性和游标稳定性。前者不限制新增符合条件的数据行,而后者则阻止新增这样的数据行。
- 幻象读:是指两次执行同一条 select语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中。一般情况下,幻象读应该正是我们所需要的。但有时候却不是,如果打开的游标,在对游标进行操作时,并不希望新增的记录加到游标命中的数据集中来。隔离级别为 游标稳定性的,可以阻止幻象读。
-第一类丢失更新: 两个事务都更新同一个行,而另一个事务异常回滚,导致两处变化都丢失
-第二类丢失更新 : 一个事务覆盖另一个事务已经提交的数据。
企业开发中大多使用READ COMMITED来避免第一类丢失更新和脏读,但不能保证第二类丢失更新和幻读以及不可重复读,所以还需采用乐观锁
乐观锁:
假定当前事务操纵数据资源时,不会有其他事务同时访问该数据资源,因此不作数据库层次上的锁定。
悲观锁:
悲观锁,正如其名,他是对数据库而言的,数据库悲观了,他感觉每一个对他操作的程序都有可能产生并发。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制
在实际应用中,每次事务操作表时要读取其版本号,操作后如果版本号和原来相等,则更新版本号,若不相等则抛出异常
==============================================================================================================================
AOP引介
AOP(Aspect Oriented Programing)面向切面编程采用横向抽取机制,以取代传统的纵向继承体系的重复性代码(如性能监控/事务管理/安全检查/缓存实现等).
横向抽取代码复用: 基于代理技术,在不修改原来代码的前提下,对原有方法进行增强.
Spring AOP 历史
- 1.2开始, Spring开始支持AOP技术(Spring AOP)
Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码. - 2.0之后, 为了简化AOP开发, Spring开始支持AspectJ(一个基于Java的AOP框架)框架.
AOP相关术语
术语 | 中文 | 描述 |
---|---|---|
Joinpoint | 连接点 | 指那些被拦截到的点.在Spring中,这些点指方法(因为Spring只支持方法类型的连接点). |
Pointcut | 切入点 | 指需要(配置)被增强的Joinpoint. |
Advice | 通知/增强 | 指拦截到Joinpoint后要做的操作.通知分为前置通知/后置通知/异常通知/最终通知/环绕通知等. |
Aspect | 切面 | 切入点和通知的结合. |
Target | 目标对象 | 需要被代理(增强)的对象. |
Proxy | 代理对象 | 目标对象被AOP 织入 增强/通知后,产生的对象. |
Weaving | 织入 | 指把增强/通知应用到目标对象来创建代理对象的过程 (Spring采用动态代理织入,AspectJ采用编译期织入和类装载期织入). |
Introduction | 引介 | 一种特殊通知,在不修改类代码的前提下,可以在运行期为类动态地添加一些Method/Field(不常用). |
其他关于AOP理论知识可参考AOP技术研究.
AOP实现
Spring AOP代理实现有两种:JDK动态代理和Cglib框架动态代理, JDK动态代理可以参考博客代理模式的动态代理部分, 在这里仅介绍CGLib框架实现.
cglib 动态代理
cglib(Code Generation Library)是一个开源/高性能/高质量的Code生成类库,可以在运行期动态扩展Java类与实现Java接口.
cglib比java.lang.reflect.Proxy
更强的在于它不仅可以接管接口类的方法,还可以接管普通类的方法(cglib项目).从3.2开始, spring-core包中内置cglib类,因此可以不用添加额外依赖.
- UserDAO(并没有实现接口)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/**
* @author jifang
* @since 16/3/3 上午11:16.
*/
public
class
UserDAO {
public
void
add(Object o) {
System.out.println(
"UserDAO -> Add: "
+ o.toString());
}
public
void
get(Object o) {
System.out.println(
"UserDAO -> Get: "
+ o.toString());
}
}
|
- CGLibProxyFactory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
public
class
CGLibProxyFactory {
private
Object target;
public
CGLibProxyFactory(Object target) {
this
.target = target;
}
private
Callback callback =
new
MethodInterceptor() {
/**
*
* @param obj 代理对象
* @param method 当期调用方法
* @param args 方法参数
* @param proxy 被调用方法的代理对象(用于执行父类的方法)
* @return
* @throws Throwable
*/
@Override
public
Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
throws
Throwable {
// 前置增强
System.out.println(
"+ Before Advice ..."
);
// 执行目标方法
//Object result = method.invoke(target, args);
Object result = proxy.invoke(target, args);
// 后置增强
System.out.println(
"+ After Advice ..."
);
return
result;
}
};
public
Object createProxy() {
// 1. 创建Enhancer对象
Enhancer enhancer =
new
Enhancer();
// 2. cglib创建代理, 对目标对象创建子对象
enhancer.setSuperclass(target.getClass());
// 3. 传入回调接口, 对目标增强
enhancer.setCallback(callback);
return
enhancer.create();
}
public
static
void
main(String[] args) {
UserDAO proxy = (UserDAO)
new
CGLibProxyFactory(
new
UserDAO()).createProxy();
proxy.get(
"hello"
);
proxy.add(
"world"
);
}
}
|
AOP小结
- Spring AOP的底层通过JDK/cglib动态代理为目标对象进行横向织入:
1) 若目标对象实现了接口,则Spring使用JDK的java.lang.reflect.Proxy
代理.
2) 若目标对象没有实现接口,则Spring使用cglib库生成目标对象的子类. - Spring只支持方法连接点,不提供属性连接.
- 标记为
final
的方法不能被代理,因为无法进行覆盖. - 程序应优先对针对接口代理,这样便于程序解耦/维护.
Spring AOP
AOP联盟为通知Advice
定义了org.aopalliance.aop.Advice
接口, Spring在Advice
的基础上,根据通知在目标方法的连接点位置,扩充为以下五类:
通知 | 接口 | 描述 |
---|---|---|
前置通知 | MethodBeforeAdvice | 在目标方法执行前实施增强 |
后置通知 | AfterReturningAdvice | …执行后实施增强 |
环绕通知 | MethodInterceptor | ..执行前后实施增强 |
异常抛出通知 | ThrowsAdvice | …抛出异常后实施增强 |
引介通知 | IntroductionInterceptor | 在目标类中添加新的方法和属性(少用) |
- 添加Spring的AOP依赖
使用Spring的AOP和AspectJ需要在pom.xml中添加如下依赖:
1
2
3
4
5
6
7
8
9
10
|
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
|
- 定义Target
1
2
3
4
5
6
7
8
9
10
|
/**
* @author jifang
* @since 16/3/3 下午2:50.
*/
public
interface
OrderService {
void
save();
Integer delete(Integer param);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
class
OrderServiceImpl
implements
OrderService {
@Override
public
void
save() {
System.out.println(
"添加..."
);
}
@Override
public
Integer delete(Integer param) {
System.out.println(
"删除..."
);
return
param;
}
}
|
- 定义Advice
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/**
* 实现MethodInterceptor接口定义环绕通知
*
* @author jifang
* @since 16/3/6 下午2:54.
*/
public
class
ConcreteInterceptor
implements
MethodInterceptor {
@Override
public
Object invoke(MethodInvocation invocation)
throws
Throwable {
System.out.println(
"前置通知 -> "
);
Object result = invocation.proceed();
System.out.println(
"<- 后置通知"
);
return
result;
}
}
|
Spring手动代理
- 配置代理
Spring最原始的AOP支持, 手动指定目标对象与通知(没有使用AOP名称空间).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<?xml version=
"1.0"
encoding=
"UTF-8"
?>
<beans xmlns=
"http://www.springframework.org/schema/beans"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http:
//www.springframework.org/schema/beans
http:
//www.springframework.org/schema/beans/spring-beans.xsd">
<!-- target -->
<bean id=
"service"
class
=
"com.fq.service.impl.OrderServiceImpl"
/>
<!-- advice -->
<bean id=
"advice"
class
=
"com.fq.advice.ConcreteInterceptor"
/>
<bean id=
"serviceProxy"
class
=
"org.springframework.aop.framework.ProxyFactoryBean"
>
<property name=
"target"
ref=
"service"
/>
<property name=
"interceptorNames"
value=
"advice"
/>
<property name=
"proxyTargetClass"
value=
"false"
/>
</bean>
</beans>
|
- Client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@RunWith
(SpringJUnit4ClassRunner.
class
)
@ContextConfiguration
(locations =
"classpath:spring/applicationContext.xml"
)
public
class
AOPClient {
@Autowired
// 必须指定使用代理对象名称, 否则不予代理
@Qualifier
(
"serviceProxy"
)
private
OrderService service;
@Test
public
void
client() {
service.save();
service.delete(
88
);
}
}
|
这种方式的缺陷在于每个
Target
都必须手动指定ProxyFactoryBean
对其代理(不能批量指定),而且这种方式会在Spring容器中存在两份Target对象(代理前/代理后),浪费资源,且容易出错(比如没有指定@Qualifier
).
Spring自动代理 – 引入AspectJ
通过AspectJ引入Pointcut切点定义
- Target/Advice同前
- 定义切面表达式
通过execution函数定义切点表达式(定义切点的方法切入)
execution(<访问修饰符> <返回类型><方法名>(<参数>)<异常>)
如:
1)execution(public * *(..))
# 匹配所有public
方法.
2)execution(* com.fq.dao.*(..))
# 匹配指定包下所有类方法(不包含子包)
3)execution(* com.fq.dao..*(..))
# 匹配指定包下所有类方法(包含子包)
4)execution(* com.fq.service.impl.OrderServiceImple.*(..))
# 匹配指定类所有方法
5)execution(* com.fq.service.OrderService+.*(..))
# 匹配实现特定接口所有类方法
6)execution(* save*(..))
# 匹配所有save开头的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
<?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:aop=
"http://www.springframework.org/schema/aop"
xsi:schemaLocation="http:
//www.springframework.org/schema/beans
http:
//www.springframework.org/schema/beans/spring-beans.xsd
http:
//www.springframework.org/schema/aop
http:
//www.springframework.org/schema/aop/spring-aop.xsd">
<!-- target -->
<bean id=
"service"
class
=
"com.fq.service.impl.OrderServiceImpl"
/>
<!-- advice -->
<bean id=
"advice"
class
=
"com.fq.advice.ConcreteInterceptor"
/>
<!-- 配置切面 : proxy-target-
class
确定是否使用CGLIB -->
<aop:config proxy-target-
class
=
"true"
>
<!--
aop:pointcut : 切点定义
aop:advisor: 定义Spring传统AOP的切面,只支持一个pointcut/一个advice
aop:aspect : 定义AspectJ切面的,可以包含多个pointcut/多个advice
-->
<aop:pointcut id=
"pointcut"
expression=
"execution(* com.fq.service.impl.OrderServiceImpl.*(..))"
/>
<aop:advisor advice-ref=
"advice"
pointcut-ref=
"pointcut"
/>
</aop:config>
</beans>
|
- Client同前
AspectJ AOP
AspectJ是一个基于Java的AOP框架,提供了强大的AOP功能,其他很多AOP框架都借鉴或采纳了AspectJ的一些思想,Spring2.0以后增加了对AspectJ切点表达式支持(如上),并在Spring3.0之后与AspectJ进行了很好的集成.
在Java领域,AspectJ中的很多语法结构基本上已成为AOP领域的标准, 他定义了如下几类通知类型:
通知 | 接口 | 描述 |
---|---|---|
前置通知 | @Before | 相当于BeforeAdvice |
后置通知 | @AfterReturning | 相当于AfterReturningAdvice |
环绕通知 | @Around | 相当于MethodInterceptor |
抛出通知 | @AfterThrowing | 相当于ThrowAdvice |
引介通知 | @DeclareParents | 相当于IntroductionInterceptor |
最终final通知 | @After | 不管是否异常,该通知都会执行 |
新版本Spring,建议使用AspectJ方式开发以简化AOP配置.
AspectJ-XML-AOP
使用AspectJ编写Advice无需实现任何接口,而且可以将多个通知写入一个切面类.
前置通知
- 定义通知
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/**
* @author jifang
* @since 16/3/3 下午5:38.
*/
public
class
Aspect {
/**
* 无返回值
*/
public
void
before1() {
System.out.println(
"前置增强before1"
);
}
/**
* 还可以传入连接点参数 JoinPoint
*
* @param point
*/
public
void
before2(JoinPoint point) {
System.out.printf(
"前置增强before2 %s%n"
, point.getKind());
}
}
|
- 装配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
<?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:aop=
"http://www.springframework.org/schema/aop"
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/aop
http:
//www.springframework.org/schema/aop/spring-aop.xsd
http:
//www.springframework.org/schema/context
http:
//www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-
package
=
"com.fq.service"
/>
<!-- 配置切面通知 -->
<bean id=
"advice"
class
=
"com.fq.advice.Aspect"
/>
<!-- AOP切面配置 -->
<aop:config>
<aop:aspect ref=
"advice"
>
<aop:pointcut id=
"pointcut"
expression=
"execution(* com.fq.service.impl.OrderServiceImpl.*(..))"
/>
<aop:before method=
"before1"
pointcut-ref=
"pointcut"
/>
<aop:before method=
"before2"
pointcut-ref=
"pointcut"
/>
</aop:aspect>
</aop:config>
</beans>
|
- 前置通知小结
- 前置通知会保证在目标方法执行前执行;
- 前置通知默认不能阻止目标方法执行(但如果通知抛出异常,则目标方法无法执行);
- 可以通过
JoinPoint
参数获得当前拦截对象和方法等信息.
后置通知
- 定义通知
1
2
3
|
public
void
afterReturning(JoinPoint point, Object result) {
System.out.printf(
"后置增强, 结果为 %s%n"
, result);
}
|
- 装配
1
|
<aop:after-returning method=
"afterReturning"
returning=
"result"
pointcut-ref=
"pointcut"
/>
|
后置通知可以获得方法返回值,但在配置文件定义返回值参数名必须与后置通知方法参数名一致(如
result
).
环绕通知
- 定义通知
1
2
3
4
5
6
7
8
9
|
public
Object around(ProceedingJoinPoint point)
throws
Throwable {
System.out.printf(
"环绕前置增强 method: %s, args: %s%n"
, point.toShortString(), Arrays.toString(point.getArgs()));
Object result = point.proceed(point.getArgs());
System.out.printf(
"环绕后置增强 result: %s%n"
, result);
return
result;
}
|
- 装配
1
|
<aop:around method=
"around"
arg-names=
"point"
pointcut-ref=
"pointcut"
/>
|
环绕通知可以实现任何通知的效果, 甚至可以阻止目标方法的执行.
抛出通知
- 定义通知
1
2
3
4
5
6
7
8
|
private
static
final
Logger LOGGER = LoggerFactory.getLogger(Aspect.
class
);
public
void
afterThrowing(JoinPoint point, Throwable ex) {
String message =
new
StringBuilder(
"method "
).append(point.getSignature().getName()).append(
" error"
).toString();
System.out.println(message);
LOGGER.error(
"{},"
, message, ex);
}
|
- 装配
1
|
<aop:after-throwing method=
"afterThrowing"
throwing=
"ex"
pointcut-ref=
"pointcut"
/>
|
throwing
属性指定异常对象名, 该名称应和方法定义参数名一致.
最终通知
- 定义通知
1
2
3
|
public
void
after(JoinPoint point) {
System.out.println(
"最终通知, 释放资源"
);
}
|
- 装配
1
|
<aop:after method=
"after"
pointcut-ref=
"pointcut"
/>
|
无论目标方法是否出现异常,该通知都会执行(类似
finally
代码块, 应用场景为释放资源).
AspectJ-Annotation-AOP
@AspectJ
是AspectJ 1.5新增功能,可以通过JDK注解技术,直接在Bean类中定义切面.
AspectJ预定义的注解有:@Before
/@AfterReturning
/@Around
/@AfterThrowing
/@DeclareParents
/@After
.描述同前.
使用AspectJ注解AOP需要在applicationContext.xml文件中开启注解自动代理功能:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
<?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:aop=
"http://www.springframework.org/schema/aop"
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/aop
http:
//www.springframework.org/schema/aop/spring-aop.xsd
http:
//www.springframework.org/schema/context
http:
//www.springframework.org/schema/context/spring-context.xsd">
<!-- 批量扫描
@Component
-->
<context:component-scan base-
package
=
"com.fq"
/>
<!-- 启用注解自动代理
@Aspect
-->
<aop:aspectj-autoproxy/>
</beans>
|
OrderService
/Client
同前
@Before
- Aspect
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/**
* @Aspect: 指定是一个切面
* @Component: 指定可以被Spring容器扫描到
*/
@Aspect
@Component
public
class
CustomAspect {
@Before
(
"execution(* com.fq.service.impl.OrderServiceImpl.*(..))"
)
public
void
before(JoinPoint point) {
System.out.printf(
"前置增强before2 %s%n"
, point.getKind());
}
}
|
@AfterReturning
1
2
3
4
|
@AfterReturning
(value =
"execution(* com.fq.service.impl.OrderServiceImpl.d*(..))"
, returning =
"result"
)
public
void
afterReturning(JoinPoint point, Object result) {
System.out.printf(
"后置增强, 结果为 %s%n"
, result);
}
|
@Around
1
2
3
4
5
6
7
8
9
10
|
@Around
(
"execution(* com.fq.service.impl.OrderServiceImpl.*(..))"
)
public
Object around(ProceedingJoinPoint point)
throws
Throwable {
long
start = System.currentTimeMillis();
Object result = point.proceed(point.getArgs());
long
time = System.currentTimeMillis() - start;
System.out.printf(
"method %s invoke consuming %d ms%n"
, point.toLongString(), time);
return
result;
}
|
如果不调用
ProceedingJoinPoint
的proceed
方法,那么目标方法就不执行了.
@AfterThrowing
1
2
3
4
5
6
7
|
@AfterThrowing
(value =
"execution(* com.fq.service.impl.OrderServiceImpl.*(..))"
, throwing =
"ex"
)
public
void
afterThrowing(JoinPoint point, Throwable ex) {
String message =
new
StringBuilder(
"method "
).append(point.getSignature().getName()).append(
" error"
).toString();
System.out.println(message);
LOGGER.error(
"{},"
, message, ex);
}
|
@After
1
2
3
4
|
@After
(
"execution(* com.fq.service.impl.OrderServiceImpl.*(..))"
)
public
void
after(JoinPoint point) {
System.out.println(
"最终通知, 释放资源"
);
}
|
@Pointcut定义切点
对于重复的切点,可以使用@Pointcut
进行定义, 然后在通知注解内引用.
- 定义切点方法
无参/无返回值/方法名为切点名:
1
2
3
4
5
6
7
8
9
10
|
/**
* @author jifang
* @since 16/3/4 上午11:47.
*/
public
class
OrderServicePointcut {
@Pointcut
(
"execution(* com.fq.service.impl.OrderServiceImpl.*(..))"
)
public
void
pointcut() {
}
}
|
- 引用切点
在Advice上像调用方法一样引用切点:
1
2
3
4
|
@After
(
"OrderServicePointcut.pointcut()"
)
public
void
after(JoinPoint point) {
System.out.println(
"最终通知, 释放资源"
);
}
|
1) 如果切点与切面在同一个类内, 可省去类名前缀;
2) 当需要通知多个切点时,可以使用||
/&&
进行连接.=====================================================================================
使用aspectJ的AOP注解实例Spring对AOP的实现提供了很好的支持。下面我们就使用Spring的注解来完成AOP做一个例子。
首先,为了使用Spring的AOP注解功能,必须导入如下几个包。aspectjrt.jar,aspectjweaver.jar,cglib-nodep.jar.
然后我们写一个接口
- package com.bird.service;
- public interface PersonServer {
- public void save(String name);
- public void update(String name, Integer id);
- public String getPersonName(Integer id);
- }
和一个接口实现类
- package com.bird.service.impl;
- import com.bird.service.PersonServer;
- public class PersonServiceBean implements PersonServer{
- @Override
- public void save(String name) {
- System.out.println("我是save方法");
- // throw new RuntimeException();
- }
- @Override
- public void update(String name, Integer id) {
- System.out.println("我是update()方法");
- }
- @Override
- public String getPersonName(Integer id) {
- System.out.println("我是getPersonName()方法");
- return "xxx";
- }
- }
下面使用Spring注解方式对这个Bean进行方法拦截
- package com.bird.service;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.After;
- import org.aspectj.lang.annotation.AfterReturning;
- import org.aspectj.lang.annotation.AfterThrowing;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Before;
- import org.aspectj.lang.annotation.Pointcut;
- /**
- * 切面
- * @author Bird
- *
- */
- @Aspect
- public class MyInterceptor {
- @Pointcut("execution(* com.bird.service.impl.PersonServiceBean.*(..))")
- private void anyMethod(){}//定义一个切入点
- @Before("anyMethod() && args(name)")
- public void doAccessCheck(String name){
- System.out.println(name);
- System.out.println("前置通知");
- }
- @AfterReturning("anyMethod()")
- public void doAfter(){
- System.out.println("后置通知");
- }
- @After("anyMethod()")
- public void after(){
- System.out.println("最终通知");
- }
- @AfterThrowing("anyMethod()")
- public void doAfterThrow(){
- System.out.println("例外通知");
- }
- @Around("anyMethod()")
- public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable{
- System.out.println("进入环绕通知");
- Object object = pjp.proceed();//执行该方法
- System.out.println("退出方法");
- return object;
- }
- }
这句话是方法切入点,execution为执行的意思,*代表任意返回值,然后是包名,.*意思是包下面的所有子包。(..)代
- @Pointcut("execution(* com.bird.service.impl.PersonServiceBean.*(..))")
表各种方法.
然后下面的注解就比较简单了,就是在使用方法前和中,还有环绕拦截/
然后在Spring的配置文件中继续配置Bean,需要打开AOP命名空间
- <?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:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="
- http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
- <aop:aspectj-autoproxy/>
- <bean id="personServiceBean" class="com.bird.service.impl.PersonServiceBean"/>
- <bean id="myInterceptor" class="com.bird.service.MyInterceptor"/>
- </beans>
然后建立一个Junit测试
- package junit.test;
- import org.junit.Test;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- import com.bird.service.PersonServer;
- public class SpringAOPTest {
- @Test
- public void inteceptorTest(){
- ApplicationContext ctx = new ClassPathXmlApplicationContext("beanAop.xml");
- PersonServer bean = (PersonServer)ctx.getBean("personServiceBean");
- bean.save(null);
- }
- }
测试结果为
- 2012-3-12 18:08:39 org.springframework.context.support.AbstractApplicationContext prepareRefresh
- 信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@dd20f6: display name [org.springframework.context.support.ClassPathXmlApplicationContext@dd20f6]; startup date [Mon Mar 12 18:08:39 CST 2012]; root of context hierarchy
- 2012-3-12 18:08:40 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
- 信息: Loading XML bean definitions from class path resource [beanAop.xml]
- 2012-3-12 18:08:40 org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory
- 信息: Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext@dd20f6]: org.springframework.beans.factory.support.DefaultListableBeanFactory@b0bad7
- 2012-3-12 18:08:40 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
- 信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@b0bad7: defining beans [org.springframework.aop.config.internalAutoProxyCreator,personServiceBean,myInterceptor]; root of factory hierarchy
- null
- 前置通知
- 进入环绕通知
- 我是save方法
- 后置通知
- 退出方法
- 最终通知