Spring第二课 AOP

AOP基础

AOP是有特定工作场合的,它只适合那些具有横切逻辑的应用场合,如性能检测,访问控制,事务管理以及日志记录。
AOP是Aspect Oriented Programing的简称,被翻译为“面向切面编程”,按照软件重构思想的观念,如果多个类中出现相同的代码,则应该考虑定义一个父类,将这些相同的代码提取到父类中,子类就可以继承和复用父类的方法,通过引入父类消除多个类重复代码的方式在大多数的情况下是可行的,但世界并非永远这样简单
示例代码
pmonitor是方法性能监控代码,它在方法调用前启动,在方法调用返回前结束,黑色字体是事务开始和事务结束。这种情况就无法通过抽象父类来解决。这就需要通过使用AOP的方式解决。
AOP希望将这些分散在各个业务逻辑代码中的相同代码通过横向切割的方式抽取到一个独立的模块中。

AOP术语

连接点(Joinpoint)
特定点是程序执行的某个特定的位置,如类开始初始化前、类初始化后、类的某个方法调用前或者调用后以及方法抛出异常后。一个类或者一段程序代码拥有一些具有边界性质的特定点,这些代码的特定点就称为连接点。
Spring仅支持方法的连接点,即仅在方法调用前、方法调用后、方法抛出异常时及方法调用前后。

切点(Pointcut)
每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点,即连接点是程序客观存在的事物。但在众多的连接点如何点位感兴趣的连接点呢?AOP通过切点定位特定的连接点。
连接点相当于数据库的记录,切点相当于查询条件,一个切点可以匹配多个连接点。
在Spring中,切点通过org.springframework.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件,SpringAOP的规则解析引擎负责解析切点所设定的查询条件,找到对应的连接点。确切的说应该是执行点。

增强(Advice)
增强是织入目标类连接点上的一段程序 代码,在Spring中增强除了用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这就是执行点的方位。结合执行点的方位信息和切点信息,就可以找到特定的连接。正位信息,所以Spring所提供的增强接口都是带访问名的。如BeforeAdvice、AfterReturningAdvice、ThrowsAdvice等。BeforeAdvice表示方法调用前的位置,AfterReturningAdvice表示方法返回后的位置,所以只有结合切点和增强才能确定特定的连接点并增强逻辑。

目标对象(Target)
目标对象表示增强逻辑的织入目标类。如果没有AOP,那么目标业务类需要自己实现所有的逻辑。

引介(Introduction)
引介是一种特殊的增强,它为类添加一些属性和方法,这样即使一个业务类没有实现某个接口,通过AOP的引介功能,也可以动态的为该业务类添加接口的实现逻辑,让业务类成为这个接口类的实现类。

织入(weaving)
织入是将增强添加到具体连接点的过程。AOP有三种织入方式:

  • 编译期织入,这要求使用特殊的java编译器。
  • 类装载期织入,这要求使用特殊的类装载器。
    动态代理织入,在运行期间为目标类添加挣钱生成子类的方式。
    Spring采用动态代理方式织入,而AspectJ采用编译期织入和类装载期织入

代理(Proxy)
一个类被AOP织入增强后,就产生了一个结果类,它是融合了原类和增强逻辑的代理类,根据不同的代理方式,代理类既可能是原类具有相同接口的类,也可能是原类的子类,所以采用与原类相同的方式调用代理类。

切面(Aspect)
切面由切点和增强(引介)组成,它既包括横切逻辑的蒂尼,也包括连接点的定义,SpringAOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入切面所指定的连接点中。
AOP的工作重心在于如何将增强应用于目标对象的连接点上。

AOP的实现者

AOP工具的设计目标是把横切的问题(如性能监控、事务管理)模块化,使用类似于OOP的方式进行切面编程工作,位于AOP工具核心的是连接点模型,它提供了一种机制,可以定位到需要在哪里发生横切。

  • AspectJ:是语言级的AOP实现,拓展了java语言,定义了AOP语法,能够在编译期提供横切代码的织入,所以他有一个专门的编译器用来生成java字节码规范的Class文件。
  • AspectWerkz
    AspectWerkz
  • JBoss AOP
    JBoss AOP
  • Spring AOP
    Spring AOP使用纯Java实现,它不需要专门的编译过程,也不需要特殊的类装载器,它在运行期通过代理的方式向目标类织入增强代码。Spring并不尝试提供最完整的AOP实现,相反,它侧重提供一种和Spring IOC容器整合的AOP实现,用以解决企业级开发中的常见问题。Spring中可以无缝的将SpringAOP、IOC和AspectJ整合在一起。
基础知识

