框架技术----Sping【Aop】

Spring-- AOP

Javaweb —Spring


SpringAOP


昨天已经分享了Spring IOC的内容,IOC模式的实现方式是DI,DI有两种,一种通过配置文件,一种通过注解,但是注解开发使用更多,IOC就是为了创建对象【都是单例的,容器创建的对象;Tomcat也是,servlet是单例多线程对象】,IOC方式替代传统的正传方式主要就是为了解耦;业务对象之间的依赖关系(比如service和dao之间)变得松散一些,可以直接修改配置文件的ref即可,或者Resource的那么即可,非常轻易就可以修改,符合开闭原则

OOP: object-orient programming

AOP : 面向切面编程,就是将无关业务逻辑看成一个一个切面,找到有哪些切面,然后进行编程

IOC实现了业务对象之间的解耦合(依赖关系), AOP实现了业务逻辑和非业务逻辑解耦合

AOP介绍

AOP aspect-orient programming 面向切面编程,是从动态的角度考虑程序的运行过程;AOP的底层就是动态代理,采用两种代理:JDK和CGLIB;(因为动态代理非常灵活,实现方式多样,为了规范思路,所以AOP就进行了整合,让程序开发更加方便)动态代理的好处就是功能增强不修改源码

AOP技术【动态代理技术】可以通过动态代理实现程序功能的统一维护。利用AOP可以对业务逻辑的各个部分进行隔离,使得业务逻辑各部分之间的耦合度降低,提高复用性,提高效率,将交叉的业务逻辑封装为aspect切面,利用AOP容器的功能将切面植入主业务逻辑 — 所谓的交叉业务逻辑: 通用的,与主业务无关的代码,比如安全检查,事务、日志、缓存等

不使用AOP(动态代理)就会出现代码纠缠,交叉业务逻辑和主业务逻辑混合在一起,【比如转账功能,需要控制权限、日志记录、加载事务、结束事务等;这些和转账的业务逻辑无直接关系;都是功能增强而已,这些代码的存在,产生了大量的冗余代码,干扰了转账代码的书写, 因为太多了】

简单点说,AOP就是规范化的动态代理,这样不同的programmer都是相同的开发思想,把动态代理的实现步骤进行了规范,让programmer用统一的方式实现动态代理 ; 相比原生的动态代理更加容易维护

AOP需要找出切面功能,合理安排切面的执行时间【目标方法前或者后】、合理的安排切面执行的位置,在那个类、方法增加功能

切面 : apspect :切面,表示增强的功能,就是一些代码,完成某个功能,非业务功能[就是交叉业务逻辑,非业务方法,【有无都不影响业务操作】比如安全检查是一个aspect;日志也是一个aspect;一般可以独立使用,可以加给各个业务]

joinpoint: 连接点,连接业务方法以及切面,其实就是切面所加载的方法【某个类中的业务方法】

pointour: 切入点,指多个连接点方法的集合,多个方法;多个方法都要加载某个切面

目标对象: 其实就是连接点所在的类,给哪个类增加功能,整个类就是目标对象

Advice: 通知,通知表示切面功能执行的时间

AOP(动态代理)功能

  1. 当要给系统存在的类修改功能,但是原有的类的功能不完善,不能直接修改源码的情况下使用AOP进行增强
  2. 当要同时给多个类,增加一个相同的功能时【切入点表达式,通配】;增加事务、增加日志输出

AOP的实现框架 AspectJ

Aop是一个规范,是动态的一个规范化以及标准,就像IOC一样------>AOP技术实现的框架 : Spring,spring主要在事务处理时使用了Aop;但是使用步骤比较繁琐

apspectJ : 是一个开源的框架专门做AOP的框架;实现方式更为便捷,使用方便,支持注解式开发,Spring中就可以加入Aspectj的实现【集成了AspectJ,直接可以使用】;AspectJ式基于Java的面向切面编程的

实现的方式: 1. 使用xml配置文件 — 事务实现使用配置方式 2.使用注解,开发中常用注解方式管理切面

切面的三要素

  • 首先就是切面的功能代码,切面要干什么
  • 切面的执行位置,使用pointout来表示切面执行的位置 pointout就是多个joinpoint
  • 切面的执行时间 使用Advice来指明时间,在目标方法前还是后

