第四篇 小曹学spring--Spring AOP 基础

来源:互联网 发布:淘宝上哪家紫砂壶好 编辑:程序博客网 时间:2024/04/28 01:09
1、AOP概述
AOP技术的适用场合:性能监测、访问控制、事务管理、日志记录

1.1、AOP到底是什么
(1)AOP的出现是为了解决纵向继承体系所不能解决的代码重复问题;
(2)将重复代码与业务代码相分离是很容易的,但将这些横向抽取出来的独立的代码与业务代码融合完成之前的功能,才是AOP主要解决的问题。

1.2、AOP术语
(1)连接点(JoinPoint):类初始化前,类初始化后,方法调用前,方法调用后,抛出异常后等具有边界性质的特定点称为连接点。Spring仅支持在方法调用前后方法抛出异常时进行织入增强。
(2)切点(PointCut):每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点,连接点是客观存在的事物。如何定位到感兴趣的连接点呢?Spring通过切点定位特定连接点,切点通过org.spring
切点和连接点的关系,用数据库查询来比拟:
  • 连接点就是数据库中的记录;
  • 切点就是查询条件;
  • 连接点和切点不是一对一的关系,一个切点可以匹配多个连接点。
(3)增强(Advice):增强就是在连接点处增加的一段代码,除此之外,增强还包含了一个执行点的方位信息。正因为增加既包含执行逻辑,又包含方位信息,所以spring所提供的增强接口都是带方位名的,如:BeforeAdvice、AfterReturningAdvice等。
所以只有结合切点和增强一起,才能确定特定连接点,并实施增强逻辑。
(4)目标对象(Target):就是增强织入的目标类。
(5)引介(Introduction):引介是一种特殊的增强,它为类添加一些新的属性和方法。
(6)织入(Weaving):织入是将增强添加到目标类具体连接点上的过程。
根据不同的技术,AOP有3种织入方式:
  • 编译期织入,这要求使用特殊的java编译器;
  • 类加载期织入,这要求使用特殊的类加载器;
  • 动态代理织入,在运行期为目标类生成子类添加增强。
spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
(7)代理(Proxy):代理类既可能是与原类具有相同的接口,也可能是原类的子类。
(8)切面(Aspect):切面由切点和增强组成。

2、基础知识
Spring AOP使用了两种代理机制:
  • 基于JDK的动态代理;
  • 基于Cglib的动态代理。

2.1、JDK动态代理
(1)jdk动态代理主要涉及java.lang.reflect包中的两个类:Proxy和InvocationHandler。
  • InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类代码,动态将横切逻辑和业务逻辑编织在一起;
  • Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的对象。
(2)使用jdk动态代理生成的目标累的子类,目标对象必须是一个接口,生成的子类实际上是一个继承了Proxy,并实现了目标对象接口的类。

2.2、Cglib动态代理
jdk动态代理只能代理接口,而cglib可以为普通类做代理。

2.3、代理知识小结
(1)不足之处:
  • 目标类的所有方法都添加了横切逻辑,不能对目标类中的特定方法添加横切逻辑;
  • 我们通过硬编码的方式指定了横切逻辑的织入点,即在目标类方法的开始和结束前织入代码;
  • 手工编写代理实例的创建过程,不同的类的代理创建时,需要再分别写代码,无法做到通用。
(2)动态代理的选择
  • Cglib创建的代理对象性能是jdk代理对象的10倍,但是cglib创建代理对象所花费的时间是jdk的8倍,所以对于单例对象或有实例池的代理,因为无须频繁创建代理对象,所以用Cglib比较合适,反之用jdk比较合适。
  • 使用Cglib动态创建子类的方式生成代理对象,不能对目标类中的final、private等方法进行代理。


3、创建增强类
3.1、增强类型
(1)AOP联盟为增强定义了org.aopalliance.aop.Advice接口,spring支持5种类型的增强,增强接口的继承关系图:



带spring标识的接口是spring扩展的接口,带aopalliance标识的接口是aop联盟的接口。
增强类型有:
  • MethodBeforeAdvice-方法前置增强;
  • AfterReturningAdvice-方法后置增强;
  • MethodInterceptor-环绕增强,它是aop联盟的接口规范;
  • ThrowsAdvice-异常抛出增强接口(主要用于事务回滚),该接口没有定义任何方法,它是一个标识接口,在运行期Spring使用反射机制自行判断,但我们必须采用以下签名形式定义增强方法:


(2)spring的代理工厂ProxyFactory
org.springframework.aop.framework.ProxyFactory是spring的代理工厂,通过ProxyFactory将增强织入到目标类中。
1)解剖ProxyFactory:
①spring定义了org.springframework.aop.framework.AopProxy接口,并提供了两个final类型的实现类:



②如何区分使用cglib与jdk的动态代理:
  • 如果通过ProxyFactory的setInterfaces(Class[]  interfaces)指定针对接口进行代理,ProxyFactory就使用JdkDynamicAopProxy;
  • 如果针对类的代理,则使用Cglib2AopProxy;
  • 此外,还可以通过ProxyFactory的setOptimize(true)方法,让ProxyFactory启动优化代理方式,这样,针对接口的代理也会使用Cglib2AopProxy。

2)在spring中配置代理:
<bean    id="xxxAdvice"    class="实现了Advice接口的增强类"/>
<bean    id="target"    class="目标对象"/>
<bean    id="xxx"    class="org.springframework.aop.framework.ProxyFactoryBean">
        <property    name="proxyInterfaces"    value="代理对象的接口,如果是多个接口,使用list元素,proxyInterfaces还有个别名:interfaces"/>
        <property    name="interceptorNames"    value="指定增强,多个增强以逗号隔开"/>
        <property    name="target"    ref="目标对象"/>
</bean>
注:interceptorNames是String[]类型,它接收增强Bean的名字而不是增强Bean的实例。之所以这样是因为ProxyBeanFactory在内部生成代理类时,需要使用增强Bean的类,而不是增强Bean的实例,可以说增强是类级别的。

3.2、引介增强
(1)引介增强是一种特殊的增强,它为目标类增加新的方法和属性,通过引介,我们可以为目标类创建新的方法和属性。
(2)Spring定义了引介增强接口IntroductionInterceptor,该接口没有任何方法,spring为该接口提供了DelegatingIntroductionInterceptor实现类,一般情况下我们通过扩展该类定义自己的引介增强。
(3)对于性能监测代理,可以增加一个开关控制,确保其可控。这样就必须引入ThreadLocal变量,从而解决单实例线程安全问题。
(4)由于引介增强一定要通过创建子类来生成代理,所以需要强制使用cglib,否则会报错,所以要配置ProxyFactoryBean的proxyTargetClass属性为true。


4、创建切面
在介绍增强时,我们注意到一个问题,增强被织入到了目标类的所有方法中。为了解决这个问题,我们需要创建切面。

4.1、切点
(1)spring通过Pointcut接口描述切点,Pointcut由ClassFilter和MethodMatcher构成,它通过ClassFilter定位到某些特定类上,通过MethodMatcher定位到某些特定方法上,这样Ponitcut就拥有了描述某些类特定方法的能力。Pointcut类图:



(2)spring支持2种方法匹配器:静态方法匹配器和动态方法匹配器。
  • 静态方法匹配器:仅对方法名签名(包括方法名和入参类型及顺序)进行匹配;
  • 动态方法匹配器:在运行期检查方法入参的值。
  • 区别:静态匹配只匹配一次,动态匹配每次调用方法都匹配,对性能影响较大
(3)spring提供了6种切点类型

4.2、切面类型
(1)spring使用org.springframework.aop.Advisor接口表示切面的概念
  • Advisor:代表一般切面,它仅包含一个Advice。因为Advice包含了横切代码和连接点的信息,所以Advice本身就是一个简单的切面,只不过它代表的横切连接点是所有目标类的所有方法,因为这个横切面太宽泛,所以一般不会直接使用。
  • PointcutAdvisor:代表具有切点的切面,它包含Advice和Pointcut两个类,这样我们就可以通过类、方法名以及方法方位等信息灵活地定义切面的连接点。
  • IntroductionAdvisor:代表引介切面。
  • 此外,Advisor都实现了org.springframework.core.Ordered接口,spring将根据Advisor定义的顺序决定织入切面的顺序。