Spring AOP使用动态代理技术在运行期织入增强的代码,Spring AOP使用两种代理机制,一种是基于JDK的动态代理,另一种是基于CGLib的动态代理,之所以需要两种代理模式,是因为JDK本身只提供接口的代理。而不提供类的代理。

JDK动态代理

java提供了动态代理技术,它允许开发者在运行期创建接口的代理实例。动态代理是实现AOP的绝好底层技术。
JDK的动态代理主要涉及java.lang.reflect包的两个类;Proxy和InvocationHandler。其中,InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类代码,动态的将横切逻辑和业务逻辑编织在一起。
而Proxy利用InvocationHandler是一个接口动态创建一个符合某一接口的实例,生成目标类的代理对象
JDK代理实现
解释说明
根据业务结合JDK代理
上面代码完成了业务类代码和横切代码的编织工作并生成了代理实例,在2处让PerformanceHandler将性能监视横切逻辑编入到ForumService实例中,然后在3处通过Proxy的newProxyInstance()静态方法未编制了业务逻辑和性能监视逻辑的handler创建一个符合ForumService接口的代理实例。该方法的第一个参数为类加载器,第二个参数表示代理实例所需要实现的一组接口,第三个入参是整合了业务逻辑和横切逻辑的编织器对象。

CGLib动态代理

使用JDK创建代理有一个限制,即它只能为借口创建代理实例,这一点可以从public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)这个接口方法可以发现,第二个参数需要代理实例实现的接口列表。虽然面向接口编程是大师Rod Johnson所推崇,但在实际开发中,许多开发者对此也深感困惑,难道对于一个简单的业务操作也要老老实实的创建5个类吗(领域对象类、DAO接口、DAO实现类、Service接口和Service实现类)?难道不能通过实现类构建程序吗?这时CGLib作为替代者填补了这项空缺。

CGlib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。
CGLib Proxy
在上面的代码中,用户可以通过getProxy(Class clazz)为一个类创建动态代理对象,该代理对象通过拓展clazz实现代理。在这个代理对象中,织入性能监视的横切逻辑(粗体部分),public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)是CGLib定义的Interceptor接口方法,它拦截所有目标类方法的调用,其中obj表示目标类的实例,method为目标类方法上的反射对象。args为方法的动态入参,proxy为代理类实例。
CGLib结合业务实现
由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中的final或private进行代理

代理知识总结

Spring AOP的底层就是通过使用JDK或CGLib动态代理技术为目标Bean织入横切逻辑的。虽然上面通过JDK或CGLib实现了性能监控的横切逻辑的动态织入,但这种存在方式存在三个明显需要改进的地方。

  • 目标类的所有方法都添加了心梗监视横切逻辑,而有时这并不是我们所期望的,我们可能只希望对业务类中的某些特定的方法添加横切逻辑。
  • 通过硬编码的方式制定了织入横切逻辑的织入点,即在目标类业务方法的开始和结束前织入代码
  • 手工编写代理实例的创建过程,在为不同类创建代理时,需要分别编写相应的创建代码,无法做到通用。
    以上三点问题在AOP中占用重要的地位,因为Spring AOP的主要工作就是围绕以上三点展开的:Spring AOP通过Pointcut指定在那些类的那些方法上织入横切逻辑,通过Advice(增强)描述横切逻辑和方法的具体织入点(方法前,方法后,方法两端等),此外,Spring通过Advisor(切面)将Pointcut和Advice组装起来,有了Advisor信息,Spring就可以利用JDK或CGLib动态代理技术采用统一的方式为目标Bean创建织入切面的代理对象了。
    CGLib所创建的动态代理对象的性能依旧比JDK所常见的动态代理对象的性能高不少(大概10倍),但CGLib在所创建代理对象时所花费的时间却比JDK动态代理多(大概8倍)。对singleton的代理对象或者具有实例池的代理,因为无须频繁的创建代理对象,所以比较适合采用CGLib动态代理技术,反之则适合采用JDK动态代理结束。
创建增强类型

Spring使用增强类定义横切逻辑,同时由于Spring只支持方法连接点,增强还包括在方法的哪一点加入横切代码的方位信息,所以增强既包含横切逻辑,又包含部分连接点信息。
AOP联盟为增强定义了org.aopalliance.aop.Advice接口,Spring支持5中类型的增强:

  • 前置增强:org.springframework.aop.BeforeAdvice代表前置增强,因为Spring只支持方法级别的增强,所以MethodBeforeAdvice是目前可用的前置增强,表示在目标方法执行前实施增强,而BeforeAdvice是为了将来版本拓展定义的。
  • 后置增强:org.springframework.aop.AfterReturningAdvice代表后置增强,表示在目标方法执行后实施增强。
  • 环绕增强:org.aopalliance.intercept.MethodInterceptor代表环绕增强,表示在目标方法执行前后实施增强。
  • 异常抛出增强:org.springframework.aop.ThrowsAdvice代表抛出异常增强,表示在目标方法抛出异常后实施增强。
  • 引介增强:org.springframework.aop.IntroductionInterceptor代表引介增强,表示在目标类中添加一些新的方法和属性。
    这些增强接口都有一些方法,通过实现这些接口方法,并在接口方法中定义横切逻辑,就可以将它们织入目标类方法的响应连接点位置。
