GitHub 18k Star 的Java工程师成神之路,不来了解一下吗!

GitHub 18k Star 的Java工程师成神之路,真的不来了解一下吗!

GitHub 18k Star 的Java工程师成神之路,真的真的不来了解一下吗!

前几天,我刚刚发布过一篇文章《自定义注解!绝对是程序员装逼的利器!!》,介绍过如何使用Spring AOP + 自定义注解来提升代码的优雅性。

很多读者看完之后表示用起来很爽,但是后台也有人留言说自己配置了Spring的AOP之后,发现切面不生效。

其实,这个问题我在用的过程中也遇到过,而且还是同一个问题一天之内遇到了两次。

说明这个问题很容易被忽略,并且这个问题带来的后果可能是极其严重的。那么,我们就来简单回顾一下问题是怎么样的。

问题重现

最初我定义了一个注解,希望可以方便统一的对一些数据库操作做缓存。于是就有了以下代码:

首先,定义一个注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cacheable { /**
* 策略名称,需要保证唯一
*
* @return
*/
public String keyName(); /**
* 超时时长,单位:秒
*
* @return
*/
public int expireTime(); }

然后自定义一个切面,对所有使用了该注解的方法进行切面处理:

@Aspect
@Component
public class StrategyCacheAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(FacadeAspect.class);
@Around("@annotation(com.hollis.cache.StrategyCache)")
public Object cache(ProceedingJoinPoint pjp) throws Throwable {
// 先查缓存,如果缓存中有值,直接返回。如果缓存中没有,先执行方法,再将返回值存储到缓存中。
}
}

然后就可以使用该注解了,使用方法如下:

@Component
public class StrategyService extends BaseStrategyService { public PricingResponse getFactor(Map<String, String> pricingParams) {
// 做一些参数校验,以及异常捕获相关的事情
return this.loadFactor(tieredPricingParams);
} @Override
@StrategyCache(keyName = "key0001", expireTime = 60 * 60 * 2)
private PricingResponse loadFactor(Map<String, String> pricingParams) {
//代码执行
}
}

以上,对loadFactor方法增加了切面,为了方便使用,我们还定义了一个getFactor方法,设置为public,方便外部调用。

但是,在调试过程中,我发现我们设置在loadFactor方法上面的切面并没有成功,无法执行切面类。

于是开始排查问题具体是什么。

问题排查

为了排查这个问题,首先是把所有的代码检查一遍,看看切面的代码是不是有问题,有没有可能有手误打错了字之类的。

但是发现都没有。于是就想办法找找问题。

接下来我把loadFactor的访问权限从private改成public,发现没有效果。

然后我尝试着在方法外直接调用loadFactor而不是getFactor。

发现这样做就可以成功的执行到切面里面了。

发现这一现象的时候,我突然恍然大悟,直捶大腿。原来如此,原来如此,就应该是这样的。

我突然就想到了问题的原因。其实原因挺简单的,也是我之前了解到过的原理,但是在问题刚刚发生的时候我并没有想到这里,而是通过debug,发现这个现象之后我才突然想到这个原理。

那么,就来说说为什么会发生这样的问题。

代理的调用方式

我们发现上面的问题关键在于loadFactor方法被调用的方式不同。我们知道,方法的调用通常有以下几种方式:

1、在类内部,通过this进行自调用:

public class SimplePojo implements Pojo {

    public void foo() {
// this next method invocation is a direct call on the 'this' reference
this.bar();
} public void bar() {
// some logic...
}
}

2、在类外部,通过该类的对象进行调用

public class Main {

    public static void main(String[] args) {
Pojo pojo = new SimplePojo();
// this is a direct method call on the 'pojo' reference
pojo.foo();
}
}

类关系及调用过程中如下图:



如果是静态方法,也可以通过类直接调用。

3、在类外部,通过该类的代理对象进行调用:

public class Main {

    public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice()); Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}

类关系及调用过程中如下图:



