AspectJ:
1.AspectJ是一个基于Java语言的AOP框架。
2.Spring2.0以后新增了对AspectJ切点表达式支持。
3.@AspectJ是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面新版本Spring框架,建议使用AspectJ方式来开发
AspectJ最强大的地方在于他的切入点表达式:
语法:execution(修饰符 返回值 包.类.方法名(参数) throws异常)
修饰符,一般省略
public 公共方法
* 任意
返回值,不能省略
void 返回没有值
String 返回值字符串
* 任意
包
com.zby.service 固定包
com.zby.oa.*.service oa包下面子包 (例如:com.zby.oa.flow.service)
com.zby.oa.. oa包下面的所有子包(含自己)
com.zby.oa.*.service.. oa包下面任意子包,固定目录service,service目录任意包
类
UserServiceImpl 指定类
*Impl 以Impl结尾
User* 以User开头
* 任意
方法名,不能省略
addUser 固定方法
add* 以add开头
*Do 以Do结尾
* 任意
(参数)
() 无参
(int) 一个整型
(int ,int) 两个
(..) 参数任意
throws ,可省略,一般不写。
当然,execution也是可以变得,但是一般用这个就够了,更详细的表达式用法,当然是查看专业文档。
AspectJ和aopalliance通知的区别:
AOP联盟的通知类型具有特性接口,必须实现,从而确定方法名称,而AspectJ的通知类型只定义了类型名称和方法格式,这意味着,我们的切面不需要实现任何方法!!!。
AspectJ通知:
before:前置通知(应用:各种校验)
在方法执行前执行,如果通知抛出异常,阻止方法运行
afterReturning:后置通知(应用:常规数据处理)
方法正常返回后执行,如果方法中抛出异常,通知无法执行,必须在方法执行后才执行,所以可以获得方法的返回值。
around:环绕通知(应用:十分强大,可以做任何事情)
方法执行前后分别执行,可以阻止方法的执行,必须手动执行目标方法
afterThrowing:抛出异常通知(应用:包装异常信息)
方法抛出异常后执行,如果方法没有抛出异常,无法执行
after:最终通知(应用:清理现场)
方法执行完毕后执行,无论方法中是否出现异常
当然,最重要也最常用的还是环绕通知,因为环绕通知必须手动执行目标方法,所以,可以代替其他几个通知。
使用XML配置Spring整合AspectJ的AOP:
1)项目整体结构如下:
2)创建maven项目,pom.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
27
28
29
30
31
32
33
34
35
36
37
38
39
|
xsi:schemaLocation=
"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
>
4.0
.
0
0.0
.
1
-SNAPSHOT
4.3
.
8
.RELEASE
4.3
.
8
.RELEASE
4.3
.
8
.RELEASE
4.3
.
8
.RELEASE
1.8
.
10
|
3)创建目标类UserService:
1
2
3
4
5
6
7
8
9
|
package
com.zby.service;
public
class
UserService {
public
void
saveUser(String username, String password) {
System.out.println(
"save user[username="
+ username +
",password="
+ password +
"]"
);
}
}
|
4)创建切面类:
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
|
package
com.zby.interceptor;
import
org.aspectj.lang.JoinPoint;
import
org.aspectj.lang.ProceedingJoinPoint;
public
class
MyAspect {
public
void
myBefore(JoinPoint joinPoint) {
System.out.println(
"前置通知 : "
+ joinPoint.getSignature().getName());
}
public
void
myAfterReturning(JoinPoint joinPoint, Object ret) {
System.out.println(
"后置通知 : "
+ joinPoint.getSignature().getName() +
" , -->"
+ ret);
}
public
Object myAround(ProceedingJoinPoint joinPoint)
throws
Throwable {
System.out.println(
"环绕通知执行方法前"
);
// 手动执行目标方法
Object obj = joinPoint.proceed();
System.out.println(
"环绕通知执行方法后"
);
return
obj;
}
public
void
myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println(
"抛出异常通知 : "
+ e.getMessage());
}
public
void
myAfter(JoinPoint joinPoint) {
System.out.println(
"最终通知"
);
}
}
|
切面类没有实现接口,但是有几种方法参数,这些不是必须的。这些传入的对象是什么?当然是我们在切面点需要的信息!用脑壳想,在给一个方法进行增强的时候,前置方法,或者后置方法,或者环绕方法,有可能需要得到原方法的哪些信息,这里面都有。
5)编写配置文件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
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
|
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop=
"http://www.springframework.org/schema/aop"
xsi:schemaLocation="http:
//www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http:
//www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
id=
"myPointcut"
/>
pointcut-ref=
"myPointcut"
returning=
"ret"
/>
pointcut-ref=
"myPointcut"
throwing=
"e"
/>
|
6)编写测试类:
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
|
package
com.zby.test;
import
org.junit.Test;
import
org.junit.runner.RunWith;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.test.context.ContextConfiguration;
import
org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import
com.zby.service.UserService;
@RunWith
(SpringJUnit4ClassRunner.
class
)
@ContextConfiguration
(locations = {
"classpath:applicationContext.xml"
})
public
class
AOPTest {
@Autowired
private
UserService userService;
@Test
public
void
testProxy() {
System.out.println(
"After Proxy......"
);
userService.saveUser(
"zby"
,
"1234567890"
);
}
}
|
7)控制台打印结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
六月
09
,
2017
2
:
07
:
56
下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getDefaultTestExecutionListenerClassNames
信息: Loaded
default
TestExecutionListener
class
names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
六月
09
,
2017
2
:
07
:
56
下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]. Specify custom listener classes or make the
default
listener classes (and their required dependencies) available. Offending
class
: [org/springframework/transaction/interceptor/TransactionAttribute]
六月
09
,
2017
2
:
07
:
56
下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener]. Specify custom listener classes or make the
default
listener classes (and their required dependencies) available. Offending
class
: [org/springframework/transaction/interceptor/TransactionAttributeSource]
六月
09
,
2017
2
:
07
:
56
下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the
default
listener classes (and their required dependencies) available. Offending
class
: [javax/servlet/ServletContext]
六月
09
,
2017
2
:
07
:
56
下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getTestExecutionListeners
信息: Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener
@6e983d8d
, org.springframework.test.context.support.DependencyInjectionTestExecutionListener
@4cf12cb4
, org.springframework.test.context.support.DirtiesContextTestExecutionListener
@6dae04e2
]
六月
09
,
2017
2
:
07
:
56
下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from
class
path resource [applicationContext.xml]
六月
09
,
2017
2
:
07
:
56
下午 org.springframework.context.support.GenericApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.GenericApplicationContext
@24024ad8
: startup date [Fri Jun
09
14
:
07
:
56
CST
2017
]; root of context hierarchy
After Proxy......
环绕通知执行方法前
前置通知 : saveUser
save user[username=zby,password=
1234567890
]
后置通知 : saveUser , -->
null
最终通知
环绕通知执行方法后
|
这个DEMO就是一个大杂烩,其实使用时使用一个环绕通知就够了。再环绕通知里面必须手动执行方法,因此我们用try-catch把方法执行包裹起来,然后在执行前和执行后写增强代码即可。
使用注解配置Spring整合AspectJ的AOP:
1)上面的一二步骤不变。
2)编写目标类UserService:
1
2
3
4
5
6
7
8
9
10
11
12
|
package
com.zby.service;
import
org.springframework.stereotype.Service;
@Service
public
class
UserService {
public
void
saveUser(String username, String password) {
System.out.println(
"save user[username="
+ username +
",password="
+ password +
"]"
);
}
}
|
3)编写切面类,使用注解:
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
57
58
59
60
61
62
63
64
65
|
package
com.zby.interceptor;
import
org.aspectj.lang.JoinPoint;
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;
import
org.springframework.stereotype.Component;
@Component
@Aspect
public
class
MyAspect {
// 多个方法需要使用这个切入点表达式,定义为一个公用的
@Pointcut
(
"execution(* com.zby.service..*(..))"
)
public
void
myPointCut() {
}
// 这里注解里面的值为上面的方法名
@Before
(
"myPointCut()"
)
public
void
myBefore(JoinPoint joinPoint) {
System.out.println(
"前置通知 : "
+ joinPoint.getSignature().getName());
}
// 当你只有一个方法,或者只在这儿用,可以直接写切入点表达式
@AfterReturning
(value =
"execution(* com.zby.service..*(..))"
, returning =
"ret"
)
public
void
myAfterReturning(JoinPoint joinPoint, Object ret) {
System.out.println(
"后置通知 : "
+ joinPoint.getSignature().getName() +
" , -->"
+ ret);
}
//
@Around
(
"myPointCut()"
)
public
Object myAround(ProceedingJoinPoint joinPoint)
throws
Throwable {
System.out.println(
"环绕通知执行方法前"
);
// 手动执行目标方法
Object obj = joinPoint.proceed();
System.out.println(
"环绕通知执行方法后"
);
return
obj;
}
@AfterThrowing
(value =
"myPointCut()"
, throwing =
"e"
)
public
void
myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println(
"抛出异常通知 : "
+ e.getMessage());
}
@After
(
"myPointCut()"
)
public
void
myAfter(JoinPoint joinPoint) {
System.out.println(
"最终通知"
);
}
}
|
4)编写配置文件applicationContext.xml:
1
2
3
4
5
6
7
8
9
10
11
12
|
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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
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-4.3.xsd">
|
5)编写测试类:
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
|
package
com.zby.test;
import
org.junit.Test;
import
org.junit.runner.RunWith;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.test.context.ContextConfiguration;
import
org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import
com.zby.service.UserService;
@RunWith
(SpringJUnit4ClassRunner.
class
)
@ContextConfiguration
(locations = {
"classpath:applicationContext.xml"
})
public
class
AOPTest {
@Autowired
private
UserService userService;
@Test
public
void
testProxy() {
System.out.println(
"After Proxy......"
);
userService.saveUser(
"zby"
,
"1234567890"
);
}
}
|
6)控制台打印结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
六月
09
,
2017
2
:
29
:
21
下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getDefaultTestExecutionListenerClassNames
信息: Loaded
default
TestExecutionListener
class
names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
六月
09
,
2017
2
:
29
:
21
下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]. Specify custom listener classes or make the
default
listener classes (and their required dependencies) available. Offending
class
: [org/springframework/transaction/interceptor/TransactionAttribute]
六月
09
,
2017
2
:
29
:
21
下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the
default
listener classes (and their required dependencies) available. Offending
class
: [javax/servlet/ServletContext]
六月
09
,
2017
2
:
29
:
21
下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener]. Specify custom listener classes or make the
default
listener classes (and their required dependencies) available. Offending
class
: [org/springframework/transaction/interceptor/TransactionAttributeSource]
六月
09
,
2017
2
:
29
:
21
下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getTestExecutionListeners
信息: Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener
@6dae04e2
, org.springframework.test.context.support.DependencyInjectionTestExecutionListener
@3bc2c9af
, org.springframework.test.context.support.DirtiesContextTestExecutionListener
@71471ecf
]
六月
09
,
2017
2
:
29
:
21
下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from
class
path resource [applicationContext.xml]
六月
09
,
2017
2
:
29
:
21
下午 org.springframework.context.support.GenericApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.GenericApplicationContext
@69f31d
: startup date [Fri Jun
09
14
:
29
:
21
CST
2017
]; root of context hierarchy
After Proxy......
环绕通知执行方法前
前置通知 : saveUser
save user[username=zby,password=
1234567890
]
环绕通知执行方法后
最终通知
后置通知 : saveUser , -->
null
|
总结:对比起来,可以看出来使用最后一种方式开发AOP很方便,这也是我们最常用的,至于spring原生的AOP,大多在一些框架里面看到。使用整合AspectJ的方式,最主要的是要注意切面表达式的书写和方法参数传入,以及怎么使用这些参数。