当前位置: 首页 > 后端技术 > Java

java实训Spring中AOP案例分析

时间:2023-04-02 00:40:22 Java

什么是AOP?AOP(AspectOrientProgramming),直译就是面向方面的编程。AOP是一种编程思想,是对面向对象编程(OOP)的补充。面向对象编程将程序抽象为各个层次的对象,而面向方面编程将程序抽象为各个方面。为什么需要AOP?在实际开发中,我们应该都遇到过这样的场景:多个模块之间存在某段重复代码,我们通常是如何处理的?在传统的面向过程的编程中,我们也是将这段代码抽象成一个方法,然后在需要的地方调用这个方法,这样当这段代码需要修改的时候,我们只需要改这个方法即可。然而,要求总是在变化。有一天,增加了一个新的需求,需要做更多的修改。我们需要再抽象出一个方法,然后在需要的地方单独调用这个方法,否则就不需要这个方法了,我们仍然必须删除调用此方法的每个地方。其实我们可以用AOP来解决涉及到多处相同修改的问题。AOP的本质AOP的本质是通过AOP框架对业务组件的多个方法的源代码进行修改。看到这里,你应该明白了,AOP其实就是代理模式的一个典型应用。根据修改AOP框架源码的时机,可以分为两类:静态AOP实现,AOP框架在编译阶段修改程序的源码,生成一个静态AOP代理类(the生成的*.class文件已经被修改掉了,需要使用特定的编译器),比如AspectJ。动态AOP实现,AOP框架在运行时动态生成代理对象(JDK在内存中动态代理,或者CGlib动态生成AOP代理类),比如SpringAOP。AOPTerminologyAOP领域的一个术语:Advice:AOP框架中的增强处理。Advice描述了aspect何时被执行以及如何进行增强处理。连接点:连接点表示应用程序执行过程中可以插入切面的点。这个点可以是方法调用,也可以是抛出的异常。在SpringAOP中,连接点始终是方法调用。PointCut:可以插入增强处理的连接点。切面:切面是建议和切入点的组合。简介:简介允许我们向现有类添加新方法或属性。编织:对目标对象进行增强处理,创建增强对象。这个过程就是编织。这些术语在不同书籍中的翻译存在差异。关键是把理解和程序结合起来,理解每个术语的意思。第一个SpringAOP项目的新模块的名称取决于您的心情。这是sping-aop配置。在pom.xml文件中添加如下两个依赖:org.springframeworkspring-context5.3.14org.springframeworkspring-aspects5.3.14模拟业务1.新建一个包服务,提供如下接口publicinterfaceSomeService{voiddoSome();voiddoSome(Stringname,Integernum);}2、在该包下新建一个子包impl,提供实现类如下("原业务方法,在实现类中");}@OverridepublicvoiddoSome(Stringname,Integernum){System.out.println("原业务方法,在实现类中,有两个参数:"+name+"->"+num);}}3.创建一个任意名称的独立包。我这里的名字叫handle,它下面有一个类叫MyAspect。代码如下:@AspectpublicclassMyAspect{/**execution()this切入点表达式

的语法为:方法修饰装饰器(可省略)、方法返回类型、方法包名+方法名+方法参数类型列表全路径*/@Before(value="execution(publicvoidcom.javafirst.service.impl.SomeServiceImpl.doSome(java.lang.String,java.lang.Integer))")publicvoidaop_before(){System.out.println("执行原业务方法之前的逻辑,这里是自动代理要执行的代码function.");}}这里使用了注解@Aspect,也就是我们前面添加的依赖,也就是Spring提供的注解这里需要知道一个概念:切点表达式4、Spring核心配置文件applicationConext.xml代码如下:这里将接触到一个新的标签,代理生成器,系统自动在内存中生成代理类,不需要我们手动展示和编写代理类。5.和我们上节学习的一样,这节的测试代码基本不会变/**@Before在目标方法之前执行*/@TestpublicvoidtestBefore(){Stringconfig="applicationContext.xml";ApplicationContextcontext=newClassPathXmlApplicationContext(config);SomeServicesomeService=(SomeService)context.getBean("someService");someService.doSome();someService.doSome("Wuyazi",87);}6.结果展示:在原业务中method在执行逻辑之前,这里是自动代理功能要执行的代码。原来的业务方法,在实现类中,有两个参数:wuyazi->87总结通过上面的过程,我们通过注解简单的掌握了代理函数的神奇之处,当然上面说的切点表达式也是有语法的,以及SpringAOP中的通知注解并不局限于@Before,这两点是本文的重点。那就往下看↓SpringAOP5种通知注解在学习通知注解之前,我们先来看一下上面提到的切点表达式。使用上面的示例,进行一些变形。您可以测试结果以便于理解。不难。execution(publicvoidcom.javafirst.service.impl.SomeServiceImpl.doSome(java.lang.String,java.lang.Integer)):表示注解函数代码修饰符为public,返回值为void,并且在这个com.javafirst.service.impl路径下的SomeServiceImpl类中的doSome(java.lang.String,java.lang.Integer)方法之前执行。execution(voidcom.javafirst.service.impl.SomeServiceImpl.doSome(java.lang.String,java.lang.Integer)):方法修饰符可以省略execution(voidcom.javafirst.service.impl.*.doSome(java.lang.String,java.lang.Integer)):doSome(java.lang.String,java.lang.Integer)方法执行(voidcom.javafirst.service.impl..(java.lang.String,java.lang.Integer)):方法执行(com.javafirst.service..*(java.lang.String,java.lang.Integer)):指定包及其子包下的所有方法,不考虑返回类型方法(可以在接口中多写一个方法进行测试)execution(voidcom.javafirst.service..(..)):指定包及其子包下的所有方法,不管是否有参数。(这个可以测试我们之前接口中定义的另外一个方法在执行前是否也会执行代理方法中的内容)在多个匹配之间,我们可以使用链接符号&&,||,!表达“和”、“或”、“不是”的关系。但是在使用XML文件配置时,这些符号有特殊的含义,所以我们用“and”、“or”、“not”来表示。这里就不举例了,看通知注解@Before预通知:通知方法在目标方法之前调用。我们已经学会了如何使用它。我们来看看这个注解的参数。方法名可以自定义,所以系统的参数之一就是JoinPoint。如果要使用它,它必须是该方法的形参列表中的第一个。功能类似于Java反射。在Method类中,可以获取方法名、方法参数等,进行不同的逻辑处理。例如,如果我们改造之前的代码,我们可以验证结果:@Before(value="execution(voidcom.javafirst.service..(..))")publicvoidaop_before(JoinPointpoint){System.out.println();System.out.println("指定包及其子包下的所有方法,不管是否有参数-执行原业务方法之前的逻辑,这里是自动代理功能要执行的代码。“);系统。out.println("方法名:"+point.getSignature().getName());System.out.println("方法参数:"+point.getArgs().length+"参数");if(point.getArgs().length==2){System.out.println("执行一个有两个参数的方法");}else{System.out.println("执行一个没有参数的方法");}}@AfterReturning通知:在目标方法执行完后执行。目标方法的返回结果也可以从该方法的参数中接收,推荐使用Object类型接收。1.在原有接口类的基础上新增方法:StringreturnPrice(doubleprice,floatdiscount);如果我们要测试post通知,必须要在执行代理方法之前获取到目标方法的返回值,所以这里定义的是一个有返回值,带参数的方法。2、实现类中的实现逻辑示例(实际开发要看业务):@OverridepublicStringreturnPrice(doubleprice,floatdiscount){if(discount>0.0f&&price>0.0){return"discountprice:"+(price*discount);}return"Originalprice:"+price;}3.在切面类中定义post通知方法/**postnotification*@paramres参数名必须与返回值一致,表示目标方法的返回值*/@AfterReturning(value="execution(java.lang.Stringcom.javafirst.service.impl..return(..))",returning="res")publicvoidaop_afterReturning(Objectres){System.out.println();System.out.println("目标方法执行结果:"+res);System.out.println("目标方法执行后输出。");}切入点表达式这里和前面一样,重点是方法的返回值和形参名的关系。两者必须一致,否则无法得到目标方法的返回结果。4.测试代码就不贴了。可以自己测试@Around来封装目标方法。简单理解:相关代码逻辑可以在目标方法之前和之后执行。我们看下面的例子:1.定义接口方法StringdoWork(Stringname);2、接口实现类中的代码:@OverridepublicStringdoWork(Stringname){System.out.println("正在打码的工程师:"+name);return"员工姓名:"+name;}注意这里返回值的执行时机。3、切面类中定义的注解通知代码:/**环绕通知

方法定义:必须是public,必须有返回值,建议Object参数必须有和ProceedingJoinPoint*@paramjoinPoint@return*/@Around(value="execution(publicjava.lang.Stringcom.javafirst.service.impl.SomeServiceImpl.doWork(java.lang.String))")publicObjectaop_around(ProceedingJoinPointjoinPoint)throwsThrowable{System.out.println();System.out.println("我是@Around方法中的输出");//通过参数控制目标方法是否可以执行ProceedJoinPointObjectargs[]=joinPoint.getArgs();if(null!=args&&args.length>0){//执行目标方法if(null!=args[0]){joinPoint.proceed();return"你传入的参数是:"+args[0];}}return"我是@Around方法return的内容";}如果没有通过参数ProceedingJoinPoint执行目标方法,默认的目标方法不会执行;注意这里的输出语句和返回值执行逻辑,从而理解环绕通知。4.测试代码/**@Around可以在目标方法前后执行相关代码*/@TestpublicvoidtestAround(){Stringconfig="applicationContext.xml";ApplicationContextcontext=newClassPathXmlApplicationContext(config);SomeServicesomeService=(SomeService)context.getBean("someService");Stringresult=someService.doWork("李春刚");//Stringresult=someService.doWork(null);System.out.println("测试结果输出:"+result);}因此,您可以自己体验一下。通过改变参数,观察我们的通知代码执行逻辑,周围的通知不等于@Before+@AfterReturning。周围通知是一个_java培训机构,可以修改目标方法的返回值。@AfterThrowing(理解)在目标方法抛出异常后执行,如果没有抛出异常,则不执行(即如果目标方法本身try-catch异常,则通知方法不执行)。下面是一个例子:1.定义接口方法voiddoOrder(Integernum);为了验证结果,我们需要通过参数创建异常。为了方便简单易懂,这里我们在target方法中使用200/num。如果参数为0,则发生异常。2、实现类,即目标类的实现代码如下:@OverridepublicvoiddoOrder(Integernum){System.out.println("在目标方法中输出,如果参数为0,计算(2022/num)会抛出异常");num=2022/num;}3.切面类,定义通知方法代码:/**异常通知:目标方法抛出异常后调用,如果没有抛出异常,不会调用

Function:monitor目标方法的函数,如果出现异常,方便开发者定位问题修复bug*@paramex*/@AfterThrowing(value="execution(publicvoidcom.javafirst.service.impl.SomeServiceImpl.doOrder(java.lang.Integer))",throwing="ex")publicvoidaop_afterThrowing(Exceptionex){System.out.println("这句话会被只在目标方法异常时才输出!\nExceptionmessage:"+ex.getMessage());}以上是核心代码,大家可以自行测试。@After(理解)finalnotification:在目标方法返回或异常发生后调用,这个通知方法会一直被调用,适用于收尾工作,如:清除缓存,删除一些数据等。1.定义接口方法voidpayMoney(Stringaddress);如上,我们使用方法的参数来“创建异常”。2、实现类中代码:@OverridepublicvoidpayMoney(Stringaddress){System.out.println("目标方法第一句内容,输出参数:"+address);System.out.println("截取地址前三个单词:"+(address.substring(0,3)));//try{//System.out.println("截取地址前三个单词:"+(address.substring(0,3)));//}catch(Exceptionex){//System.out.println("目标方法有try-catch");//}System.out.println("目标方法异常后的输出语句。");}3。在切面类中定义通知代码:/**最终通知:必须执行,并且在目标方法之后*/@After(value="execution(..SomeServiceImpl.payMoney(..))")publicvoidaop_after(){System.out.println("\n切面类中的输出内容!");}结果大家可以自己验证一下。这个不难理解,类似于Java中的finally{intry-catch-finally}代码块,会一直执行。@Pointcut解决什么问题:我们在定义通知的时候,在每个方法上添加注解,注解中有切入点表达式。当我们定义的方法较多,需要更改路径或方法名等时,既麻烦又容易出错,所以@Pointcut注解解决了这个问题。使用方法:定义一个方法,方法体中不需要内容,在方法上加上@Pointcut注解,你应该想到了,注解也有一个value属性,那么表达式写在这里,如果以后想改,就改这个地方;而原来添加注解通知方法的地方只需要将表示流水的入口点的值改成这里定义的方法名(带括号)即可。我们来演示一下,以我们在文末学到的一段注解代码为例:@After(value="aop_pointcut()")notification:在切面类中输出内容!");}/**pre-notification,为了测试@Pointcut*/@Before(value="aop_pointcut()")publicvoidaop_before_pointcut(){System.out.println("aop_before_pointcut()前置通知:切面类输出内容!\n");}/**定义@Pointcut注解*/@Pointcut(value="execution(..SomeServiceImpl.payMoney(..))")privatevoidaop_pointcut(){}测试代码不用修改,直接看结果,这个其实也不难,和我们之前学习动态SQL时用到的include标签的作用是一样的。总结看到这里,我们已经学习完了Spring的两个核心内容,后面再学习两个内容:Spring集成MyBatis和Spring事务。Spring的面向切面编程是一种编程思想。其实并没有你想的那么难懂,尤其是有java编程基础的情况下。记住本文演示的5个通知注解,它们的用法,代码执行流程,那个阶段我们应该处理什么业务逻辑,后面会用到。文章来自codeniche