那么,Spring的AOP其实是第三种调用方式,就是通过代理对象调用,只有这种调用方式,才能够在真正的对象的执行前后,能够让代理对象也执行相关代码,才能起到切面的作用。

而对于使用this的方式调用,这种只是自调用,并不会使用代理对象进行调用,也就无法执行切面类。

问题解决

那么,我们知道了,想要真正的执行代理,那么就需要通过代理对象进行调用而不是使用this调用的方式。

那么,这个问题的解决办法也就是想办法通过代理对象来调用目标方法即可。

这种问题的解决网上有很多种办法,这里介绍一个相对简单的。其他的更多的办法大家可以在网上找到一些案例。搜索关键词"AOP 自调用"即可。

获取代理对象进行调用

我们需要修改一下前面的StrategyService的代码,修改成以下内容:

@Component
public class StrategyService{ public PricingResponse getFactor(Map<String, String> pricingParams) {
// 做一些参数校验,以及异常捕获相关的事情
// 这里不使用this.loadFactor而是使用AopContext.currentProxy()调用,目的是解决AOP代理不支持方法自调用的问题
if (AopContext.currentProxy() instanceof StrategyService) {
return ((StrategyService)AopContext.currentProxy()).loadFactor(tieredPricingParams);
} else {
// 部分实现没有被代理过,则直接进行自调用即可
return loadFactor(tieredPricingParams);
}
} @Override
@StrategyCache(keyName = "key0001", expireTime = 60 * 60 * 2)
private PricingResponse loadFactor(Map<String, String> oricingParams) {
//代码执行
}
}

即使用AopContext.currentProxy()获取到代理对象,然后通过代理对象调用对应的方法。

还有个地方需要注意,以上方式还需要将Aspect的expose-proxy设置成true。如果是配置文件修改:

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

如果是SpringBoot,则修改应用启动入口类的注解:

@EnableAspectJAutoProxy(exposeProxy = true)
public class Application { }

总结

以上,我们分析并解决了一个Spring AOP不支持方法自调用的问题。

AOP失败这个问题,其实还是很严重的,因为如果发生非预期的失效,那么直接问题就是没有执行切面方法,更严重的后果可能是诸如事务未生效、日志未打印、缓存未查询等各种问题。

所以,还是建议大家看完此文之后,统查一下自己的代码,是否存在方法自调用的情况。这种情况下,任何切面都是无法生效的!

关于作者:Hollis,一个对Coding有着独特追求的人,阿里巴巴技术专家,《程序员的三门课》联合作者,《Java工程师成神之路》系列文章作者。

