关于AOP,主要参考http://www.cnblogs.com/xrq730/p/4919025.html
顺便说一句,这位大神的博客真是厉害。
一些基本概念罗列如下:
AOP核心概念
1、横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
2、切面(aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象
3、连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
4、切入点(pointcut)
对连接点进行拦截的定义
5、通知(advice)
所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
6、目标对象
代理的目标对象
7、织入(weave)
将切面应用到目标对象并导致代理对象创建的过程
8、引入(introduction)
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
实际上我们在使用时,简单的实现只用关注以下几点
1.要切入什么功能——这就是切面的组件
2.切入的时机
3.在哪里切入–切入点pointcut
下面就举一个简单的例子说明
<?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:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd">
<!--将TestAop中的hello方法切入到cn.note包以及他的子包下-->
<bean name="testAop" class="com.note.aop.TestAop"></bean>-->
<aop:config>
<!-- 将testaop定义为切面组件-->
<aop:aspect ref="testAop">
<!-- 什么时候,向哪些方法切入 method是要切入的方法,pointcut:向哪些地方切入-->
<aop:before method="hello"
pointcut="within(cn.note..*)"/>
</aop:aspect>
</aop:config>
</beans>
将TestAop中的hello方法切入到cn.note包以及他的子包下,这样cn.note包下及子包下程序在运行前都会执行hello方法。
大致的意思都明白了,但是切入点pointcut里面究竟该怎么写还是有点费解。
这里我参考了
https://zhuanlan.zhihu.com/p/23356489
首先 是关于within的写法
大神们的对within的解释为:within 表示方法属于一个特定的类
我个人觉得 within是一种类型的匹配,只要在winthin所定义的范围之内的类都会被切入
例如
within(org.service.*)
表示的是org.service这个包下所有的类
within(org.service..*)
表示的是org.service这个包以及他的子包下所有的类
在日常编码中,我们碰到最多的是其实是execution
所以对于这个pointcut我们一定要熟悉
excution 的具体格式如下
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
翻译为
execution(访问修饰符? 返回值类型 方法所在的类的路径? 方法名(方法的参数) 方法抛出的异常?)
带问号的是可以省去的
例如对于下面的这样的一个方法
public void demo1(String str) throws Exception{}
我们可以将其写为
execution(void demo1(String str))
如果返回类型也不做要求的话
可以写成
execution(* demo1(String str))
如果参数也不重要的话
可以写成(..),表示参数个数任意,类型任意
execution(* demo1(..))
更过分的是,你连方法名也不怎么在意
你更可以这样
execution(* demo*(..))
不过 一般来说 我们都希望在某个包下某个类中切入
一般 我们都会写成这样
execution(* org.service.UserService.*(..))
表示org.service.UserService下所有方法。。
切记,execution必须要表示到方法级别
例如,你要是写成
execution(* org.service.*)
只到类这一级别,spring容器就会无法识别。
更为详细的pointcut,我们可以看
https://zhuanlan.zhihu.com/p/23356489
定义了切点之后,我们需要定义何时调用方法,即需要定义通知。
AspectJ提供了五种定义通知的标注:
@Before:前置通知,在调用目标方法之前执行通知定义的任务
@After:后置通知,在目标方法执行结束后,无论执行结果如何都执行通知定义的任务
@After-returning:后置通知,在目标方法执行结束后,如果执行成功,则执行通知定义的任务
@After-throwing:异常通知,如果目标方法执行过程中抛出异常,则执行通知定义的任务
@Around:环绕通知,在目标方法执行前和执行后,都需要执行通知定义的任务
通过标注定义通知只需要两个步骤:
将以上五种标注之一添加到切面的方法中
在标注中设置切点的定义
这里主要是around通知 比较特殊,具体案例如下
@Around("execution(* cn.tedu.note.service.*Service.*(..))")
public Object around(
ProceedingJoinPoint joinPoint)
throws Throwable{
System.out.println("开始");
//调用业务方法
Signature m=joinPoint.getSignature();
System.out.println(m);//输出方法签名
//在执行方法之前
Object value=joinPoint.proceed();//执行方法
//执行方法之后
System.out.println("结束");
return value;//业务方法的返回值
}
下面讲解 基于注解的AOP配置
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:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd">
<!-- 扫描到控制器组件 -->
<context:component-scan
base-package="cn.note.aop"/>
<!-- 用于支持注解版的AOP @Aspect -->
<aop:aspectj-autoproxy/>
</beans>
就只有两句话,第一句是把cn.note.aop包下带注解的类交给spring管理
第二句用于支持aop的注解
具体类的实现
package cn.note.aop;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component//组件扫描
@Aspect//aop标识
public class ExceptionLogger {
@After("within(cn.note.web.*)")
public void log(){
System.out.println("进入controller之后");
}
//错误日志报告
@AfterThrowing(throwing="ex",pointcut="execution(* cn.note.web.*.*(..))")
public void logEx(Exception ex){
try {
FileWriter fw=new FileWriter("D:\\error.text");
PrintWriter ps=new PrintWriter(fw);
ex.printStackTrace(ps);
ps.flush();
ps.close();
fw.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
关于多个切面的顺序,参考大神的博客
<aop:config>
<aop:aspect id="time" ref="timeHandler" order="1">
<aop:pointcut id="addTime" expression="execution(* com.xrq.aop.HelloWorld.*(..))" />
<aop:before method="printTime" pointcut-ref="addTime" />
<aop:after method="printTime" pointcut-ref="addTime" />
</aop:aspect>
<aop:aspect id="log" ref="logHandler" order="2">
<aop:pointcut id="printLog" expression="execution(* com.xrq.aop.HelloWorld.*(..))" />
<aop:before method="LogBefore" pointcut-ref="printLog" />
<aop:after method="LogAfter" pointcut-ref="printLog" />
</aop:aspect>
</aop:config>
要想让logHandler在timeHandler前使用有两个办法:
(1)aspect里面有一个order属性,order属性的数字就是横切关注点的顺序
(2)把logHandler定义在timeHandler前面,Spring默认以aspect的定义顺序作为织入顺序