springboot2.x默认使用的代理是cglib代理操作

背景

因为项目优化,打算写个日志的切面类,于是起了个springboot 工程,在这里面测试。结果在springboot 里面测试正常,能正确打印日志,但是把代码复制到实际项目中,在进入切面打印日志的时候总是报错,报空指针错误。

经调试发现每次都是在获取注解上的属性时报错。当时百思不得解。后来灵光一闪,想到可能是项目中获取到的是接口方法,而springboot是实现类的method ,所以可以拿到注解的属性。

但是仔细一想,Springboot里面也是接口,难道不应该走JDK动态代理吗?那拿到这个方法的应该也是接口的方法,带着这个疑问,我开始了我的探索之旅。

验证

springboot 项目

spring 项目

发现springBoot 竟然走的是cglib代理,起代理的是实现类,所以能拿到方法上注解的属性,而我的项目是个传统的spring 项目,service是接口,走的是JDK动态代理,通过切点拿到的是接口的方法,而接口上又没有注解,所以按照springboot的写法是拿不到注解的,拿不到注解也就拿不到注解属性,所以报错。

解决办法

springboot的写法

 private Method getMethod(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        //获取方法签名
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        return method;
    }

 private String getAnnotationDesc(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        Method method = getMethod(joinPoint);
        String value = method.getAnnotation(MyLog.class).value();
        return value;
    }

spring 的写法

private Method getMethod(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        //获取方法签名
        Class<?> targetClass = joinPoint.getTarget().getClass();
        String methodName = joinPoint.getSignature().getName();
        Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
        Method method = targetClass.getMethod(methodName, parameterTypes);
        return method;
    }
private String getAnnotationDesc(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        Method method = getMethod(joinPoint);
        String value = method.getAnnotation(MyLog.class).value();
        return value;
    }

可以看到spring项目的方法是先获取目标类,然后再通过目标类获取目标方法,然后再获取方法上的注解。

深度追踪

springboot 为什么将默认的代理改成了cglib,这会导致什么问题?如果我们想要事务走JDK动态代理,该如何做?

带着这些疑问,我翻阅了springboot的相关issue ,发现很多人提这个问题。

先关issue如下:

issue1

issue2

issue2

springboot团队之所以默认的代理模式设置成cglib代理,看看spring的官方团队是怎么解释的

This was changed in 1.4 (see 5423). We've generally found cglib proxies less likely to cause unexpected cast exceptions.

他们认为使用cglib更不容易出现转换错误。springboot 默认的配置文件的位置在

/org/springframework/boot/spring-boot-autoconfigure/2.1.7.RELEASE/spring-boot-autoconfigure-2.1.7.RELEASE.jar!/META-INF/spring-configuration-metadata.json

{
      "name": "spring.aop.proxy-target-class",
      "type": "java.lang.Boolean",
      "description": "Whether subclass-based (CGLIB) proxies are to be created (true), as opposed to standard Java interface-based proxies (false).",
      "defaultValue": true
    },

如果在事务中强制使用JDK动态代理,以往的知识告诉我们,我们需要将proxyTargetClass 设置成false,于是我们在springboot 中发现注解@EnableTransactionManagement 或者@EnableAspectJAutoProxy默认就为false,说明这里面的属性不起作用

@EnableAspectJAutoProxy(proxyTargetClass = false)
@EnableTransactionManagement(proxyTargetClass = false)

同理 @EnableCaching 上的proxyTargetClass 属性也是失效的。如果偏要springboot 走JDK动态代理,那么需要在application.properties里面配置

spring.aop.proxy-target-class=false

