1、AOP(Aspect Oriented Program):面向切面编程-通过预编译方式和动态代理实现程序功能的统一维护的一种技术。降低业务逻辑部分的耦合度,提高程序的可重用性,提高开发效率。
从表面上来说,减少了代码的拷贝和对公共方法的显示调用,使用aop利用切面的方式实现公共方法的调用。
2、概念
Advice(增强):Advice 定义了在 Pointcut
里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
##增强类型
AOP联盟为增强定义了 org.aopalliance.aop.Advice接口,Spring支持五种类型的增强。带《Spring》标识的接口是Spring所定义的扩展增强接口,带《aoppaliliance》标识的接口是AOP联盟定义的接口。按照增强在目标类方法中的连接点位置,可以分为以下5类。
1.前置增强
2.后置增强
3.环绕增强
4.异常抛出增强
5.引介增强:表示在目标类中添加一些新的方法和属性。
这些增强接口都有一些方法,通过实现这些接口方法,并在接口方法中定义横切逻辑,就可以为他们织入目标类方法的相应连接点位置。
###前置增强
/** * @author kai O'Brian * @create 2020/6/16 22:02 */ public interface Waiter { void greetTo(String name); void serveTo(String name); }
/** * 天真的服务员只会简单的打招呼和闷头服务 * @author kai O'Brian * @create 2020/6/16 22:02 */ public class NaiveWaiter implements Waiter { @Override public void greetTo(String name) { System.out.println("greet to "+name); } @Override public void serveTo(String name) { System.out.println("serving to"+name); } }
import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; /** * 礼貌用语增强 * Spring目前只提供方法调用前的前置增强 * @author kai O'Brian * @create 2020/6/16 22:05 */ public class GreetingBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { String clientName = (String)args[0]; System.out.println("How are you ! Mr."+clientName); } }
/**
* @author kai O'Brian
* @create 2020/6/16 22:16
*/
public class BeforeAdviceTest {
public static void main(String[] args) {
Waiter target = new NaiveWaiter();
GreetingBeforeAdvice advice = new GreetingBeforeAdvice();
//1、Spring提供的代理工厂
ProxyFactory pf = new ProxyFactory();
//2、设置代理目标
pf.setTarget(target);
//3、为目标类添加增强
pf.addAdvice(advice);
//4、生成代理类 生成的为CGLib类型的代理类
Waiter proxy = (Waiter) pf.getProxy();
proxy.greetTo("liukai");
proxy.serveTo("liukai");
}
}
解析ProxyFactory
ProxyFactory底层使用JDK或CGLib动态代理技术将增强应用到目标类中。
/** * @author kai O'Brian * @create 2020/6/16 22:28 */ public class BeforeAdviceJdkTest { public static void main(String[] args) { Waiter target = new NaiveWaiter(); GreetingBeforeAdvice advice = new GreetingBeforeAdvice(); //1、Spring提供的代理工厂 ProxyFactory pf = new ProxyFactory(); //2.0 设置代理接口 pf.setInterfaces(Waiter.class); //2、设置代理目标 pf.setTarget(target); //3、为目标类添加增强 pf.addAdvice(advice); //4、生成代理类 $Proxy类型的代理类 Waiter proxy = (Waiter) pf.getProxy(); proxy.greetTo("liukai"); proxy.serveTo("liukai"); } }
import com.ijianghu.advice.impl.NaiveWaiter; import org.springframework.aop.framework.ProxyFactory; /** * @author kai O'Brian * @create 2020/6/16 22:32 */ public class BeforeAdviceOptimizeTest { public static void main(String[] args) { Waiter target = new NaiveWaiter(); GreetingBeforeAdvice advice = new GreetingBeforeAdvice(); //1、Spring提供的代理工厂 ProxyFactory pf = new ProxyFactory(); //2.0 设置代理接口 pf.setInterfaces(Waiter.class); //2、设置代理目标 pf.setTarget(target); //3、为目标类添加增强 pf.addAdvice(advice); //3.0 设置启用优化 pf.setOptimize(true); //4、生成代理类 CGLib类型的代理类 Waiter proxy = (Waiter) pf.getProxy(); proxy.greetTo("liukai"); proxy.serveTo("liukai"); } }
ProxyFactory通过addAdvice(Adveice)方法添加一个增强,用户可以使用该方法添加多个增强。
多个增强形成一个增强链,他们的调用顺序和添加顺序一致,可以通过addAdveice(int,Advice)方法将增强添加到增强链的具体位置,第一个位置为0.
##在Spring中配置
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="greetingAdvice" class="com.ijianghu.advice.GreetingBeforeAdvice"/> //1、定义增强类 <bean id="target" class="com.ijianghu.advice.impl.NaiveWaiter"/> //2、定义目标类 <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean" p:proxyInterfaces="com.ijianghu.advice.Waiter" //3、指定代理的接口 p:interceptorNames="greetingAdvice" //4、指定使用的增强 1处 p:target-ref="target" //5、指定对哪个bean进行增强 /> </beans>
将proxyTargetClass设置为true后,无需设置proxyInterfaces属性,即使设置也会被ProxyFactoryBean忽略。
##后置增强
/** * 服务完后使用礼貌用语,后置增强 * @author kai O'Brian * @create 2020/6/17 11:24 */ public class GreetingAfterAdvice implements AfterReturningAdvice { /** * * @param returnValue //目标实例方法返回的结果 * @param method //目标类的方法 * @param args // 目标实例方法的入参 * @param target // 目标类实例 * @throws Throwable */ @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("Please enjoy yourself!"); } }
spring配置
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="greetingAdvice" class="com.ijianghu.advice.GreetingBeforeAdvice"/> <bean id="target" class="com.ijianghu.advice.impl.NaiveWaiter"/> <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean" p:proxyInterfaces="com.ijianghu.advice.Waiter" p:interceptorNames="greetingAdvice" p:target-ref="target" /> </beans>
ProxyFactoryBean内部在生成代理类时,需要使用增强Bean的类,而非增强Bean的实例,以织入增强类中所写的横切逻辑代码,所以说增强是类级别的。
##环绕增强
/** * @author kai O'Brian * @create 2020/6/17 21:50 */ public class GreetingInterceptor implements MethodInterceptor { /** * 获取目标类方法的执行,并在前后添加横切逻辑 * @param invocation * @return * @throws Throwable */ @Override public Object invoke(MethodInvocation invocation) throws Throwable { Object[] args = invocation.getArguments(); //目标方法入参 String clientName = (String)args[0]; System.out.println("How arr you ,Miss. "+clientName); //在目标方法执行前调用 Object obj = invocation.proceed(); //通过反射机制调用目标方法 System.out.println("please enjoy yourself"); // 目标方法执行后调用 return obj; } }
spring配置
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="greetingAdvice" class="com.ijianghu.advice.GreetingBeforeAdvice"/> <bean id="greetingAfterAdvice" class="com.ijianghu.advice.after.GreetingAfterAdvice"/> <bean id="greetingInterceptor" class="com.ijianghu.advice.GreetingInterceptor"/> <bean id="target" class="com.ijianghu.advice.impl.NaiveWaiter"/> <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean" p:proxyInterfaces="com.ijianghu.advice.Waiter" p:interceptorNames="greetingInterceptor" p:target-ref="target" /> <!-- p:interceptorNames="greetingAdvice,greetingAfterAdvice"--> </beans>
spring直接使用AOP联盟定义的MethodInterceptor作为环绕增强的接口。
MethodInvocation不但封装了目标方法及其入参数组,还封装了目标方法所在的实例对象,通过proceed()方法反射调用目标实例相应的方法。环绕增强可以达到前置增强和后置增强同样的联合效果。
##异常抛出增强
最适合的应用场景是事务管理,当参与事务的某个DAO发生异常时,事务管理器必须回滚事务。
/** * @author kai O'Brian * @create 2020/6/17 22:10 */ public class TransactionManager implements ThrowsAdvice { public void afterThrowing(Method method,Object[] args,Object target,Exception ex) throws Throwable{ System.out.println("-----------------"); System.out.println("method:"+method.getName()); System.out.println("抛出异常:"+ex.getMessage()); System.out.println("成功回滚事务"); } }
spring配置
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="transactionManager" class="com.ijianghu.advice.exceptipon.TransactionManager"/> <bean id="target" class="com.ijianghu.advice.exceptipon.ForumService"/> <bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="transactionManager" p:target-ref="target" p:proxyTargetClass="true" /> </beans>
ThrowsAdvice异常抛出接口没有定义任何方法,它是一个标签接口,在运行期Spring使用反射机制自行判断,必须采用以下签名形式定义异常安排出的增强方法。
afterThrowing(Method method,Object[] args,Object target,Exception ex)
方法名必须为afterThrowing,方法入参规定如下:前3个入参可选(3个入参 要么提供要么不提供),最后一个入参是Throwable或其子类
afterThrowing(SQLException ex)
afterThrowing(RuntimeException ex)
afterThrowing(Method method,Object[] args,Object target,RuntimeException ex) 都为合法的。
可以在同一个异常抛出增强中定义多个afterThrowing()方法,当目标类方法抛出异常时,Spring会自动选用最匹配的增强方法。
标签接口是没有任何方法和属性的接口。
#切面
增强被织入目标类的所有方法中。假如希望有选择地织入目标类的某些特定方法中,需要使用切点进行目标连接点的定位。
描述连接点是SpringAOP编程最主要的工作,现在来了解一下SpringAOP如何定位连接点。
增强提供了连接点方位信息,如织入到方法前,方法后等,而切点进一步描述了织入哪些类的哪些方法上。
Spring通过org.springframework.aop.Pointcut 接口描述切点。Pointcut有ClassFilter和methodMatcher构成。、
public interface Pointcut { /** * Return the ClassFilter for this pointcut. * @return the ClassFilter (never {@code null}) */ ClassFilter getClassFilter(); /** * Return the MethodMatcher for this pointcut. * @return the MethodMatcher (never {@code null}) */ MethodMatcher getMethodMatcher(); /** * Canonical Pointcut instance that always matches. */ Pointcut TRUE = TruePointcut.INSTANCE; }
@FunctionalInterface public interface ClassFilter { /** * Should the pointcut apply to the given interface or target class? * @param clazz the candidate target class * @return whether the advice should apply to the given target class */ boolean matches(Class<?> clazz); /** * Canonical instance of a ClassFilter that matches all classes. */ ClassFilter TRUE = TrueClassFilter.INSTANCE; /* * Copyright 2002-2019 the original author or author *
* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.aop; import java.lang.reflect.Method; /** * Part of a {@link Pointcut}: Checks whether the target method is eligible for advice. * Sring支持两种方法匹配器:静态方法匹配器和动态方法匹配器。 * <p>A MethodMatcher may be evaluated <b>statically</b> or at <b>runtime</b> (dynamically). * Static matching involves method and (possibly) method attributes. Dynamic matching * also makes arguments for a particular call available, and any effects of running * previous advice applying to the joinpoint. **/ public interface MethodMatcher { /** * Perform static checking whether the given method matches.*/ boolean matches(Method method, Class<?> targetClass); /** * Is this MethodMatcher dynamic, that is, must a final call be made on the
* 方法匹配器类型 由该返回值决定
* */ boolean isRuntime(); /** **/ boolean matches(Method method, Class<?> targetClass, Object... args); /** * Canonical instance that matches all methods. */ MethodMatcher TRUE = TrueMethodMatcher.INSTANCE; }
Spring2.0支持注解切点和表达式切点。注解切点通过java5.0的注解定义切点,表达式切点通过字符串表达式定义切点,两者都使用AspectJ的切点表达式语言。
##切点类型
1.静态方法切点
2.动态方法切点
3.注解切点
4.表达式切点
5.流程切点
6.复合切点
##切面类型
由于增强既包含横切代码,由包含部分连接点信息(方法前,方法后),所以可以仅通过增强类生成一个切面。
但切点仅代表目标类连接点的信息(类和方法的定位),所以仅有切点无法制作出一个切面,必须结合增强才能制作出切面,
Spring使用org.aopalliance.aop.Advice.Advisor 接口表示切面的意思,一个切面同时包含横切代码和连接点信息。
切面可以分为三类:一般切面、切点切面、引介切面。
Advisor:代表一般切面,仅包含一个Advice.因为Advice包含了横切代码和连接点信息(所有目标类的所有方法),横切面太宽泛,一般不会直接使用。
PointcutAdvisor:代表具有切点的切面,包含Advice和Pointcut两个类,可以通过类、方法名、及方法方位灵活地定义切面的连接点。
IntruductionAdvisor:代表引介切面,引介切面是对应引介增强的特殊的切面,应用于类层面上,所以引介切点使用ClassFileter进行定义。
###PointcutAdvisor6个具体的实现类
DefaultPointcutAdvisor:通过任意Pointcut和Advice定义一个切面,唯一不支持的是引介的切面类型,一般可以通过扩展该类实现自定义的切面。
NameMatchMethodPointcutAdvisor:通过该类可以定义按方法名定义切点的切面。
RegexpMethodPointcutAdvisor:按正则表达式匹配方法名进行切点定义的切面,可以通过扩展该实现类进行操作。内部通过JdkRegexpMethodPointcut构造出正则表达式方法名切点。
StaticMethodMatcherPointcutAdvisor:静态方法匹配器切点定义的切面,默认情况下匹配所有的目标类。
AspectJExpressionPointcutAdvisor:用于AspectJ切点表达式定义切点的切面。
AspectJPointcutAdvisor:用于ApectJ语法定义切点的切面。
###静态方法匹配切面
定义切面
/** * @author kai O'Brian * @create 2020/6/18 7:50 */ public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor { @Override public boolean matches(Method method, Class<?> targetClass) { //切点方法匹配规则:方法名为greetTo return "greetTo".equals(method.getName()); } public ClassFilter getClassFilter(){ //切点类匹配规则:为Waiter或waiter的子类 return new ClassFilter() { @Override public boolean matches(Class<?> clazz) { return Waiter.class.isAssignableFrom(clazz); } }; } }
spring配置
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="waiterTarget" class="com.ijianghu.advice.impl.NaiveWaiter"/> <bean id="sellerTarget" class="com.ijianghu.advice.staticadvisor.Seller"/> <bean id="greetingAdvice" class="com.ijianghu.advice.GreetingBeforeAdvice"/> <!--向切面注入一个前置增强--> <bean id="greetingAdvisor" class="com.ijianghu.advice.staticadvisor.GreetingAdvisor" p:advice-ref="greetingAdvice" /> <!--通过一个父Bean,定义公共的配置信息--> <bean id="parent" abstract="true" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="greetingAdvisor" p:proxyTargetClass="true" /> <bean id="waiter" parent="parent" p:target-ref="waiterTarget"/> <bean id="seller" parent="parent" p:target-ref="sellerTarget"/> <!-- p:interceptorNames="greetingAdvice,greetingAfterAdvice"--> </beans>
StaticMethodMatcherPointcutAdvisor除具有advice属性外,还可以定义两个属性
classFilter:类匹配过滤器
order:切面织入时的顺序
###静态正则表达式方法匹配切面
spring配置
beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="waiterTarget" class="com.ijianghu.advice.impl.NaiveWaiter"/> <bean id="sellerTarget" class="com.ijianghu.advice.staticadvisor.Seller"/> <bean id="greetingAdvice" class="com.ijianghu.advice.GreetingBeforeAdvice"/> <bean id="regexAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" p:advice-ref="greetingAdvice"> <!--用正则表达式定义目标类全限定名的匹配模式串--> <property name="patterns"> <list> <!--匹配模式串 匹配模式串匹配的是目标类方法的全限定名,即带类名的方法名--> <value>.*greet.*</value> </list> </property> </bean> <bean id="waiter1" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="regexAdvisor" p:target-ref="waiterTarget" p:proxyTargetClass="true" /> <bean id="seller1" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="regexAdvisor" p:target-ref="sellerTarget" p:proxyTargetClass="true" /> </beans>
pattern:只有一个匹配模式串
patterns:用于定义多个模式匹配串,这些匹配模式串之间是“或”的关系。
####正则表达式语法
只要程序的类包具有良好的命名规范,皆可以使用简单的正则表达式描述出目标方法。
RegexBuddy是使用正则表达式的良好帮手。
###动态切面
低版本中,Spring提供了用于创建动态切面的DynamicMethodMatcherPointcutAdvisor抽象类,该类在功能上和其他类有重叠,因此在Spring2.0已过期。
可以使用DefautlPointcutAdvisor和DynamicMethodMatcherPointcut来完成相同的功能。
/** * @author kai O'Brian * @create 2020/6/13 12:58 */ public class GreetingDynamicPointcut extends DynamicMethodMatcherPointcut { private static List<String> specialClientList = new ArrayList<String>(); static { specialClientList.add("John"); specialClientList.add("Tom"); } public ClassFilter getClassFilter(){ return new ClassFilter() { @Override public boolean matches(Class<?> clazz) { System.out.println("调用getClassFilter()对"+clazz.getName()+"做静态检查"); return Waiter.class.isAssignableFrom(clazz); } }; } //对方法进行静态切点检查 public boolean matches(Method method, Class<?> targetClass) { System.out.println("matches(method,targetClass)对"+targetClass.getName()+"."+ method.getName()+"做静态检查."); return "greetTo".equals(method.getName()); } //对方法进行动态切点检查 @Override public boolean matches(Method method, Class<?> targetClass, Object... args) { System.out.println("matches(method,targetClass)对"+targetClass.getName()+"."+ method.getName()+"做动态检查."); String clientName = (String)args[0]; return specialClientList.contains(clientName); } }
spring配置
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="waiterTarget" class="com.ijianghu.advice.impl.NaiveWaiter"/> <bean id="sellerTarget" class="com.ijianghu.advice.staticadvisor.Seller"/> <bean id="greetingAdvice" class="com.ijianghu.advice.GreetingBeforeAdvice"/> <!--向切面注入一个前置增强--> <bean id="dynamicAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="pointcut"> <bean class="com.ijianghu.advice.dynaimcadvisor.GreetingDynamicPointcut"/> </property> <property name="advice"> <bean class="com.ijianghu.advice.GreetingBeforeAdvice"/> </property> </bean> <bean id="waiter2" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="dynamicAdvisor" p:target-ref="waiterTarget" p:proxyTargetClass="true" /> <!--通过一个父Bean,定义公共的配置信息--> <bean id="parent" abstract="true" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="greetingAdvisor" p:proxyTargetClass="true" /> <bean id="waiter" parent="parent" p:target-ref="waiterTarget"/> <bean id="seller" parent="parent" p:target-ref="sellerTarget"/> <!-- p:interceptorNames="greetingAdvice,greetingAfterAdvice"--> </beans>
Spring在创建代理织入切面时,对目标类中所有的方法进行静态切点检查,在生成织入切面的代理对象后,第一次调用代理类的每一个方法时都会进行一次静态切点检查,如果本次检查就能从候选者列表中将该方法排除,则以后对该方法的调用就不再执行静态切点检查;对于在静态切点检查时匹配的方法,在后续调用该方法时,将执行动态切点检查。
在定义动态切点时,切勿忘记同时覆盖matches(Method method, Class<?> targetClass) 方法,通过静态切点检查排除大部分方法。
静态切面:指在生成代理对象时就确定了增强是否需要织入目标类的连接点上
动态切面:指必须在运行期根据方法入参的值来判断增强是否需要织入目标类的连接点上。
###流程切面
/** * @author kai O'Brian * @create 2020/6/18 21:56 */ public class WaiterDelegate { private Waiter waiter; public void service(String clientName){ waiter.greetTo(clientName); waiter.serveTo(clientName); } public void setWaiter(Waiter waiter){ this.waiter = waiter; } }
spring配置
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="waiterTarget" class="com.ijianghu.advice.impl.NaiveWaiter"/> <bean id="sellerTarget" class="com.ijianghu.advice.staticadvisor.Seller"/> <bean id="greetingAdvice" class="com.ijianghu.advice.GreetingBeforeAdvice"/> <bean id="controlFlowPointcut" class="org.springframework.aop.support.ControlFlowPointcut"> <!--指定流程切点的类--> <constructor-arg type="java.lang.Class" value="com.ijianghu.advice.flowadvisor.WaiterDelegate"/> <!--指定流程切点的方法--> <constructor-arg type="java.lang.String" value="service"/> </bean> <bean id="controlFlowAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor" p:pointcut-ref="controlFlowPointcut" p:advice-ref="greetingAdvice" /> <bean id="waiter3" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="controlFlowAdvisor" p:target-ref="waiterTarget" p:proxyTargetClass="true" /> </beans>
流程切面和动态切面从某种程度上说可以算是一类切面,因为二者都需要在运行期判断动态的环境。对于流程切面来说,每次代理类在调动目标类方法时,都要判断方法调用堆栈中是否有满足流程切点要求的方法。流程切面对性能的影响很大。
###复合切点切面
/** * @author kai O'Brian * @create 2020/6/18 22:15 */ public class GreetingComposablePointcut { public Pointcut getIntersectionPointcut(){ ComposablePointcut cp = new ComposablePointcut(); Pointcut pt1 = new ControlFlowPointcut(WaiterDelegate.class, "service"); NameMatchMethodPointcut pt2 = new NameMatchMethodPointcut(); pt2.addMethodName("greetTo"); return cp.intersection(pt1).intersection((Pointcut)pt2); } }
spring配置
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="waiterTarget" class="com.ijianghu.advice.impl.NaiveWaiter"/> <bean id="sellerTarget" class="com.ijianghu.advice.staticadvisor.Seller"/> <!--声明一个增强--> <bean id="greetingAdvice" class="com.ijianghu.advice.GreetingBeforeAdvice"/> <!--声明一个复合切点对象--> <bean id="cpt" class="com.ijianghu.advice.composable.GreetingComposablePointcut"/> <!--定义一个默认切面 此处使用了util命名空间的标签引用另一个bean的属性--> <bean id="composableAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor" p:pointcut="#{cpt.intersectionPointcut}" p:advice-ref="greetingAdvice" /> <!--使用复合切面--> <bean id="waiter4" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="composableAdvisor" p:target-ref="waiterTarget" p:proxyTargetClass="true" /> </beans>
###引介切面
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--<bean id="pmonitor" class="com.ijianghu.advice.introduce.ControllablePerformanceMonitor"/>--> <!--<bean id="target" class="com.ijianghu.advice.introduce.ForumServiceImpl"/>--> <bean id="forumServiceTarget" class="com.ijianghu.advice.introduce.ForumServiceImpl"/> <bean id="introduceAdvisor" class="org.springframework.aop.support.DefaultIntroductionAdvisor"> <constructor-arg> <bean class="com.ijianghu.advice.introduce.ControllablePerformanceMonitor"/> </constructor-arg> </bean> <bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="introduceAdvisor" p:target-ref="forumServiceTarget" p:proxyTargetClass="true" /> <!--<bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean" p:interfaces="com.ijianghu.advice.introduce.Monitorable" p:interceptorNames="pmonitor" p:target-ref="target" p:proxyTargetClass="true" />--> <!--指定引介增强所需要实现的接口--> <!--只能通过为目标类创建子类的方式生成引介增强的代理,所以必须将proxyTargetClass设置为true p:proxyTargetClass="true"--> </beans>
引介切面是引介增强的封装器,通过引介切面,可以更容易地为现有对象添加任何接口的实现。
#自动创建代理
在上面的例子中,都通过ProxyFactoryBean创建织入切面的代理,每个需要被代理的Bean都需要一个ProxyFactoryBean进行配置,对于拥有众多需要代理Bean的系统,原来的配置显得不尽人意。
Spring提供了自动代理机制,让容器自动生成代理。在内部,Spring使用BeanPostProcessor自动完成这项工作。
##实现类介绍
这些基于BeanPostProcessor的自动代理创建器的实现类,将根据一些规则 自动在容器实例化Bean时为匹配的Bean生成代理实例。代理创建器可以分为3类。
1. 基于Bean配置名规则的自动代理创建器,实现类为BeanNameAutoProxyCreator
2.基于Advisor匹配机制的自动代理创建器,实现类为DefaultAdvisorAutoProxyCreator
3.基于Bean中AspectJ注解标签的自动代理创建器,实现类为AnnotationAwareAspectJAutoProxyCreator
类继承图
所有的自动代理创建器都实现了BeanPostProcessor,在容器实例化Bean时,BeanPostProcessor将对它进行加工,所以,自动代理创建器有机会对满足匹配规则的Bean自动创建代理对象。
##BeanBeanNameAutoProxyCreator
spring配置
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--可以使用List子元素或逗号、空格、分号的方式设定多个Bean名称--> <bean id="waiter" class="com.ijianghu.advice.impl.NaiveWaiter"/> <bean id="seller" class="com.ijianghu.advice.staticadvisor.Seller"/> <bean id="greetingAdvice" class="com.ijianghu.advice.GreetingBeforeAdvice"/> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" p:beanNames="**er" p:interceptorNames="greetingAdvice" p:optimize="true" /> </beans>
BeanNameAutoProxyCreator 有一个beanName属性,允许用户指定一组需要自动代理的Bean名称,Bean名称可以使用通配符*。
一般情况下,不会为FactoryBean的Bean创建代理代理,如果刚好有这样的需求,则需要在beanNames中指定添加$的Bean名称,如<property name="beanName" value="$waiter"/>等。
BeanNameAutoProxyCreator的interceptorNames属性指定一个或多个增强Bean的名称。
##DefaultAdvisorAutoProxuCreator
DefaultAdvisorAutoProxuCreator能够扫描容器中的Advisor,并将Advisor自动织入匹配的目标bean中,即为匹配的目标bean自动创建代理。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--基于DefaultAdvisorAutoProxuCreator自动创建代理类--> <bean id="waiter" class="com.ijianghu.advice.impl.NaiveWaiter"/> <bean id="seller" class="com.ijianghu.advice.staticadvisor.Seller"/> <bean id="greetingAdice" class="com.ijianghu.advice.GreetingBeforeAdvice"/> <bean id="regexAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" p:patterns=".*serve.*" p:advice-ref="greetingAdice" /> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> <!--指定引介增强所需要实现的接口--> <!--只能通过为目标类创建子类的方式生成引介增强的代理,所以必须将proxyTargetClass设置为true p:proxyTargetClass="true"--> </beans>
##AOP无法增强的原因
JDK动态代理通过接口来实现方法拦截,所以必须确保要拦截的目标方法在接口中有定义,否则无法实现拦截。
CGLib动态代理中通过动态生成代理子类来实现方法拦截,所以必须确保要拦截的目标方法可以被子类访问,也就是目标方法必须定义为非final,非私有实例方法。
除了以上两点,再来看一个简单的实例:
#总结
AOP是OOP的延伸,它为程序开发提供了一个崭新的思考,可以将重复性的横切逻辑抽取到统一的模块中。通过OOP的纵向抽取和AOP的横向抽取,程序才可以真正解决重复性代码问题。
Spring采用JDK动态代理和CGLib动态代理技术在运行期织入增强,所以不需要装备特殊的编译器或类加载器就可以使用AOP功能。
要使用JDk动态代理,目标类必须实现接口,而CGLib不对目标类做任何限制,它通过动态生成目标类子类的方式提供代理。
JDk在创建代理对象时的性能高于CGLib,而生成的代理对象的运行性能却比CGLib低。如果是Singleton的代理,推荐使用CGLib动态代理。
二、spring事务:
隔离级别:
read-uncommited:对于未提交事务的读取 脏读、不可重复读、幻读
read-commited:对于单条数据,两个事务之间的读取和修改 避免脏读 存在不可重复读(第二事务对一条数据进行操作之后,第一事务的二次读取结果不一致)、幻读
repeatable-read:对于一定范围内的数据,两个事务之间的读取和操作 避免脏读、不可重复读、存在幻读(第一事务对数据进行了批量修改,过程中第二事务修在该范围内进行了新增操作,导致修改数据的丢失)
serializable:串行化读 避免脏读、不可重复读、幻读