springaop在电商项目哪里体现的_Spring AOP(上)

AOP应该是写过写的东西,但最近项目中又接触到,拿来复盘下。

我默认大家已经掌握知乎专栏《动态代理》的内容,所以这里不会再解释什么是动态代理。

主要内容:

  • AOP的一些概念
  • 为什么需要切点表达式

老实说,没学SpringBoot之前,AOP难度要大很多。因为配置略微繁琐,而培训班又讲得太复杂了,一下子抛出很多概念,诸如连接点、切入点、织入等,其实就单纯使用AOP来说,这些概念根本没必要提。

个人认为,要掌握AOP只需要了解以下4个概念:

  • 通知方法
  • 目标对象
  • 切面
  • 切点表达式

Spring AOP底层也是动态代理,所以我们按动态代理的思路去理解AOP:

ad0e84e1f1b7817b9a98f416d483ff1c.png
  • 通知方法:打印日志
  • 目标对象:target

调用代理对象proxyCalculator.add()就会在调用target.add()的前后加上日志打印。

你会发现,单纯的动态代理并没有所谓“切面”和“切点表达式”的概念,这两个概念是Spring AOP对动态代理的扩展。那么,为什么Spring AOP要引入“切面”和“切入点表达式”呢?我们应该从动态代理上找原因。

一般来说,大部分人动态代理是这样写的:

public class ProxyTest {
	public static void main(String[] args) throws Throwable {
		CalculatorImpl target = new CalculatorImpl();
		Calculator calculatorProxy = (Calculator) getProxy(target);
		calculatorProxy.add(1, 2);
		calculatorProxy.subtract(2, 1);
	}

	private static Object getProxy(final Object target) throws Exception {
		Object proxy = Proxy.newProxyInstance(
				target.getClass().getClassLoader(),/*类加载器*/
				target.getClass().getInterfaces(),/*让代理对象和目标对象实现相同接口*/
				new InvocationHandler(){/*代理对象的方法最终都会被JVM导向它的invoke方法*/
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						System.out.println(method.getName() + "方法开始执行...");
						Object result = method.invoke(target, args);
						System.out.println(result);
						System.out.println(method.getName() + "方法执行结束...");
						return result;
					}
				}
		);
		return proxy;
	}
}

059b2cfc9628155472a3f435fdc7b615.png

上面代码其实类似于下面这张图:

c5a68b6b5f9f3c2e04ac8796ebae66f1.png

如果要实现不同的增强逻辑,只需写多个不同的getProxy(),然后需要代理时只要传入target就能得到增强。

动态代理在复用性上已经远远优于静态代理,但上面的代码还有改善空间。

主要在于ProxyTest的定位有问题,模糊的定位导致我们无法对ProxyTest做进一步优化。比如我问你,ProxyTest属于什么呢?Controller、Service、Dao还是Util?

而Spring则明确地把ProxyTest这样的类称为切面类。

所谓的切面,其实就是把所有通知方法集中起来,都放在一个类中,这个类叫做切面类。

726938245417eea741094d84baa40491.png

有了明确的定位后,Spring并没有止步于此,毕竟单纯使用动态代理也可以编写“切面类”。那Spring AOP做了什么改进呢?

我们再来看看之前ProxyTest:

@RestController
class Controller {
 
    @Autowired
    private UserService userService;
    
    @GetMapping("/test")
    public void get() {
       切面类 qiemian = new 切面类();
       Proxy proxyUserService = qiemian.getProxy(userService)
       proxyUserService.addUser();
    }
    
}

由于ProxyTest现在的定位是“管理所有的通知方法”,所以如果一个对象希望得到增强,必然要通过ProxyTest得到代理对象。

但有个问题!

如果我们希望对Service做日志记录,就需要在Controller中调用getProxy()得到Service的代理对象,是显式地硬编码在Controller中的。

坏处在于:

  • 由于是硬编码,如果后期想要撤销日志打印的功能,需要手动一条条改
  • 不够优雅

Spring希望消除这种硬编码的代理风格,改为无感知的动态代理,让程序员可以像使用普通对象一样使用代理对象(@Autowired注入时干脆就是代理对象)。

那就必须制定目标对象和通知方法之间的对应规则。

嗯,什么?目标对象和通知方法之间有对应规则吗?不是直接传入的吗?

是啊,正因为以前使用动态代理都是直接传入,所以久而久之很多人觉得它们之间没有对应关系。

我问你,一个系统中所有的对象都需要代理吗?显然不是吧。那么为什么有些对象有代理功能,有些对象没有呢?

因为你编码时手动把需要代理的对象传入getProxy()然后得到了代理对象。此时对应的规则在你心中,你在编写代码时根据自己的意愿指定哪些对象需要代理。

但如果你希望Spring帮你完成自动代理,就必须把心中的规则具象化为一段代码,一段Spring能读懂的代码,这样它才可能按你的规则完成代理。

这套给Spring看的规则就是切点表达式。

40a637a06a73d020e8ecf2418bf557b8.png

049ddcbeb7a8212a13b43a4d98b5b10f.png

来看看Spring AOP有没有解决硬编码问题。

如果要想取消代理,纯动态代理方式需要跑到Controller中删除getProxy()方法调用:

8aa51a1300cd4fb240731c121a86fd08.png

而Spring AOP是通过切点表达式控制,只需要注释切点表达式就阻止了这一切,就像天上有无数风筝,但线被我攥在手里:

c64e2740ff4c56ae2d1b29e7bdef50c4.png

总而言之,切面类是为了统一管理通知方法,切点表达式定制哪些对象需要代理,以及如何代理(用哪个通知方法增强)。

具体的切点表达式规则以及AOP的实际案例,下一篇再写。我觉得概念最重要,AOP写起来倒是很简单。

2020-04-07 23:12:22

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值