前置增强
实例:服务员提供服务(1.欢迎顾客2.提供服务)
public interface Waiter {
	void greetTo(String name);
	void serveTo(String name);
}

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+"....");
	}
}
NaviteWaiter只是简单地向顾客打招呼,没有使用礼貌。

public class GreetAfterAdvice 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);
	}
	
}
BeforeAdvice是前置增强的接口,方法前置增强的MethodBeforeAdvice接口是其子类,
Spring目前只提供方法调用的前置增强,在以后的版本中可能会看到Spring提供的其他
类型的前置增强,这正是BeforeAdvice接口存在的意义。MethodBeforeAdvice接口仅
定义了唯一的方法:before(Method method, Object[] args, Object target) throws 
Throwable。其中,method为目标类的方法,args为目标类方法的入参,而obj为目标
类的实例。当该方法发生异常时,将阻止目标类方法的执行。

public class MyTest{
	
	public static void main(String[] arg) throws Exception {
		Waiter target=new NaiveWaiter();
		GreetAfterAdvice advice=new GreetAfterAdvice();
		//spring提供的代理工厂
		ProxyFactory pFactory=new ProxyFactory();
		//设置代理目标
		pFactory.setTarget(target);
		pFactory.addAdvice(advice);
		Waiter waiter=(Waiter) pFactory.getProxy();
		waiter.greetTo("lisi");
		waiter.serveTo("tom");
	}
}

解析ProxyFactory
ProxyFactory内部就是使用JDK或CGLib动态代理技术将应用增强到目标类当中。Spring定义了org.springframework.aop.framework.AopProxy.并提供了两个final类型的实现类。
分别是:Cglib2AopProxy和JdkDynamicAopProxy。
从字面意思上就可以看出他它们分别使用Cglib和Jdk的动态代理。如果通过ProxyFactory使用的setInterfaces(Class[] interfaces)方法指定目标接口进行代理,表示ProxyFactory使用JdkDynamicAopProxy。如果是针对类的代理,或者启用代理优化(pFactory.setOptimize(true))则使用Cglib2AopProxy。
值得一提的是,在使用CGLib动态代理技术时,必须引入CGLib库。

在Spring中配置
使用ProxyFactory比直接使用CGLib或者JDK动态代理技术创建代理省事的多,当然也可以通过Spring配置文件来配置。

<bean id="greetingAdvice" class="com.spring.GreetAfterAdvice" />
	 <bean id="target" class="com.spring.NaiveWaiter" />
	 <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
	 p:proxyInterfaces="com.spring.Waiter"
	 p:interceptorNames="greetingAdvice"
	 p:target-ref="target"
	 />
	 
ProxyFactoryBean负责为其他Bean创建代理实例,它在内部使用,ProxyFactory完成工作
  • target:代理目标对象。
  • proxyInterfaces:代理索要实现的接口,可以是多个接口。该属性还有一个别名属性interfaces。
  • interceptorNames:需要织入目标对象的Bean列表,采用Bean的名称指定,这些Bean必须是实现了org.aopalliance.intercepter.MethodInterceptor或org.springfamework.aop.Advisor的Bean,配置中的顺序对应调用中的顺序。
  • singleton:返回的代理是否是单实例,默认是单实例。
  • optimize:当设置为true时,强制使用CGLib动态代理,对于Singleton的代理推荐使用CGLib的动态代理。
  • proxyTargetClass:是否对类进行代理(而不是对接口进行代理),当设置为true时,使用CGLib动态代理,这时无需设置proxyInterfaces属性,即使设置了也会被ProxyFactoryBean忽略。
后置增强

后置增强是在目标类方法后调用

假设服务生在每次服务后,也要使用规范礼仪用语。
public class GreetAfterAdvice implements MethodBeforeAdvice,AfterReturningAdvice{

	@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);
	}
	@Override
	public void afterReturning(Object returnValue, Method method, 
	Object[] args, Object target) throws Throwable {
		String clientName=(String) args[0];
		System.out.println("Finish! Mr."+clientName);
	}
}
实现了前置和后置增强的接口。
public class MyTest{
	
