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() {
}