切面的执行时间

通知Advice【增强】 在AspectJ使用注解来表示时间 ,一共有五个: @Before @AfterReturning @Around @AfterThrowing @After

切面执行位置pointout 切入点表达式

表示切面执行的位置,使用的是切入点表达式

AspectJ定义了专门的表达式并用于指定切入点,原型是

excution(modifiers-pattern?  ret-type-pattern  declaring-type-pattern?  name-pattern(param-pattern) throws-pattern)

modifiers-pattern : 访问的权限类型

ret-type-pattern: 返回值类型

declaring-type-pattern: 包名类型

name-pattern(param-pattern): 方法名(参数类型何参数个数)

throws-pattern: 抛出的异常的类型

? : 表示可选的部分

也就是execution(访问权限 方法返回值类型 包名类名 方法声明(参数) 异常类型) execution表达式就是就是要定位方法,方法最核心的就是返回值类型和方法名(参数列表) — 因为存在重载

其实切入点表达式要匹配的对象就是目标方法的方法名,所以execution表达式中明显就是方法的签名,其中最核心的是返回值类型和方法名; (访问权限、包名类名、抛出异常可以省略)

在可省略部分可以使用通配符

< * 可以匹配0到任意多个字符

< … 用在方法参数中,表示任意多个参数;用在包名之后,表示当前包和子包的路径

< + 用在类名之后,表示当前类和子类;用在接口之后,表示当前接口以及实现类

比如: 包名.类名.方法名 ---->方法名的上一级就是类名

execution(public **(..)) 指定切入点为任意的公共方法

execution(* set*(..)) 任何一个名称以set开始的方法

ececution(* com.xyz.service.*.*(..)) 切入点为service包下面的任意类的任意的方法【service包下面的所有方法】

execution(* com.xyz.service..*.*(..))  service包或者子包里任意类的任意方法

execution(* *..service.*.*(..)) +  指定所有包下面的service子包下面所有类中所有的方法为切入点

AspectJ的步骤

给已经存在的一些类和方法,增加服务的功能,前提是不该变原来的类的代码

  1. 加入AspectJ的依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.13</version>
</dependency>

//当然,前提式加入spring-context依赖,要使用IOC
  1. 创建目标类:接口和其实现类【Aop是规范化的动态代理】对目标类加上切面、功能增强
package cfeng.service;

public interface ManagerService {
    //简单的测试方法
    public void doSome(String name);
}


//实现类-----> 目标类
package cfeng.service.impl;

import cfeng.service.ManagerService;

public class PayService implements ManagerService {
    @Override
    public void doSome(String name) { //joinpoint
        System.out.println("执行业务方法操作业务");
    }
}
  1. 创建切面类【普通的类】:在类上面加上注解@Aspect; 类中定义方法,方法就是切面执行的功能代码 在方法的上面加入aspectJ中的通知注释,指定执行的实际,笔记@Before; 同时还需要指定切入点表达式execution

aspect注解用来表示当前类是一个切面类:用来给业务方法增加功能,这个类有功能代码;在切面类定义的上方

切面中的方法就是存放的功能代码,方法是public的,没有返回值,参数可以没有,参数不是自定义的,有几个参数可供选择

package cfeng.aspect;

import org.aspectj.lang.annotation.Aspect;

import java.util.Date;

/**
 * Aspect是AspectJ中的注解,用来声明类为切面类,
 * 位置 : 写在类的上方,类中存放的都是切面的功能代码
 */
@Aspect
public class TestAspect {

    /**
     * 定义方法,方法实现切面的功能
     * 方法的定义要求 : 公共的public  方法没有返回值
     * 方法可以没有参数,可以有参数,但是参数不是自定义的,有几个参数类型可以使用
     */
    /**
     * 加上前置通知的注解@Before  属性value为切入点表达式,这里只是加载到doSome上,直接具体
     *  切入点表达式 : 访问权限  返回值类型  包名.类名.方法名(参数类型,参数类型) throws  异常类型
     */
    @Before(value = "execution(public void cfeng.service.impl.PayService.doSome(String))")
    public void doLog() {
        System.out.println("非业务方法, 切面功能,方法前执行功能,方法的执行时间 " + new Date());
    }
    //这样,切面三要素,时间,位置,功能代码都有了