	public static void main(String[] arg) throws Exception {
		Waiter target=new NaiveWaiter();
		GreetAfterAdvice advice=new GreetAfterAdvice();
		//spring提供的代理工厂
		ProxyFactory pFactory=new ProxyFactory();
		//设置代理目标
		pFactory.setTarget(target);
		pFactory.addAdvice(advice);
		Waiter waiter=(Waiter) pFactory.getProxy();
		waiter.greetTo("lisi");
		waiter.serveTo("tom");
	}
}
结果:
How are you! Mr.lisi
greet to lisi....
Finish! Mr.lisi
How are you! Mr.tom
serving to tom....
Finish! Mr.tom
可以看见在方法的执行前后分别进行了添加增强逻辑。
afterReturning(Object returnValue, Method method, Object[] args, Object target)
returnValue:是目标实例方法返回结果
method:为目标类方法
args:为实例入参参数
obj:为目标类实例
环绕增强

环绕增强允许在目标类方法调用前后织入横切逻辑,它综合实现了前置、后置增强功能。

public class GreetAfterAdvice implements MethodInterceptor{
	//截获目标方法的执行,并在前后添加横切逻辑
	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		Object[] args=invocation.getArguments();
		String name=(String) args[0];
		System.out.println("Hello "+name);
		//通过反射机制调用方法
		Object obj=invocation.proceed();
		System.out.println("end "+name);
		return obj;
	}
}

public class MyTest{
	
	public static void main(String[] arg) throws Exception {
		Waiter target=new NaiveWaiter();
		GreetAfterAdvice advice=new GreetAfterAdvice();
		//spring提供的代理工厂
		ProxyFactory pFactory=new ProxyFactory();
		//设置代理目标
		pFactory.setTarget(target);
		pFactory.addAdvice(advice);
		Waiter waiter=(Waiter) pFactory.getProxy();
		waiter.greetTo("lisi");
		waiter.serveTo("tom");
	}
}

结果:
Hello lisi
greet to lisi....
end lisi
Hello tom
serving to tom....
end tom

Spring直接使用AOP联盟所定义的MethodInterceptor作为环绕增强的接口,该接口拥有唯一的接口方法
Object invoke(MethodInvocation invocation) throws Throwable,MethodInvocation 不但
封装了目标方法及其入参数组,还封装了目标目标方法所在的实例对象。
通过invocation.getArguments()可以获取目标方法的入参数组。
通过invocation.proceed()可以反射调用目标实例的响应方法。
异常抛出增强

异常抛出增强最适合的场景是事务管理,当参与事物的某个DAO发生异常时,事务管理器就必须回滚事务。

public class TranscationManage implements ThrowsAdvice{
	public void afterThrowing(Method method,Object[] args,Object target,
			Exception ex) {
		System.out.println("----------------");
		System.out.println("method:"+method.getName());
		System.out.println("抛出异常:"+ex.getMessage());
		System.out.println("成功回滚事务!");
	}
}

ThrowsAdvice异常抛出增强接口没有定义任何方法,它是一个标签接口(标签接口就是代表没有任何属性和方法的接口),在运行时,Spring采用反射机制自行判断。
==afterThrowing(Method method,Object[] args,Object target,Exception ex) ==
这个方法名必须为afterThrowing,方法入参如下:Method method、Object[] args、Object target这三个参数要么都提供,要么都不写。其它的多参数和少参数都是错误的。
最后一个参数Exception ex是Throwable或其子类。
可以在同一个异常中抛出增强中定义多个afterThrowing()方法,当目标方法抛出异常时,Spring会自动选择最匹配的增强方法。

引介增强

引介增强是一种特殊的增强类型,它不是在目标方法周围织入增强,而是为目标类创建新的方法和属性。所以引介增强连接点是类级别的,而非方法级别的。通过引介增强可以为目标类添加一个接口的实现,即原来目标类未实现某个接口,通过引介增强可以为目标类创建某个接口的代理。

创建切面

在介绍增强时,增强被织入目标类的所有方法中。假设我们希望有选择的织入目标类的某些特定的方法中,就需要使用切点进行目标连接点定位,描述连接点时进行AOP编程最主要的工作。
增强提供了连接点的方位信息,如织入到方法的前面、后面等,而切点进一步描述了织入那些类的那些方法上。
Spring通过org.springframework.aop.Pointcut接口描述切点,Pointcut由ClassFilter和MethodMatcher构成,它通过ClassFilter定位到某些特定类上,通过MethodMatcher定位到某些特定方法上,这样Pointcut就拥有了描述某些类的某些特定方法的能力。
Spring支持两种方法匹配器:静态方法匹配器和动态方法匹配器。所谓静态方法匹配器,仅对方法名签名(包括方法名和入参类型及顺序)进行匹配。而动态方法匹配器会在运行时检查方法入参的值。静态匹配仅会判别一次,而动态匹配因为每次调用方法的入参都可能不一样,所以每次调用方法都必须判断。因此动态代理对性能影响较大。一般情况下动态匹配不常使用。方法匹配器的类型由isRuntime()方法的返回值决定,返回false表示静态方法匹配器,返回true表示动态方法匹配器。

