Spring AOP的基础概念
=============================================================
AOP(Aspect-Oriented Programming), 即 面向切面编程, 它与 OOP( Object-Oriented Programming, 面向对象编程) 相辅相成, 提供了与 OOP 不同的抽象软件结构的视角.
在 OOP 中, 我们以类(class)作为我们的基本单元, 而 AOP 中的基本单元是 Aspect(切面)。
==============================================================
基础概念图:
有了上面这张原理图,那么关于AOP面向切面编程的核心几个概念,就可以顺利铺开了:
1》join point【连接点】
在spring aop中,认为原有代码中所有的方法都是join point。
2》point cut【切点】
在不改变原有代码的情况下,想多干点事情,那就需要定义point cut,切点。切点的任务是通过一组表达式来匹配要在哪个join point切入,并且匹配要在这个join point的什么位置切入。
3》advice【增强逻辑】
根据1,2切入了原有代码后,要做些什么事情?这多做的事情就是advice,也就是多处理的一些逻辑。比如,你的原有方法是对数据的保存方法,项目交付后,新需求是需要将这些保存操作在日志中记录下来,并且不能更改原有代码,这就是增强逻辑。
advice增强逻辑你是准备放在原有代码之前还是之后,有以下几种:
-
before advice, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)
-
after return advice, 在一个 join point 正常返回后执行的 advice
-
after throwing advice, 当一个 join point 抛出异常后执行的 advice
-
after(final) advice, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.
-
around advice, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice.
4》weaving【织入】
现在有了原有代码,有了新的增强逻辑。将这两部分代码连接在一起的过程,就是织入weaving。
根据不同的实现技术, AOP织入有三种方式:
-
编译器织入, 这要求有特殊的Java编译器.
-
类装载期织入, 这需要有特殊的类装载器.
-
动态代理织入, 在运行期为目标类添加增强(Advice)生成子类的方式.
Spring 采用动态代理织入, 而AspectJ采用编译器织入和类装载期织入.
5》Target【目标对象】
原有代码和增强逻辑织入在一起,重新生成的就是目标对象Target,也叫adviced object.
Spring Aop使用运行时代理的方式实现Aspect,因此adviced object是一个代理对象。
【在 Spring AOP 中, 一个 AOP 代理是一个 JDK 动态代理对象或 CGLIB 代理对象。】
注意, adviced object 指的不是原来的类, 而是织入 advice 后所产生的代理类.
【关于java中代理的概念,类型,区别和理解:
https://www.cnblogs.com/hongcong/p/5806024.html
https://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html
可以去看上面两篇文章,我自己还没有心思去研究。
但我记住了一句话:CGLIB方式不能代理final类。也就是说Spring AOP的切口不能切在final类上了。
】
6》aspect【切面】
aspect切面,是由point cut和advice组合而成的。既包含了连接点也就是切点的定义,也有增强逻辑的具体实现。
===========================================
到这里,一个概念就顺利的出来了:Spring AOP就是负责实施切面的框架, 它将切面所定义的横切逻辑织入到切面所指定的连接点中.
===========================================
Spring Aop的实现和使用的各种情况
=======================================================
要在Spring中通过注解方式使用AOP,需要下面两步:
1》在配置文件中配置
<!-- 自动扫描注解 --> <context:component-scan base-package="com.sxd" /> <!--Spring aop 使用注解的方式--> <aop:aspectj-autoproxy />
2》定义切面【aspect】
切面包括 切点【point cut】 和 增强逻辑【advice】
【下面aspect中已经显示了point cut的各种定义表达式 和 各种类的advice】
【具体使用,应该按照实际使用逻辑选择性使用即可!!!】
package com.sxd.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.util.Objects; /** * 切面定义 * ①在类上定义@Aspect和@Component * ②@Pointcut()定义切点 * ③adive方法上,声明在哪一个切点上切入 加入增强逻辑 */ @Aspect @Component public class Aspects { /** * execution() 表达式:用来匹配执行方法的连接点 * 【..(两个点)代表零个或多个】 * 【本例子中 第一个*代表方法的返回值类型可以为任何类型 如果本方法为void,也符合切入条件】 * 【本例子中 第一个..代表controller包以及子包下】 * 【本例子中 第二个*代表这个包下的任意类】 * 【本例子中 第三个*代表这个包下的任意类中的任意方法】 * 【本例子中 第二个..代表有无参数都可以被切入】 * * args 如果想要切入的方法的参数要符合什么类型的话 * 【本例子代表入参中,第一个参数类型需要为String类型的才会被切入,之后有零个入参或多个入参】 * */ @Pointcut(value = "execution(* com.sxd.controller..*.*(..)) && args(String,..)") public void pointCut1(){} /** * within() 表达式用于匹配这个包下的任意类 */ @Pointcut(value = "within(com.sxd.controller.*)") public void pointCut2(){} /** * this() 表达式限定了匹配这个类的实例下的方法 */ @Pointcut(value = "this(com.sxd.controller.MainController)") public void pointCut3(){} /** * bean() 匹配IOC容器中的bean的名称 */ @Pointcut(value = "bean(memberService)") public void pointCut4(){} /** * 下面是各种advice的展示 * * 1》advice的执行优先级: around方法执行前》before》【方法自己】》around方法执行后》after》afterReturning * 2》若有异常 * advice的执行优先级:around方法执行前》before》【方法自己】》around方法执行后》after》afterReturning * 很奇怪为什么两次都是一样的执行优先级,为什么没有进afterThrowing().因为使用了around。 * 3》注意:Spring AOP的环绕通知会影响到AfterThrowing通知的运行,不要同时使用! * * 4》注意:在切面的advice里面,一定不要让异常抛出去,影响原方法的执行和返回。 * 5》JoinPoint 即原方法的入参 * 6》returning即原方法的返回值 * 7》如果原方法没有返回值,而这里的advice定义了returning,即使pointCut可以匹配上切点,也不会切入原方法 */ @Before("pointCut1()") public void justBefore(JoinPoint joinPoint){ System.out.println("切入方法前"); } @After("pointCut1()") public void justAfter(JoinPoint joinPoint){ Object[] arr = joinPoint.getArgs(); if(Objects.nonNull(arr) && arr.length >0){ System.out.println((String)arr[0]); System.out.println((Integer)arr[1]); } System.out.println("切入方法后"); } @AfterReturning(pointcut = "pointCut1()",returning = "returnVal") public void justAfterReturn(JoinPoint joinPoint,Object returnVal){ System.out.println(returnVal.toString()); System.out.println("在方法执行完,并未抛异常,能正确返回值的情况下,在返回值之前切入"); } @AfterThrowing(pointcut = "pointCut1()",throwing = "err") public void justAfterThrow(JoinPoint joinPoint,Throwable err){ System.out.println("在方法执行,抛异常的情况下,切入"); } @Around("pointCut1()") public Object justAround(ProceedingJoinPoint proceedingJoinPoint){ System.out.println("环绕型切入,方法执行前"); Object a = null; try { a = proceedingJoinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } System.out.println("环绕型切入,方法执行后"); return a; } }
3》上面两步 就把aop写完了 ,最后要测试一下各种情况
package com.sxd.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class MainController { @ResponseBody @RequestMapping("do") public String doSomething(){ return "无参"; } @ResponseBody @RequestMapping("do2") public String doSomething2(String a){ return "有参"+a; } @ResponseBody @RequestMapping("do3") public String doSomething3(Integer a,String b){ return "整数"+a+"---字符串"+b; } @ResponseBody @RequestMapping("do4") public String doSomething4(String a,Integer b){ return "字符串"+a+"---整数"+b; } @ResponseBody @RequestMapping("do5") public String doSomething5(String a,Integer b) throws Throwable{ return "字符串转成数字"+Integer.parseInt(a)+"---整数"+b; } @ResponseBody @RequestMapping("do6") public void doSomething6(){ System.out.println("无返回值的"); } }
发请求 测试即可。
======================================================================
补充:
1》两个或多个位置定义切点
方法1:
@Pointcut(value = "(execution(* net.shopxx.xgn.controller.MemberCybController.cjtg(..))) or (execution(* net.shopxx.hunan.MemberCybController.cjtg(..)))") public void endbgDeal() { }
方法2:
@Pointcut(value = "execution(* net.shopxx.xgn.controller.MemberCybController.cjtg(..))") public void endbgDeal1(){ } @Pointcut(value = "execution(* net.shopxx.hunan.MemberCybController.cjtg(..))") public void endbgDeal2(){ } @Pointcut(value = "endbgDeal1() || endbgDeal2()") public void endbgDeal() { }