当前位置: 首页 > 科技观察

图文详解SpringAOP,你学会了吗?

时间:2023-03-17 23:29:00 科技观察

如果说IOC是Spring的核心,那么面向切面编程AOP就是Spring的另一个最重要的核心。本文将主要对以下六点进行详细讲解:1.AOP的定义2.AOP的作用3.AOP的应用场景4.SpringAOP术语AOP核心概念SpringAOP通知分类SpringAOP织期5.三种使用方法SpringAOP方法一:使用Spring自带的AOP方法二:使用Aspectj实现切面(普通POJO的实现方法)三:使用Aspectj实现切面(基于注解的实现)六、SpringAOPJDK动态代理JDK动态的实现原理代理优缺点CGLib代理CGLIB组成结构AOP定义AOP(AspectOrientProgramming),直译就是面向方面的编程,AOP是一种编程思想,是对面向对象编程(OOP)的补充。面向切面编程,一种在不修改源代码的情况下,动态统一地为程序增加额外功能的技术,如下图所示:业务处理逻辑分离,比如Spring的事务,通过事务注解配置,Spring会在业务方法中自动开启和提交业务,并在业务处理失败时执行相应的回滚策略。AOP的作用AOP采用水平抽取机制(动态代理)来替代传统垂直继承机制的重复代码。它的应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。主要作用是将功能需求和非功能需求分离,让开发者专注于某个关注点或横切逻辑,减少对业务代码的侵入,增强代码的可读性和可维护性。简单的说,AOP的作用就是保证开发者可以在不修改源代码的情况下,为系统中的业务组件添加一些通用的功能。AOP应用场景典型AOP应用场景:日志记录、事务管理、权限验证、性能监控,AOP可以拦截指定方法,增强方法,如事务、日志、权限、性能监控等,不侵入业务在代码中,业务和非业务处理逻辑是分离的。SpringAOP的术语在深入了解SpringAOP之前,让我们对AOP的一些基本术语有一个大概的了解。AOP核心概念SpringAOP通知分类SpringAOP织期SpringAOP三种使用方法AOP编程其实很简单。纵观AOP编程,程序员只需要参与三个部分:1.定义公共业务组件。2.定义切入点,一个切入点可以横切多个业务组件。3.定义增强处理。增强处理是在AOP框架中编织到公共业务组件中的处理动作。因此,AOP编程的关键是定义入口点和定义增强处理。一旦定义了合适的入口点和增强处理,AOP框架就会自动生成一个AOP代理,即:代理对象的方法=增强处理+代理对象的方法。方法一:使用Spring自带的AOPpublicclassLogAdviceimplementsMethodBeforeAdvice,AfterReturningAdvice,MethodInterceptor{@Overridepublicvoidbefore(Methodmethod,Object[]objects,Objecttarget)throwsThrowable{//pre-notification}@OverridepublicvoidafterReturning(Objectresult,Methodmethod,Object[]objects,Objecttarget)throwsThrowable{//post-notification}@OverridepublicObjectinvoke(MethodInvocationmethodInvocation)throwsThrowable{//环绕通知//在目标方法之前执行methodInvocation.proceed();//目标方法//执行完目标方法后returnresultVal;}}配置通知时需要实现org.springframework.aop包下的一些接口:预通知:MethodBeforeAdvice。发布通知:AfterReturningAdvice。环绕建议:MethodInterceptor。异常通知:ThrowsAdvice。创建代理对象通知(Advice)切入点:通过正则表达式描述指定的切入点(一些指定的方法)Advisor(高级通知)=Advice(通知)+Pointcut(切入点)创建自动代理*ServiceBean*TaskBeanlogAdviceBeanperformanceAdvisorBean方法二:使用Aspectj实现切面(普通POJO的实现)导入Aspectj相关依赖org.aspectjaspectjrt1.9.5org.aspectjaspectjweaver1.9.5通知的名字方法是随机的,没有限制。publicclassLogAspectj{//pre-advicepublicvoidbeforeAdvice(JoinPointjoinPoint){System.out.println("==========[Aspectjpre-advice]==========");}//After-advice:方法正常执行后,有返回值,执行after-advice:如果方法执行异常,则不执行after-advicepublicvoidafterReturningAdvice(JoinPointjoinPoint,ObjectreturnVal){System.out.println("==========[Aspectj发布建议]==========");}publicvoidafterAdvice(JoinPointjoinPoint){System.out.println("==========[AspectjPostAdvice]==========");}//AroundAdvicepublicObjectaroundAdvice(ProceedingJoinPointjoinPoint)throwsThrowable{System.out.println("##########【环绕通知中的前置通知】###########");对象returnVale=joinPoint.proceed();System.out.println("##########【环绕通知中的发布通知】##########");返回返回值;}/***异常通知:当方法异常时,执行Advice*/publicvoidthrowAdvice(JoinPointjoinPoint,Exceptionex){System.out.println("发生异常:"+ex.getMessage());}}使用Aspectj实现切面,使用SpringAOP配置方法三:使用Aspectj实现方面(基于注解的实现)//声明当前类作为Aspect切面,交给Spring容器管理//预通知@Before(EXPRESSION)publicvoidbeforeAdvice(JoinPointjoinPoint){System.out.println("==========[Aspectj预通知]==========");}//后通知:方法正常执行后,有返回值,执行后通知:如果方法执行异常,则不执行后通知@AfterReturning(value=EXPRESSION,returning="returnVal")publicvoid之后rReturningAdvice(JoinPointjoinPoint,ObjectreturnVal){System.out.println("==========[Aspectj发布通知]==========");}//发布通知@After(EXPRESSION)publicvoidafterAdvice(JoinPointjoinPoint){System.out.println("==========[Aspectj发布通知]==========");}//AroundAdvice@Around(EXPRESSION)publicObjectaroundAdvice(ProceedingJoinPointjoinPoint)throwsThrowable{System.out.println("##########【SurroundAdvice中的Pre-Advice】##########");对象returnVale=joinPoint.proceed();System.out.println("##########【环绕通知中的发布通知】##########");返回返回值;}//异常通知:当方法发生异常时,执行这个通知@AfterThrowing(value=EXPRESSION,throwing="ex")publicvoidthrowAdvice(JoinPointjoinPoint,Exceptionex){System.out.println("**********【Aspectj异常通知】执行开始**********");System.out.println("发生异常:"+ex.getMessage());System.out.println("********[Aspectj异常通知]执行结束************");}}SpringAOP实现原理Spring的AOP实现原理其实很简单,通过动态代理实现的SpringAOP采用两种混合实现方式:JDK动态代理和CGLib动态代理。JDK动态代理:SpringAOP的首选方法。只要目标对象实现接口,就会使用JDK动态代理。目标对象必须实现接口CGLIB代理:如果目标对象没有实现接口,可以使用CGLIB代理。JDK动态代理Spring默认使用JDK的动态代理来实现AOP。如果一个类实现了一个接口,Spring会使用这个方法来实现动态代理。JDK需要两个组件来实现动态代理。第一个是InvocationHandler接口。我们在使用JDK的动态代理时,需要写一个类来实现这个接口,然后重写invoke方法,其实就是我们提供的代理方法。如下源码所示:/***动态代理**@authormikechen*/publicclassJdkProxySubjectimplementsInvocationHandler{privateSubjectsubject;publicJdkProxySubject(Subjectsubject){this.subject=subject;}@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{System.out.println("beforepre-notification");对象结果=空;尝试{结果=method.invoke(subject,args);}catch(Exceptionex){System.out.println("ex:"+ex.getMessage());扔前;}finally{System.out.println("通知后");}返回结果;}}那么JDK动态代理需要用到的第二个组件就是Proxy类。我们可以通过这个类的newProxyInstance方法返回一个代理对象。生成的代理类实现了原类的所有接口,并代理了接口的方法。当我们通过代理对象调用这些方法时,底层会调用我们通过反射实现的invoke方法。publicclassMain{publicstaticvoidmain(String[]args){//获取InvocationHandler对象,在构造方法中注入目标对象InvocationHandlerhandler=newJdkProxySubject(newRealSubject());//获取代理类对象SubjectproxySubject=(Subject)Proxy.newProxyInstance(Main.class.getClassLoader(),newClass[]{Subject.class},handler);//调用目标方法proxySubject.request();proxySubject.response();}运行结果:前置通知前执行目标对象的请求方法...后置通知前前置通知执行目标对象的响应方法...后置通知后JDK动态代理优缺点JDK动态代理是JDK原生,无需任何依赖即可使用;通过反射机制生成代理类的速度比CGLib运行字节码生成代理类的速度更快;缺点是如果要使用JDK动态代理,被代理的类必须实现接口,否则无法代理;JDK动态代理无法为接口中没有定义的方法实现代理。假设我们有一个实现接口的类,我们为其其中一个不属于该接口的方法配置了一个方面。Spring仍然会使用JDK的动态Proxy,但是由于配置切面的方法不是接口的一部分,所以不会织入为该方法配置的切面。JDK动态代理在执行代理方法时,需要通过反射机制进行回调。这时候方法执行的效率比较低;CGLib代理CGLIB组成结构Cglib是一个功能强大、高性能的代码生成包,被许多AOP框架广泛使用。为它们提供方法拦截,如下图,Cglib与Spring等应用的关系:底层是字节码Bytecode,字节码是Java为了保证“一次编译,到处运行”而产生的一种虚拟指令格式,这样如iload_0、iconst_1、if_icmpne、dup等。字节码之上是ASM,这是一个直接操作字节码的框架。ASM的应用需要熟悉Java字节码和Class结构。在ASM之上的是CGLIB、Groovy和BeanShell。后两者不是Java系统的内容而是脚本语言。他们通过ASM框架生成字节码来变相执行Java代码,这说明在JVM中执行程序不一定非得写Java代码——只要能生成Java字节码,JVM并不关心程序的来源字节码。当然,Java代码生成的JVM字节码是编译器直接生成的,是最“正统”的JVM字节码。在CGLIB、Groovy和BeanShell之上是Hibernate和SpringAOP等框架。这一层大家都很熟悉。最上层是Applications,即具体的应用。一般是web项目或者本地运行的程序。因此,Cglib的实现是基于字节码的,使用开源的ASM来读取字节码,实现类的增强功能。