此时项目中走的就是JDK动态代理。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Spring如何基于Proxy及cglib实现动态代理

    spring中提供了两种动态代理的方式,分别是Java Proxy以及cglib JavaProxy只能代理接口,而cglib是通过继承的方式,实现对类的代理 添加一个接口以及对应的实现类 public interface HelloInterface { void sayHello(); } public class HelloInterfaceImpl implements HelloInterface { @Override public void sayHello() { System.

  • 详解Spring的两种代理方式:JDK动态代理和CGLIB动态代理

    代理模式 代理模式的英文叫做Proxy或Surrogate,中文都可译为"代理",所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动.在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用 A. 抽象主题角色 声明了真实主题和代理主题的共同接口,这样一来在任何可以使用真实主题的地方都可以是使用代理主题 B. 代理主题(Proxy)角色: 代理主题角色内部含有对真实主题的引用,从而可以在任何时候操作真实主题对象:代理主题角

  • springboot做代理分发服务+代理鉴权的实现过程

    还原背景 大家都做过b-s架构的应用,也就是基于浏览器的软件应用.现在呢有个场景就是FE端也就是前端工程是前后端分离的,采用主流的前端框架VUE编写.服务端采用的是springBoot架构. 现在有另外一个服务也需要与前端页面交互,但是由于之前前端与服务端1交互时有鉴权与登录体系逻辑控制以及分布式session存储逻辑都在服务1中,没有把认证流程放到网关.所以新服务与前端交互则不想再重复编写一套鉴权认证逻辑.最终想通过服务1进行一个代理把前端固定的请求转发到新加的服务2上. 怎么实现 思路:客户

  • Spring中的两种代理JDK和CGLIB的区别浅谈

    一.原理区别: Java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理. 而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理. 1.如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 2.如果目标对象实现了接口,可以强制使用CGLIB实现AOP 3.如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换 如何强制使用

  • 基于SpringBoot2.0默认使用Redis连接池的配置操作

    SpringBoot2.0默认采用Lettuce客户端来连接Redis服务端的 默认是不使用连接池的,只有配置 redis.lettuce.pool下的属性的时候才可以使用到redis连接池 redis: cluster: nodes: ${redis.host.cluster} password: ${redis.password} lettuce: shutdown-timeout: 100 # 关闭超时时间 pool: max-active: 8 # 连接池最大连接数(使用负值表示没有限制

  • java代理模式(静态代理、动态代理、cglib代理)

    目录 代理模式 静态代理 代码 接口 被代理对象 代理对象 测试 动态代理 代码: 接口 目标对象 代理对象 测试 cglib代理 代码: 目标对象 代理对象 测试 应用 总结 代理模式 代理模式(Proxy Pattern)是一种结构性模式.代理模式为一个对象提供了一个替身,以控制对这个对象的访问.即通过代理对象访问目标目标对象,可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能. 被代理的对象可以是远程对象.创建开销答得对象或需要安全控制得对象.代理模式主要有三种形式,分别

  • springboot2.x默认使用的代理是cglib代理操作

    背景 因为项目优化,打算写个日志的切面类,于是起了个springboot 工程,在这里面测试.结果在springboot 里面测试正常,能正确打印日志,但是把代码复制到实际项目中,在进入切面打印日志的时候总是报错,报空指针错误. 经调试发现每次都是在获取注解上的属性时报错.当时百思不得解.后来灵光一闪,想到可能是项目中获取到的是接口方法,而springboot是实现类的method ,所以可以拿到注解的属性. 但是仔细一想,Springboot里面也是接口,难道不应该走JDK动态代理吗?那拿到这

  • java代理 jdk动态代理应用案列

    java代理有jdk动态代理.cglib代理,这里只说下jdk动态代理,jdk动态代理主要使用的是java反射机制(既java.lang.reflect包) 原理是(歌手.经纪人做例子): 建立一个公共的接口,比如:歌手public interface Singer: 用具体的类实现接口,比如:周杰伦,他是歌手所以实现Singer这个类,class MySinger implements Singer 建立代理类,这里也就是经纪人,他需要实现InvocationHandler类,并重写invok

  • Java 动态代理与CGLIB详细介绍

    静态代理模式 因为需要对一些函数进行二次处理,或是某些函数不让外界知道时,可以使用代理模式,通过访问第三方,间接访问原函数的方式,达到以上目的. interface Hosee{ String sayhi(); } class Hoseeimpl implements Hosee{ @Override public String sayhi() { return "Welcome oschina hosee's blog"; } } class HoseeProxy implement

  • 浅谈Java代理(jdk静态代理、动态代理和cglib动态代理)

    一.代理是Java常用的设计模式,代理类通过调用被代理类的相关方法,并对相关方法进行增强.加入一些非业务性代码,比如事务.日志.报警发邮件等操作. 二.jdk静态代理 1.业务接口 /** * 业务接口 * @author pc * */ public interface UserService { // 增加一个用户 public void addUser(); // 编辑账户 public void editUser(); } 2.业务实现类 /** * 业务实现类 * @author pc

  • JAVA中的静态代理、动态代理以及CGLIB动态代理总结

    代理模式是java中最常用的设计模式之一,尤其是在spring框架中广泛应用.对于java的代理模式,一般可分为:静态代理.动态代理.以及CGLIB实现动态代理. 对于上述三种代理模式,分别进行说明. 1.静态代理 静态代理其实就是在程序运行之前,提前写好被代理方法的代理类,编译后运行.在程序运行之前,class已经存在. 下面我们实现一个静态代理demo: 静态代理 定义一个接口Target package com.test.proxy; public interface Target { p

  • JDK动态代理与CGLib动态代理的区别对比

    案例: public interface ForumService { void removeTopic(int topicId); void removeForum(int forumId); } 对相关方法进行性能监控 public class ForumServiceImpl implements ForumService { public void removeTopic(int topicId) { // PerformanceMonitor.begin("com.hand.proxy

  • 解决Spring JPA 使用@transaction注解时产生CGLIB代理冲突问题

    Spring JPA 使用@transaction注解时产生CGLIB代理冲突 在使用JPA进行数据库的删除操作时需要使用@Transactional注解来支持事物: @Modifying @Transactional @Query(" delete from FollowerInfo " + " where crmAuth = :crmAuth and investUserId = :invUserId") void deleteByCrmAuthAndInvUs

  • Spring AOP里的静态代理和动态代理用法详解

    什么是代理? 为某一个对象创建一个代理对象,程序不直接用原本的对象,而是由创建的代理对象来控制原对象,通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间 什么是静态代理? 由程序创建或特定工具自动生成源代码,在程序运行前,代理类的.class文件就已经存在 通过将目标类与代理类实现同一个接口,让代理类持有真实类对象,然后在代理类方法中调用真实类方法,在调用真实类方法的前后添加我们所需要的功能扩展代码来达到增强的目的. 优点

随机推荐