首先AOP是一种叫面向切面编程的思想,他并不是只用在Spring中,在其他很多AOP的框架中都有用到,如AspectJ,AspectWerkz。众所周知在Java中是面向对象编程,而AOP的面向切面编程更像是对面向对象编程的一种补充。他是通过一个叫切面的东西来统一处理很多相似的功能,例如我们在银行中的查询余额、取钱、存钱都会存在一个验证用户的操作,而这个操作我们就可以抽取出来实现代码复用。当我们要存钱或取钱的时候我们会将验证的功能横向的切入进来,也称横向开发,这样的好处很明显,不会改动原有的代码,大大降低了模块与模块之间的耦合度,还会增强原有代码的功能,看到这里是不是感觉和代理模式很相似了,没错AOP的底层实现就是运用的动态代理这个我们下面再说。
AOP主要是用来做一些与业务关联不大,比较通用的一些功能。下面来举个例子:给原有的方法添加日志功能,加一个输出日志的模块,然后在配置文件中去声明一个切点(触发原有方法的操作,相当于监听)和一个切面,在切点与切面绑定在切面中去触发查看日志的代码。这样做就相当于代理了原先的方法,增强了一个日志的功能。
public class ProductService {
public void doSomeService(){
System.out.println("我这里是业务类");
}
}
public class LoggerAspect {
public void log(){
System.out.println("我是切面类,我用来管理日志");
}
}
<bean name="s" class="cn.spring.ProductService">
bean>
<bean id="loggerAspect" class="cn.spring.LoggerAspect"/>
<aop:config>
<aop:pointcut id="loggerCutpoint" expression="execution(* cn.spring.ProductService.*(..)) "/>
<aop:aspect id="logAspect" ref="loggerAspect">
<aop:after pointcut-ref="loggerCutpoint" method="log"/>
aop:aspect>
aop:config>
public class TestSpring {
public static void main(String[] args ){
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"cn/spring/applicationContext.xml"});
ProductService s = (ProductService)context.getBean("s");
s.doSomeService();
}
}
像我们常用的功能如事务,缓存都是基于AOP实现的,如Spring Cache的底层实现就是动态代理,代理原先的方法,然后加上了一个缓存的功能。这些抽取出来的功能模块统称为切面。
AOP的底层是代理模式,用一句话说代理模式我觉得就是“代理原来的对象,增强原来对象的属性”。 分为静态代理和动态代理,静态代理只能代理一个类或对象,而动态代理可以代理多个类或对象。这个代理对外界是无感知的,对方并不知道或者是并不在意是不是被代理。
Spring的底层使用了两种方式实现了动态代理,一种是JDK的动态代理,还有一种是CGLib。 两者都有一定限制。
那什么时候用哪一种代理呢?
Spring默认产生代理对象的行为是:如果你的Bean有对应的接口,是使用的基于JDK的动态代理,否则是使用CGLIB。但这样说其实不准确,Spring用了下面这个配置来控制它,如果这个配置是false,才是上面我们说的这个逻辑。而如果这个配置是true,则所有的要使用AOP的Bean都使用CGLIB代理,不管它是不是有接口。而我们使用最新版的SpringBoot的话,这个值默认就是true。
spring.aop.proxy-target-class=true
也就是说我们现在用SpringBoot的话AOP的底层实现就是运用的CGLib。
JDK自带的动态代理和CGLib有什么区别呢?
二者的实现原理不同,JDK自带的是实现了一个接口基于反射来的,而CGLIB是基于修改字节码生成子类来实现的,底层是ASM的开源库。
哪些方法可以被代理?
如果是使用JDK动态代理,那只有public方法可以被代理。而如果使用CGLIB,除了private方法,都可以被代理。(当然,final方法除外)。
在这里要注意一个问题就是只有一个Bean调用另一个Bean的方法,才会走代理。
上面两个特性也就解释了为什么有时候你的@Transactional不生效的原因:
● 在私有方法上不生效
● 在final方法上不生效
● 同一个类里面方法互相调用不生效
也就解释了在一个类中调用同类的方法为什么Spring Cache的缓存会无效的问题。
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
//处理代理实例上的方法调用并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理的本质就是反射机制
System.out.println("执行了"+method.getName()+"方法");
seeHouse();
Object result = method.invoke(target, args);
return result;
}
public void seeHouse(){
System.out.println("中介看房子");
}
}
大家可能会遇到过或者听说过Spring的循环依赖的问题。Spring使用了“三级缓存”来解决Bean的循环依赖,但可能很多人不知道为什么要使用三级缓存,其实这个也跟AOP有关。
如果没有AOP,其实Spring使用二级缓存就可以解决循环依赖的问题。若使用二级缓存,在AOP情形下,注入到其他Bean的,不是最终的代理对象,而是原始目标对象。
因为Spring对Bean有一个生命周期的定义,而代理对象是在Bean初始化完成后,执行后置处理器的时候生成的。所以不能在二级缓存的时候就直接生成代理对象,放进缓存。
总结:只要学不死,就往死里学!!