深坑啊!同一个Spring AOP的坑,我一天踩了两次!的更多相关文章

  1. spring aop 加在Controller层造成类初始化两遍

    写一个测试项目,在配置动态数据源的时候采用的AOP切面到Controller层中,根据参数判断是否切合数据源,结果发现,每次Controller层的类都会初始化两次! 后来测试发现,把切面放到Serv ...

  2. 朱晔和你聊Spring系列S1E6:容易犯错的Spring AOP

    阅读PDF版本 标题有点标题党了,这里说的容易犯错不是Spring AOP的错,是指使用的时候容易犯错.本文会以一些例子来展开讨论AOP的使用以及使用过程中容易出错的点. 几句话说清楚AOP 有关必要 ...

  3. Spring AOP: 织入的顺序

    spring AOP 采用和 AspectJ 一样的优先顺序来织入增强处理:在进入连接点时,高优先级的增强处理将先被织入:在退出连接点时,高优先级的增强处理会后被织入. 当不同的切面里的两个增强处理需 ...

  4. Spring AOP和事务的相关陷阱

    1.前言 2.嵌套方法拦截失效 2.1 问题场景 2.2 解决方案 2.3 原因分析 2.3.1 原理 2.3.2 源代码分析 3.Spring事务在多线程环境下失效 3.1 问题场景 3.2 解决方 ...

  5. Java Spring AOP用法

    Java Spring AOP用法 Spring AOP Java web 环境搭建 Java web 项目搭建 Java Spring IOC用法 spring提供了两个核心功能,一个是IoC(控制 ...

  6. Spring AOP @Aspect

    spring提供了两个核心功能,一个是IoC(控制反转),另外一个便是Aop(面向切面编程),IoC有助于应用对象之间的解耦,AOP则可以实现横切关注点(如日志.安全.缓存和事务管理)与他们所影响的对 ...

  7. (转)Spring AOP的底层实现技术

    AOP概述 软件的编程语言最终的目的就是用更自然更灵活的方式模拟世界,从原始机器语言到过程语言再到面向对象的语言,我们看到编程语言在一步步用更自然.更强大的方式描述软件.AOP是软件开发思想的一个飞跃 ...

  8. Spring Aop基于注解的实现

    一.AspectOriented Programing,面向切面编程.   AOP主要用于日志记录,性能统计,安全控制(权限控制),事务处理,异常处理等.将日志记录,性能统计,安全控制,事务处理,异常 ...

  9. 转:Spring AOP 注解方式实现的一些“坑”

    使用过Spring AOP的注解方式实现,也入过不少坑,现在做一下记录,希望有同样需求的朋友可以少走弯路 使用之前还是先过一下官方文档吧,至少可以少走弯路,不用担心英文读不懂,其实只看代码例子就能理解 ...

随机推荐

  1. java基础知识总结(续写)

    1.两个容易搞混的C盘文件夹 文件名 描述 Progrm Files 默认存储的64位软件 Progrm Files(x86) 默认存储32位软件 2.常用基础DOS命令(Windows+R打开命令) ...

  2. 【源码项目+解析】C语言/C++开发,打造一个小项目扫雷小游戏!

    一直说写个几百行的小项目,于是我写了一个控制台的扫雷,没有想到精简完了代码才200行左右,不过考虑到这是我精简过后的,浓缩才是精华嘛,我就发出来大家一起学习啦,看到程序跑起来能玩,感觉还是蛮有成就感的 ...

  3. hdu6376 度度熊剪纸条-----01背包

    题目:度度熊有一张纸条和一把剪刀.   纸条上依次写着 N 个数字,数字只可能是 0 或者 1.     度度熊想在纸条上剪 K 刀(每一刀只能剪在数字和数字之间),这样就形成了 K+1 段.   他 ...

  4. kali 運行 chrome

    0x00前提 已經安裝好google chrome . 0x01 在終端執行命令: google-chrome,發現如圖: 錯誤提示:在root下只能使用 --no-sandbox選項來運行chrom ...

  5. linux(centos8):用systemctl管理war包形式的jenkins(java 14 / jenkins 2.257)

    一,如何安装jenkins? 参见: https://www.cnblogs.com/architectforest/p/13685904.html 说明:刘宏缔的架构森林是一个专注架构的博客,地址: ...

  6. 第十四章 nginx代理配置

    一.nginx代理 1.常见模式 1.常见模式:1)正向代理2)反向代理​2.区别1)区别在于形式上服务的"对象"不一样2)正向代理代理的对象是客户端,为客户端服务3)反向代理代理 ...

  7. 教你如何帮助前端同学快速生成API接口代码

    最近我们团队开源的后端微服务框架go-zero增长势头比较迅猛,这篇文章我讲讲go-zero对前端团队的作用,并通过一个示例来给大家演示我们是怎么做的,希望能给后端的同学也可以帮助前端同学提高开发效率 ...

  8. IDEA提示类注释的wrong tag警告的解决办法

    当创建一个类的时候,给类写上注释时,出现类似于"Wrong tag 'date' less... (Ctrl+F1)"这种警告,解决方式是: alt + enter -> a ...

  9. MySQL设置慢查询

    MySQL的慢查询日志是用来记录在MySQL中响应时间超过阀值的语句,则会被记录到慢查询日志中(运行时间超过long_query_time值的SQL语句):   慢查询相关参数: slow_query ...

  10. 模板c++

    #define _CRT_SECURE_NO_WARINGS #include <iostream> using namespace std; int main(void) { retru ...