当前位置: 首页 > Web前端 > HTML

SpringAOP全面讲解(超详细)

时间:2023-03-27 23:32:16 HTML

如果说IOC是Spring的核心,那么面向切面编程AOP就是Spring的另一个最重要的核心@mikechenAOP定义了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包下的一些接口。由代理对象通知(建议)Pointcut:通过正则表达式描述指定切入点(部分指定方法)Advisor(高级通知)=Advice(通知)+Pointcut(切入点)创建自动代理*ServiceBean*TaskBeanlogAdviceBeanperformanceAdvisorBean方法二:使用Aspectj实现切面(普通POJO实现)导入Aspectj相关依赖org.aspectjaspectjrt1.9.5org.aspectj<artifactId>aspectjweaver1.9.5通知方法的名称可以随意启动,没有限制publicclassLogAspectj{//pre-notificationpublicvoidbeforeAdvice(JoinPointjoinPoint){System.out.println("==========[Aspectj预通知]==========");}//Post-notification:方法正常执行后,有返回值,执行post-advice:如果方法执行过程中出现异常,则不执行post-advicepublicvoidafterReturningAdvice(JoinPointjoinPoint,ObjectreturnVal){System.out.println("==========[AspectjAfterAdvice]===========");}publicvoidafterAdvice(JoinPointjoinPoint){System.out.println("==========[AspectjAfterAdvice]===========");}//AroundAdvicepublicObjectaroundAdvice(ProceedingJoinPointjoinPoint)throwsThrowable{System.out.println("##########【前者在环绕通知设置通知】##########");对象returnVale=joinPoint.proceed();System.out.println("##########【环绕通知中发布通知】##########");返回返回值;}/***异常通知:当方法发生异常时,执行通知*/publicvoidthrowAdvice(JoinPointjoinPoint,Exceptionex){System.out.println("发生异常:"+ex.getMessage());}}使用Aspectj实现切面,使用SpringAOP配置方法三:使用Aspectj实现切面(注解实现)//声明当前类为Aspect切面,交给Spring容器管理@Component@AspectpublicclassLogAnnotationAspectj{privatefinalstaticStringEXPRES小号ION="执行(*com.apesource.service.impl.*.create*(..))";//预通知@Before(EXPRESSION)publicvoidbeforeAdvice(JoinPointjoinPoint){System.out.println("==========[Aspectj预通知]==========");}//post-notification:方法正常执行后,有返回值,执行post-notification:如果方法执行过程中出现异常,则不执行post-advice@AfterReturning(value=EXPRESSION,returning="returnVal")publicvoidafterReturningAdvice(JoinPointjoinPoint,ObjectreturnVal){System.out.println("==========【Aspectj事后通知】===========");}//post-advice@After(EXPRESSION)publicvoidafterAdvice(JoinPointjoinPoint){System.out.println("==========[AspectjPostAdvice]==========");}//AroundAdvice@Around(EXPRESSION)publicObjectaroundAdvice(ProceedingJoinPointjoinPoint)throwsThrowable{System.out.println("##########【环绕通知中的前置通知】##########");对象returnVale=joinPoint.proceed();系统。你t.println("##########【环绕通知中的发布通知】##########");返回返回值;}//异常通知:方法异常时,执行advice@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等位于字节码之上。它们是CGLIB、Groovy和BeanShell。后两者不是Java系统的内容而是脚本语言。他们通过ASM框架生成字节码来变相执行Java代码,这说明在JVM中执行程序不一定要写Java代码----只要能生成Java字节码,JVM无所谓关于字节码的来源。当然,Java代码生成的JVM字节码是编译器直接生成的,是最“正统”的JVM字节码,位于CGLIB之上,Groovy、BeanShell是Hibernate、SpringAOP等框架。这一层大家都很熟悉。最上层是Applications,即具体的应用。一般是Web项目或者本地运行的程序。因此,Cglib的实现是基于字节码的,开源的ASM用于读取字节码,为类实现增强功能。