前言:去年到现在一直没有很好的时间完成这个spring基础+源码的博客目标,去年一年比较懒吧,所以今年我希望我的知识可以分享给正在奋斗中的互联网开发人员,以及未来想往架构师上走的道友们我们一起进步,从一个互联网职场小白到一个沪漂湿人,一路让我知道分享是一件多么重要的事情,总之不对的地方,多多指出,我们一起徜徉代码的海洋!
我这里做每个章节去说的前提,不是一定很标准的套用一些官方名词,目的是为了让大家可以理解更加清楚,如果形容的不恰当,可以留言出来,万分感激
1、Aop编程概念
概念:AOP (Aspect Oriented Programing) ⾯向切⾯编程,等同于Spring动态代理开发,⾯向切⾯编程是以切⾯为基本单位的程序开发,通过切⾯间的彼此协同,相互调⽤,完成程序的构建切⾯ = 切⼊点 + 额外功能
本质就是Spring得动态代理开发,通过代理类为原始类增加额外功能。
好处:利于原始类的维护
类似有其他的:
OOP (Object Oritened Programing) ⾯向对象编程 ,如Java,以对象为基本单位的程序开发,通过对象间的彼此协同,相互调⽤,完成程序的构建
POP (Producer Oriented Programing) ⾯向过程(⽅法、函数)编程 比如C语言,以过程为基本单位的程序开发,通过过程间的彼此协同,相互调⽤,完成程序的构建
上节末尾讲到Spring组装,实际上组装成了切面!!!
注意:AOP编程不可能取代OOP,OOP编程有意的补充,只是补充,不是取代!!
2、Aop编程开发步骤
实际上aop编程就是动态代理开发的过程!
- 原始对象
- 额外功能 (MethodInterceptor)
- 切⼊点
- 组装切⾯ (额外功能+切⼊点)
3、切面的名词解释
为什么Spring把这个(额外功能+切⼊点) 叫做切面,不叫方便面呢?
要想理解这个命名方式,就要站在几何学的角度来说明下
我们知道面是由线构成的,而线是由点构成的,对于这些点都是具有相同性质的,比如桌子,由一个一个点构成,一个一个点都是木头做的
所以,面 = 点 + 相同的性质
所以你类比下切面 = 切点 + 额外功能,这个额外功能,是不是都具备相同的性质,是不是都是日志增强的相同方法,相同性质的点,一个一个在一起就构成了切面了!
这三个Service,理解为一个一个业务,而每个业务的连接点,是对应的方法,如果这些方法都做了事务增强,那么这些方法都具备相同的事务功能的性质,联系在一起,就成了一个切面!!
体会不到也没关系,当我没说,哈哈!重点关注怎么实现aop和底层原理。
4、AOP的底层实现原理
4.1、核心问题
1. AOP如何创建动态代理类?(动态字节码技术)
2. Spring工厂如何加⼯创建代理对象?
通过原始对象的id值,获得的是代理对象
4.2、动态代理类的创建
作为Spring来说,创建动态代理有两种方式,一种是通过原生的jdk的方式来帮我们动态创建代理,一种是嫁接了第三方的框架cglib来帮我动态创建代理,我们一个一个来关注
1、JDK动态代理
既然是代理,那么他的关键作用是为原始对象增加额外功能,所以第一步我们肯定要先创建出原始对象,其次我们才能创建动态代理类,因为我们摒弃了Spring框架而使用jdk的方式,所以我们通过new的方式来创建原始对象!!!
我们先创建个原始对象,然后需要用jdk的方法Proxy.newProxyInstance(,,,)来生成代理对象,所以newProxyInstance的返回值,就是给我们创建好的动态代理对象,我们用userServiceProxy接收
来看下里面的几个参数
- ClassLoader
- interfaces
- InvocationHandler
这三个参数的相关含义,我们来回忆下我们代理创建的三要素,和开发4个步骤不要搞混了(原始对象、额外功能、切点、组装切点+切面)
1、原始对象
2、额外功能
3、代理对象要和原始对象实现相同的接口(迷惑别人)
我们刚刚已经有了原始对象UserServiceImpl,那么剩下2,3两个要素,是不是要交给Proxy.newProxyInstance()的三个参数去使用,从而创建代理对象?
而InvocationHandler,就对应着我们的额外功能,讲Spring的动态代理如何写额外功能的时候,我们是不是实现了一个MethodInterceptor这个接口,并实现了他的invoke(MethodInvocation methodInvocation)方法?同理这个jdk的InvocationHandler也是一样,是个接口的方式。
对于InvocationHandler要实现的invoke方法,我们看下几个参数
第一个proxy,第二个method吗,第三个args,后面来说参数含义,这里先混眼熟
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
对比下我们之前的MethodInterceptor的invoke方法,实际上参数的MethodInvocation,是对上面几个参数的高度封装!
public interface MethodInterceptor extends Interceptor { Object invoke(MethodInvocation invocation) throws Throwable; }
而我们对于额外功能的书写,都是在方法执行之前,是不是都要调用invocation.proceed(); 然后获得原始方法的返回值Object ret = invocation.proceed();我就可以把额外功能书写在这个proceed之前,之后,等等,这个要是不清楚,看下前面的一个章节!
那么我们剩下的只需要解决一个问题,就是对于jdk代理的第三个参数InvocationHandler,是怎么让原始方法运行呢?
参数细化下
proxy:代表的是代理对象,可以忽略,我们用newProxyInstance创建的代理对象,会同时传给这个参数proxy,这个目前没有作用了,不需要关注
method:代表的额外功能增加的原始方法,比如login,register
Object args:代表的是原始方法的参数,如果额外功能加给的是login,就代表的是login(String username , String password)这两个参数,和最开始那个MethodBeforeAdvise的参数一样一样的
我已经找到原始方法,参数也找到了,能不能让这个原始方法运行呢?我们说不可以,因为什么,从反射角度来说,我们要让userService.login(String username, String password)运行,一定要三个
一个是方法执行的对象userService,其次login,然后参数,这样就可以细化到哪个具体的方法执行!!但是目前我们只有两个,一个是原始方法,一个是参数,怎么办呢?看到我们第一步创建
的UserService对象没?所以是不是恍然大明白的感觉没有??哈哈哈
所以我们要这么写
public Object invoke(Object proxy, Method method, Object[] args){ method.invoke(userService, args); }
有人该说了,好麻烦啊,还不如Spring的MethodInterceptor直接proceed多香,那肯定是,这Spring给我们做了高度封装,而这个是原始的jdk代理流程,对着图再回忆下
那么还有一个参数,interfaces,刚刚三要素最后一个是 代理对象和原始对象实现相同的接口,为了去对外迷惑,所以这个interfaces指原始对象实现的接口,通过这个参数interfaces,告诉Proxy.newProxyInstance你需要实现什么样的接口,这个你必须要告诉代理创建何种接口类型的代理对吧,不然这么多接口,我怎么知道你要我创建哪个接口的代理类呢?
因为第一步原始对象UserService userService = new UserServiceImpl();我们就可以通过userService.getClass().getInterfaces();来得到这个原始对象所实现的接口!作为interfaces参数,传进去,
所以这样,我在创建代理的时候,获得了原始对象提供的接口interfaces,根据这个接口实现同名的方法,加入额外功能InvocationHandler,从而完美的创建代理对象!
等等,我们是不是还有一个ClassLoader对象没有分析?差点忘记了,来分析下类加载器的作用
1、通过类加载器,把对应类的字节码加载到JVM!
2、通过类加载器创建类的class对象,进而创建这个类的对象!(这个不懂就要去看看JVM了)
比如要创建一个User对象user,首先要类加载器创建User的Class对象,进而通过new User()创建user对象
而类加载器,就是帮我们把Class文件,加载到JVM的一个过程,而类加载得到了Class文件后,帮我们生成Class对象,进而创建类对象。
那如何获得类加载器呢,虚拟机会为每一个Class文件,会自动分配,不需要程序员操心,所以你只要拿到类的Class,就可以拿到类加载器
动态代理类为啥需要类加载器呢,实际上,动态代理是不存在源文件Class的,前面我们用Spring动态代理的时候,没有看到过动态代理类的源文件,但是我们也说过动态代理类实际上是根据动态字
节码技术创建字节码的,直接把字节码写入虚拟机的过程,没有一个加载字节码的过程,甚至Class文件都不会给你生成,实际上Proxy.newProxyInstance()就是用动态字节码的技术来创建代理对象
而newProxyInstance(interfaces,InvocationHandler)就可以帮我们生成了对应的动态代理字节码了,然后把这个字节码直接写入虚拟机,没有加载的过程,也没有生成Class文件的过程,因为你连Class文件都没有,怎么会给你分配虚拟机呢。
于是有了这个动态代理字节码,就可以创建代理类对象了吗?肯定不行啊,虚拟机都没有的话,怎么帮你创建对象呢,所以你要借一个虚拟机过来,借谁的都可以,不用还哈哈,来帮你生成代理对象,比如叫做userServiceProxy!!这就是为什么入参有个ClassLoader,很好解释
参数我们说完了,开始编码
public class JDKProxy { public static void main(String[] args) { //1. 创建原始对象 final UserService userService = new UserServiceImpl(); //2. jdk创建动态代理 InvocationHandler invocationHandler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("---------------JDKProxy.invoke log--------------"); //原始方法运行 Object ret = method.invoke(userService, args); return ret; } }; UserService userServiceProxy = (UserService) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), userService.getClass().getInterfaces(), invocationHandler); userServiceProxy.login("chenxin", "123455"); userServiceProxy.register(new User()); } }
然后原始对象
public class UserServiceImpl implements UserService { @Override public boolean login(String username, String password) { System.out.println("UserServiceImpl.login"); return true; } @Override public void register(User user) { System.out.println("UserServiceImpl.register"); } }
运行结果
---------------JDKProxy.invoke log-------------- UserServiceImpl.login ---------------JDKProxy.invoke log-------------- UserServiceImpl.register
我们就在需要运行的方法前进行了代理的增强----额外功能
注意几点,我们在使用内部类的过程中,如果1.8以前访问外部类的局部变量,需要把局部变量定义成final类型,而1.8及之后就不需要定义final。
//1. 创建原始对象 final UserService userService = new UserServiceImpl();
那么jdk代理我们就告一段落!!!
2、Cglib的动态代理
前面我们说了JDK动态代理,那么Cglib和JDK共同特点是,都创建了动态代理的对象实现了额外功能,那么二者的区别是什么呢?
JDK动态代理首先是要有个接口——UserService
其次要有个原始对象实现了接口的方法——UserServiceImpl
再有个代理对象——userServiceProxy
这样保证我们代理类和原始类方法一致,迷惑调用者,再深入点来说为什么代理要这么做,我要做额外功能,是不希望破坏原有功能的,只希望在原始的功能上增加新的功能,做成可插拔式,所以这也是一种编程思想!!
JDK通过Proxy.newProxyInstance来创建动态代理,但是朋友们想想,在现实开发中,极有可能有个原始类,他没有实现任何的接口。
此时我想为这个没有实现任何接口的原始类进行额外功能的创建,进而来创建代理对象呢?
jdk是需要有原始接口的,而不存在原始接口,我们该怎么创建动态代理呢?
甭管有没有实现接口,我一定还是为了要做成,增加额外功能的形式,不破坏原有功能的基础上,是不是还是得做一个类,要和原有方法保持一模一样,这样才能出去迷惑对方!!
也就是即便只有一个UserService,他不是一个接口,而是一个业务类,里面有两个方法login和register,我要做额外功能增加,在没接口情况下,我怎么保证另外做一个类,和原始对象里面的方法保
持一模一样呢?想到了什么,对了, 继承!!!!
于是Cglib就是这么玩的,代理类实际上通过继承的方式,来动态创建代理类,进而创建对象。那么原始的方法,是不是都继承下来了!所以一句话,Cglib基于继承关系创建的动态代理!
CGlib创建动态代理的原理:⽗⼦继承关系创建代理对象,原始类作为⽗类,代理类作为⼦类, 这样既可以保证二者⽅法⼀致,同时在代理类中提供新的实现(额外功能+原始⽅法)
既然知道了父子继承关系,那就不存在接口了,我们直接定义一个UserService的类
public class UserService { public void login(String username, String password){ System.out.println("UserService.login"); } public void register(User user){ System.out.println("UserService.register"); } }
这个UserService不是接口,而是一个普通的类
不管用什么方式来创建代理,第一步都要创建原始对象
public class CglibTest { public static void main(String[] args) { UserService userService = new UserService(); } }
Cglib给我们提供了一些api,Enhance这个类,叫做增强,最终调用create方法创建代理!
对比下Proxy.newProxyInstance(ClassLoader, interfaces, invocationhandler),需要有类加载器,代理类对应的接口,还有额外功能的书写位置invocationHandler。
那么Cglib同样是创建动态代理,应该和jdk的物质准备,是高度一致的,只不过第二样,不再需要接口了,而是你代理类对应的父类
所以Enhance要setClassLoader,要setSupperClass设置父类,更要setCallback设置额外功能,参数是个MethodInterceptor接口类型,但是这个接口是Cglib包里的,之前我们说的是aopaliance包下的
来编码
所以里面的MethodInteceptor和我们之前讲的invocationHandler高度一致!也是要让原始方法先运行,然后在运行前增加额外功能!
public class CglibTest { public static void main(String[] args) { UserService userService = new UserService(); Enhancer enhancer = new Enhancer(); enhancer.setClassLoader(CglibTest.class.getClassLoader()); enhancer.setSuperclass(userService.getClass()); MethodInterceptor methodInterceptor = new MethodInterceptor() { //和invocationHandler高度一致,method代表原始方法,objects代表参数, @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("----------------CglibTest.intercept log-----------"); Object ret = method.invoke(userService, objects); return ret; } }; enhancer.setCallback(methodInterceptor); UserService userServiceProxy = (UserService) enhancer.create(); userServiceProxy.login("chenxin", "dddd"); userServiceProxy.register(new User()); } }
结果:
----------------CglibTest.intercept log----------- UserService.login ----------------CglibTest.intercept log----------- UserService.register
总结下创建代理的方式:
Jdk代理:通过接口创建代理
Cglib代理:通过继承父类实现的代理
5、再看BeanPostProcesser
我们回顾下,对于Spring在创建对象的时候,会通过bean标签的id,创建这个对象,那么Spring工厂可以提供BeanPostProcesser的方式,来对创建的对象进行加工,当时加工也很简单,只对user对象的username属性进行了改变,重新赋值的过程,然后把我
们加工好的对象返回给调用者,那么调用者就会享受到Spring工厂为我们加工的user对象!!实际上动态代理也是通过BeanPostProcesser进行加工的,结合动态代理,看看这个过程变成什么样!
再看一张图,这是加入了动态代理,对于BeanPostProcesser的过程,before环节我们不需要加工,因为对于开发来说我们很少对这些对象进行两次加工,所以我们都会在after的时候做加工,刚刚好这个Object bean是不是就是拿到了需要加工的对象userService,所以我们既然有了原始对象,那么是不是就可以调用Proxy生成jdk动态代理的过程?最终是不是要创建一个代理对象?通过Proxy.newProxyInstance()是不是就可以把我们的原始对象,加工成代理对象?这里就跟着上面,不多说了!
来编码
1、实现BeanPostProcesser,进行加工
2、配置文件中对BeanPostProcesser进行配置
接口:
public interface UserService { void register(User user); boolean login(String name, String password); }
实现类:
public class UserServiceImpl implements UserService { @Override public void register(User user) { System.out.println("UserServiceImpl.register 业务运算+dao"); } @Override public boolean login(String username, String password) { System.out.println("UserServiceImpl.login"); return true; } }
定义一个ProxyBeanPostProcessor,这下面的代码只要看了上面,应该不用再重复了吧,记不得看看上面说的!!
public class ProxyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { InvocationHandler invocationHandler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("--------------ProxyBeanPostProcessor.invoke log--------------"); Object ret = method.invoke(bean, args); return ret; } }; UserService userServiceProxy = (UserService) Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), invocationHandler); return userServiceProxy; } }
配置文件
<bean id="userService" class="com.chenxin.spring5.aop.UserServiceImpl"> </bean> <bean id="proxyBeanPostProcessor" class="com.chenxin.spring5.postprocessor.ProxyBeanPostProcessor"> </bean>
做个Test
@Test public void test8(){ ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) ctx.getBean("userService"); boolean result = userService.login("chenxin", "233"); userService.register(new User()); }
结果可以知道:
--------------ProxyBeanPostProcessor.invoke log-------------- UserServiceImpl.login --------------ProxyBeanPostProcessor.invoke log-------------- UserServiceImpl.register 业务运算+dao
有发现一个问题吗,我目前切入点没有做,也就意味着所有方法都被增强了!!!注意,我们是模拟Spring创建aop的时候BeanPostProcesser加工过程,核心问题我们要了解到!
6、基于注解的AOP编程
不管是不是基于注解,依旧完成的是aop开发,有以下几个开发步骤
1. 原始对象
2. 额外功能
3. 切⼊点
4. 组装切⾯
第一步我们来下
做一个原始对象
public interface UserService { void register(User user); boolean login(String name, String password); }
public class UserServiceImpl implements UserService { @Override public void register(User user) { System.out.println("UserServiceImpl.register 业务运算+dao"); } @Override public boolean login(String username, String password) { System.out.println("UserServiceImpl.login"); return true; } }
然后2.3.4步实际上是创建切面!!!
先有额外功能,再有切点,形成切面
我们知道一切皆对象,对于切面,我们是不是可以想象成一个对象?Spring给我们提供了一个注解@Aspect,声明在你创建的类上,这个类就顺理成章成为切面类了
package com.chenxin.spring5.aspect; import org.aspectj.lang.annotation.Aspect; @Aspect public class MyAspect { }
还少点什么,额外功能+切点
记得我们之前说怎么实现额外功能的?是不是要实现MethodInterceptor接口,复写里面的public Object invoke(MethodInvocation methodInvocation)方法,然后调用methodInvocation.proceed()让原始方法运行。后续只要在proceed之前,之后,加入我们额
外的功能,即可?那在这切面类中我怎么提供额外功能呢?
如果你觉得你的方法是额外方法,你只需要加个注解@Arround注解,方法名你可以随便取,但是返回值,必须是Object,方法名我定义成arround,你随便命名都可以,实际上你对比下invoke方法就知道为什么返回值是Object了,invoke方法的返回值,代表原始方法的返回值,今天的arround方法返回值,也代表着原始方法的返回值。
而原来的MethodInvitation参数,那么我arround是不是也应该有个可以让原始方法运行的参数?当然,叫做ProceedingJointPoint,翻译成执行切点,切点是不是我们说的作用在哪个具体的业务方法?
额外功能有了,是不是剩下切入点了
在@Arround中添加即可,@Arround("execution (* login(..))")等同我们配置文件的做法,只是用了新的编程方式取代老的方式!、
@Aspect public class MyAspect { /** * 额外功能完成 * @param proceedingJoinPoint * @return * @throws Throwable */ @Around("execution(* login(..))") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("--------------MyAspect.around log-------------"); Object ret = proceedingJoinPoint.proceed(); return ret; } }
xml,重点,你要告知Spring基于aop注解的方式编程了,加这个标签
<aop:aspectj-autoproxy />
<bean id="userService" class="com.chenxin.spring5.aspect.UserServiceImpl"> </bean> <bean id="arround" class="com.chenxin.spring5.aspect.MyAspect"></bean> <aop:aspectj-autoproxy />
测试下
@Test public void test8(){ ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); com.chenxin.spring5.aspect.UserService userService = (com.chenxin.spring5.aspect.UserService) ctx.getBean("userService"); boolean result = userService.login("chenxin", "233"); userService.register(new User()); }
--------------MyAspect.around log------------- UserServiceImpl.login UserServiceImpl.register 业务运算+dao
注意细节:
1、切入点复用
你想想,日后我想为login增加其他的额外功能,比如事务tx
@Aspect public class MyAspect { /** * 额外功能完成 * @param proceedingJoinPoint * @return * @throws Throwable */ @Around("execution(* login(..))") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("--------------MyAspect.around log-------------"); Object ret = proceedingJoinPoint.proceed(); return ret; } @Around("execution(* login(..))") public Object aroundTx(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("--------------MyAspect.around Tx-------------"); Object ret = proceedingJoinPoint.proceed(); return ret; } }
结果:
--------------MyAspect.around log------------- --------------MyAspect.around Tx------------- UserServiceImpl.login UserServiceImpl.register 业务运算+dao
发现没切入点表达式,出现了冗余,其次,如果我不想在login上加这些额外功能了,想在register上加,那么@Around("execution(* login(..))")要改成register....
后续维护,不好修改
所以我们要切入点复用:切入点的配置提取到独立的函数去
这个函数有个要求,必须是public void,而且没有方法内部逻辑,方法名你随便
@Pointcut("execution(* login(..))") public void myPointCut(){ }
其他的地方这么改下就可以了
@Aspect public class MyAspect { @Pointcut("execution(* login(..))") public void myPointCut(){ } /** * 额外功能完成 * @param proceedingJoinPoint * @return * @throws Throwable */ @Around(value = "myPointCut()") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("--------------MyAspect.around log-------------"); Object ret = proceedingJoinPoint.proceed(); return ret; } @Around(value = "myPointCut()") public Object aroundTx(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("--------------MyAspect.around Tx-------------"); Object ret = proceedingJoinPoint.proceed(); return ret; } }
这样是不是灵活很多,你需要多少个切点,你自己定义函数好了,然后作用在不同的额外方法上面,整体形成一个切面!!后面只需要改一个切入点就可以了
2、动态代理的创建⽅式
AOP底层实现 2种代理创建⽅式
- JDK 通过实现接⼝ 做新的实现类⽅式 创建代理对象
- Cglib通过继承⽗类 做新的⼦类 创建代理对象
默认情况 AOP编程 底层应⽤JDK动态代理创建⽅式 如果切换Cglib有两种方式
1. 对于基于注解AOP开发
<aop:aspectj-autoproxy proxy-target-class="true" /> false表示采用jdk的方式,true表示用cglib的方式创建动态代理
上面说的只有这个
<aop:aspectj-autoproxy />的时候,只开启了注解的aop开发,默认使用的是false,即JDK代理
2. 对于传统的AOP开发 加上proxy-target-class="true",和1、的效果一样,切换代理模式,false表示采用jdk的方式,true表示用cglib的方式创建动态代理
<aop:config proxy-target-class="true"> <aop:pointcut id="pc" expression="@annotation(com.chenxin.spring5.aop.log.Log)"/>--> <aop:advisor advice-ref="around" pointcut-ref="pc"/>--> </aop:config>
测试下吧
true的情况
false的情况
7、AOP开发中遇到的坑
上面我们实现了login方法的切面,我们修改成UserServiceImpl里的所有方法都做日志和事务的额外功能!
@Pointcut("execution(* *..UserServiceImpl.*(..))") public void myPointCut(){ }
其他不变,结果很nice!
--------------MyAspect.around log------------- --------------MyAspect.around Tx------------- UserServiceImpl.login --------------MyAspect.around log------------- --------------MyAspect.around Tx------------- UserServiceImpl.register 业务运算+dao
于是我干了这么一个事情,我要在register里调用login方法
public class UserServiceImpl implements UserService { @Override public void register(User user) { System.out.println("UserServiceImpl.register 业务运算+dao"); this.login("chenxin","211111"); } @Override public boolean login(String username, String password) { System.out.println("UserServiceImpl.login"); return true; } }
这么做有什么意义呢,不然,其实在实战过程中,在极其复杂的业务下,极有可能同一个Service类中,不同的业务方法相互调用的场景!!!!
如果后续我只调用register,不调用login,而login是在register内部调用的,结果会是都加上额外功能吗?
@Test public void test8(){ ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); com.chenxin.spring5.aspect.UserService userService = (com.chenxin.spring5.aspect.UserService) ctx.getBean("userService"); userService.register(new User()); }
结果出乎意料,只有register加上了额外功能
--------------MyAspect.around log------------- --------------MyAspect.around Tx------------- UserServiceImpl.register 业务运算+dao UserServiceImpl.login
为什么会这样呢?其实很简单,我们知道this指向的是当前对象,但是Spring给我们的对象是什么,是代理对象,并不是原始对象,而你用this.login()调用的是userServiceImpl这个原始对象的login方法
但是你用aop代理生成的对象,实际上是代理对象,所以这个过程不赖Spring,因为你调错了,那么我们怎么想让login也加上呢?
你只要可以获取代理对象就可以,最简单的方式,在register内加上ApplicationContext ctx = new ......,然后你getBean,再去调用login,但是你想想,你是在Service里
之前我们说过,ApplicationContext是重量级工厂,并且一个应用只能创建一个这个工厂,所以在Service中再书写一边创建工厂的代码,实在不客观
那么Spring为了解决这个问题,给我们暴露了一个接口叫做ApplicationContextAware,称为工厂意识,可以意识到工厂,识别到工厂,强词夺理好了我也解释不来
然后你要实现一个方法叫做setApplicationContext,把Spring创建好的工厂,传递给你当前类的一个工厂对象中,代码我实现下
public class UserServiceImpl implements UserService, ApplicationContextAware { private ApplicationContext applicationContext; @Override public void register(User user) { System.out.println("UserServiceImpl.register 业务运算+dao"); UserService userService = (UserService) applicationContext.getBean("userService"); userService.login("chenxin","211111"); } @Override public boolean login(String username, String password) { System.out.println("UserServiceImpl.login"); return true; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
这样是不是就不需要创建工厂,就可以实现在register中调用login,也会使得额外功能起作用了?
8、AOP阶段总结
贴张图,好好看,我不废话了!!
好了,本节就到这个,至此,Spring的基础部分我差不多讲了,事务的东西,以及一些关于注解开发,我们后面中级课程见,下一节,和持久层整合!!敬请期待