【AOP】Spring AOP基础 + 实践 完整记录

Spring AOP的基础概念

=============================================================

AOP(Aspect-Oriented Programming), 即 面向切面编程, 它与 OOP( Object-Oriented Programming, 面向对象编程) 相辅相成, 提供了与 OOP 不同的抽象软件结构的视角.
在 OOP 中, 我们以类(class)作为我们的基本单元, 而 AOP 中的基本单元是 Aspect(切面)。

==============================================================

基础概念图:

【AOP】Spring AOP基础 + 实践 完整记录

有了上面这张原理图,那么关于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() {
}
上一篇:利用Python来定位你所在地方!精确到五十米内!


下一篇:wgs84 转百度经纬度坐标