    public void doTransa() {
        //方法的最后, 提交事务
        System.out.println("非业务方法, 方法执行完毕后, 执行事务");
    }
}
  1. 创建Spring配置文件,声明对象,将对象交给容器统一管理,可以使用注解,或者配置文件

    • 声明目标对象
    • 声明切面对象
    • 声明AspectJ框架中的自动代理生成器,完成代理对象的自动创建【不然自己写proxy】 这里加上auto-proxy之后,IDEA自动为文件新引入了一个约束文件【会把容器中所有的目标对象,一次性都生成代理对象
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 
           http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd 
           http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    
    和之前引入扫描器一样,这里新引入了约束文件,同时创建了新的命名空间,aop;这样原本的约束就变多了,命名空间也变大了,除了基本的不需要加修饰的,还有context: 和aop:
    
//创建Spring的配置文件 applicationContext.xml

 <!--声明目标对象、目标对象 -->
    <bean id="payService" class="cfeng.service.impl.PayService"/>
    <bean id="testAspect" class="cfeng.aspect.TestAspect"/>
    <!-- 声明自动代理生成器,使用的是AspectJ框架内部的功能,创建
        目标对象的代理对象,是在内存中实现的,修改目标对象内存中的结构,创建为代理对象,
        所以目标对象就是被修改后的代理对象
    -->
    <aop:aspectj-autoproxy/>

其实这里的aop:aspectj-autoproxy和之前IOC的组件扫描器 context:component-scan 差不多,当解析到这里时,就是会扫描容器中的所有的对象,根据他们的类型去找是否含有注解@Aspect;进入到切面对象方法,根据注解识别功能代码插入的位置和时间,然后找到容器中的目标类的对象进行改造生成最终使用的代理对象 — 所以说这里在自动扫描autoproxy之前容器中应该有目标对象和切面对象

  1. 使用测试类来从spring容器中获取目标对象【其实就是代理对象,就像mybatis中getMapper一样】,通过代理执行方法,完成功能增强
package cfeng;

import cfeng.service.ManagerService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class testSomeService {

    @Test
    public void testSpring(){
        ApplicationContext container = new ClassPathXmlApplicationContext("applicationContext.xml");
        //从容器中获取目标对象(这是已经动态代理过的,已经增强了原来对象的功能; 还是获取后转为接口的类型
        ManagerService proxy = (ManagerService) container.getBean("payService");
        //通过代理对象【目标对象增强过了】,实现执行时,目标方法的增强
        proxy.doSome("lisi");
    }
}

接下来测试几个切面执行时间的注解,都是使用的这个测试类的测试方法

切面执行时间

前面已经提到过,切面的执行时间,在AspectJ框架中,使用注解来实现的,常用的注解有5个,这里分别用测试类测试执行

@Before 前置通知【方法可以有JoinPoint参数】

在目标方法之前执行,被注解为前置通知的方法,可以包含一个JoinPoint类型的参数,该类型的对象本身就是切入点表达式,通过该参数,可以获得 切入点表达式、方法签名、目标对象等 【切入点表达式 通过注解的value属性进行定义】

不只是前置通知,所以的通知方法都可以包含JointPoint类型的参数【因为要切入点表达式】— 切入点 如果切面方法中需要使用到切入方法的信息,就加入JoinPoint参数,需要注意,这个参数的值由框架赋予,必须是第一个位置的参数

 @Before(value = "execution(* *..PayService.do*(..))")
    public void doLog(JoinPoint joinPoint) {
        System.out.println("获取方法【切入点】的签名、完整定义" + joinPoint.getSignature());
        System.out.println("获取方法的名称" + joinPoint.getSignature().getName());
        System.out.println("非业务方法, 切面功能,方法前执行功能,方法的执行时间 " + new Date());
    }

------------------------执行结果------------------------
获取方法【切入点】的签名、完整定义void cfeng.service.ManagerService.doSome(String)
获取方法的名称doSome
非业务方法, 切面功能,方法前执行功能,方法的执行时间 Mon Jan 10 16:54:33 CST 2022
执行业务方法操作业务

可以看到成功获取到了切入点的信息;框架利用反射机制,这里的JoinPoint就是代指的切入的方法;日志为念一般需要输出执行的方法的信息