切点类型

Spring提供6种切点类型:

  • 静态方法切点:org.springframework.aop.support.StaticMethodMatcherPointcut是静态方法切点抽象类,默认情况下它是匹配所有的类。StaticMethodMatcherPointcut有两个主要的子类,分别是:NameMatchMethodPointcut和AbstractRegexpMethodPointcut,前者提供简单的字符串匹配方法签名。而后者使用字符串匹配方法签名。
  • 动态方法切点:org.springframework.aop.support.DynamicMethodMatcherPointcut,是动态方法的抽象类,默认情况下,匹配所有的类。
  • 注解切点:org.springframework.aop.support.annotation.AnnotationMethodMatcherPointcut实现了类表示注解的切点。
  • 表达式切点:org.springframework.aop.support.ExpressionPointcut接口主要是为了支持AspectJ切点表达式。
  • 流程切点:org.springframework.aop.support.ControlFlowPointcut实现了类表示控制流程切点。
  • 复合切点:org.springframework.aop.support.ComposablePointcut是为了创建多个切点而提供的方便操作类。它的所有方法都返回ComposablePointcut类,这样就可以使用链接表达式对切点进行操作。
切面类型

由于增强既包含横切代码又包括部分连接点信息。所以仅通过增强类生成一个切面。但切点仅代表目标类连接点的部分信息(类和方法的定位),所以仅有切点无法制作出一个切面,必须结合增强才能制作出切面。Spring使用接口表示切面的概念,一个切面同时包含横切代码和连接点的信息。切面可以分为三类,一般切面,切点切面,引介切面。

  • Advisor:代表一般切面,仅仅包含一个Advice。因为Advice包含了横切代码和连接点信息,所以Advice本身就是一个简单的切面,只不过它代表横切的连接点是所有目标类的所有方法,因为这个横切面太宽泛,所以一般不会使用。
  • PointcutAdvisor:代表具有切点的切面,包含Advice和Pointcut两个类,这样就可以通过类,方法名及方法方位信息等信息灵活的定位切面连接点,提供更具适用性的切面。
  • IntroductionAdvicsor:代表引介切面,引介切面是对应引介增强的特殊切面,它应用于类层面上,所以引介切点使用ClassFilter进行定义。
静态普通方法名匹配切面

StaticMethodMatcherPointcutAdvisor代表一个静态方法匹配切面,它通过StaticMethodMatcherPointcut来定义切点,并通过类过滤和方法名来匹配所定义的切点。

通过Spring配置文件配置
public class Waiters{
	public void greetTo(String name) {
		System.out.println("greet to "+name+"....");
	}
	public void serveTo(String name) {
		System.out.println("serving to "+name+"....");
	}
}

public class Seller {
	public void greetTo(String name) {
		System.out.println("Seller greet to "+name);
	}
}

public class GreetAdvice implements MethodBeforeAdvice{

	@Override
	public void before(Method method, Object[] args, Object target) throws
	 Throwable {
		//输出切点
		System.out.println(target.getClass().getName()+"."+method.getName());
		String name=(String) args[0];
		System.out.println("Advisor,Welcome "+name);
	}
}

public class GreetAdvisor extends StaticMethodMatcherPointcutAdvisor{
	
	private static final long serialVersionUID = -44213783386576028L;
	
	public boolean matches(Method method, Class<?> targetClass) {
		// 切点方法的匹配规则
		return "greetTo".equals(method.getName());
	}
	public ClassFilter getClassFilter() {
		return new ClassFilter() {
			
			@Override
			public boolean matches(Class<?> clazz) {
				// 切点类匹配规则为:Waiters类或者子类
				return Waiters.class.isAssignableFrom(clazz);
			}
		};
	}
}

<bean id="waiterTarget" class="com.spring.Waiters" />
	 <bean id="sellerTarget" class="com.spring.Seller" />
	 <bean id="greetingAdvice" class="com.spring.GreetAdvice" />
	 <bean id="GreetAdvisor" class="com.spring.GreetAdvisor" 
	 p:advice-ref="greetingAdvice" />
	 
	 <bean id="parent" abstract="true"
	 class="org.springframework.aop.framework.ProxyFactoryBean"
	 p:interceptorNames="GreetAdvisor"
	 p:proxyTargetClass="true" />
	 <bean id="waiters" parent="parent" p:target-ref="waiterTarget"/>
	 <bean id="seller" parent="parent" p:target-ref="sellerTarget"/>

