AOP
简介
AOP(Aspect Oriented Programming):面向切面编程
OOP(Object Oriented Programming):面向对象编程
面向切面编程:基于OOP基础之上新的编程思想;指在运行期间,将某段代码动态的切入到指定方法的指定位置进行运行的这种编程方式
动态代理
可以使用动态代理 在目标方法执行前后进行执行
- 缺点
- jdk默认的动态代理,如果目标对象没有实现任何的接口,是无法为他创建代理对象的
- 实现起来比较繁琐
//这是帮Calculator生成的代理对象类
//getProxy(Calculator calculator) ---calculator:被代理的对象
public class CalculatorProxy{
public static Calculator getProxy(final Calculator calculator){
//loader:类加载器
ClassLoader loader = calculator.getClass().getClassLoader();
//类所实现的所有接口
Class<?>[] interfaces = calculator.getClass().getInterfaces();
//方法执行器,帮我们目标对象执行目标方法
InvocationHandler h = new InvocationHandler(){
//Object proxy:代理对象,不要动这个对象
//Method method:当前将要执行的目标对象的方法
//Object[] args:方法调用时外界传入的参数值
@Override
public Object invoke(Object proxy,Method method,Object[] args)
throws Throwable{
//利用反射执行目标方法
//result:目标方法执行后的返回值
System.out.println("方法执行前,我被输出了")
Object result = method.invoke(calculator,args);
System.out.println("方法执行后,我被输出了")
//返回值必须返回出去,外界才能拿到真正执行后的返回值
return result;
}
};
//Proxy为目标对象创建代理对象
Object proxy = Proxy.newProxyInstance(loader,interfaces,h);
return (Calculator)proxy;
}
}
注意!
综上缺点,spring实现了AOP功能,底层就是动态代理,实现简单,而且没有强制要求目标对象必须实现接口
AOP专业术语
连接点(Join point):
- 能够被拦截的地方:Spring AOP是基于动态代理的,所以是方法拦截的。每个成员方法都可以称之为连接点~
切点(Poincut):
- 具体定位的连接点:上面也说了,每个方法都可以称之为连接点,我们具体定位到某一个方法就成为切点。
增强/通知(Advice):
- 表示添加到切点的一段逻辑代码,并定位连接点的方位信息。
- 简单来说就定义了是干什么的,具体是在哪干
- Spring AOP提供了5种Advice类型给我们:前置、后置、返回、异常、环绕给我们使用!
织入(Weaving):
- 将
增强/通知
添加到目标类的具体连接点上的过程。
引入/引介(Introduction):
-
引入/引介
允许我们向现有的类添加新方法或属性。是一种特殊的增强!
切面(Aspect):
- 切面由切点和
增强/通知
组成,它既包括了横切逻辑的定义、也包括了连接点的定义。
AOP使用场景
- AOP加日志保存到数据库
- AOP做权限验证
- AOP做安全检查
- AOP做事务控制
基于注解的AOP配置
使用步骤
- 步骤一:导包
- 步骤二:配置
- 步骤三:测试
-
Spring支持面向切面编程的包
基础版
-
spring-aspects-4.0.0
加强版
com.springsource.net.sf.cglib-2.2.0
com.springsource.org.aspectj.weaver-1.6.8
com.springsource.org.aopalliance-1.0.0
-
-
配置
将目标类和切面类(封装了通知方法的类)加入到ioc容器中
-
告诉spring哪一个是切面类
- 在切面类上面加上 @Aspect 注解
-
告诉spring,切面类里面的方法是 何时何地 运行
5个通知注解
在目标方法执行之前执行:@Before(前置通知)
在目标方法正常返回之后执行:@AfterReturning(返回通知)
在目标方法出现异常的时候执行:@AfterThrowing(异常通知)
在目标方法结束的时候执行:@After(后置通知)
@Around(环绕通知)
切入点表达式——execution(访问权限符 返回值类型 方法签名)eg:@Before("execution(public int com.Calculator.add(int,int))")
-
开启AOP注解的配置
<!--首先需要导入aop命名空间--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
测试
-
第一种情况:如果目标对象有实现的接口,容器中保存的是jdk为我们创建的代理类
注意,如果目标对象有实现的接口,从IOC容器中获取到目标对象的时候,如果想要用类型,一定要用他的接口类型,不要用它本类
Calculator的接口 bean = ioc.getBean(Calculator的接口.class); //或者 Calculator的接口 bean2 = (Calculator的接口)ioc.getBean(Calculator.class);
-
第二种情况:如果目标对象没有实现接口,容器中保存的是cglib为我们创建的代理类,这时候按照类型获取bean的时候就可以使用本类类型
Calculator bean = ioc.getBean(Calculator.class);
-
切入点表达式写法(通配符)
固定格式:execution(访问修饰符 返回值类型 方法全类名(参数表))
通配符
*
:匹配一个或多个字符
eg:execution(public int com.My. (int, ))——以My开头的类并且第一个参数是int类型,第二个参数是任意类型的所有 方法
注意!访问修饰符位置不能使用 “ * ”表示任意
访问修饰符位置不写就表示任意,但是也只能匹配public的,其他访问修饰符不能匹配
..
:匹配任意多个参数,任意类型参数
eg:execution(public int com.My. (..))*——以My开头的类的所有方法(参数类型不限制)
最模糊的匹配规则: execution( (..))
第一个*表示任意返回值,第二个 *表示任意包任意类任意方法
或者 execution( . (..))
第一个表示任意返回值,第二个 *表示任意包任意类,第三个 *表示任意方法最精确的匹配规则:execution(public int com.Calculator.add(int,int))
-
切入表达式之间 可以使用“&&”“||”“!”
- “&&”:同时满足
- “||”:满足其中一个即可
- “!”:非
通知方法的执行顺序
try{
@Before
method.invoke(obj,args);
@AfterReturning
}catch(){
@AfterThrowing
}finally{
@After
}
- 正常执行
- @Before(前置通知)——@After(后置通知)——@AfterReturning(返回通知)
- 异常执行
- @Before(前置通知)——@After(后置通知)——@AfterThrowing(异常通知)
JoinPoint获取目标方法的信息
我们可以在通知方法运行的时候,拿到目标方法的详细信息
- 只需要为通知方法的参数列表上写上一个参数
@Before("execution(public int com.Calculator.add(int,int))")
public static void log(JoinPoint joinPoint){
//获取到目标方法运行时使用的参数列表
joinPoint.getArgs();
//获取到目标方法的签名
Signature signature = joinPoint.getSignature();
//获取到目标方法的方法名
signature.getName();
//
}
-
接受目标方法抛出的异常和返回的值
通知注解中加入 returning 指定哪个参数接受目标方法的返回值
通知注解中加入 throwing指定哪个参数接受目标方法抛出的异常
@Before(value="execution(public int com.Calculator.add(int,int))",
returning="result",
throwing="exception")
public static void log(JoinPoint joinPoint,Object result,Exception exception){
//获取到目标方法运行时使用的参数列表
joinPoint.getArgs();
//获取到目标方法的签名
Signature signature = joinPoint.getSignature();
//获取到目标方法的方法名
signature.getName();
//
}
注意!通知方法唯一的要求就是通知方法的参数列表一定不能乱写,因为通知方法是spring利用反射调用的,每次调用方法需要确定这个方法的参数表的值,所以参数表上的每一个参数,spring都要知道是什么
环绕通知@Around(以及通知的执行顺序)
- @Around:环绕通知是spring中强大的通知
try{
@Before
method.invoke(obj,args);
@AfterReturning
}catch(){
@AfterThrowing
}finally{
@After
}
- @Before、@AfterReturning、@AfterThrowing、@After四合一通知就是环绕通知
- 环绕通知
@Around("execution(public int com.Calculator.add(int,int))")
public Object myAround(ProceedingJoinPoint pjp){
//就是利用反射调用目标方法,就是 method.invoke(obj,args)
Object[] args = pjp.proceed();
try{
System.out.println("前置通知")
//方法的返回值
Object proceed = pjp.proceed(args)
System.out.println("返回通知")
}catch(){
System.out.println("异常通知")
}finally{
System.out.println("后置通知")
}
//方法的返回值也一定要返回出去
return proceed;
}
注意!环绕通知中的代码是优先进行,它优先于普通通知执行
-
执行顺序
【环绕通知前置】
【普通通知前置】
目标方法执行
【环绕通知返回/环绕通知异常】
【环绕后置】
【普通通知后置】
【普通通知返回/普通通知异常】
- 注意!【环绕通知前置】和【普通通知前置】这两个执行顺序随机
重用抽取切入表达式
- 随便声明一个没有实现的返回void的空方法
- 给方法上标注一个@Pointcut注解
@Pointcut("execution(public int com.Calculator.add(int,int))")
public void MyPointCut(){
}
//别的方法如果想使用
@Before("MyPointCut()")
public void log(){
...
}
多切面运行顺序
假设有两个切面类 A和B
@Order:指定切面进入顺序,数值越小,优先级越大;如果不设置默认按照类名首字母顺序先后进入
@Component
@Aspect
@Order(1)
public class A {
...(A切面通知方法)
}
@Component
@Aspect
@Order(2)
public class B {
...(B切面通知方法)
}
-
第一种情况:两个切面都没有环绕通知
- 执行顺序为
- 【A切面普通前置通知】
- 【B切面普通前置通知】
- 目标方法执行
- 【B切面普通后置通知】
- 【B切面普通异常通知/B切面普通返回通知】
- 【A切面普通后置通知】
- 【A切面普通异常通知/A切面普通返回通知】
注意!先进后出
- 执行顺序为
-
第二种情况:两个切面中A切面有环绕通知
- 执行顺序为
- 【A切面环绕前置通知】
- 【A切面普通前置通知】
- 【B切面普通前置通知】
- 目标方法执行
- 【B切面普通后置通知】
- 【B切面普通异常通知/B切面普通返回通知】
- 【A切面环绕异常通知/A切面环绕返回通知】
- 【A切面环绕后置】
- 【A切面后置通知】
- 【A切面异常通知/A切面返回通知】
注意!环绕只是影响当前切面,并不会影响其他切面
- 执行顺序为
基于配置文件的AOP配置
配置步骤
- 步骤一:在ioc容器中注册切面类
<bean id="A" class="com.component.A"></bean>
<bean id="B" class="com.component.B"></bean>
-
步骤二:在容器中指定哪个bean是切面类
需要引入aop命名空间
<aop:config>
<aop:aspect ref="A"></aop:aspect>
<aop:aspect ref="B"></aop:aspect>
</aop:config>
- 步骤三:指明切面类中的通知方法都何时何地运行
<aop:config>
<aop:pointcut expression="execution(* com.component.*(..))" id="mypoint"/>
<aop:aspect ref="A">
<aop:before method="logBefore" pointcut="execution(* com.component.*(..))"/>
<aop:after method="logAfter" pointcut="mypoint"/>
<aop:after-returning method="logAfterReturning" pointcut-ref="mypoint" returning="result"/>
<aop:after-throwing method="logException" pointcut-ref="mypoint" throwing="exception"/>
</aop:aspect>
<aop:aspect ref="B"></aop:aspect>
</aop:config>