@Before 属性为Value: value值为切入点表达式,用来表示切面功能执行的位置 ; 该方法加载方法的上面

特点 : 在目标方法之前执行,不会改变目标方法的执行结果,不会影响目标方法的执行【只是单独的功能而已】

 @Before(value = "execution(public void cfeng.service.impl.PayService.doSome(String))")
    public void doLog() {
        System.out.println("非业务方法, 切面功能,方法前执行功能,方法的执行时间 " + new Date());
    }

按照上面的测试类进行执行的结果时

非业务方法, 切面功能,方法前执行功能,方法的执行时间 Mon Jan 10 15:03:44 CST 2022
执行业务方法操作业务

成功在目标对象之前实现了切面的切入【创建了代理对象进行了功能增强,AOP让动态代理规范,只需要创建切面类即可,动态代理对象的生成是AspectJ自动完成的】

这里的切入点表达式有点长,之前分析的时候就说过,这里其实是可以省略访问权限的

execution(void cfeng.service.impl.PayService.doSome(String))

同时,这里的包名也是可以省略的,因为只有一个PayService

execution(void *…PayService.doSome(String)

还可以再省略一点,因为这里只有一个do的方法;返回值类型也就可以通配

execution(* *..PayService.do*(..))   本来表示的是PayService下面的do*方法,这里类少,是唯一的

还可以直接极简,将类名包名直接省略

execution(* do*(..))

需要注意: 一个切入点是可以同时加入很多个通知的

//这里将doLog方法拷贝一份,再执行

非业务方法, 切面功能,方法前执行功能,方法的执行时间 Mon Jan 10 15:29:02 CST 2022
非业务方法, 切面功能,方法前执行功能,方法的执行时间 Mon Jan 10 15:29:02 CST 2022
执行业务方法操作业务

很好理解,因为可能有很多不同功能的切面类,加载同一个方法之前的通知是可能有很多个

切面的通知不会影响目标方法的执行和结果

也就是说,如果切面类出错,或者切入点不准确(找不到切入的位置);目标方法是正常执行的

为了更加形象说明问题,测试类中加入输出proxy对象类型的代码
System.out.println("proxy对象是 : " + proxy.getClass().toString());

----------------------正确的执行结果---------------------------
proxy对象是 : class jdk.proxy2.$Proxy10
非业务方法, 切面功能,方法前执行功能,方法的执行时间 Mon Jan 10 15:42:15 CST 2022
执行业务方法操作业务
 
    
//这里就写一个匹配不到的切入点表达式
@Before(value = "execution(* *..PayService.do*())")   ---> 没有无参的do*方法
   
-----------------执行的结果---------------------------------------
proxy对象是 : class cfeng.service.impl.PayService
执行业务方法操作业务

目标方法是正常执行了的,只是没有成功加入切面;AspectJ框架的auto

可以对比一下原来的动态代理手工实现这个步骤,这里为了简化直接使用了匿名对象

ManagerService service = new PayService();
ManagerService peroxy1 = (ManagerService) Proxy.newProxyInstance(service.getClass().getClassLoader(), service.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //手动实现功能的增强,输出日志
                new TestAspect().doLog();
                Object result = null;
                //返回值就是目标方法的返回值
                result =  method.invoke(service,args);
                //………………其他的代码
                return result;
            }
        });
peroxy1.doSome("lisi");
System.out.println("proxy1 动态代理对象类型为 :" + peroxy1.getClass().toString());

------------------------执行结果-------------------------------
非业务方法, 切面功能,方法前执行功能,方法的执行时间 Mon Jan 10 16:20:10 CST 2022
执行业务方法操作业务
proxy1 动态代理对象类型为 :class jdk.proxy2.$Proxy11

所以AOP实现了自动的代理对象创建【手动实现动态代理,需要使用到目标对象和切面对象,所以需要在容器中创建】—生成的对象类型和AOP生成的是相同的,都是class jdk.proxy2.$Proxy

所以切面位置不正确或者切面中有异常时不会影响目标类的正常的执行的

@AfterReturning 后置通知,属性returning

