1、AOP编程的概念
概念:AOP面向切面编程,等同于spring代理开发。面向切面编程是以切面为基本单位的开发,通过切面相互调用。切面 = 切入点 + 额外功能
本质就是Spring动态代理,通过代理类为原始类增强方法
好处:方便对原始类进行扩展
2、AOP编程开发步骤
实际上就是动态代理的过程:
- 原始对象
- 额外方法
- 切入点
- 组装切面
3、切面的解释
切面 = 额外功能 + 切入点
4、AOP底层原理
4.1、核心问题
- AOP是如何创建代理对象?
动态字节码技术
- Spring工厂如何加工创建代理对象?
通过原始对象的id,获取的是代理对象
4.2、动态代理类的创建
作为spring,实现动态代理有两种方式
- 第一种是通过原生的jdk的方式来帮助我们实现动态代理;
- 第二种是第三方的框架cglib来实现的
4.3、JDK动态代理
我们使用JDK为我们提供的代理方式来创建代理对象,使用Proxy.newProxyInstance()方法,我们来分析下这个方法的三个参数的意义:
- ClassLoader:这是一个类加载器,我们直接采用userService的即可。java中的每一个类都需要通过类加载器来加载class字节码文件,来生成对象
- interfaces:接口,我们需要传递一个原始方法实现的接口
- InvocationHandler:额外的功能,我们的原始方法需要增加哪些额外功能,这个接口的实现类是由我们程序员自己来编写的
实现InvocationHandler接口后,我们需要实现invoke方法,
我们再来看下这个InvocationHandler接口中的三个参数的意义:
- proxy:代理类
- method:原始方法
- args:调用方法的参数
现在我们已经有了需要调用的方法名,方法参数,现在只需要在具有原始类,我么就可以创建对象进行增强了。刚好我们在上面创建了一个原始类对象,这样就可以使用我们的JDK代理来帮助我们完成任务了
public void testJDKProxy(){ //原始类 final UserService userService = new UserServiceImpl(); InvocationHandler invocationHandler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("--------JDKProxy---------"); Object ref = method.invoke(userService, args); return ref; } }; UserService proxyUserService = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), invocationHandler); boolean flag = proxyUserService.login("zhangsan", "1223", "18"); }
运行结果:
4.4、CGLIB代理
前面我们演示了JDK代理,那么我们的cglib和jdk代理的区别是啥?
jdk代理还需要一个接口
jdk还需要一个实现了接口的原始类
在通过Proxy.newProxyInstance()创建出代理对象
而在现实开发中,很多时候,我们的原始类并没有实现任何接口,那么我们应该如何操作呢?
于是cglib就出现了,他是通过继承来实现的,继承父类的方法,来迷惑调用者,来达到增强方法的目的。
cglib动态代理原理:父子的继承创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证方法的一致,同时代理类还可以提供新的实现(额外功能 + 原始方法)
public class UserServiceNoInterface { public boolean login(String loginName, String password, String age){ System.out.println("UserServiceNoInterface.login"); return true; } }
@Test public void testCGLIBProxy(){ final UserServiceNoInterface serviceNoInterface = new UserServiceNoInterface(); Enhancer enhancer = new Enhancer(); //设置加载类 enhancer.setClassLoader(UserServiceNoInterface.class.getClassLoader()); //设置父类 enhancer.setSuperclass(UserServiceNoInterface.class); //设置增强方法 MethodInterceptor methodInterceptor = new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("-----------CGLIBProxy--------------"); Object invoke = method.invoke(serviceNoInterface, objects); return invoke; } }; enhancer.setCallback(methodInterceptor); //创建代理对象 UserServiceNoInterface userServiceNoInterface = (UserServiceNoInterface)enhancer.create(); userServiceNoInterface.login("zhangsan", "123", "16"); }
测试效果
JDK代理:基于接口实现
CGLIB代理:基于继承实现
5、回顾BeanPostProcessor
在Spring中,我们是通过bean标签中的id来创建对象。那么Spring提供的BeanPostProcessor的方式,来对创建的对象进行增强加工。然后将增强后的对象返回。我们就来通过代理来看看BeanPostProcessor是如何来实现这个事情的
创建接口
public interface UserService { public void register(User user); public boolean login(String loginName, String password); public boolean login(String loginName, String password, String age); }
原始类
public class UserBeanPostProcessor implements UserService { @Override public void register(User user) { } @Override public boolean login(String loginName, String password) { return false; } @Override public boolean login(String loginName, String password, String age) { System.out.println("--------UserBeanPostProcessor.login--------"); return false; } }
定义一个BeanPostProcessor。我们主要是模拟spring给我们返回增强对象的过程。
public class ProxyBeanPostProcessor implements BeanPostProcessor { public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } public Object postProcessAfterInitialization(final 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.postProcessAfterInitialization---------"); Object invoke = method.invoke(bean, args); return invoke; } }; UserService userService = (UserService) Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), invocationHandler); return userService; } }
配置文件
<bean id="beanPostProcessor" class="com.wx.service.impl.UserBeanPostProcessor"></bean> <bean id="proxyBeanPostProcessor" class="com.wx.proxy.ProxyBeanPostProcessor"></bean>
测试结果
6、AOP编程(注解版)
我们AOP的开发,有以下几个步骤:
- 创建原始类
- 额外功能
- 切入点
- 组装切面
接下来我们按照这个步骤来进行操作
创建原始类:
public interface UserService { public void register(User user); public boolean login(String loginName, String password); public boolean login(String loginName, String password, String age); }
public class UserAnnotationAop implements UserService { @Override public void register(User user) { System.out.println("UserAnnotationAop.register"); } @Override public boolean login(String loginName, String password) { System.out.println("UserAnnotationAop.login"); return false; } @Override public boolean login(String loginName, String password, String age) { System.out.println("UserAnnotationAop.loginNew"); return false; } }
我们的2,3,4步其实就是在构造切面类
我们的切面,其实就是有额外功能加上切入点组装成的切面: 额外功能 + 切入点 = 切面
Spring为我们提供了一个注解,@Aspect这个注解就会告诉Spring这是一个切面类
@Aspect public class MyAspect { }
那么我们现在有了切面,还需要额外功能。我们需要在额外方法上加上@Around注解,方法名可以随便取,但是返回值必须是Object。
以前的额外方法,需要实现MethodInterceptor接口,实现public Object invoke(MethodInvocation methodInvocation)方法,现在我们的参数是ProceedingJoinPoint。额外功能有了,只差我们的切入点了,切入点就是@Around("execution(* login(..))"),这样我们的就完成了AOP编程的基本步骤。
@Aspect public class MyAspect { @Around("execution(* login(..))") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("MyAspect.around log"); Object proceed = proceedingJoinPoint.proceed(); return proceed; } }
添加配置文件,让Spring帮助我们管理这几个bean对象
<bean id="annotationAop" class="com.wx.service.impl.UserAnnotationAop"></bean> <bean id="myAspect" class="com.wx.proxy.MyAspect"></bean> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
切入点复用
如果我们的切面类中,有多个额外方法但是使用同一个切入点,那么我们的代码就是这样的,看起来重复,有没有办法优化呢?
@Aspect public class MyAspect { @Around("execution(* login(..))") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("MyAspect.around log"); Object proceed = proceedingJoinPoint.proceed(); return proceed; } @Around("execution(* login(..))") public Object around2(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("MyAspect.around log"); Object proceed = proceedingJoinPoint.proceed(); return proceed; } }
我们可以使用这个@PointCut注解来进行优化
@Aspect public class MyAspect { @Around("myPointCut()") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("MyAspect.around log"); Object proceed = proceedingJoinPoint.proceed(); return proceed; } @Around("myPointCut()") public Object around2(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("MyAspect.around log"); Object proceed = proceedingJoinPoint.proceed(); return proceed; } @Pointcut("execution(* login(..))") public void myPointCut(){ } }
测试结果
7、动态代理的两种方式使用
我们上文中提到过,Spring动态代理有jdk代理和cglib代理,我们来看看在程序中如何配置,让spring来帮助我们进行切换。spring默认使用jdk动态代理。
false代表是jdk,true代表是cglib
配置文件中修改参数
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
不适用注解开发,我们是这么修改的
<aop:config proxy-target-class="true"> ... </aop:config>
8、AOP中的小细节
随着我们的业务越来越复杂,我们经常会出现service方法中调用其他的service方法的时候,这样会不会出现问题呢?
修改切入点
@Pointcut("execution(* *..UserAnnotationAop.*(..))") public void myPointCut(){ }
修改service代码
public class UserAnnotationAop implements UserService { @Override public void register(User user) { System.out.println("UserAnnotationAop.register"); } @Override public boolean login(String loginName, String password) { System.out.println("UserAnnotationAop.login"); this.register(new User()); return false; } @Override public boolean login(String loginName, String password, String age) { System.out.println("UserAnnotationAop.loginNew"); return false; } }
我们在login方法中调用register方法,那么我们额外功能能否作用于register方法呢?我们来测试看看
出现这个问题的原因是,这里的this并不是指向我们的代理对象,所以没有办法触发我们的额外功能。Spring为我们提供了一个ApplicationContextAware接口,来解决这种问题
public class UserAnnotationAop implements UserService, ApplicationContextAware { private ApplicationContext applicationContext; @Override public void register(User user) { System.out.println("UserAnnotationAop.register"); } @Override public boolean login(String loginName, String password) { System.out.println("UserAnnotationAop.login"); // this.register(new User()); UserService annotationAop = (UserService) applicationContext.getBean("annotationAop"); annotationAop.register(new User()); return false; } @Override public boolean login(String loginName, String password, String age) { System.out.println("UserAnnotationAop.loginNew"); return false; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
测试
9、AOP总结
AOP编程就是4步:
- 原始对象
- 额外功能
- 切入点
- 组装切面
具体我们可以看下注解版和非注解版的对比