注意
上诉内容为博主看Spring 解密的摘要和个人理解
Spring 解密是一本很好的Spring 入门书籍
Spring AOP 中的Joinpoint
在Spring AOP中 仅支持方法级别的Joinpoint 更确切的说只支持方法执行(Method Execution)类型的Joinpoint
虽然Spring AOP仅提供方法拦截 但是在实际的开发过程中 这已经可以满足80%的开发需求了
- Spring AOP要提供一个简单而强大的AOP框架 并不想因大而全使得框架本身过于臃肿 坚持KEEP IT SIMPLE STUPID原则
- 对于类中的属性级别的Joinpoint 如果提供这个级别的拦截支持 那么就破坏了面向对象的封装 可以通过setter和getter方法达到同样的目的
- 如果应用需求 完全超过了Spring AOP所提供的功能 可以求助 AspectJ
Spring AOP中的Pointcut
org.springframework.aop.Pointcut作为Spring框架中所有Pointcut的最顶层抽象(终极父类) 该接口定义了两个方法用来帮助捕捉系统中的相应Joinpoint
public interface Pointcut {
ClassFilter getClassFilter();//用于匹配将被织入操作的对象
MethodMatcher getMethodMatcher();//用于匹配将被织入的相应方法
Pointcut TRUE=TruePointcut.INSTANCE;
}
//ClassFilter数对Joinpoint所处的对象那个进行Class级别的类型匹配
public interface ClassFilter {
//当织入的目标对象的Class类型与Pointcut所规定的类型相符合时 matches方法将会返回true
//当织入对象返回false 则不对这个类型的目标对象进行织入操作
boolean matches(Class clazz);
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
//如果不限制任意类型Class 可以使用ClassFilter TRUE = TrueClassFilter . INSTANCE;
//当Pointcut中返回的ClassFilter类型为该类型实例 时 将会针对系统中所有目标类以及他们的实例进行匹配
//如果只希望对系统中FOO类型进行的类执行织入 可以进行一下定义
public class FooClassFilter implements ClassFilter{
public boolean matches(Class clazz){
return Foo.class.equals(clazz);
)
)
如果PointCut的类型是TruePointcut 默认会对系统中的所有对象 以及对象上所有被支持的Joinpoint进行匹配
public interface MethodMatcher {
//对无参数方法进行拦截
boolean matches(Method method,Class targetClass);
boolean isRuntime();
//对有参数方法进行拦截 并检查这些方法调用的参数
boolean matches(Method method, Class targetClass, Object[] args);
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
- 对无参数方法进行拦截 isRuntime(是动态的吗)返回false 表示不会考虑具体Joinpoint的方法参数 这种类型的MethodMatcher称之为StaticMethodMatcher(静态的) 因为不用每次都检查参数 所以对于同样类型的方法匹配结果 就可以在框架内部缓存以提高性能
public abstract class StaticMethodMatcher implements MethodMatcher {
@Override
public final boolean isRuntime() {
return false;
}
@Override
public final boolean matches(Method method, @Nullable Class<?> targetClass, Object... args) {
// should never be invoked because isRuntime() returns false
throw new UnsupportedOperationException("Illegal MethodMatcher usage");
}
}
- isRuntime方法返回true时 表明该MethodMatcher将会每次都对方法调用的参数进行匹配检查
这种类型的MethodMatcher称之为 DynamicMethodMatcher 因为每次都要对方法参数进行检查
当 isRutime方法返回ture时 并且当方法boolean matches (Method method, Class targetClass ) ;也返回ture 三参数的matches才会被执行 如果两参的matches方法返回false 三参数的方法肯定不会执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mBmL2Yu4-1583599119400)(./img/Pointcut族谱.png)];
常见的Pointcut
- NameMatchMethodPointcut
这是最简单的Pointcut实现 属于StaticMatcherPointcut的子类 可以根据自身指定的一组方法名称与Joinpoint处的方法的方法名称进行匹配(根据指定的一组方法名称匹该方法下的Joinpoint)
NameMatchMethodPointcut pointcut=new NameMatchMethodPointcut();
//当我们只有一个方法名称要匹配时的便捷方法。使用此方法或setMappedNames,不要两者都使用。
pointcut.setMappedName("matches");
//或者传入多个方法名
pointcut.setMappedNames(new String[]{"matches","isRuntime"});
//使用通配符进行简单的模糊匹配
pointcut.setMappedNames(new String[]{"match*","*matches"});
- JdkRegexMethodPointcut和Perl5RegexpMethodPointcut
AbstractRegexpMethodPointcut 提过基于正则表达式MethodPointcut AbstractRegexpMethodPointcut 声明了pattern和patterns属性 可以指定一个或者多个正则表达式的匹配模式(Pattern)
JdkRegexMethodPointcut和Perl5RegexpMethodPointcut是它的具体实现
JdkRegexpMethodPointcut pointcut=new JdkRegexpMethodPointcut();
pointcut.setPattern(".*match.*");
pointcut.setPattern(new String[]{"模式一","模式二" });
public class Bar
{
public void doSth(){
}
}
//.*doSth.* 匹配Bar的doSth方法
注意 使用正则表达式来匹配相应的Joinpoint所处的方法时, 必须匹配整个方法签名(Method Signature)
也可以通过则正表达式的转义指定doSth方法 cn.spring21.sample.Bar.doSth
- AnnotationMatchingPointcut
根据目标对象中是否存在指定类型的注解来匹配Joinpoint 要使用该类型的Pointcut 首先需要声明相应的注解
//Pointcut的ClassLevelAnnotation
@Retention (RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassLevelAnnotation{
}
//Pointcut的MethodLevelAnnotation
@Retention (RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodLevelAnnotation{
}
@Target指定该注解可以标注的类型 ClassLevelAnnotation用于类层次 MethodLevelAnnotation只能用于方法层次
@ClasLevelAnnotation
public class GenericTargetObject{
@MethodLevelAnnotation
public void gMethod1(){
System.out.println("gMethod1");
}
}
//该AnnotationMatchingPointcut仅指定类级别的注解 GenericTargetObject类中的所有方法执行的时候 将全部匹配 不管该方法是否指定了注解
AnnotationMatchingPointcut pointcut=new AnnotationMatchingPointcut(ClassLewvelAnnotation.class);
//使用静态方法来构建类级别的注解对应的AnnotionMatchingPointcut实例
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forClassAnnotation
(ClassLevelAnnotation.class);
// 如果只指定方法级别的注解而忽略类级别的注解 则Pointcut仅匹配特定的标注了指定注解的方法定义
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.for MethodAnnotation (MethodLevelAnnotation.class )
//只有标注了@ClassLevelAnnotation的类定义中同时标注了@MethodLevelAnnotation的方法才会匹配
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut (ClassLevelAnnotation.class, MethodLevelAnnotation.class );
- ComposablePointcut
ComposablePointcut就是Spring AOP提供的可以进行Pointcut逻辑运算实现 他可以进行Pointcut之间的 并以及交 运算
ComposablePointcut pointcutl = new ComposablePolntcut(classFilterl,methodMatcherl);
ComposablePointcut pointcut2 = ...;
ComposablePointcut unitedPoincut = pointcutl.union(pointcut2);//并运算
ComposablePointcut intersectionPointcut = pointcutl.intersection(unitedPoincut);//交运算
assertEquals(pointcutl,intersectionPointcut);
Pointcut定义划分了两部分 一部分是ClassFiter和MethodMatcher 为什么这样划分 一是为了重用这些定义 而是为了可以互相组合
ComposablePointcut pointcut3=pointcut2.union(classFilter).instersection(methodMatcher1);
//这里复用了classFilter和methodMatcher1 并进行了逻辑组合运算
…
//捕捉系统里数据访问对象中的查询方法所在的Joinpoint (加入点只是一个概念 并不是某个实体)
public class QueryMethodPointcut extends StaticMethodMatcherPointcut {
public boolean matches(Method method, Class clazz) {
return method.getName().startsWith("get ")
&& clazz.getPackage().getName() .startsWith("...dao");
}
}
Spring AOP 中的Advice
Spring AOP 加入了开源组织AOP Aliance 鉴于此 Spring的Advice实现全部遵循AOP Aliance规定的接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tLjKpodM-1583599119404)(./img/SpringAdvice类图.png)];
Advice实现了将被织入到Pointcut规定的Joinpoint处的横切逻辑
Advice按照其自身实例(instance)能否在目标对象类的所有实例共享这一标准(通过是否含有目标对象参数判断??) 分为 per-class类型的Advice和per-instance类型的Advice
per-class 类型的Advice
该类型的Advice的实例可以在目标对象类的所有实例之间共享
这种类型的Advice通常只是提供方法拦截的功能 该类型不会为目标对象类保存任何状态或者添加新的特性
- Before Advice
Before Advice所实现的横切逻辑将在相应的Joinpoint之前执行 在Before Advice执行完成之后 将会继续向下执行 可以通过相应异常的形式中断程序流程
public interface MethodBeforeAdvice extends BeforeAdvice{
void before(Method method,Object[] args,Object target) throws Throwable;
}
我们可以使用Before Advice进行整个系统的某些资源初始化或者其他一些准备性的工作
假设我们系统需要在文件系统的指定位置生成一些数据文件 为了复用代码 避免不必要的代码散落 我们可以为系统中相应目标类提供一个Before Advice 对文件系统的指定路径进行统一的检查或者初始化
public class ResourceSetupBeforeAdvice implements MethodBeforeAdvice {
private Resource resource;
public ResourceSetupBeforeAdvice(Resource resource)
{
this.resource = resource;
}
public void before(Method method,Object[] args, Object target)
throws Throwable {
if(!resource.exists())
{
FileUtils.forceMkdir(resource.getFileO );
}
}
}
- ThrowsAdvice
org.springframework.aop.ThrowsAdvice对应通常AOP概念中的After-ThrowsAdvic
注意 该接口中没有定义任何方法 但是在实现相应的ThrowsAdvice的时候 我们的方法定义需要遵循如下规则
void afterThrowing([Method, args, target], ThrowableSubclass);
可以根据将要拦截的Throwable的不同类型 在同一个ThrowsAdvice中实现对个afterThrowing方法
public class ExceptionBarrierThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Throwable t)
{
//普通异常处理逻辑
}
public void afterThrowing(RuntimeException e)
{
//运行时异常处理逻辑
}
public void afterThrowing(Method m,Object[] args,Object target,ApplicationException e)
{
//处理应用程序生成的异常
}
)
- AfterReturningAdvice
org.springframework.aop.AfterReturningAdvice接口定义了Spring的AfterReturningAdvice
public interface AfterReturningAdvice extends AfterAdvice {
void afterRetuming(Object returnValue, Method method, Object[] args, Object target)
throws Throwable;
}
通过Spring中的AfterReturningAdvice 我们可以访问当前Joinpoint的方法返回值,方法,方法参数以及所在的目标对象
只有在方法正常返回的情况下 AfterReturningAdcice才会执行 AfterReturningAdvice可以访问到方法的返回值 但不可以更改返回值
为了便于运营人员验证系统状态 FX的批处理程序在正常完成之后会往数据库的指定表中插入运行状态 运营人员可以通过验证这些状态判断相应的批处理任务是否成功执行 所以我们可以根据他的返回值判断批处理任务是够成功执行 所以我们可以实现一个AfterReturningAdvice对所有批处理任务的执行进行拦截
public class TaskExecutionAfterRetumingAdvice implements AfterReturningAdvice {
private SqlMapClientTemplate sqlMapClientTemplate;
public void afterRetuming(Object returnValue, Method m, Object[] args,
Object target) throws Throwable {
Class clazz = target.getClass();
getSqlMapClientTemplate().insert("BATCH.insertTaskStatus",clazz.getName());
}
public SqlMapClientTemplate getSqlMapClientTemplate(){
return sqlMapClientTemplate;
}
public void setSqlMapClientTemplate(SqlMapClientTemplate SqlMapClientTemplate) {
this.SqlMapClientTemplate = SqlMapClientTemplate;
}
}
- Around Advice
Spring AOP没有提供After(Finally)Advice 使得我们没有一个合适的Advice类型来承载类似于系统资源清除之列的横切逻辑 Spring AOP的AfterReturningAdvice不能更改Joinpoint所在方法的返回值 使得我们在方法正常返回后无法对其进行更多的干预 而这些Methodlnterceptor都可以做到
//org.aopalliance.intercept.Methodlnterceptor
//拦截在到达目标的接口上的调用。
//用户应该实现invoke(MethodInvocation)方法来修改原始行为。
public interface Methodlnterceptor extends Interceptor {
//MethodInvocation 方法调用的描述,在方法调用时提供给拦截器。方法调用是一个连接点,可以被方法拦截器拦截。控制对相应的Joinpoint的拦截行为
Object invoke(MethodInvocation invocation) throws Throwable;
}
//proceed() 方法可以让程序执行继续沿着调用链传播 继续执行链中的下一个拦截器。
如果我们在哪一个MethodInterceptor中没有调用proceed() 那么程序将会阻塞?? Joinpoint上的调用链将被中断 同一Joinpoint上的其他MethodInterceptor的逻辑以及Joinpoint方法逻辑将不会执行(调用process将会执行调用中的下一个执行器)
//简单的检测系统某些方法的执行性能
public class PerformanceMethodlnterceptor implements Methodlnterceptor {
private final Log logger = LogFactory.getLog(this.getClass());
public Object invoke(Methodlnvocation invocation) throws Throwable {
Stopwatch watch = new Stopwatch();
...
try
{
watch.start();
return invocation.proceed();
}finally
{
watch.stop();
if(logger.islnfoEnabled()))
{
logger.info(watch.toString());
}
}
}
}
我们可以在proceed()方法 也就是Joinpoint处的逻辑执行之前或者之后插入相应的逻辑 甚至捕获proceed()方法可能抛出的异常
//某个销售系统规定 在商场优惠期间 所有商品一律8折出售 那么我们就应该在系统中所有取得商品价格的位置插入这样的横切逻辑
Public class DiscountMethodlnterceptor implements Methodlnterceptor {
private static final Integer DEFAULT_DISCOUOT_RATIO = 80;
private static final IntRange RATIO_RANGE = new IntRange (5, 95 ) ;
private Integer discountRatio = DEFAULT_DISCOUOT__RATIO;
private boolean campaignAvailable;
public Object invoke (Methodlnvocation invocation ) throws Throwable {
Object returnValue = invocation.proceed();
if (RATIO_RANGE.containsInteger (getDiscountRatio ( ) ) && isCampaignAvailableO )
return ( ( Integer ) returnValue) *getDiscountRatio ( ) /100;
{
return returnValue;
}
//是否可用
private boolean isCampaignAvailable() {
return campaignAvailable;
}
public void setCampaignAvailable(boolean campaignAvailable) {
this.carepaignAvailable = campaignAvailable;
}
//获取折扣
public Integer getDiscountRatio() {
return discountRatio;
}
public void setDiscountRatio(Integer discountRatio) {
this.discountRatio = discountRatio;
)
)
DiscountMethodlnterceptor interceptor = new DiscountMethodlnterceptor();
interceptor.setCampaignAvailable(true);
interceptor.setDiscountRatio(90);
<bean id="discountInterceptor" class= "...DiscountMethodInterceptor">
<property name="campaignAvailable" value="true"/>
<property name= "discountRatio" value= "90"/>
</bean>
per-instance 类型的Advice
Introduction
Introduction 可以在不改动目标类定义的情况下 为目标类添加新的属性以及行为
就好比我们的开发人员 如果公司人员紧张 没有配备测试人员 那么通常这时开发人员就是测试人员
与per-class类型的Advice不同 per-instance类型的Advice不会在目标类所有对象实例之间共享 而是为不同的实例对象保存他们各自的状态以及相关逻辑
在Spring为目标对象添加新的属性和行为必须声明相应的接口以及相应的实现 在通过特定的拦截器将新的接口定义以及实现类中的逻辑附加到目标对象(目标对象的代理对象)之上
/**
IntroductionInterceptor 继承了MethodInterceptor以及DynamicIntroductionAdvice
*/
public interface Introductionlnterceptor extends Methodlnterceptor,
DynamiclntroductionAdvice {
)
//界定当前的IntroductionInterceptor为哪些接口类提供相应的拦截功能
public interface DynamicIntroductionAdvice extends Advice {
boolean implementsinterface(Class intf);
}
//org.aopalliance.intercept.Methodlnterceptor
//拦截在到达目标 的接口上的调用。
public interface Methodlnterceptor extends Interceptor {
//MethodInvocation 方法调用的描述,在方法调用时提供给拦截器。方法调用是一个连接点,可以被方法拦截器拦截。控制对相应的Joinpoint的拦截行为
Object invoke(MethodInvocation invocation) throws Throwable;
}
//proceed() 方法可以让程序执行继续沿着调用链传播 继续执行链中的下一个拦截器。
Introductionlnterceptor 继承了Methodlnterceptor,DynamiclntroductionAdvice
通过DynamiclntroductionAdvice 我们可以界定当前的IntroductionInterceptor为哪些接口类提供相应的拦截方法
通过MethodInterceptor IntroductionInterceptor就可以处理新添加的接口上的方法调用
注意 对于Interceptor的proceed()方法 毕竟 当前位置已经是"航程的终点" 及调用链上只有一个方法
如果把每个目标对象实例看做盒装牛奶生长线 的牛奶 现在要添加新的东西 比如生产合格证 生产合格证就是新的 Introduction逻辑 IntroductionInterceptor就是把和格证加到牛奶上的那个"人"
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rhvbsef1-1583599119406)(./img/Introduction相关类图.png)]
我们主要关注 DynamicIntroductionAdvice为首的动态分支和IntroductionInfo为首的静态可配置分支
DynamicIntroductionAdvice我们可以在运行时再去判定当前Introduction可应用到的目标接口类型 而IntroductionInfo 却要预先定义
public interface Introductionlnfo {
Class[] getlnterfaces();
}
- DelegatingIntroductionInterceptor
delegat(委托) DelegatingIntroductionInterceptor不会自己实现将要添加到目标对象上的新的逻辑行为 而是委派给其他的实现类
public interface IDeveloper {
void developSoftware();
}
public class Developer implements IDeveloper {
public void developSoftware() {
System.out.println("I am happy with programming");
}
}
DelegatingIntroductionInterceptor为Developer添加新的状态或者行为可以按照如下步骤进行
- 为要添加的新的状态和行为定义接口
public interface ITester {
boolean isBusyAsTester() ;
void testSoftware();
}
- 给出新接口的实现类 接口实现类给出将要添加到目标对象那个的具体逻辑 当目标对象将要行使新职能的时候 会通过该实现类寻求帮助(??实际上调用的是该实习类)
public class Tester implements ITester {
private boolean busyAsTester;
public void testSoftware() {
System.out.println("I will ensure the quality");
}
public boolean isBusyAsTester(){
return busyAsTester;
}
public void setBusyAsTester(boolean busyAsTester) {
this.busyAsTester = busyAsTester;
}
}
- 通过DelegatingIntroductionInterceptor进行Introduction拦截 有了新增加职能的接口定义以及相应实现类
//便捷实现IntroductionInterceptor接口。
//子类仅需要扩展此类并实现要自己引入的接口在这种情况下,委托是子类实例本身
ITester delegate = new Tester();
Delegatinglntroductionlnterceptor interceptor = new Delegatinglntroductionlnterceptor(delegate);
// 进行织入
ITester tester = (ITester)weaver.weave(developer).with(interceptor).getProxy();
tester.testSoftware();
- DelegatingIntroductionInterceptor会使用它锁持有的统一个"delegate"接口实例 供同一目标对象类的所有实例共享使用
- DelegatePerTargetObjectIntroductionInterceptor
便捷实现IntroductionInterceptor接口。
这与DelegatingIntroductionInterceptor的不同之处在于,可以使用此类的单个实例来建议多个目标对象,并且每个目标对象将具有自己的委托(而DelegatingIntroductionInterceptor共享相同的委托,因此在所有目标上都具有相同的状态)
DelegatePerTargetObjectIntroductionInterceptor 会在内部持有一个目标对象与相应Introdution逻辑实现类之间的映射关系 当每个目标对象上的新的接口方法被调用时 该对象就会拦截调用 然后以目标对象作为键 到它持有的那个映射关系中取得当前目标对象实例的Introduction实现类实例 每个不同的目标对象都对应着实现类实例 如果为找到对应的Introduction实现类实例 DelegatePerTargetObjectIntroductionInterceptor将会为其创建一个新的
//只需告诉该对象相应的委托接口类型(ITest)和对应实现类型
DelegatePerTargetObjectlntroductionlnterceptor interceptor =
new DelegatePerTargetObjectlntroductionlnterceptor(DelegateImpl.class,IDelegate.class);
//通过织入器传入的不同目标对象 生成对应着的实现类实例
如果上诉的实现类无法满足需求可以直接扩展这两个类
public class TesterFeaturelntroductionlnterceptor extends
Delegatinglntroductionlnterceptor implements ITester
private static final long serialVersionUID = -3387097489523045796L;
private boolean busyAsTester;
@Override
public Object invoke (Methodlnvocation mi ) throws Throwable {
if (isBusyAsTester()
&& StringUtils.contains (mi.getMethod() .getName() , "developSoftware" ) ){
throw new RuntimeException ( "你想累死我" );
}
return super.invoke (mi ) ;
}
public boolean isBusyAsTester ( ) {
return busyAsTester;
}
public void setBusyAsTester (boolean busyAsTester ) {
this.busyAsTester = busyAsTester;
}
public void testSoftware() {
System.out.println ( "I will ensure the quality. " );
}
}
Introduction的性能问题 与AspectJ直接通过编译器将Introduction织入目标对象不同 Spring AOP采用的是动态代理机制 在性能上 Introduction型的Advice要损塞不少 如果有必要可以考虑使用AspectJ的Introduction
Spring AOP中的Aspect
当所有的Pointcut和Advice准备好之后 就可以将其封装为Aspect
Advisot 代表Spring中的Aspect 但与正常的Aspecct不同 Advisor通常只持有一个Pointcut和一个Advice 而理论上 Aspect是可以有多个Pointcut和多个Advice
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wVoGiHCy-1583599119407)(./img/Advisor.png)]
PointcutAdvisor家族
实际上 org.springframework.aop.PointAdvisor是真正的定义一个Pointcut和一个Advice的Advisor
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yy6XacUO-1583599119408)(./img/PointcutAdvisor及相关子类.png)]
- DefaultPointcutAdvisor
该类型是最通用的PointcutAdvisor实现 除了不能为其指定Introduction类型的Advice之外 剩下的任何类型额的Pointcut 任何类型的Advice都可以通过该类型实现
Pointcut pointcut =...;//任何Pointcut类型
Advice advice =...;//除了Introduction类型的Advice
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor (pointcut,advice) ;
//或者
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor (advice) ;
advisor.setPointcut (pointcut ) ;
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor ( ) ;
advisor.setPointcut (pointcut );
advisor.setAdvice (advice) ;
//并不推荐这样使用 实际上Spring中任何的bean都可以通过Ioc容器来管理 Spring AOP中的任何概念对此也同样适用
<!-- 使用Spring Ioc容器注册和使用Spring AOP的各种概念实体 -->
<bean id="pointcut " class= " ...">
</bean>
<bean id="advice" class="... ">
</bean>
<bean id="advisor " class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut" ref = "pointcut " / >
<property name= "advice " ref ="advice " / >
</bean>
- NameMatchMethodPointcutAdvisor
NameMatchMethodPointcutAdvisor是细化后的DefaultPointcutAdvisor 它限定了自身可以使用的Pointcut类型 为NameMatchodPointcut 并且无法更改 除了不能为其指定Introduction类型的Advice之外 其他的类型额的Advice都可以使用
当通过NameMatchMethodPointcutAdvisor的setMappedName和setMappedNames方法设置警备拦截的方法名称的时候 实际上是在操作NameMatchodPointcutAdvisor下的NameMathodPointcut实例
Advice advice = ...;//除了Introduction类型的Advic
NameMatchMethodPointcutAdvisor advisor = new
NameMatchMethodPointcutAdvisor (advice) ;
advisor.setMappedName ("methodName2Intercept" ) ;
//或者
NameMatchMethodPointcutAdvisor advisor=new NameMatchMethodPointcutAdvisor(advice);
advisor.setMappedNames(new String[]{ “methodl", "method2"});
<bean id="advice" class="...">
</bean>
<bean id="advisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice">
<ref bean="advice"/>
</property>
<property name= "mappedNames">
<list>
<value>methodl</value>
...
</list>
</property>
</bean>
- RegexpMethodPointcutAdvisor
和NameMatchMethodPointcutAdvisor相似 它限定了自身可以使用的Pointcut类型 是正则表达式的Pointcut
RegexpMethodPointcutAdvisor 自身内部持有一个AbstractRegexpMethodPointcut的实例. 默认情况下 RegexpMethodPointcutAdvisor会使用JdkRegaxpMethodPointcut
<bean id="advice" class="...">
</bean>
<bean id="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="pattern">
<value>cn\.spring21\..*\.methodNamePattem</value>
</property>
<property name="advice">
<ref bean="advice"/>
</property>
<property name="perl5">
<value>false</value>
</property>
</bean>
- DefaultBeanFactoryPoincutAdvisor
该类自身绑定了BeanFactory 所以 要使用DefaultBeanFactoryPointcutAdvisor 我们的应用铁定要绑定到Spring的IOC容器
我们可以从容器中的Advice注册的beanName来关联对应的Advice. 只有当对应的Pointcut匹配成功之后 才去实例化对应的Advice 减少了容器启动初期Advisor和Advice之间的耦合性
<bean id= "advice" class= "">
</bean>
<bean id= "pointcut " class= "org.springframework.aop.support. NameJMatchMethodPointcut " >
<property name= "mappedName" value= "doSth" / >
</bean>
<bean id= "advisor" class="org.springframework.aop.support. **
DefaultBeanFactoryPointcutAdvisor">
<property name= "pointcut " ref ="pointcut "/ >
<property name="adviceBeauName" value="advice"/>
</bean>
<!-- 注意对应的advice的配置属性名称为"adviceBeanName" -->
IntroductionAdvisor分支
IntroductionAdvisor与PointcutAdvisor最本质的区别就是 IntroductionAdvisor只能应用于类级别的拦截(??) 只能使用Introduction型的Advice 而不能像PointAdvisor那样 可以使用任何类型的Pointcut 以及差不多任何类型的Advice 也就是说 IntroductionAdvisor纯粹就是为了Introduction而生的
IntroductionAdvisor只有一个默认实现DefaultIntroductionAdvisor
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T9WHktcA-1583599119409)(./img/IntroductionAdvisor类结构图.png)]
<bean id="introductionInterceptor" class="org.springframework.aop.support.DelegatingIntroductionInterceptor">
<constructor-arg>
<bean class="...DelegateImpl">
</bean>
</constructor-arg>
</bean>
<bean id="introductionAdvisor" class="org.springframework.aop.support.DefaultlntroductionAdvisor">
<constructor-arg index="0" >
<ref bean="introductionlnterceptor"/>
</constructor-arg>
<constructor-arg index="l" >
<ref bean="introductionlnterceptor"/>
<constructor-arg>
</bean>
在构造该实例的时候 传入两个"instroductionInterceptor" 一个是作为Introduction型的Advice实例 后则是作为IntroductionInfo的实例 这是因为 DelegatingIntroductionInterceptor实现了IntroductionInfo接口
Ordered的作用
如果系统存在对同一个JoinPoint 执行多个横切逻辑的操作 那么这时执行顺序就显得格外重要 即如果这些Advisor所关联的Advisor所关联的Advice之间存在优先顺序依赖
当在同一个JoinPoint调用 process方法时 会按照调用链执行 整个流程向是进栈和出栈 先进后出 所以 优先级高的将先进后出
``Spring在处理统一Jointpoint处的多个Advisor的时候 实际上会按照指定的顺序和优先级来执行它们 顺序号决定优先级 顺序号越小 优先级越高 优先级排在前面的 将被优先执行
默认情况选下 Spring会按照他们的声明书序来应用他们
<!-- 可以通过以下进行配置 -->
<bean id="pemissionAuthAdvisor " class= "..PermissionAuthAdvisorw" >
<property name="order” value= "1"/>
</bean>
<bean id= "exceptionBarrierAdvisor" class= "...ExceptionBarrierAdvisor" >
<property name="order" value="0"/>
</bean>
`` 在Spring 框架中 我们可以通过让相应的Advisor以及其他顺序紧要的bean实现org.springframework.core.Ordered接口来明确指定相应顺序号
如何与ProxyFactory打交道
ProxyFactory 是SpringAOP 最基本的织入器
Spring AOP是及与代理模式的AOP实现 织入过程中他会返回织入横切逻辑的目标对象 为ProxyFactory提供必要的"生产原资料" ProxyFactory就会返回哪个织入完成的代理对象
ProxyFactory weaver=new ProxyFactory("目标对象");
// 或者
// ProxyFactory weaver = new ProxyFactory();
// weaver.setTarget ( task ) ;
Advisor advisor =weaver.addAdvisor (advisor ) ;
Object proxyObject = weaver.getProxy ( ) ;
使用ProxyFactory只需要指定另个基本的东西
- 第一个是要对其进行织入的目标对象 可以直接通过构造方法传入 或者通过setter反复设置
- 第二个是要应用到目标对象的Aspect 在Sprin中叫做Advisor
wear.addAdvice(…)该代码直接指定各种类型的Advice
//前提 如果不使用Advice
- 对于Introduction 之外的Advice类型 ProFactory内部就会为这些Advice构造相应的Advisor 只不过在位他们构造Advisor中使用的Pointcut为Pointcut.TRUE 它将适用于系统中所有可识别的Joinpoint(那如果Advisor中规定了Pointcut 会怎么处理??)
- 如果添加的类型是Introduction类型 如果是IntroductionInfo的子类实现 因为他本身包含了必要的描述信息 框架内部会为其构造一个DefaultIntroductionAdvisor 而如果是DynamicIntroductionAdvice的子类实现 框架内将抛出AopConfigException
//目标类
public interface ITask {
void execute(TaskExecutionContext ctx);
)
public class MockTask implements ITask {
public void execute(TaskExecutionContext ctx) {
System.out.println("task executed.");
)
}
//织入到Joinpoint处的横切逻辑 某个Advoce实现
public class PerformanceMethodlnterceptor implements Methodlnterceptor {
private final Log logger = LogFactory.getLog(this.getClass());
public Object invoke(Methodlnvocation invocation) throws Throwable {
Stopwatch watch = new Stopwatch();
try
{
watch.start();
return invocation.proceed();
}
finally
{
watch.stop();
if(logger.islnfoEnabled())
{
logger,info(watch.toString),*
}
}
}
}
//基于接口的代理
MockTask task = new MockTask();
ProxyFactory weaver = new ProxyFactory(task);
//告知我们要对哪个接口进行代理
//即使不指定 默认情况下 ProxyFactory只坚持目标类实现了相应的接口 也会对目标类进行基于接口的代理
weaver.setlnterfaces(new Class[]{ITask.class));
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
advisor.setMappedName("execute");
advisor.setAdvice(new PerformanceMethodlnterceptor() );
weaver.addAdvisor(advisor);
ITask proxyObject =(ITask)weaver.getProxy();
proxyObject.execute(null);
- 基于类的代理
public class Executable {
public void execute(){
System.out.println("Executable without any Interfaces");
)
}
ProxyFactory weaver = new ProxyFactory(new Executable());
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
advisor.setMappedName("execute");
advisor.setAdvice(new PerformanceMethodlnterceptor());
weaver.addAdvisor(advisor);
Executable proxyObject =(Executable)weaver.getProxy();
proxyObject.execute();
System.out.println(proxyObject.getClass() );
简而言之 以下情况会使用类代理
- 目标类没有实现任何接口
- 如果ProxyFactory的proxyTargetClass属性设置为true ProxyFactory会采用基于类的代理
- optimize属性设置为true ProxyFactory会采用基于类的代理
- Introduction的织入
Introduction的Advice比较特殊
- Introduction可以为已经存在的对象那个类型添加新的行为 只能应用于对象级别的拦截 而不是通常的方法拦截 所以 Introduction的织入过程中 不需要指定Pointcut而只需要指定目标接口类型
- Spring 的Introduction 支持只能通过接口定义为当前对象添加新的行为 所以需要在织入时指定目标接口类型
ProxyFactory weaver = new ProxyFactory(new Developer());
weaver.setlnterfaces(new Class[](IDeveloper.class,ITester.class));
TesterFeaturelntroductionlnterceptor advice = new TesterFeaturelntroductionlnterceptor();
weaver.addAdvice(advice);
//DefaultlntroductionAdvisor advisor = new DefaultlncroductionAdvisor(advice,advice);
//weaver.addAdvisor(advisor);
Object proxy = weaver.getProxy();
((ITester)proxy).testSoftware();
((IDeveloper)proxy).devolopSoftware();
//TesterFeatureIntroductionlnterceptor是IntroductionInfo的子类
对Introduction 进行织入 新添加的接口类型必须通过setInterfaces指定
ProxyFactory的本质
了解ProxyFactory 我们得先从它的根说起 即org.springframework.aop.framework.AopProxy 该接口的定义如下
public interface AopProxy {
Object getProxy();
Object getProxy(ClassLoader classLoader);
)
Spring AOP框架内使用AopProxy对使用的不同的代理实现机制进行了抽象
即 针对不同的代理实现机制提供相应的AopProxy子类实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GDryfhS4-1583599119410)(./img/AopProxy相关类结构图.PNG)]
基于动态代理的实现类 JdkDynamicAopProxy 因为动态代理需要通过InvocationHandler提供调用拦截 所以该类同时实现了InvocationHandler接口
不同AopProxy实现的实例化过程采用工厂模式进行封装
public interface AopProxyFactory {
AopProxy createAopProxy (AdvisedSupport config) throws AopConfigException;
}
//AdvisedSupport AOP代理配置管理器的基类。 AdvisedSupport封装了相关信息 来决定生成什么类型的AopProxy
ProxyConfig其实就是一个普通的JavaBean 他定义了5个boolean型的属性 分别控制在生成代理对象的时候 应该采取哪些行为措施
- proxyTargetClass 如果proxyTargetClass属性设置为true 则ProxyFactory将会使用CGLIB对目标对象进行代理 默认值为false
- optimize 该属性的主要用于告知代理对象是否需要采取进一步的优化措施 是否忽略 添加或者移除了相应的Advice 该属性值为ture时将采取CGLIB进行代理对象的生成 默认情况下为false
- opaque 该属性用于控制生成的代理对象那个是否可以强制转型为Advised 默认值为false 表示任何生成的代理对象都可强制转型为Advised 我们可以通过该类型查询代理对象那个的一些状态
- exposeProxy 可以让Spring AOP框架在生成代理对象时 将代理对象绑定到ThreadLocal 如果目标对象需要访问当前代理对象 可以通过AopContext.currentProxy()取得 默认值为false
- frozen 如果将frozen设置为true 那么一旦针对代理对象生成的各项信息配置完成 则不允许更改 默认情况下为false
要生成代理对象 只有ProxyConfig提供的控制信息还不够 我们还需要生成代理对象的一些具体信息 Advised接口则负责目标类生成代理对象 要为代理对象加入何种横切逻辑等 默认情况下 Spring AOP框架返回的代理代理对象都可以强制转型为Advised
- 我们可以使用Advised接口访问相应代理对象所支持的Advisor 进行增删的操作
- 即使代理对象已经生成完毕 更多的时候应用于测试场景
注意
上诉内容为博主看Spring 解密的摘要和个人理解
Spring 解密是一本很好的Spring 入门书籍