文章目录
一、AOP回顾
aop介绍
Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
特点:
字节码随用随创建,随用随加载
分类:
1.基于接口的动态代理
提供者是:JDK官方
使用要求:被代理类最少实现一个接口。
涉及的类:Proxy
创建代理对象的方法:newProxyInstance
模板:
@Component
public class ProxyServiceFactory {
@Bean("proxyObject")
public ProxiedObject getProxyObject(ProxiedObject proxiedObject){
//创建代理对象
ProxiedObject ret = (ProxiedObject)Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(),
proxiedObject.getClass().getInterfaces(),//当增强对象是接口时,
//可写成new class[]{proxiedinterface.class}
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//定义返回值
Object retValue = null;
try {
//在方法之前增强
retValue = method.invoke(proxiedObject, args);
//在方法之后增强
} catch (Exception e) {
//异常增强
e.printStackTrace();
}
return retValue;
}
}
);
return ret;
}
}
2.基于子类的动态代理
提供者是:第三方cglib包,在使用时需要先导包(已集成至spring-core当中)
使用要求:被代理类不能是最终类,不能被final修饰
涉及的类:Enhancer
创建代理对象的方法:create
模板:
@Component
public class ProxyServiceFactory {
@Bean("proxyObject")
public ProxiedObject getProxyObject(ProxiedObject proxiedObject) {
//创建增强器
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(proxiedObject.getClass());
//设置回调
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//前置增强
Object invoke = method.invoke(proxiedObject, objects);
//后置增强
return invoke;
}
});
ProxiedObject ret = enhancer.create();
return ret;
}
}
作用:
不修改源码的基础上对方法增强
spring中aop相关术语:
Joinpoint(连接点):
所谓连接点是指那些被拦截到的点。在spring中,指的是方法,因为spring只支持方法类型的连接点。
Pointcut(切入点):
所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
Advice(通知/增强):
所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介):
引介是一种特殊的通知在不修改类代码的前提下, 可以在运行期为类动态地添加一
些方法或Field。
Target(目标对象):
代理的目标对象。
Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程。
spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Proxy(代理):
一个类被AOP织入增强后,就产生一个结果代理类。
Aspect(切面):
是切入点和通知(引介)的结合。
二、Spring-AOP常用注解
@EnableAspectJAutoProxy
源码:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
//指定是否采用cglib进行代理。默认值是false,表示使用jdk的代理。
boolean proxyTargetClass() default false;
//指定是否暴露代理对象,通过AopContext可以进行访问。
boolean exposeProxy() default false;
}
作用:
改注解标注在主配置类上,表示开启spring对注解aop的支持。
该注解是采用运行期增强的注解
@Aspect
源码:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Aspect {
/*
默认我们的切面类应该为单例的。但是当切面类为一个多例类时,需要给value指定预
处理的切入点表达式。
也就是@Aspect("perthis(与@Before相同的切入点表达式)")
它支持指定切入点表达式,或者是用@Pointcut修饰的方法名称(要求全
限定方法名)
*/
String value() default "";
}
作用:
声明该类是一个切面类
注意:当多个切面类有着对同一类方法的增强的时候,执行顺序按照类名的字典序升序进行。
使用@order自定义执行的先后顺序
源码:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {
int value() default 2147483647;
}
value值小的先执行。
注意:
当同一个切面类有多个切面表达式相同,且通知类型相同的方法时,执行顺序按照方法名字按照ASCII码值比较顺序,当遇到重载方法的时候,会继续往参数表那边进行比较,直到比较出不一致来。
@Pointcut
源码:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Pointcut {
//切入点表达式
String value() default "";
/*参数可以是execution中的,也可以是
args中的。通常情况下不使用此属性也可以获得切入点方法参数。
*/
String argNames() default "";
}
当我们有多个的通知切入点表达式相同的时候,可使用该注解进行抽取,实现切入点表达式的通用化。
使用方法:
@Component
@Aspect
public class Test1{
/**
* 通用切入点表达式
* 在value属性的中使用了&&符号,表示并且的关系。
* &&符号后面的args和execution一样,都是切入点表达式支持的关键字,表示匹配
参数。指定的内容可以是全限定类名,或者是名称。当指定参数名称时,要求与方法中形参名称相
同。
* argNames属性,是定义参数的名称,该名称必须和args关键字中的名称一致。
*/
@Pointcut(value = "execution(* cn.edu.szu.test.*.*
(cn.edu.szu.POLO.User))&& args(user)",argNames = "user")
private void pt1(User user){}
//使用时
@Before(value="pt1()",argNames="user")
public Object advice1(User user){}
}
当希望其他的切面类也可以使用pt1这个切入点表达式的时候,需要将pt1()这个函数声明为public,在其他的类使用的时候·需要使用pt1的全限定名(类似于cn.edu.szu.test.Test1.pt1())
由于该注解只能作用与方法上,如要单独抽取出来,则需新创建一个类,将所抽取部分声明为该类的一个公有方法,其他需要使用的改切点表达式的切面类仍需使用该方法的全限定名。
有意思的是由于spring解析aop的时候是先解析@Before注解,再根据@Before注解去找切入点表达式,所以抽取切入点表达式的类不需要加入ioc容器
配置通知/增强的相关注解
spring配置通知的相关注解执行的先后顺序:
try{
try{
//TODO--前置通知--@Before
Object ret=method.invoke(target,args);
}finally{
//TODO --最终通知--@After
}
//TODO--@AfterReturning
return ret;
}catch(Exception e){
//TODO--异常通知,也就是@AfterThrowing执行
}
@Before
源码:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Before {
//切入点表达式
String value();
//切入点表达式的参数
String argNames() default "";
}
作用:
该注解标注的方法为前置通知/增强
@AfterReturning
源码:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterReturning {
//切入点表达式
String value() default "";
//同value
String pointcut() default "";
//指定切入点方法返回值的变量名称。它必须和切入点方法返回值名称一致。
String returning() default "";
String argNames() default "";
}
作用:
用于配置后置通知。后置通知的执行是在切入点方法正常执行之后执行。
需要注意的是,由于基于注解的配置时,spring创建通知方法的拦截器链时,后置
通知在最终通知之后,所以会先执行@After注解修饰的方法。
@AfterThrowing
源码:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterThrowing {
/**
* 切入点表达式
*/
String value() default "";
/**
* 同value
*/
String pointcut() default "";
/**指定切入点方法执行产生异常时的异常对象变量名称。它必须和异常变量
* 名称一致。
*/
String throwing() default "";
String argNames() default "";
}
作用:
用于配置异常通知。
@After
源码:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface After {
//切入点表达式
String value();
String argNames() default "";
}
作用:
配置最终通知
@Around
源码:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Around {
String value();
String argNames() default "";
}
作用:
环绕通知
使用方法:
@Around("execution(* com.itheima.service.impl.*.*(..))")
public Object method(ProceedingJoinPoint pjp){
//1.定义返回值
Object rtValue = null;
try{
//前置通知--TODO
//获取方法执行所需的参数
Object[] args = pjp.getArgs();
//执行切入点方法
rtValue = pjp.proceed(args);
//后置通知--TODO
}catch (Throwable t){
//异常通知--TODO
}finally {
//最终通知--TODO
}
return rtValue;
}
@DeclareParents
源码:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DeclareParents {
/*
用于指定目标类型的表达式。当在全限定类名后面跟上+时,表示当前类
及其子类*/
String value();
/*指定提供方法或者字段的默认实现类。
*/
Class defaultImpl() default DeclareParents.class;
}
作用:
用于给被增强的类提供新的方法。(实现新的接口)
用法:
考虑以下场景,有一个接口Origin在设计之初考虑的不是很全面,有缺陷,我们希望修复这个缺陷,但是我们没有这个接口实现类的修改权限,或者直接修改这个类可能会引起其他问题。这时我们可以通用过这个注解通过动态代理的方式对Origin这个类进行增强(实现新的接口)。
增强的新的功能为Extension接口,还有其实现类ExtensionImpl
@Component
@Aspect
public class AspectClass{
@DeclareParents(value =
"cn.edu.szu.Origin+",defaultImpl =
ExtensionImpl.class)
private Extension extension;
}
配置之后,我们从ioc容器当中获取的Origin的实现类同时也实现了Extension接口,使用增强效果的方式有两种:
1.我们可以手动将获取的类上转型为Extension类型从而使用Extension接口当中的方法,从而实现增强。
2.在前置通知当中使用。
如:
@Before(value = "cn.edu.szu.Origin.method1() &&
args(arg) && this(Extension )")//重要的是这个this(Extension)
public void printLog(Object arg,Extension extension ){
}
@EnableLoadTimeWeaving*
源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(LoadTimeWeavingConfiguration.class)
public @interface EnableLoadTimeWeaving {
//是否开启LTW
AspectJWeaving aspectjWeaving() default AspectJWeaving.AUTODETECT;
enum AspectJWeaving {
ENABLED,//开启
DISABLED,//不开启
AUTODETECT;//如果类路径下能读取到META‐INF/aop.xml文件,则开启LTW,否则关闭
}
}
作用:
用于切换不同场景下实现增强。
使用场景:
在Java 语言中,从织入切面的方式上来看,存在三种织入方式:编译期织入、类
加载期织入和运行期织入。
编译期织入是指在Java编译期,采用特殊的编译器,将切面织入到Java类中;
而类加载期织入则指通过特殊的类加载器,在类字节码加载到JVM时,织入切面;
运行期织入则是采用CGLib工具或JDK动态代理进行切面的织入。
AspectJ提供了两种切面织入方式,第一种通过特殊编译器,在编译期,将AspectJ
语言编写的切面类织入到Java类中,可以通过一个Ant或Maven任务来完成这个操作;
第二种方式是类加载期织入,也简称为LTW(Load Time Weaving)
执行目标类方法时增强
使用:
1.导入依赖:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-instrument -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
<version>5.3.15</version>
</dependency>
2.创建META‐INF/aop.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<aspects>
<!-- 1切面类 -->
<aspect name="cn.edu.szu.AspectClass" />
</aspects>
<weaver>
<!-- 2 指定需要进行织入操作的目标类范围 -->
<include within="cn.edu.szu..*" />
</weaver>
</aspectj>
3.spring配置类:
@Configuration
@EnableLoadTimeWeaving//开启类加载时织入
public class SpringConfiguration{
}
4.找到spring-instrument-5.3.15.jar的文件路径
E:\Maven\apache-maven-3.6.3\repository\org\springframework\spring-instrument\5.3.15\spring-instrument-5.3.15.jar
5.在idea中点击编辑配置:
将文件路径添加至VMOption一栏保存即可。