本篇博客重点讲解AOP的概念和知识点的介绍,下一篇博客重点通过案例和注释来分析讲解spring aop的通知等概念
1.定义AOP术语
1).切面(aspect):要实现的交叉功能,是系统模块化的一个切面或领域。如日志记录、事务处理、安全机制操作。
2).连接点:应用程序执行过程中插入切面的地点,可以是方法调用,异常抛出,或者要修改的字段。
3).通知:切面的实际实现,他通知系统新的行为。如在日志通知包含了实现日志功能的代码,如向日志文件写日志。通知在连接点插入到应用系统中。
4).切入点:定义了通知应该应用在哪些连接点,通知可以应用到AOP框架支持的任何连接点。
5).引入:为类添加新方法和属性。
6).目标对象:被通知的对象。既可以是你编写的类也可以是第三方类。
7).代理:将通知应用到目标对象后创建的对象,应用系统的其他部分不用为了支持代理对象而改变。
8).织入:将切面应用到目标对象从而创建一个新代理对象的过程。织入发生在目标对象生命周期的多个点上:
编译期:切面在目标对象编译时织入.这需要一个特殊的编译器.
类装载期:切面在目标对象被载入JVM时织入.这需要一个特殊的类载入器.
运行期:切面在应用系统运行时织入.
2.SpringAOP实现
用java编写spring通知;在spring中所有的通知都是以java类的形式编写的。切入点定义在配置文件中编写,所以切面代码和配置文件对我们来说都很熟悉。对于其他框架(Aspectj),需要特定的语法编写,如果使用的话,还需学习新的语言。
spring的运行时通知对象
spring在运行期创建代理,不需要特殊的编译器. spring有两种代理方式:
1).若目标对象实现了若干接口,spring使用JDK动态代理,即JDK的java.lang.reflect.Proxy类代理,该类让spring动态产生一个新类,它实现了所需的接口,织入了通知,并且代理对目标对象的所有请求。
2).若目标对象没有实现任何接口,spring使用CGLIB代理,即使用cglib库生成目标对象的子类。使用该方式时需要注意:对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统。对类代理是让遗留系统或无法实现接口的第三方类库同样可以得到通知,这种方式应该是备用方案。标记为final的方法不能够被通知。spring是为目标类产生子类。任何需要被通知的方法都被复写,将通知织入。final方法是不允许重写的。
spring实现了aop联盟接口
spring只支持方法连接点,不提供属性接入点;spring的观点是属性拦截破坏了封装。面向对象的概念是对象自己处理工作,其他对象只能通过方法调用的得到的结果。
3.创建通知
通知类型 | 接口 | 描述 |
Around 环绕通知 | Org.springframework.aop. MethodInterceptor | 拦截对目标方法调用 |
Before 前置通知 | Org.springframework.aop. BeforeAdvice | 在目标方法调用前调用 |
After 后置通知 | Org.springframework.aop. AfterReturningAdvice | 在目标方法调用后调用 |
Throws 异常通知 | Org.springframework.aop. ThrowsAdvice | 当目标方法抛出异常时调用 |
前置通知
public interface MethodBeforeAdvice{
void before(Method m,Object[] os ,Object target){
}
}
该接口提供了获得目标方法、参数和目标对象的机会。不能够改变运行时参数,即不能替换参数对象和目标对象。
注意在方法结束后不返回任何值东西。原因是该接口返回后,目标方法将会被调用,应该返回目标对象的返回值。该接口唯一能;阻止目标方法被调用的途径是抛出异常或(System.exit())。
ProxyFactoryBean是一个在BeanFactory中显式创建代理对象的中心类,可以给它一个要实现的接口、一个要代理的目标对象、一个要织入的通知,并且他将创建一个崭新的代理对象。
后置通知
同前置通知类似。
public interface AfterReturningAdvice{
public void afterReturning(Object returnValue,Method
m,Object[] os,Object target);
}
环绕通知
public interface MethodInterceptor extends Interceptor{
Object invoke(MethodInvocation invocation);
}
该接口同前两种通知有两个重要区别:
1).该通知能够控制目标方法是否真的被调用。通过invocation.proceed()方法来调用;
2).该通知可以控制返回的对象。可以返回一个与proceed()方法返回对象完全不同的对象。但要谨慎使用。
异常通知
public interface ThrowsAdvice{}
该接口为标识性接口,没有任何方法,但实现该接口的类必须要有如下形式的方法:
void afterThrowing(Throwable throwable);
void afterThrowing(Method m,Object[] os,Object target,Throwable throwable);
第一个方法只接受一个参数:需要抛出的异常。
第二个方法接受异常、被调用的方法、参数以及目标对象。
引入通知
以前定义的通知类型是在目标对象的方法被调用的周围织入。引入通知给目标对象添加新的方法和属性。
4.定义切入点
如果不能表达在应用系统的什么地方应用通知的话,通知将毫无用处,这就是切入点的用处。切入点决定了一个特定的类的特定方法是否满足一定的规则。若符合,通知就应用到该方法上。
在spring中定义切入点
public interface Pointcut{
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
切入点根据方法和类决定何处织入通知。ClassFilter接口决定了一个类是否符合通知的要求:
public interface ClassFilter{
boolean matches(Class clazz);
}
实现该接口的类决定了以参数传递进来的类是否应该被通知。实现该接口的类一般根据类名决定,但不一定必须如此。该接口总是包含了一个简单的ClassFilter接口实现-ClassFilter.TRUTE。它是规范的适合任何类的ClassFilter实例,适合用于只根据方法决定时候符合要求的切入。ClassFilter通过类过滤切面,MethodMatcher通过方法过滤切面。
public interface MethodMatcher{
boolean matches(Method m,Class targetClass);1.
boolean isRuntime();2.
boolean matchers(Method m,Class target,Object[] args);3.
}
1根据目标类和方法决定方法是否被通知。因为可以静态的判断,所以可以在AOP代理被创建时候调用一次这个方法。该方法的结果最终决定了通知是否被织入。如果1.返回true;2.被调用来决定MethodMatcher的类型。有两种类型:静态和动态。静态切入点的意思是通知总是被执行。如果一个切入点是静态的,该方法返回false.动态切入点根据运行时方法的参数值决定通知是否需要执行。如果切入点是动态的,该方法返回true。和1.方法类似,该方法也是在代理创建时运行一次。如果切入点是静态的,3.永远不会执行,对于动态切入点,需要根据运行时的参数决定方法是否被通知,所以会增加系统的负担,尽量使用静态切入点。
5.理解Advisor
大多数切面是由定义切面行为的通知和定义切面在什么地方执行的切入点组合而成的。spring认识到了这一点,提供了Advisor类。他把通知和切入点组合到一个对象中。更确切地说PointcutAdvisor提供了这些功能。
public interface PointcutAdvisor{
Pointcut getPointcut();
Advice getAdvice();
}
这样方便在一个地方定义切入点和通知。
使用spring的静态切入点
静态切入点只在代理创建的时候执行一次而不是在运行期间每次方法调用都执行,所以性能比动态切入点好,是首选的切入点方式。spring为创建静态切入点提供了方便的父类。staticMethodMatcherPointcut. StaticMethodMatcherPointcut
NameMatchMethodPointcut:
public void setMappedName(String);
public void setMappedNames(String);
当调用的方法名字与给出的映射名字匹配时,切点才匹配。也可在名字的开始和结束使用通配符。定义切入点
正则表达式切入点:RegexpMethodPointcut
符号 | 描述 | 示例 | 匹配 | 不匹配 |
. | 匹配任何单个字符 | setFoo. | setFooB | setFoo setFooBar |
+ | 匹配前一个字符一次或多次 | setFoo.+ | setFooBar setFooB | setFoo |
* | 匹配前一个字符0次或多次 | setFoo.* | setFoo setFooB, setFooBar |
|
\ | 匹配任何正则表达式符号 | \.setFoo. | bar.setFoo | setFoo |
谨慎使用引入通知
spring通知是在运行时织入,而不象其他的AOP框架将通知织入到类的字节码当中。这意味着,从其他方法创建或得道的对象不会被引入通知。例如直接实例化或返序列化的对象。
属性 | 使用 |
target | 代理的目标对象 |
proxyInterfaces | 代理应该实现的接口列表 |
interceptorNames | 需要应用到目标对象上的通知的名字。可以是拦截器、Advisor或其他通知类型的名字。这个属性必须按照beanFactory中的使用顺序设置。 |
singleton | 是否返回同一个实例。 |
aopProxyFactory | 是用的ProxyFactoryBean实现。spring有两种实现:jdk动态代理和CGLIB。通常不需要使用该属性。 |
exposeProxy | 目标对象是否需要得到当前代理。 |
frozen | 一旦工厂被创建,是否可以修改代理的通知。当为true时,运行时就不能修改ProxyFactoryBean了。通常不需要使用这个属性。 |
optimize | 是否对创建的代理进行优化(只适用于CGLIB)。这会带来性能的提升,不过要慎用。 |
ProxyTargetClass | 是否代理目标对象,而不是接口。只能在使用CGLIB (即部署了cglib包)时使用。 |