AOP思想:
AOP是对OOP的延伸,采取了横向抽取机制取代纵向继承提醒重复代码。将影响了多个类的公共行为封装到一个可重用模块,减少系统中重复代码,降低模块耦合。
AOP常见场景:
性能监控、事务管理、安全监测、缓存优化、记录日志等。
相关概念:
Aspect(切面):是切入点和通知(引入)的结合。
Joinpoint(连接点):指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
Pointcut(切入点):一个通知将被引发的一系列连接点的机会。AOP框架必须允许开发者指定切入点。Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上。
Introduction(引入):添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口。
Target(目标对象):包含连接点的对象。也被称作被通知或被代理对象。POJO。
Weaving(织入):组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
Proxy(代理):AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
Advice(通知/增强):通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)。
分析AOP:
AOP采用动态代理模式,Spring在代理类中包裹切面,运行期间把切面织入到Bean中,Spring用代理类封装了目标类,同时拦截了被通知方法的调用,处理完通知后,再把调用转发给真正的目标Bean,也正因为是动态代理,所以Spring的AOP只支持到方法连接点而无法提供字段和构造器接入点(AspectJ和JBoss可以),所以Spring无法创建细粒度的通知。
在这个动态代理的过程中,AOP设计可分为两大块:
1、为目标对象建立代理对象(如何生成代理对象)。
2、启动代理对象的拦截器来完成各种横切面的织入(如何织入横切面同时如何拦截对目标对象方法的调用)。
Spring提供两种方式生成代理对象。JDKProxy和CGLIB。两种代理方式的选择是由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认策略是如果目标类是接口,则采用JDK动态代理技术(JDK只能对接口进行代理),否则使用CGLIB来生成代理。
JDK动态代理利用反射原理,给对象动态的生产代理对象,在执行的方法前后来执行相关内容。缺点在于:1、只能代理实现了接口的目标对象。2、基于反射,效率低
CGLIB是针对类来实现代理的,原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,采用继承的方式。基于字节码实现效率高于反射,但它不能代理final方法,生成的是目标类的子类。
何为代理对象?
Spring AOP的核心技术是动态代理,对于增强的对象方法,在用户调用这个对象方法(request)的时候其实是调用Spring AOP提前为其生成好的代理对象(Proxy)的相应方法,这个代理对象的方法实现就包含了preOperation—request—postOperation,通过对对象方法的这种拦截,增强了目标对象的方法操作,这种方式就是代理。
如何生成代理对象?
具体AOP代理对象的生成,根据不同的需要分别由:AspectJProxyFactory、ProxyFactoryBean、ProxyFactory来完成。它们通过继承ProxyConfig、AdviceSupport、ProxyCreatorSupport等基类,实现其功能。
Spring的AspectJProxyFactory、ProxyFactoryBean和ProxyFactory封装了代理对象AopProxy的生成过程,代理对象的生成实现过程由JDK的Proxy和CGLIB第三方来实现。
如何拦截对目标对象方法的调用?
对于JDK的代理对象,拦截使用的是InvocationHandler的invoke回调入口,对于CGLIB的代理对象,拦截是有设置好的回调callback方法(intercept方法)来完成。
不管是什么方式生成的代理对象,对拦截器的调用都是通过proceed方法实现的。在该方法中完成对目标对象的增强功能。
Aop的实现方式:
1、基于代理的AOP
/** * 定义一个编码接口 * @author hp16 * @date 2018/12/316:25 */ public interface CondingService { void coding(); } /** * 实现编码接口 * @author hp16 * @date 2018/12/316:27 */ public class CodingServiceImpl implements CondingService { @Override public void coding() { System.out.println("快点写代码"); } } /** * 编码增强类,实现一个前置和后置的通知 * @author hp16 * @date 2018/12/316:28 */ public class CodingHelper implements MethodBeforeAdvice, AfterReturningAdvice { @Override public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable { System.out.println("编码后,要QC代码"); } @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("编码前,要打开IDEA"); } }
配置基于代理的AOP
<bean id ="codingHelper" class="com.imooc.concurrency.aop.CodingHelper"/> <bean id="codingImpl" class="com.imooc.concurrency.CodingServiceImpl"/> <!-- 定义切点 pattern正则匹配所有的coding方法--> <bean id ="codingPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut"> <property name="pattern" value=".*coding"></property> </bean> <!-- 切面 增强+切点结合 形成完整切面--> <bean id="codingHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="advice" ref="codingHelper"/> <property name="pointcut" ref="codingPointcut"/> </bean> <!-- 定义代理对象 通过ProxyFactoryBean生成最终代理对象--> <bean id="codingProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="codingImpl"/> <property name="interceptorNames" value="codingHelperAdvisor"/> </bean>
2、纯java对象切面
通过spring内部机制自动扫描,而不需要使用代理
<!-- 创建一个增强 advice --> <bean id ="codingHelper" class="com.imooc.concurrency.aop.CodingHelper"/> <!-- 目标类 --> <bean id="codingImpl" class="com.imooc.concurrency.CodingServiceImpl"/> <!-- 配置切点和通知--> <bean id ="codingAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice" ref="codingHelper"></property> <property name="pattern" value=".*coding"/> </bean> <!-- 自动代理配置 --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
3、@Aspect注解
/** * 注解方式添加增强 * @author hp16 * @date 2018/12/316:37 */ @Aspect @Component public class CodingHelperDemo { @Pointcut("execution(* *.coding(..))") public void codingPoint(){} @Before("codingPoint()") public void beforeCoding(){ System.out.println("编码前,要打开IDEA"); } @AfterReturning("codingPoint()") public void afterCoding(){ System.out.println("编码后,要QC代码"); } }
配置文件:
<!--扫描包 --> <context:component-scan base-package="com.tgb" annotation-config="true"/> <!-- ASPECTJ注解 --> <aop:aspectj-autoproxy proxy-target-class="true" /> <!-- 目标类 --> <bean id="coding" class="com.imooc.concurrency.aop.CodingServiceImpl"/>
4、注入形式的Aspect切面
/** * 注入形式的Aspcet切面 * @author hp16 * @date 2018/12/316:37 */ @Aspect @Component public class CodingHelperDemo2 { public void beforeCoding(){ System.out.println("编码前,要打开IDEA"); } public void afterCoding(){ System.out.println("编码后,要QC代码"); } }
配置文件:
<!-- 目标类 --> <bean id="coding" class="com.imooc.concurrency.aop.CodingServiceImpl"/> <bean id ="codingHelper" class="com.imooc.concurrency.aop.CodingHelperDemo2"/> <aop:config> <aop:aspect ref="codingHelper"> <aop:before method="beforeCoding" pointcut="execution(* *.coding(..))"/> <aop:after method="afterCoding" pointcut="execution(* *.coding(..))"/> </aop:aspect> </aop:config>