4.3、静态普通方法名匹配切面:StaticMethodMatcherPointcutAdvisor
StaticMethodMatcherPointcutAdvisor抽象类唯一需要定义的是matches()方法,在默认情况下,该切面匹配所有的类,所以需要覆盖getClassFilter()方法,让它匹配特定的类。
(1)切面中包含增强,定义增强Advice后,将其装配到切面Advisor中,切面中通过编码的方式定义了切点。所以xml配置发生了点变化:
<bean    id="target"    class="目标对象"/>
<bean    id="xxxAdvice"    class="实现了Advice接口的增强类"/>
<bean    id="xxxAdvisor"    class="实现了Advisor接口的切面类">
        <property    name="advice"    ref="xxxAdvice"/>
</bean>
<bean    id="xxx"    class="org.springframework.aop.framework.ProxyFactoryBean">
        <property    name="proxyTargetClass"    value="true"/>
        <property    name="interceptorNames"    value="xxxAdvisor切面类"/>
        <property    name="target"    ref="目标对象"/>
</bean>
切面类(覆盖getClassFilter()方法,只对Waiter类增强):



4.4、静态正则表达式方法匹配切面:RegexpMethodPointcutAdvisor
(1)StaticMethodMatcherPointcutAdvisor中我们只能通过方法名定义切点,这种描述方式不够灵活,使用正则表达式就灵活多了。RegexpMethodPointcutAdvisor正是正则表达式方法匹配的切面类,一般情况下无须扩展该类。
(2)示例配置:
<bean    id="regexpAdvisor"    class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property    name="advice"    ref="xxxAdvice"/>
        <property    name="patterns">
                <list>
                        <value>.*方法名部分.*</value>
                </list>
        </property>
</bean>
注:
  • 匹配模式串匹配的是目标类方法的全限定名,即带类名的方法名;
  • patterns用于定义多个匹配规则,它们之间是或的关系;
  • pattern用于定义只有一个匹配规则的情况。
(3)正则表达式语法:



4.5、动态切面
(1)低版本中,spring提供了DynamicMethodMatcherPointcutAdvisor抽象类,因为该类在功能上和其他类有重复,因此该类已经过期。我们现在使用DefaultPointcutAdvisorDynamicMethodMatcherPointcut来完成相同的功能。
(2)DynamicMethodMatcherPointcut是个抽象类,它将isRuntime()方法标识为final且返回true,这样其子类就一定是一个动态的切点了。该类默认匹配所有类和方法,因此需要通过扩展该类编写符合要求的动态切点。
(3)动态切面配置:
<bean    id="target"    class="目标对象"/>
<bean    id="xxxAdvice"    class="实现了Advice接口的增强类"/>
<bean    id="xxxAdvisor"    class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property    name="pointcut">
                <bean    class="DynamicMethodMatcherPointcut子类"/>
        </property>
        <property    name="advice"    ref="xxxAdvice"/>
</bean>
<bean    id="xxx"    class="org.springframework.aop.framework.ProxyFactoryBean">
        <property    name="proxyTargetClass"    value="true"/>
        <property    name="interceptorNames"    value="xxxAdvisor切面类"/>
        <property    name="target"    ref="目标对象"/>
</bean>
(4)静态检查和动态检查的代码:



(5)因为动态检查影响性能,所以spring采用这样的机制:
  • 在创建代理时对目标类的每个连接点执行静态切点检查,如果静态检查都不匹配,则在运行时不再进行动态检查;
  • 如果静态检查是匹配的,则在运行时进行动态切点检查;
  • 第一次调用代理类的每一个方法都会进行一次静态检查,如果本次检查不通过,则后续方法调用不再进行静态检查;
  • 如果第一次调用代理类的方法时静态检查通过,则后续只进行动态检查,不再进行静态检查。
(6)静态切面和动态切面的区别:
静态切面:是指在生成代理对象时,就确定了增强是否需要织入到目标类的连接点上;
动态切面:是指必须在运行期根据方法入参的值来判断增强是否需要织入到目标类连接点上。

4.6、流程切面
(1)spring的流程切面由DefaultPointcutAdvisorControlFlowPointcut实现。流程切点代表由某个方法直接或间接发起调用的其他方法。
比如:A类中的mA方法中调用了B类的mB和C类的mC方法,现在希望mA方法中调用的所有其他方法都织入增强,则需要用到流程切面。
(2)xml配置如下:
<bean    id="target"    class="目标对象"/>
<bean    id="xxxAdvice"    class="实现了Advice接口的增强类"/>
<bean    id="xxxPointcut"    class="org.springframework.aop.support.ControlFlowPointcut">
        <constructor-arg    type="java.lang.Class"    value="指定流程切点的类,即上面的A类或代理类"/>
        <constructor-arg    type="java.lang.String"    value="指定流程切点的方法"/>