在目标方法执行之后执行,由于是目标方法返回值之后执行,所以可以获取目标方法的返回值,然后可以操作这个值,该注解的returning属性可以接收方法返回值的变量名称;所以该注解的切面方法中,除了JointPoint类型的参数之外,还有可以包含接收返回值的参数,类型为Object

    /**
     * @param res 这里代表的是参数的返回值
     * 后置通知@AfterReturning 除了参数vlaue指出切入点表达式,还有returning表示的是目标方法的返回值,注意,自定义的变量名和通知方法的形参名相同
     */
    @AfterReturning(value = "execution(* *..doSome(String))",returning="res")
    public void doReturning(Object res) {
        System.out.println("交叉业务逻辑,获得返回值为 " + res);
    }

这里就是后置通知的使用,可以获得这个返回值来进行操作

需要注意的是,这里相当于是res = doOther();然后再继续使用; 如果是String类型的,那么结果是不可变的

这里相当于是传值;所以是不会修改原来方法的返回值的;只有环绕通知才能修改结果

@Around 环绕通知, ProceedingJoinPoint参数

再目标方法执行之前之后执行,被注解为环绕增强的方法要有返回值,Object类型,并且方法可以返回一个包含ProceedingJoinPoint类型的参数,有一个proceed方法,用于执行目标方法,如果有目标方法有返回值,该方法的返回值就是目标方法的返回值。 该增强方法实际上是拦截了目标方法的执行; 注解的属性还是使用value即可,没有其他特殊的

也就是这个注解的方法和之前不同,必须有一个返回值;参数ProceedIingJoinPint相当于是代表的是JDK动态代理的Method,proceed方法相当于invoke就可以执行【最强的功能,控制方法是否被调用执行,可以修改目标结果】其实这个就相当于InvocationHandler接口的作用

 /**
     * 环绕注解 是最强的注解, 要求方法必须是有返回值的,其中参数ProceedingJoinPoint就是Method;其方法proceed可以执行目标方法
     */
    @Around(value = "execution(* *..PayService.do*(..))")
    public Object doLog(ProceedingJoinPoint method) throws Throwable {
        //方法前进行输出时间
        System.out.println("执行时间" + new Date());
        //执行目标方法
        String res = (String) method.proceed();
        //对结果进行处理
        System.out.println("目标方法之后执行业务代码");
        return res.toUpperCase();
    }

这里执行结果的时候,之前的后置通知也加入到这个切入点

执行时间Mon Jan 10 18:43:21 CST 2022
执行业务方法操作业务
交叉业务逻辑,获得返回值为 lisi -----> 后置通知的结果
目标方法之后执行业务代码
LISI

其他的都是环绕通知执行的;所以这里就相当于上面的JDK动态代理;最主要就是拦截了方法的执行

需要注意的是,Around中不需要通过JoinPoint来获得目标方法的信息,ProceedingJoinPoint就可以当作这个使用

public interface ProceedingJoinPoint extends JoinPoint

直接就可以通过这个对象,来获得目标方法的信息

比如现在通过获得输入的值,拦截方法,满足条件的时候再执行目标方法【功能增强,过滤】

@Around(value = "execution(* *..PayService.do*(..))")
    public Object doLog(ProceedingJoinPoint method) throws Throwable {
        String res = null;
        //方法前进行输出时间
        System.out.println("执行时间" + new Date());
        //执行目标方法,需要满足条件,获取目标方法传入的参数的值
        Object[] args = method.getArgs();
        if(args != null && args.length > 0) {
            String name = (String)args[0];
            if("lisi".equals(name)) {
                res = (String) method.proceed();
            }
        }
        //对结果进行处理
        System.out.println("目标方法之后执行业务代码");
        return res;
    }
一定要注意防范空指针异常

执行的结果;将后置通知AfterReturning去掉了

执行时间Mon Jan 10 18:54:33 CST 2022
执行业务方法操作业务
目标方法之后执行业务代码
方法的返回值是 lisi

------------------输入张三----------------
执行时间Mon Jan 10 18:56:16 CST 2022
目标方法之后执行业务代码
方法的返回值是 null

可以发现成功过滤,输入错误不会执行目标方法;环绕通知用于做事务管理

@AfterThrowing 异常通知 thrwing属性

在目标方法抛出异常之后执行,这个注解的throwing属性用于指定所发生的异常类对象;被注解为异常通知的方法可以包含一个参数Throwable,参数名称为throwing指定的名称,表示发生的异常的对象【和后置通知类似,一个是异常对象,一个是返回值对象】公共的,没有返回值;

