AOP配置详解
解释完了AOP的底层,学习了各个术语,接下来开始应用。<br />这里先写一个需要被增强的类
UserDaoImpl.java
package com.ezeta.dao;
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("保存操作");
}
@Override
public String update() {
System.out.println("更新");
return "update---res";
}
@Override
public void delete() {
System.out.println("删除操作");
}
@Override
public void find() {
System.out.println("find");
}
}
<a name="jKn1I"></a>
1. 前置步骤
- step1:引入依赖jar包:
Spring基本jar包(略)、aop相关jar包,(aop-jar包,aspectJ-jar包,aopalliance-jar包)<br />最后一个jar包是aop联盟。
image.png
- step2:配置文件中aop约束:
applicationContext.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: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">
</beans>
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd"
这两个就是aop约束
<a name="ffIP8"></a>
2. 编写切面类
要讲封装好的各个通知封装到切面类中。
Myaspect.java
public class Myaspect {
public void check(){
System.out.println("权限校验");
}
public void log(Object res){
System.out.println("日志记录"+res);
}
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("开启事务");
Object proceed = joinPoint.proceed();
System.out.println("提交事务");
return proceed;
}
public void exceptionM(Throwable ex){
System.out.println("有异常"+ex.getMessage());
}
public void after(){
System.out.println("after通知");
}
}
以上每个通知都是不同的通知类型,下面会详说。<br />当然,既然是Spring框架的AOP,当然要把切面类也交给SpringIOC管理。(bean的配置略)
<a name="QrG8F"></a>
3.开始配置AOP
<aop:config>
<!--切点:给哪个方法增强-->
<aop:pointcut id="savepoint1" expression="execution(* com.ezeta.dao.UserDaoImpl.save(..))"/>
<aop:pointcut id="savepoint2" expression="execution(* com.ezeta.dao.UserDaoImpl.update(..))"/>
<aop:pointcut id="savepoint3" expression="execution(* com.ezeta.dao.UserDaoImpl.delete(..))"/>
<aop:pointcut id="savepoint4" expression="execution(* com.ezeta.dao.UserDaoImpl.find(..))"/>
<!--配置切面:增强是功能是什么 -->
<aop:aspect ref="myaspect">
<!--前置通知-->
<aop:before method="check" pointcut-ref="savepoint1" />
<!--后置通知-->
<aop:after-returning method="log" pointcut-ref="savepoint2" returning="res"/>
<!--环绕通知-->
<aop:around method="around" pointcut-ref="savepoint3"/>
<!--异常通知-->
<aop:after-throwing method="exceptionM" pointcut-ref="savepoint4" throwing="ex"/>
<!--最终通知-->
<aop:after method="after" pointcut-ref="savepoint4"/>
</aop:aspect>
</aop:config>
以上每个aop配置也都对应着是不同的通知类型,下面会详说。<br />这里只需先看简单的配置过程:(这里将切入点的类和切面类的bean写上)
<a name="hX8ZS"></a>
<bean id="userDao" class="com.ezeta.dao.UserDaoImpl"></bean>
<bean id="myaspect" class="com.ezeta.aop.Myaspect"></bean>
<aop:config>
<!--配置切点:给哪个方法增强-->
<aop:pointcut id="savepoint1" expression="execution(* com.ezeta.dao.UserDaoImpl.save(..))"/>
<!--配置切面:增强是功能是什么 -->
<aop:aspect ref="myaspect">
<!--配置通知-->
<aop:before method="check" pointcut-ref="savepoint1" />
</aop:aspect>
</aop:config>
可见,在aop配置标签中,先配置切点,expression语句是execution(* 切点的全路径.方法名(..);<br />然后配置切面,将切面的Springbean的id传进来;<br />在切面配置标签内配置通知,传通method=知方法名、pointcut-ref=切点id;<br />这样,SpringAOP就可以通过全路径找到切点,通过bean找到切面,通过方法名找到切面的通知,以及通过切点id与通知一起进行织入(产生代理)。
<a name="msi1O"></a>
4. 通知的分类
介绍了AOP配置的整个流程,接下来对上述代码通知&配置文件中所用的所有通知类型进行归纳,其中有注释说明可供参考
<a name="1JKqA"></a>
1. 前置通知
在基本配置AOP处所举例用到的就是前置通知,在切点执行之前执行,没什么特殊的。
<a name="fOdy7"></a>
2. 后置通知
顾名思义在切点执行之后执行,注意标签不是after是after-returning,既然有“returning”说明这个标签还有特殊的地方。它比普通前置通知多一个属性returning="res",它可以接收切点的返回值,通过通知的参数传进来,参数类型可以直接设Object(见代码),但是要求属性returning的值必须是和通知的参数名字一致。例子中将切点update方法增了个String的返回值“ return "update---res";”,然后由它的通知log(Object res)方法接收。
<a name="8mbBf"></a>
3. 环绕通知
依然顾名思义,是能让通知在切点执行前后都有增强,甚至还能阻止切点运行,这点非常像Filter过滤器.<br />环绕通知的配置与前置一样就不说了,就是标签变成around。<br />环绕的通知方法写法就有些要求:
1. 有个参数**ProceedingJoinPoint joinPoint,**参数就是切点这个方法;
1. 要抛个异常;
1. 如果要让切点执行,需要调用参数joinPoint的方法:**Object proceed = joinPoint.proceed();**并取得返回值proceed,调用这个方法前后就可以写一些环绕它的增强的内容了;4.最后这个通知要返回proceed对象。
<a name="lay20"></a>
4. 异常抛出通知
只有切点抛出异常,才会调用这个通知方法,标签里面多个属性throwing="ex",在通知方法中用参数接收切点抛出的异常(参数类型当然是异常类啦),这里参数名也是和属性throwing的值一致。
<a name="g5ILj"></a>
5. 最终通知
after标签是最终通知才会用,首先是后执行的通知,其次是和上面异常抛出通知比较,最终通知是不论抛没抛异常都执行。
<a name="LuYno"></a>
5. AOP切入点表达式
基于execution函数完成,也就是 属性expression=的值啦。<br />格式:execution ([访问修饰符] 方法返回值 包名.类名.方法名(参数));
- public com.ezeta.demo2.GoodsDaoImpl.save(..) ,“...”代表参数为任意参数
- com.ezeta.demo2.GoodsDaoImpl.save(..),“*”代表省略返回值(即任意返回值)
- com.ezeta.demo2.GoodsDaoImpl+.save(..) “+”代表这个类以及当前类和子类
- * com.ezeta.*.(..) ,第一个“__”代表com.ezeta下的所有子包和子类;第二个“*”代表类里面的任意方法。
6. AOP注解开发
和IOC&DI一样,Spring喜欢注解开发。<br />AOP注解很简单,其中主要就是对切面类打上各种注解
- step1:在配置文件中开启aop注解开发(和IOC&DI一样)
当然被代理类,切面类的<bean/>也要写好
<aop:aspectj-autoproxy/>
- step2:在切面类上添加注解@Aspect(告诉Spring这是切面)
step3:在各个通知上面添加相应注解@注解(value="[execution表达式]")
@Aspect
public class Myaspect {
@Before(value="execution(* com.ezeta.dao.UserDaoImpl.save(..))")
public void check(){
System.out.println("权限校验");
}
@AfterReturning(value="execution(* com.ezeta.dao.UserDaoImpl.update(..))",
returning="res")
public void log(Object res){
System.out.println("日志记录"+res);
}
@Around(value="execution(* com.ezeta.dao.UserDaoImpl.delete(..)))
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("开启事务");
Object proceed = joinPoint.proceed();
System.out.println("提交事务");
return proceed;
}
@AfterThrowing(value="execution(* com.ezeta.dao.UserDaoImpl.find(..))",
throwing="ex")
public void exceptionM(Throwable ex){
System.out.println("有异常"+ex.getMessage());
}
@After(value="execution(* com.ezeta.dao.UserDaoImpl.find(..))")
public void after(){
System.out.println("after通知");
}
}
可以发现,通知的注解配置其实和标签名字一样,并且就是讲标签中的属性放到注解的参数当中了(只用写execution表达式即可)。其余没有任何变化,比如返回值名字要和通知参数一样等。
前置通知、环绕通知、最终通知一模一样
后置通知注解参数加上returning(逗号隔开各个参数)就接收返回值,也可以不加(不接收),异常通知同理。
还可以配置切入点:<br />在切面类当中,使用注解@Pointcut(value="[execution表达式]"),在下面跟一个空方法,方法名即为切点id
@Aspect
public class Myaspect {
@Before(value="Myaspect.pointcut1() || Myaspect.pointcut2()")
public void check(){
System.out.println("权限校验");
}
@Pointcut(alue="execution(* com.ezeta.dao.UserDaoImpl.save(..)) || execution(* com.ezeta.dao.StudentDaoImpl.save(..))")
private void pointcut1(){}//空方法
@Pointcut(alue="execution(* com.ezeta.dao.UserDaoImpl.save2(..))")
private void pointcut2(){}
}
可以看出,可以直接在通知注解参数中写此类的切点注解的方法,其中“||”可以连接多个切点,或者多个execution表达式。
没有总结<br />完