</bean>
<bean    id="xxxAdvisor"    class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property    name="pointcut"    ref="xxxPointcut"/>
        <property    name="advice"    ref="xxxAdvice"/>
</bean>
<bean    id="xxx"    class="org.springframework.aop.framework.ProxyFactoryBean">
        <property    name="proxyTargetClass"    value="true"/>
        <property    name="interceptorNames"    value="xxxAdvisor切面类"/>
        <property    name="target"    ref="目标对象"/>
</bean>
(3)流程切面和动态切面从某种程度上可以算是一类切面,因为两者都是在运行期判断,所以流程切面对性能的影响也很大。

4.7、复合切点切面
(1)在前面的例子中,我们定义的只有一个切点。spring通过ComposablePointcut,可以将多个切点以并集或交集的方式组合起来,提供切点之间复合运算的功能。
(2)ComposablePointcut中提供了运算切点集合的方法。

4.8、引介切面
(1)引介切面是引介增强的封装器,引介切面IntroductionAdvisor的类继承图:



①IntroductionInfo描述了目标类需要实现的新接口;
IntroductionAdvisor和PointcutAdvisor不同,它仅有一个类过滤器ClassFilter而没有MethodMatcher,这是因为引介切面的切点是类级别的,而Pointcut的切点是方法级别的。
③IntroductionAdvisor有2个实现类:DefaultIntroductionAdvisor和DeclareParentsAdvisor,前者是最常用的实现类,后者是spring2.0新增的实现AspectJ语言的实现类。


5、自动创建代理
在前面所有的例子中,我们都通过ProxyFactoryBean创建织入切面的代理,每个被代理的Bean都需要使用一个ProxyFactoryBean进行配置。spring为我们提供了自动代理机制,让容器为我们自动生成代理,解放了繁琐的配置工作。

5.1、基于BeanPostProcessor自动创建代理
(1)spring基于bean后处理器,在实例化bean时,为匹配的bean生成代理实例。
(2)这些代理创建器可以分为3类:
  • 基于Bean配置名规则的自动代理创建器:允许为一组特定配置名的Bean自动创建代理实例的代理创建器,实现类为BeanNameAutoProxyCreator
  • 基于Advisor匹配机制的自动代理创建器:它会对容器中所有的Advisor进行扫描,自动将这些切面应用到匹配的Bean中(即为目标Bean创建代理实例),实现类为DefaultAdvisorAutoProxyCreator
  • 基于Bean中AspectJ注解标签的自动代理创建器:为包含AspectJ注解的Bean自动创建代理实例,它的实现类是AnnotationAwareAspectJAutoProxyCreator,该类是spring2.0新增类。
代理实现器类图如下:



5.2、BeanNameAutoProxyCreator
(1)使用Bean的名称进行自动代理的xml配置:
<bean    id="target"    class="目标对象"/>
<bean    id="xxxAdvice"    class="实现了Advice接口的增强类"/>
<bean    class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
            <!--  Bean名称可以使用*通配符,若有多个Bean名称,可以使用list元素,也可以使用逗号或者空格分隔-->
            <property    name="beanNames"    value="*xxx"/>
            <property    name="interceptorNames"    value="xxxAdvice"/>
            <property    name="optimize"    value="true"/>
</bean>
(2)一般情况下我们不会为FactoryBean的Bean创建代理,如果刚好有这样的需求,我们需要在beanNames中指定Bean名称前添加“&”。

5.3、DefaultAdvisorAutoProxyCreator
(1)使用Advisor匹配的自动代理器xml配置:
<bean    id="target"    class="目标对象"/>
<bean    id="xxxAdvice"    class="实现了Advice接口的增强类"/>
<bean    id="regexpAdvisor"    class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property    name="advice"    ref="xxxAdvice"/>
        <property    name="patterns"    value=".*方法名部分.*"/>
</bean>
<bean    class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>



0 0
原创粉丝点击