注:作者本人也是初学者,所以本文有些总结性见解可能存在问题,但是多数问题都是在上网查询过资料后总结的,如果有逻辑或者原理上的错误,或者见解不同,欢迎在评论区讨论!!!
目录
第二种情况:需要增强的类并没有实现某个接口(也适用于有接口)(cblib代理)
Spring的AOP
1.什么是AOP
面向切面编程,通过预编译方式和运行期动态带来实现程序的统一维护的一种技术,利用AOP可以对业务逻辑的各个部分进行隔离,降低各部分的耦合度, 提高程序的可重用性,提高开发效率,aop是oop的延续,是Aspect Oriented Programming的缩写,意思是面向切面编程。
末日丧尸类电影应该都看过吧,末日神器之一就是绑在扫帚棍子上的水果刀。水果刀本身的形状功能没有变,但是我们通过绑上一根棍子加大了他的攻击范围,这就是AOP。
2.AOP的作用及优势
作用:在程序运行期,在不改变源码的情况下,可以对方法进行增强
优势:减少重复性代码,提高开发效率,并且便于维护
3.AOP的底层实现
第一种情况:需要增强的类实现了某个接口(JDK动态代理)
AOP底层是通过Spring提供的动态代理技术实现的,在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时,进行增强功能的介入,在调用目标对象的方法,从而完成功能增强。
public interface TargetInterface { void show(); }
public class Target implements TargetInterface{ @Override public void show() { System.out.println("使用了普通show方法"); } }
public class ProxyDemo { public static void main(String[] args) { final Target tg = new Target(); final TargetInterface target = (TargetInterface) Proxy.newProxyInstance(Target.class.getClassLoader(), Target.class.getInterfaces(), new InvocationHandler() { @Override //参数分别为proxy对象,method对象(反射机制Class类中的三大对象之一),方法的属性 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("使用增强方法去增强show方法"); //通过原对象和方法对象,以及方法参数,利用反射机制启动方法 Object invoke = method.invoke(tg,args); //return原方法的返回值 return invoke; } }); target.show(); } }
这里我们不难看出,通过这样的方法我们在使用反射机制使用原方法前,可以进行一些列调用前的操作。在原封不动获取原方法返回值后,同样可以对原方法的返回值进行加工。
第二种情况:需要增强的类并没有实现某个接口(也适用于有接口)(CGLIB代理)
这里就是基于CGLIB进行动态代理增强。
科普CGLIB:CGLIB是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。
public class Demo1 { public static void main(final String[] args) { //目标对象 final Target target = new Target(); //使用动态生成给代理对象 基于cglib //1.创建增强器对象 Enhancer enhancer = new Enhancer(); //2.设置父类(目标类)(设置目标类为代理对象的父类) enhancer.setSuperclass(Target.class); //3.设置回调 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("记录日志..."); //执行方法 Object invoke = method.invoke(target, args); System.out.println("使用aop增强了show方法"); return invoke; } }); //由设置的父类创建对象 Target proxy = (Target) enhancer.create(); proxy.show(); } }
注意:增强器的create方法,此方法会依据以上第2步设置父类的操作,将代理对象的父类设置成为目标类,并且使用多态的方式返回一个Object类型对象,实际上就是:Object o = new 继承目标类的代理类()。所以最后使用Target强转,强转后的对象任然是利用了多态的方式的实现。
使用CGLIB方式结合多态,同样可以完成第一种情况的增强操作。
public class Demo1 { public static void main(final String[] args) { //目标对象 final TargetInterface target = new Target(); //使用动态生成给代理对象 基于cglib //1.创建增强器对象 Enhancer enhancer = new Enhancer(); //2.设置父类(目标类)(设置的是代理对象的父类为目标类的父类,此时代理对象类和目标类是兄弟类关系) enhancer.setSuperclass(TargetInterface.class); //3.设置回调 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("记录日志..."); //执行方法 Object invoke = method.invoke(target, args); System.out.println("使用aop增强了show方法"); return invoke; } }); //由设置的目标类的父类创建对象 TargetInterface proxy = (TargetInterface) enhancer.create(); proxy.show(); } }
-
如果代理对象父类设置的是Target,也就是目标类,那么代理对象就是Target的子类,增强后后创建对象就相当于把Target的子类强转成TargetInterface,当然,强转成Target也没有问题
-
如果代理对象父类设置的是TargetInterface,那么代理对象就是TargetInterface的实现类,此时Target和代理类是实现了同一个接口的兄弟类,不能够强转为Target。因此强转成TargetInterface可以,但是,此时代理对象与Target类是属性兄弟关系,无法强转。
4.AOP相关术语
-
目标对象:代理的目标对象
-
代理:一个类被AOP植入增强,产生的结果代理类
-
连接点:那些被拦截到的点(一般指被拦截到的方法)
-
切入点:针对于哪些连接点进行拦截定义
-
增强/通知:指定连接点之后的事情就是通知
-
切面:切入点和通知的结合
这里我任然拿开篇说的例子来说明
假设现在世界末日,到处都是被病毒感染的丧尸,你需要升级手头能用的日常工具,在末日中生存下来:
-
首先我们需要一把能够杀死丧尸的待增强武器——水果刀(目标对象)
-
其次我们需要把这个水果刀增强为长柄水果刀(产生的代理对象)
-
我们开始思考这把水果刀可以从刀把,是否淬毒等方面进行增强(连接点,即业务层所有方法)
-
我们发现淬毒杀不了丧尸,所有我们可以在刀把处增强(切入点,也就是被检测到需要增强的方法)
-
我们把水果刀加装了长木棍(织入)
-
你完成了攻击距离的增强(通知,具体增强的内容,分为前置通知、后置通知、异常通知、最终通知、环绕通知)
-
水果刀的刀把处(切入点)完成了攻击距离的增强(通知)(切面=切入点+通知)
基于xml的aop开发
1.基本步骤
-
导包
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency>
-
创建目标接口和目标类
//目标类 public class UserServiceImpl { public void method(){ System.out.println("UserMethod..."); } }
-
创建切面类(也就是为目标类增强方法的类,类中的方法从目标类非方法的不同运行阶段执行,对方法进行增强)
public class MyAspect { public void before(){ System.out.println("前置方法增强..."); } }
-
配置
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--配置目标类--> <bean id="userService" class="com.tencent.service.UserServiceImpl"></bean> <!--配置切面类--> <bean id="myAspect" class="com.tencent.service.MyAspect"></bean> <!--配置切点表达式和增强的植入关系--> <aop:config> <!--引用myAspect的Bean为切面类--> <aop:aspect ref="myAspect"> <!--配置目标类的切入点--> <aop:before method="before" pointcut="execution(public void com.tencent.service.UserServiceImpl.method())"></aop:before> </aop:aspect> </aop:config> </beans>
2.配置文件书写步骤
1.使用<bean>标签配置目标类(设置id,class)
2.使用<bean>标签配置切面类(设置id,class)
3.使用<aop:config>标签开始配置增强
4.在<aop:config>内部使用<aop:aspect>标签引入切面类,来增强目标类(使用ref引入)
5.使用<aop:通知类型>标签设置用于增强的方法(切面类中)和需要增强的方法(目标类中),其中method属性用来引入用于增强的方法,pointcut属性使用通知配置语法来引入需要增强的方法
3.通知配置语法
<aop:before method="before" pointcut="execution(* com..*.*(..))"></aop:before> <aop:通知类型 method="通知方法名称" pointcut="切入点表达式"></aop:通知类型>
4.AOP五大通知类型
通知类型有5个:前置通知(before),后置通知(after-returning),异常通知(after-throwing),最终通知(after),环绕通知。这五个通知一一对应切面类的五个方法(自己手写),在其对应的方法对目标方法增强后进行通知。
这五个通知的中文名不同的文章有不同的翻译,可能有的把after翻译为后置通知,而我把after-returning翻译为后置通知。当然这都没错,我这么翻译是基于自己对这五个通知的理解,但是所有文章的英文名一定对应其后面的功能。
前置通知before通知的方法在目标方法运行前执行
后置通知after-returning通知的方法在方法调用后正常返回的时候通知,可以获取返回值,发生异常的时候不会执行
异常通知after-throwing通知的方法在目标方法出现异常后执行
最终通知after通知的方法在方法执行完后,无论程序是否发生异常都会执行的
而最特殊的环绕通知around通知的方法是前四个通知的结合体
以下为切面类代码
public class Logger { //前置通知 public void beforePrintLog(){ System.out.println("在调用方法前记录操作日志"); } //后置通知 public void afterPrintLog(){ System.out.println("在调用方法后记录操作日志"); } //异常通知 public void exceptionPrintLog(){ System.out.println("出现异常后记录操作日志"); } //最终通知 public void finalPrintLog(){ System.out.println("最终记录操作日志"); } //环绕通知 public Object aroundPrintLog(ProceedingJoinPoint proceedingJoinPoint){ //设置空对象,用于记录目标方法被增强后的返回值 Object returnValue = null; try { //此时相当于执行前置方法 System.out.println("前置方法"); //通过ProceedingJoinPoint对象获取原本方法的参数 Object[] args = proceedingJoinPoint.getArgs(); System.out.println("参数列表" + Arrays.toString(args)); //通过ProceedingJoinPoint对象执行目标方法 System.out.println("执行目标方法"); returnValue = proceedingJoinPoint.proceed(args); //此处执行后置方法 System.out.println("后置方法"); return returnValue; } catch (Throwable throwable) { //此处相当于执行异常方法 System.out.println("异常方法"); throwable.printStackTrace(); }finally { //此处相当于执行最终方法 System.out.println("最终方法"); return returnValue; } } }
6.切入点表达式
execution(public void com.tencent.service.UserServiceImpl.method())
1).语法:execution([访问权限修饰符] 返回值 包名.类名.方法名(参数))
2).修饰符:修饰符可以省略 ——> execution(返回值 包名.类名.方法名(参数))
3).返回值:可以是*表示,代替任意返回值
execution(* 包名.类名.方法名(参数))
4).包名,类名,方法名都可以使用*代替
execution(* *.*.*(参数))
5).参数列表可以使用..两个点,表示任意个数、任意类型参数
execution(* *.*.*(..))
基于注解的aop开发
1.配置文件
<!-- 配置注解作用域 --> <context:component-scan base-package="com.tencent"></context:component-scan> <!--aop的自动代理--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2.注解
1.@Aspect:在类上进行注解,表示该类是一个切面bean,相当于原本用来导入切面bean的<aop:aspect>标签
2.@Before @AfterReturning @AfterThrowing @After @Around这五个注解都作用于方法上,分别对应前置通知,后置通知,异常通知,最终通知和环绕通知,每个注解用于表示该方法是对应通知。通知中使用切入点表达式,以此表示此方法用于增强的目标方法。使用这些注解,通知了何时(五个时间)在目标方法(execution中配置的方法)执行哪个方法(注解下方的方法)
3.@Pointcut:作用在方法上,与作用的方法内容无关,作用的方法仅仅就是一个标志,注解中可以配置execution切入点表达式,而作用的方法就相当于一个id,结合2中的五个注解,就可以达到减少代码冗余的操作。
以下为切面类代码
@Component("logger") @Aspect public class Logger { @Pointcut("execution(* com..AccountServiceImpl.*(..))") public void kong(){}; @Before("kong()") public void beforeOp(){ System.out.println("before..."); } @AfterReturning("kong()") public void afterOp(){ System.out.println("after..."); } @AfterThrowing("kong()") public void exceptionOp(){ System.out.println("exception..."); } @After("kong()") public void finalOp(){ System.out.println("final..."); } @Around("kong()") public Object middle(ProceedingJoinPoint pjp){ //定义返回值 Object returnValue = null; try { //启动前置方法 System.out.println("before..."); //执行当前方法 Object[] args = pjp.getArgs(); System.out.println("当前方法参数为" + Arrays.toString(args)); returnValue = pjp.proceed(); //启动后置方法 System.out.println("after..."); return returnValue; } catch (Throwable throwable) { System.out.println("exception..."); throwable.printStackTrace(); }finally { System.out.println("finally..."); return returnValue; } } }