public class MyTest{
	@SuppressWarnings("resource")
	public static void main(String[] arg) throws Exception {
		String configPath="classpath:applicationContext.xml";
	ApplicationContext aContext=new ClassPathXmlApplicationContext(configPath);
		Waiters waiters= (Waiters) aContext.getBean("waiters");
		Seller seller=(Seller) aContext.getBean("seller");
		waiters.greetTo("lisi");
		waiters.serveTo("zhangsan");
		seller.greetTo("seller");
	}
}
结果:
com.spring.Waiters.greetTo
Advisor,Welcome lisi
greet to lisi....
serving to zhangsan....
Seller greet to seller

可见切面只织入Waiter的greetTo()方法方法调用前的连接点上,Waiter的serveTo()方法和Seller中的
greetTo()方法没有织入切面。

正则表达式
正则表达式
正则表达式
转义字符

动态切面

Spring提供了用于创建动态切面的DynamicMethodMatcherPointcutAdvisor抽象类,因为该类在功能上和其它类有重叠,会给开发者造成选择上的困惑,所以在Spring2.0已经过期,可以使用DefaultPointcutAdvisor和DynamicMethodMatcherPointcut完成相同的功能。
DynamicMethodMatcherPointcutAdvisor是一个抽象类,它将isRuntime()标识为final且返回true,这样其子类就一定是一个动态切点,该抽象类默认匹配所有的类和方法,因此需要拓展该类编写符合要求的动态切点。

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) {
				// 切点类匹配规则为:Waiters类或者子类
				System.out.println("调用getClassFilter()对"+clazz.getName()
				+"做静态检查");
				return Waiters.class.isAssignableFrom(clazz);
			}
		};
	}
	
	@Override
	public boolean matches(Method method, Class<?> targetClass) {
		System.out.println("调用matches()对"+targetClass.getName()+"."
		+method.getName()+"做动态检查");
		return "greetTo".equals(method.getName());
	}
	@Override
	public boolean matches(Method method, Class<?> targetClass, Object... args) {
		System.out.println("调用matches()对"+targetClass.getName()+"."
		+method.getName()+"做动态检查");
		String name=(String) args[0];
		return specialClientList.contains(name);
	}
}

GreetingDynamicPointcut类既有用于静态切点检查的方法,又有用于动态切点检查的方法,由于动态
切点检查会对性能造成很大影响,所以应当尽量避免在运行时每次都对目标的各个方法进行动态检查。
Spring采用这样的机制:在创建代理时对目标类每个链接点使用静态切点检查,如果仅通过静态切点检查
就知道连接点是不匹配的,则在运行时就不再进行动态检查;如果静态切点检查是匹配的,则在运行时才
进行动态切点检查。
在动态切点类中,定义静态切点检查的方法可以避免不必要的动态检查操作,从而极大的提高运行效率。

 <bean id="waiterTarget" class="com.spring.Waiters" />
	 <bean id="dynamicAdvisor" 
	 class="org.springframework.aop.support.DefaultPointcutAdvisor">
	 	<property name="pointcut">
	 		<bean class="com.spring.GreetingDynamicPointcut" />
	 	</property>
	 	<property name="advice">
	 		<bean class="com.spring.GreetAdvice" />
	 	</property>
	 	</bean>
	 <bean id="waiter2"
	 class="org.springframework.aop.framework.ProxyFactoryBean"
	 p:interceptorNames="dynamicAdvisor"
	 p:target-ref="waiterTarget"
	 p:proxyTargetClass="true" />
public class MyTest{
	@SuppressWarnings("resource")
	public static void main(String[] arg) throws Exception {
		String configPath="classpath:applicationContext.xml";
		ApplicationContext aContext=
		new ClassPathXmlApplicationContext(configPath);
		Waiters waiters= (Waiters) aContext.getBean("waiter2");
		waiters.greetTo("lisi");
		waiters.serveTo("zhangsan");
		waiters.greetTo("John");
		waiters.serveTo("John");
	}
}
结果:
调用getClassFilter()对com.spring.Waiters做静态检查
调用matches()对com.spring.Waiters.greetTo做静态检查
调用getClassFilter()对com.spring.Waiters做静态检查
调用matches()对com.spring.Waiters.serveTo做静态检查
调用getClassFilter()对com.spring.Waiters做静态检查
调用matches()对com.spring.Waiters.toString做静态检查
调用getClassFilter()对com.spring.Waiters做静态检查
调用matches()对com.spring.Waiters.clone做静态检查
调用getClassFilter()对com.spring.Waiters做静态检查
调用matches()对com.spring.Waiters.greetTo做静态检查
调用matches()对com.spring.Waiters.greetTo做动态检查
greet to lisi....
调用getClassFilter()对com.spring.Waiters做静态检查
调用matches()对com.spring.Waiters.serveTo做静态检查
serving to zhangsan....
调用matches()对com.spring.Waiters.greetTo做动态检查
com.spring.Waiters.greetTo
Advisor,Welcome John
greet to John....
serving to John....

