spring day02——AspectJ 切面编程(xml方式)
百度百科文档:
http://baike.baidu.com/link?url=2h5jS5pGRtcNexdPQBt1CSgPXpqFyyCu0WR-dJKh9mLxjdpLloT9XsY7pQIwGdOLQB1n7Shfoqr65JXiOtmDEK
早期传统Spring1.2 AOP编程 , 确定目标 --- 编写通知(通知需要实现一个接口)
---- 配置ProxyFactoryBean生成代理对象,配置非常复杂,
spring2.0 支持AspectJ 语法 ,简化AOP开发。
开发方法还是三步:
- 确定目标对象(bean)
- 编写通知,对目标对象增强(advice)
- 配置切入点(pointcut)、切面(aspect)
- AspectJ 提供Advice类型
普通的pojo即可。(不需要实现接口)
AspectJ提供不同的通知类型:
- Before 前置通知,相当于BeforeAdvice
- AfterReturning 后置通知,相当于AfterReturningAdvice
- Around 环绕通知,相当于MethodInterceptor
- AfterThrowing抛出通知,相当于ThrowAdvice
- After 最终final通知,不管是否异常,该通知都会执行
- DeclareParents 引介通知,相当于IntroductionInterceptor (不要求掌握)
相比传统Spring AOP通知类型多了 After最终通知 (类似 finally )。
实现步骤:
第一步:确定目标对象,即确定bean对象
第二步:advice通知(编写)
第三步:配置切面(包括切入点),让切入点关联通知
第一步:新建web项目spring4_d02_c05
导入jar包 (11个)
基本spring开发 6个 (4+2)(包含配置文件)
Spring测试集成 1个
Spring AOP编程 4个 (2对)
- com.springsource.org.aopalliance-1.0.0.jar: AOP联盟规范
- spring-aop-4.2.4.RELEASE.jar:springAOP 整合扩展
- com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar:aspectJ官方包
- spring-aspects-4.2.4.RELEASE.jar:spring对aspectJ集成
AOP联盟包:
路径:spring-framework-3.0.2.RELEASE-dependencies\org.aopalliance\com.springsource.org.aopalliance\1.0.0
Aspectj包:
路径:spring-framework-3.0.2.RELEASE-dependencies\org.aspectj\com.springsource.org.aspectj.weaver\1.6.8.RELEASE
共需要导入的jar包
在src下,创建applicationContext.xml。
第一步: 引用aop的名称空间
查看:spring-framework-4.2.4.RELEASE\docs\spring-framework-reference\html中的xsd-config.html,搜素aop,发现:
需要我们引入aop的文件约束。如图
<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"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
配置aop提示
第二步:编写Before 前置通知Advice增强 :
创建包:com.igeek,复制上个项目中的
CustomerServiceImpl.java,ICustomerService.java,ProductService.java到包中
配置bean
<!-- 1.确定了要增强的target对象 -->
<!-- 对于spring来说,目标对象:就是bean对象 -->
<!-- 基于接口类 -->
<bean id="customerService" class="com.igeek.CustomerServiceImpl"/>
<!-- 基于一般类 -->
<bean id="productService" class="com.igeek.ProductService"/>
创建类:MyAspect.java
编写MyAspect.java
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {
//前置通知
//普通的方法。方法名随便,但也不能太随便,一会要配置
public void firstbefore(){
System.out.println("------------第一个个前置通知执行了。。。");
}
}
将前置通知配置到spring的容器中
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="com.igeek.MyAspect"/>
- 配置切入点和切面(让切入点关联通知)
第三步:配置切面(包括切入点),让切入点关联通知
核心配置文件applicationContext.xml中添加:
<!-- 3:配置aop -->
<aop:config>
<!-- 切入点:拦截哪些bean的方法 -->
<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
<!--
切面:要对哪些方法进行怎样的增强
aop:aspect:aspejctj的方式!
ref:配置通知
-->
<aop:aspect ref="myAspectAdvice">
<!-- 第一个前置通知 :在访问目标对象方法之前,先执行通知的方法
method:advice类中的方法名,
pointcut-ref="myPointcut":注入切入点
目的是让通知关联切入点
-->
<aop:before method="firstbefore" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
使用SpringTest测试代码:
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class SpringTest {
//注入要测试bean
@Autowired
private ICustomerService customerService;
@Autowired
private ProductService productService;
//测试
@Test
public void test(){
//基于接口
customerService.save();
customerService.find();
//基于类的
productService.save();
productService.find();
}
}
测试结果:
和传统的aop配置相比,更灵活,advice不需要实现接口,简单的pojo就可以了;一个通知可以增强多个连接点,一个连接点可以被多次增强。
【扩展优化】:
1.将切入点放入aspectj标签里面写,同时配置多个通知方法
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="cn.itcast.spring.c_aspectjaop.MyAspect"/>
<!-- 3:配置aop -->
<aop:config>
<!--
切面:要对哪些方法进行怎样的增强
aop:aspect:aspejctj的方式!
ref:配置通知
-->
<aop:aspect ref="myAspectAdvice">
<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
<!-- 第一个前置通知 :在访问目标对象方法之前,先执行通知的方法
method:advice类中的方法名,
pointcut-ref="myPointcut":注入切入点
目的是让通知关联切入点
-->
<aop:before method="firstbefore" pointcut-ref="myPointcut"/>
<aop:before method="firstbefore2" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
2.配置多个通知方法:
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {
//前置通知
//普通的方法。方法名随便,但也不能太随便,一会要配置
public void firstbefore(){
System.out.println("------------第一个前置通知执行了。。。");
}
public void firstbefore2(){
System.out.println("------------第二个前置通知执行了222。。。");
}
}
- 执行结果:表示在执行目标对象方法之前执行
AspectJ切面编程,相比于传统的SpringAOP,定义的通知方法更多。
- 分析各种通知应用
Before前置通知
案例应用: 实现权限控制 (即:权限不足的时候,抛出异常)、 记录方法调用信息日志
第一步:配置MyAspect类(切面),配置before方法(通知)
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {
//前置通知的:方法运行之前增强
//应用: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志
//参数:org.aspectj.lang.JoinPoint
//参数:连接点对象(方法的包装对象:方法,参数,目标对象)
public void before(JoinPoint joinPoint){
//分析:抛出异常拦截的
//当前登录用户
String loginName = "Rose";
System.out.println("方法名称:"+joinPoint.getSignature().getName());
System.out.println("目标对象:"+joinPoint.getTarget().getClass().getName());
System.out.println("代理对象:"+joinPoint.getThis().getClass().getName());
//判断当前用户有没有执行方法权限
if(joinPoint.getSignature().getName().equals("find")){
if(!loginName.equals("admin")){
//只有超级管理员admin有权限,其他人不能执行某个方法,比如查询方法
throw new RuntimeException("您没有权限执行方法:"+joinPoint.getSignature().getName()+",类型为:"+joinPoint.getTarget().getClass().getName());
}
}
}
}
通过JoinPoint 连接点对象,获取目标对象信息 !
这里注意:引包不要引错了,使用aspectj中的连接点(org.aspectj.lang.JoinPoint):
第二步:Spring容器中配置,配置applicationContext.xml
<!-- 3:配置aop -->
<aop:config>
<aop:aspect ref="myAspectAdvice">
<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
<!-- 前置通知 -->
<aop:before method="before" pointcut-ref="myPointcut" />
</aop:aspect>
</aop:config>
第三步:使用SpringTest.java进行测试:
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class SpringTest {
//注入要测试bean
@Autowired
private ICustomerService customerService;
@Autowired
private ProductService productService;
//测试
@Test
public void test(){
//基于接口
customerService.save();
customerService.find();
//基于类的
productService.save();
productService.find();
}
}
提示:
Aspectj的文档
JoinPoint使用参考文档:资料\jar\aspectj-1.7.3\doc\runtime-api\index.html
提示:和传统aop的对比发现,aspectj更灵活,一个类中可以写N个增强方法,但传统的只能是一个类对应一个方法。
AfterReturing 后置通知
特点:在目标方法运行后,返回值后执行通知增强代码逻辑。
应用场景:与业务相关的,如网上营业厅查询余额后,自动下发短信功能。
分析: 后置通知可以获取到目标方法返回值,如果想对返回值进行操作,使用后置通知(但不能修改目标方法返回 )
第一步:配置MyAspect类(切面),配置afterReturing方法(通知)
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {
//应用场景:与业务相关的,如网上营业厅查询余额后,自动下发短信。
//后置通知:会在目标方法执行之后调用通知方法增强。
//参数1:连接点对象(方法的包装对象:方法,参数,目标对象)
//参数2:目标方法执行后的返回值,类型是object,“参数名”随便,但也不能太随便,一会要配置
public void afterReturing(JoinPoint joinPoint,Object returnVal){
//下发短信:调用运行商的接口,短信猫。。。
System.out.println("-++++++++-后置通知-当前下发短信的方法"+"-尊敬的用户,您调用的方法返回余额为:"+returnVal);
//同时可以可以在下发后,记录日志:
System.out.println("----日志记录,操作的类型:"+joinPoint.getTarget().getClass().getSimpleName()
+",操作的方法:"+joinPoint.getSignature().getName()
);
}
}
第二步:Spring容器中配置,配置applicationContext.xml
<!-- 3:配置aop -->
<aop:config>
<aop:aspect ref="myAspectAdvice">
<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
<!-- 后置通知
returning:配置方法中的参数名字,与通知方法的第二个参数的名字,名字必须对应。
在运行的时候,spring会自动将返回值传入该参数中。
-->
<aop:after-returning method="afterReturing" returning="returnVal" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
第三步:使用SpringTest.java进行测试:
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class SpringTest {
//注入要测试bean
@Autowired
private ICustomerService customerService;
@Autowired
private ProductService productService;
//测试
@Test
public void test(){
//基于接口
customerService.save();
customerService.find();
//基于类的
productService.save();
productService.find();
}
}
Around 环绕通知
特点:目标执行前后,都进行增强(控制目标方法执行)
应用场景:日志、缓存、权限、性能监控、事务管理
增强代码的方法要求:
接受的参数:ProceedingJoinPoint(可执行的连接点)
返回值:Object返回值(即目标对象方法的返回值)
抛出Throwable异常。
【示例】
第一步:配置MyAspect类(切面),配置around方法(通知)
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {
//应用场景:日志、缓存、权限、性能监控、事务管理
//环绕通知:在目标对象方法的执行前+后,可以增强
//参数:可以执行的连接点对象ProcessdingJoinPoint(方法),特点是调用proceed()方法可以随时随地执行目标对象的方法(相当于目标对象的方法执行了)
//必须抛出一个Throwable
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
//目标:事务的控制:
//开启事务:
System.out.println("-----开启了事务。。。。。。。。。");
//执行了目标对象的方法
Object resultObject = proceedingJoinPoint.proceed();
//结束事务
System.out.println("-----提交了事务。。。。。。。。。");
return resultObject;//目标对象执行的结果
}
}
第二步:Spring容器中配置,配置applicationContext.xml
<!-- 3:配置aop -->
<aop:config>
<aop:aspect ref="myAspectAdvice">
<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
第三步:使用SpringTest.java进行测试:
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class SpringTest {
//注入要测试bean
@Autowired
private ICustomerService customerService;
@Autowired
private ProductService productService;
//测试
@Test
public void test(){
//基于接口
customerService.save();
customerService.find();
//基于类的
productService.save();
productService.find();
}
}
AfterThrowing 抛出通知(异常通知)
作用:目标代码出现异常,通知执行。记录异常日志、通知管理员(短信、邮件)
应用场景:处理异常(一般不可预知),记录日志
【示例】
第一步:配置MyAspect类(切面),配置aterThrowing方法(通知)
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {
//作用:目标代码出现异常,通知执行。记录异常日志、通知管理员(短信、邮件)
//只有目标对象方法抛出异常,通知才会执行
//参数1:静态连接点(方法对象)
//参数2:目标方法抛出的异常,参数名随便,但也不能太随便
public void afterThrowing(JoinPoint joinPoint,Throwable ex){
//一旦发生异常,发送邮件或者短信给管理员
System.out.println("++管理员您好,"+joinPoint.getTarget().getClass().getName()+"的方法:"
+joinPoint.getSignature().getName()+"发生了异常,异常为:"+ex.getMessage());
}
}
第二步:Spring容器中配置,配置applicationContext.xml
<!-- 3:配置aop -->
<aop:config>
<aop:aspect ref="myAspectAdvice">
<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
<!-- 抛出通知
throwing:通知中的方法的第二个参数,异常类型的参数的名字,在运行的时候,spring会自动将异常传入该参数中。-->
<aop:after-throwing method="afterThrowing" throwing="ex" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
第三步:使用SpringTest.java进行测试:
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class SpringTest {
//注入要测试bean
@Autowired
private ICustomerService customerService;
@Autowired
private ProductService productService;
//测试
@Test
public void test(){
//基于接口
customerService.save();
customerService.find();
//基于类的
productService.save();
productService.find();
}
}
此时发现通知的方法并没有执行。
那我们在目标对象的方法中故意抛出异常,大家看看效果
测试:
在ProductService.java中save的方法中,制造异常:
//没有接口的类
public class ProductService {
public void save() {
System.out.println("商品保存了。。。。。");
//故意制造异常
int d = 1/0;
}
public int find() {
System.out.println("商品查询数量了。。。。。");
return 99;
}
}
查看测试结果:
After 最终通知
作用:不管目标方法是否发生异常,最终通知都会执行(类似于finally代码功能)
应用场景:释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 )
【示例】
第一步:配置MyAspect类(切面),配置after方法(通知)
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {
//应用场景:释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 )
//最终通知:不管是否有异常都会执行
public void after(JoinPoint joinPoint){
//释放数据库连接
System.out.println("数据库的connection被释放了。。。。。,执行的方法是:"+joinPoint.getSignature().getName());
}
}
第二步:Spring容器中配置,配置applicationContext-aspectJ.xml
<!-- 3:配置aop -->
<aop:config>
<aop:aspect ref="myAspectAdvice">
<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
<!-- 最终通知 -->
<aop:after method="after" pointcut-ref="myPointcut"/>
<!-- 以上代码也可以写成:pointcut切入点表达式:只能给一个通知方法来用,相当于省略了<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
<aop:after method="after" pointcut="bean(*Service)"/>-->
</aop:aspect>
</aop:config>
第三步:使用SpringTest.java进行测试:
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class SpringTest {
//注入要测试bean
@Autowired
private ICustomerService customerService;
@Autowired
private ProductService productService;
//测试
@Test
public void test(){
//基于接口
customerService.save();
customerService.find();
//基于类的
productService.save();
productService.find();
}
}
查看测试结果:
五种通知小结:
(1)只要掌握Around(环绕通知)通知类型,就可实现其他四种通知效果。
(2)因为你可以在环绕通知的方法中编写如下代码:
try {
//前置通知
Object result = proceedingJoinPoint.proceed();
//后置通知
}catch(Exception){
//抛出通知
}finally{
//最终通知
}
各种Advice方法可接收的参数和返回值小结(参考)
方法格式:
public returnType method (param)
public 返回值类型 方法名 (参数类型 参数名)
返回值类型:void和Object
方法名:任意名称(但是也不能太随意)
参数类型:
* 参数类型为JoinPoint接口类型,返回值类型为void
* 参数类型为ProceedingJoinPoint接口类型,返回值类型为Object
具体为:
通知类型 |
输入参数(可选) |
返回值类型 |
其他 |
Before前置通知 |
JoinPoint(静态连接点信息) |
void |
|
AfterReturning后置通知 |
JoinPoint, Object |
void |
|
Around环绕通知 |
ProceedingJoinPoint(可执行的连接点信息) |
Object |
throws Throwable |
AfterThrowing抛出通知 |
JoinPoint, Throwable |
void |
|
After最终通知 |
JoinPoint |
void |
|
总结:
- 注解方式装配bean对象
- 混合配置
- 测试集成 (@runwith @ContextConfiguration(核心配置))---熟悉
- AOP的思想(如何实现),AOP在哪些地方使用?
- aop的原理(动态代理:jdk,cglib)
- AspectJ 切面编程(xml方式)
- 各种通知应用