可以做异常的监控程序,监控目标方法执行是否有异常

//在目标方法中制造异常
@Override
    public String doOther(String name) {
        int i = 0;
        i = 100/i; //制造一个简单的异常
        System.out.println("执行业务方法doOther,返回了一个name");
        return name.toUpperCase();
    }

 @AfterThrowing(value = "execution(* *..PayService.doOther(..)))",throwing = "ex")
    public void doThrowing(Exception ex) {
       //方法抛出异常后执行
        System.out.println("对不起,发生了异常" + ex.toString()); //名称一定要一致
    }

执行的结果为

对不起,发生了异常java.lang.ArithmeticException: / by zero

可以发现发生异常,该切面的功能代码执行,输出了异常的类型

@After 最终通知

这个注解的方法是公共的,没有返回值,没有参数,如果有就是JoinPoint, 其特点 : 总是会执行,并且在目标方法之后执行; 有点类似finally;一般是用来做资源的清除的

 @After(value = "execution(* *..PayService.doOther(..)))")
    public void doThrowing() {
       //方法抛出异常后执行
        System.out.println("该切面总是会执行,一般在目标方法之后,用于做资源清除的");
    }

执行的结果是

执行业务方法doOther,返回了一个name
该切面总是会执行,一般在目标方法之后,用于做资源清除的

就算抛出异常也会执行,和之前的finally相同

辅助注解@Pointcut

定义和管理切入点; 如果项目中有很多切入点表达式是重复的,可以复用的,那么就是用这个注解

属性 : value 切入点表达式

位置 : 自定义方法的上面

当使用@PointCut在一个方法的上面,此时这个方法的名称就是切入点表达式的别名;其他的通知中,vlaue属性就可以使用这个方法的名称来代替切入点表达式

//所以,可以直接在切面类中定义一个空方法,加上注解,之后这个方法的名称就可以代替切入点表达式
 @AfterReturning(value = "mypt()",returning = "res")
    public void doReturning(Object res) {
        System.out.println("交叉业务逻辑,获得返回值为 " + res);
        if(res.equals("LISI")) {
            System.out.println("你好,AspectJ");
        }
    }

    @After(value = "mypt()")
    public void doThrowing() {
       //方法抛出异常后执行
        System.out.println("该切面总是会执行,一般在目标方法之后,用于做资源清除的");
    }

    @Pointcut(value = "execution(* *..PayService.doOther(..)))")
    public void  mypt(){
        //空方法,只是为了使用名称
    }

注意,一定要加上括号;这样方便进行辅助管理,不需要写很长的切入点表达式

没有接口使用CGLIB

上面是按照JDK动态代理来思考的,那如果目标类没有实现接口呢?只是一个简单的业务类

public class PayService {
    public String doSome(String name) {
        System.out.println("执行业务方法操作业务");
        return  name;
    }

    public String doOther(String name) {
        System.out.println("执行业务方法doOther,返回了一个name");
        return name.toUpperCase();
    }
}

这个时候将测试类的测试代码修改一下

 PayService proxy = (PayService) container.getBean("payService");
 //通过代理对象【目标对象增强过了】,实现执行时,目标方法的增强
 proxy.doOther("张三");

惊奇的发现执行的结果没有发生任何的变化

执行业务方法doOther,返回了一个name
交叉业务逻辑,获得返回值为 张三
该切面总是会执行,一般在目标方法之后,用于做资源清除的

那么查看这个代理对象的类型

class cfeng.service.impl.PayService$$EnhancerBySpringCGLIB$$16262742

可以看到其中的关键字为CGLIB,说明这个时候采用的是CGLIB动态代理【只要可以继承就可以实现】Spring框架自动应用这种动态代理— 也是修改的目标对象;因为是直接从容器种获取目标类的bean

当有接口的时候,默认使用的JDK动态代理,如果期望这个时候还是使用CGLIB代理,要在配置文件的自动创建代理对象的代码,加上属性proxy-target-class = “true”

<aop:aspectj-autoproxy proxy-target-class="true"/>

也就是说,这里相当于设置有接口的时候采用的底层,如果是true,就是CGLIB,false就是JDK🌳

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值