Spring会在创建代理织入切面时,对目标类中的所有方法进行静态切点检查,在生成织入切面的代理对象
之后,第一次调用代理类的每一个方法时都会一次静态切点检查,如果本次检查就能从候选列表将方法排除,
则以后对该方法的调用就不再执行动态切点检查。对于那些静态方法检查匹配的方法,在后续调用该方法时,
则将执行动态切点检查。

如果将GreetingDynamicPointcut类中getClassFilter()和静态检查matches(Method method, 
Class<?> targetClass))方法注释掉。则每次调用代理对象的任何一个方法,都会执行动态切点检查。
这将导致很大的性能问题,所以在定义动态切点时,一定要覆盖getClassFilter()和静态检查matches
(Method method, Class<?> targetClass))方法,通过静态切点检查排除大部分方法。

其实动态代理的动态,是相对于那些编译期生成的代理和类加载期生成的代理而言。动态代理是运行时产生的代理,。在Spring中不管是静态切面还是动态切面,都是通过动态代理的技术来实现的,所谓的静态切面,是指在生成代理对象就确定了增强是否需要织入目标类的连接点上,而动态切面是指必须在运行期根据方法入参的值来判断增强是否需要织入目标类连接点上。

自动创建代理

之前都是通过ProxyFactoryBean创建织入切面的代理,每个需要被代理的Bean都需要使用一个ProxyFactory进行配置,虽然可以使用父子bean进行配置,但还是很麻烦。
Spring提供了自动代理机制,让容器自动生成代理,把开发人员从繁琐的配置中解脱出来。在内部Spring使用BeanPostProcessor自动完成这项工作。
这些基于BeanPostProcessor的自动代理创建器的实现类,将根据一些规则自动在容器实例化Bean时,为匹配的Bean生成代理实例,这些代理创建器可以分为3类:

  • 基于Bean配置名规则的自动代理创建器,允许为一组特定配置名的Bean自动创建代理实例的代理创建器,实现类为BeanNameAutoProxyCreator。
  • 基于Advisor匹配机制的自动代理创建器,它会对容器中所有的Advisor进行扫描,自动将这些切面应用到匹配的Bean中,实现类为DefaultAdvisorAutoProxyCreator
  • 基于Bean中AspectJ注解标签的自动带来创建器:为包含AspectJ注解的Bean自动创建代理实例,实现类为:AnnotationAwareAspectJAutoProxyCreator

BeanNameAutoProxyCreator基于Bean配置名规则的自动代理创建器

public class 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+"....");
	}
}

public class Seller {
	public void greetTo(String name) {
		System.out.println("Seller greet to "+name);
	}
}


public class GreetAdvice implements MethodBeforeAdvice{

	@Override
	public void before(Method method, Object[] args, Object target) 
	throws Throwable {
		//输出切点
		System.out.println(target.getClass().getName()+"."+method.getName());
		String name=(String) args[0];
		System.out.println("Advisor,Welcome "+name);
	}
}

<bean id="waiter" class="com.spring.Waiter" />
	<bean id="seller" class="com.spring.Seller" />
	<bean id="greetAdvice" class="com.spring.GreetAdvice" />
	<bean class="org.springframework.aop.framework.autoproxy.
	BeanNameAutoProxyCreator" 
		p:beanNames="*er"
		p:interceptorNames="greetAdvice"
		p:optimize="true"
	/>
	BeanNameAutoProxyCreator有一个beanNames属性,它允许用户指定一组需要自动代理的Bean名称
	,bean名称可以使用*通配符,假设Spring容器中除了waiter和seller外还有其它的bean如果以“er”
	为规则可以将这两个bean和其它的bean区分开。使用通配符会带有一定风险,在这个例子中假设有其它
	bean也以“er”结尾,则自动代理创建器也会为该bean创建代理,为了保险起见,可以如下这样设置:
	beanNames=“waiter,seller”
	一般情况下不会为FactoryBean的Bean创建代理,如果刚好有这样的需求,则需要在beanNames中
	指定添加$的bean名称,如<property name=“beanNames” value="$waiter" />
	BeanNameAutoProxyCreator的interceptorNames属性指定一个或多个增强Bean的名称。此外,
	还有一个常用的optimize属性,如果将此属性设置为true,则将强制使用CGLib动态代理技术。

