Java Spring-AOP CGlib和JDK动态代理实现
编码是为了解决生活中的问题,譬如现在我想用筷子吃饭的时候看手机,那么首先会有两个对象。
现在如果是面向对象编程思想,我们会new一个筷子对象,new一个手机对象,然后先调用 chopsticks.pickUp() ,再调用 phone.user() 。
那有没有一种做法,在不改动现有两个对象类内容的情况下,我直接把手机的使用方法增强到筷子的拿起方法里,这样我直接调用这个增强后的操作就能完成吃饭的时候玩手机这个功能,这个就是AOP的思想,即面向切面编程。
AOP:Aspect Oriented Programming,面向切面编程,将创建Bean的权利给第三方,第三方创建的Bean是代理Bean的代理类,代理增强的方法和Bean本身的方法当作一个切面,是对面向对象编程OOP的升华。OOP是纵向对一个事物的抽象,一个对象包括静态的属性信息,包括动态的方法信息等。而AOP是横向的对不同事物的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面,而用这种思维去设计编程的方式叫做面向切面编程。
根据这个例子,再介绍几个AOP的术语:
属性 | 属性名 | 对应 | 说明 |
---|---|---|---|
Target | 目标对象 | Chopsticks | 被增强的方法所在的对象 |
Proxy | 代理对象 | ChopsticksProxy | 对目标对象进行增强后的对象,客户端实际调用的对象 |
Joinpoint | 连接点 | pickUp()、putDown() | 目标对象中可以被增强的方法 |
Pointcut | 切入点 | pickUp() | 目标对象中实际被增强的方法 |
Advice | 通知\增强 | use() | 增强部分的代码逻辑 |
Aspect | 切面 | pickUp()+use() | 增强和切入点的组合 |
Weaving | 织入 | 组合pickUp()+use()的动作 | 将通知和切入点组合动态组合的过程 |
此时use()方法的代码逻辑叫做通知,通知有5种类型。
类型 | 类型名 | 说明 |
---|---|---|
before | 前置通知 | 在切点方法执行前执行 |
afterReturning | 后置通知 | 在切点方法执行后执行,切点执行抛异常时不执行 |
around | 环绕通知 | 在切点方法执行前执行前环绕通知,切点方法执行后执行后环绕通知,切点执行抛异常时不执行后环绕通知 |
afterThrowing | 异常通知 | 在切点方法执行抛异常时执行 |
after | 最终通知 | 不管切点方法执行有无异常都在最后执行 |
那么实现此操作,有两种实现方式,一种是 JdkDynamicAopProxy 实现,一种是 CglibAopProxy 实现。
JdkDynamicAopProxy源码获取代理对象:
CglibAopProxy源码获取代理对象:
从源码中我们可以看出来区别:
- JDK动态代理:基于接口动态生成实现类的代理对象,目标类有接口
- Cglib动态代理:基于被代理对象动态生成子对象为代理对象,目标类无接口且不能使用final修饰
一般如果目标有接口的话,Spring默认使用JDK去动态代理实现,也可以配置指定使用Cglib实现。
下面简单的Demo介绍一下两种实现的基本原理:
JDK动态代理Demo:
//实现BeanPosrProcessor接口,实现Bean处理器的after方法将代理类封装成BeanDefinition对象注册到BeanDefinitionMap中
//实现ApplicationContextAware接口,实现aware回调方法,获取Spring容器中的增强方法类
public class AopBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
//目的:对Chopsticks中的pickUp()进行增强,增强的方法存在于Phone
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//生成当前Bean的Proxy对象
Object beanProxy = Proxy.newProxyInstance(bean.getClass().getClassLoader(),
bean.getClass().getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//Phone的获取(从Spring容器中获取)
Phone phone= applicationContext.getBean(Phone.class);
//执行增强对象的方法,此处举例前置通知类型
phone.use();
//执行目标对象的目标方法
Object result = method.invoke(bean, args);
return result;
}
});
return beanProxy;
return bean;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//获取Spring容器
this.applicationContext = applicationContext;
}
}
Cglib实现Demo:
public class CGlibTest {
public static void main(String[] args) {
//CGlib基于父类(目标类)生成Proxy
//目标对象
Chopsticks chopsticks= new Chopsticks();
//通知对象
Phone phone= new Phone();
//编写CGlib代码
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(Chopsticks .class);//生成的代理对象就是Chopsticks的子类
//设置回调
enhancer.setCallback(new MethodInterceptor() {
@Override
//intercept方法相当于JDK的Proxy的invoke方法
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//执行增强对象的方法,此处举例前置通知类型
phone.use();
Object invoke = method.invoke(target, objects);//执行目标方法
return invoke;
}
});
//生成代理对象
Chopsticks o = (Chopsticks ) enhancer.create();
//测试
o.show();
}
}