通过这样的配置后,容器在创建waiter和seller Bean的实例后,就会自动为他们创建代理对象,而这
一操作对于使用者来说,是完全透明的。
public class MyTest{
	public static void main(String[] arg) throws Exception {
		ApplicationContext apContext=
		new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
		Waiter waiter =(Waiter) apContext.getBean("waiter");
		Seller seller =(Seller) apContext.getBean("seller");
		waiter.greetTo("zhangsan");
		seller.greetTo("lisi");
	}
}
结果:
com.spring.Waiter.greetTo
Advisor,Welcome zhangsan
greet to zhangsan....
com.spring.Seller.greetTo
Advisor,Welcome lisi
Seller greet to lisi

DefaultAdvisorAutoProxyCreator 基于Advisor匹配机制的自动代理创建器
我们知道切面Advisor是切点和增强的复合体,Advisor本身已经包含了足够的信息,如横切逻辑(要织入什么)和连接点(织入哪里)
DefaultAdvisorAutoProxyCreator 能够扫描容器中的Advisor,并将Advisor自动织入匹配的目标Bean中,即为匹配的目标Bean自动创建代理。

<bean class="org.springframework.aop.framework.autoproxy.
DefaultAdvisorAutoProxyCreator" />
	<bean id="waiter" class="com.spring.Waiter" />
	<bean id="seller" class="com.spring.Seller" />
	<bean id="greetAdvice" class="com.spring.GreetAdvice" />
	<bean id="regexpAdvisor" 
	class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" 
		p:patterns=".*greet.+"
		p:advice-ref="greetAdvice"
	/>
public class MyTest{
	public static void main(String[] arg) throws Exception {
		ApplicationContext apContext=new 
		ClassPathXmlApplicationContext("classpath:applicationContext.xml");
		Waiter waiter =(Waiter) apContext.getBean("waiter");
		Seller seller =(Seller) apContext.getBean("seller");
		waiter.serveTo("haha");
		waiter.greetTo("zhangsan");
		seller.greetTo("lisi");
	}
}
结果:
com.spring.Waiter.greetTo
Advisor,Welcome zhangsan
greet to zhangsan....
com.spring.Seller.greetTo
Advisor,Welcome lisi
Seller greet to lisi

由结果可以看出waiter.serveTo("haha");方法没有被增强。Waiter和Seller的greetTo方法都被增强
AOP无法增强疑难问题剖析

在使用Spring AOP的时候,或多或少会碰到一些方法无法被增强的问题,有时同一个类里面的方法有的可以被增强,有的无法被增强,要分析原因,首先要从Spring的AOP机制入手,从上文SpringAOP基础知识的学习可以知道,AOP底层实现有两种方法:一种是基于JDK的动态代理,一种是基于CGLib的动态代理。
在JDK动态代理中通过接口来实现方法拦截,所以必须确保要拦截的目标方法在接口中有定义,否则将无法实现拦截。
在CGLib动态代理中,通过动态生成代理子类来实现方法拦截,所以必须确保要拦截的目标方法可以被子类访问,也就是目标方法必须定义为非final、非私有的实例方法。

小结

AOP是OOP的延伸,它为程序开发提供了一个崭新的思考角度,可以将重复性的横切逻辑抽取到统一模块中,通过OOP纵向抽象和AOP横向抽取,程序才可以真正解决重复性代码问题。
Spring采用JDK动态代理和CGLib动态代理在运行期织入增强,所以不需要装备特殊的编译器或者类装载器就可以使用AOP功能,使用JDK动态代理,目标类必须实现接口,而CGLib不对目标类做任何限制,它通过动态生成目标类子类的方式提供代理。
JDK在创建代理对象时的性能高于CGLib,而生成的代理对象的运行性能却比CGLib低。如果是singleten的代理则推荐使用CGLib动态代理。

Spring只能在方法级别上织入增强,Spring提供四种类型的方法增强,分别是前置增强后置增强、环绕增强和异常增强。此外还有一种特殊的引介增强。引介增强是类级别的,它为目标类织入新的接口实现。从广义上说增强其实是一种简单的切面,它既包括横切代码又包括切点信息,只不过它的切点只是简单的方法相对位置信息,所以增强一般需要和切点联合才可以表示更具有实用性的切面。
在Spring中,普通的切点通过目标类名和方法名描述连接点信息。流程切点是比较特殊的切点,它通过方法调用堆栈的环境信息来决定连接点。有时候需要通过切点的交叉或者合并来描述一个最终的切点,这时候可以使用ComposablePointcut的复合切点。

切面是增强和切点的联合体,可以很方便的通过Spring提供的ProxyBeanFactory,将切面织入不同的类中,当然,为每个目标类手工配置一个目标类是比较繁琐的。Spring利用BeanPostProcessor可干涉Bean声明周期的机制,提供了一些可以自动创建代理、织入切面的自动代理创建器,其中DefaultAdvisorAutoProxyCreator是功能强大的自动代理创建器,它可以将容器中的所有Advisor自动织入目标Bean中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值