Spring 的AOP
AOP 专门用于处理系统中分布于各个模块(不同方法)中的交叉关注点的问题,在Java EE应用中,常常通过AOP 来处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等, AOP 已经成为一种非常常用的解决方案。
AspectJ 是一个基于Java 语言的AOP 框架, 掌握AspectJ 是学习 Spring AOP 的基础。
AspectJ 下载传送门:Index of /eclipse/tools/aspectj/http://mirrors.neusoft.edu.cn/eclipse/tools/aspectj/
AspectJ 使用入门
首先编写两个简单的Java 类,这两个Java 类用于模拟系统中的业务组件:
运行下面这串代码
执行结果是没有任何悬念的, 程序显示如下输出:
执行Hello 组件的foo ()方法
执行He llo 组件的addUser 添加用户:孙悟空
执行World 组件的bar ()方法
假设现在客户要求在执行所有业务方法之前先执行权限检查,传统的方式不仅容易引入新的错误,而且维护成本相当大。
如果使用AspectJ 的AOP 支持,则只要添加如下特殊的“Java 类”即可:
代码里的 aspect 不是Java 支持的关键字,它只是AspectJ 才能识别的关键字。
上面的粗体字代码也不是方法,它只是指定在执行某些类的某些方法之前, AspectJ 将会自动先调
用该代码块中的代码。
由于Java 无法识别 AuthAspect.java 文件的内容,所以要使用 ajc.bat 命令来编译上面的Java 程序:
ajc -d . *.java
可以把 ajc.bat 理解成增强版的javac .exe 命令,都用于编译Java 程序,区别是ajc.bat 命令可识别AspectJ 的语法。
运行后如下:
模拟进行权限检查. ..
执行Hello 组件的foo ()方法
模拟进行权限检查. ..
执行Hello 组件的addUser 添加用户:孙悟空
模拟进行权限检查. ..
执行World 组件的bar ()方法
完全不需要对Hello.java 、World.java 等业务组件进行任何修改,但同时又可以满足客户的需求。
如果反编译前面程序生成的Hello.class 、World.class 文件,将发现该Hello.class 、World.class 文件不是由Hello.java 、World.java 文件编译得到的, Hello.class 、World . class 里新增了很多内容一一这表明AspectJ 在编译时己增强了Hello.class 、World.class 类的功能,因此AspectJ 通常被称为编译时增强的AOP 框架。
总结
AOP 要达到的效果是,保证在程序员不修改源代码的前提下,为系统中业务组件的多个业务方法添加某种通用功能。但AOP 的本质是,依然要去修改业务组件的多个业务方法的源代码一一只是这个修改由AOP 框架完成,程序员不需要修改!
AOP 实现可分为两类(按AOP 框架修改源代码的时机):
静态AOP 实现: AOP 框架在编译阶段对程序进行修改,即实现对目标类的增强,生成静态的AOP 代理类(生成的*.class 文件己经被改掉了,需要使用特定的编译器〉。以 AspectJ 为代表。
动态AOP 实现: AOP 框架在运行阶段动态生成AOP 代理,即在运行时生成目标类的代理类,在内存中以 JDK 动态代理(实现与目标类实现相同的接口) 或 cglib动态代理(成为目标类的子类) 生成AOP 代理类),以实现对目标对象的增强。以 SpringAOP 为代表。
AOP 的基本概念
AOP 从程序运行角度考虑程序的流程,提取业务处理过程的切面。
AOP 面向的是程序运行中各个步骤,希望以更好的方式来组合业务处理的各个步骤。
AOP 框架并不与特定的代码耦合, AOP 框架能处理程序执行中特定的切入点(Pointcut),而不与某个具体类耦合。
AOP 框架具有如下两个特征:
(1)各步骤之间的良好隔离性。
(2)源代码无关性。
下面是关于面向切面编程的一些术语:
切面( Aspect ):切面用于组织多个Advice(增强处理), Advice(增强处理) 放在切面中定义。
连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或者异常的抛出。在SpringAOP中,连接点默认是方法的调用。
增强处理( Advice): AOP 框架在特定的切入点执行的增强处理。处理有"around"、"before"和" after"等类型。
切入点( Pointcut ):可以插入增强处理的连接点。简而言之,当某个连接点满足指定要求时,该连接点将被添加增强处理,该连接点也就变成了切入点。例如如下代码:
pointcut xxxPointcut(): execution( void H*.say*() )
上面的代码指如果该方法属于H 开头的类,且方法名以say 开头,则该方法的执行将变成切入点。如何使用表达式来定义切入点是AOP 的核心,Spring 默认使用AspectJ 切入点语法。
引入:将方法或字段添加到被处理的类中。Spring 允许将新的接口引入到任何被处理的对象中。例如,你可以使用一个引入,使任何对象实现IsModified 接口,以此来简化缓存。
目标对象:被AOP 框架进行增强处理的对象,也被称为被增强的对象。如果AOP 框架采用的
是动态AOP 实现,那么该对象就是一个被代理的对象。
AOP 代理: AOP 框架创建的对象,简单地说,代理就是对目标对象的加强。Spring 中的AOP代理可以是 JDK 动态代理,也可以是 cglib 代理。前者为实现接口的目标对象的代理,后者为不实现接口的目标对象的代理。
织入( Weaving ):将增强处理添加到目标对象中,井创建一个被增强的对象(AOP 代理)的过程就是织入。织入有两种实现方式:编译时增强(如AspectJ )和运行时增强(如SpringAOP )。Spring 和其他纯JavaAOP 框架一样,在运行时完成织入。
Spring 的 AOP 支持
Spring 中的AOP 代理由Spring 的IoC 容器负责生成、管理,其依赖关系也由IoC 容器负责管理。因此, AOP代理可以直接使用容器中的其他Bean 实例作为目标,这种关系可由IoC 容器的依赖注入提供。
Spring默认使用JDK动态代理来创建AOP 代理。Spring 也可以使用cglib 代理,在需要代理类而不是代理接口的时候, Spring 会自动切换为使用cglib代理。
SpringAOP 使用纯Java 实现。它不需要特定的编译工具, SpringAOP 也不需要控制类装载器层次,因此它可以在所有的Java Web 容器或应用服务器中运行良好。
Spring 目前仅支持将方法调用作为连接点(Joinpoint) ,如果需要把对成员变量的访问和更新也作为增强处理的连接点,则可以考虑使用AspectJ 。
Spring 侧重于AOP 实现和Spring IoC 容器之间的整合。Spring 可以无缝地整合Spring AOP, IoC和AspectJ ,使得所有的AOP 应用完全融入基于Spring 的框架中。
纵观AOP 编程,其中需要程序员参与的只有三个部分:
(1)定义普通业务组件。
(2)定义切入点,一个切入点可能横切多个业务组件。
(3)定义增强处理, 增强处理就是在AOP 框架为普通业务组件织入的处理动作。
进行AOP 编程的关键就是定义切入点和定义增强处理。一旦定义了合适的切入点和增强处理,AOP 框架将会自动生成AOP 代理。通常建议使用AspectJ 方式来定义切入点和增强处理,在这种方式下, Spring 依然有如下两种选择来定义切入点和增强处理:
(1)基于注解的 “零配置” 方式:使用@Aspect、@Pointcut 等注解来标注切入点和增强处理。
(2)基于 XML 配置文件的管理方式:使用Spring 配置文件来定义切入点和增强处理。
基于注解的“ 零配置 ”方式
关于idea配置aspectj的过程,这里只提几点,当然也有更方便的做法,自行百度:
(1)安装aspectj时会指定jdk,后面的实验都要基于该jdk版本,不要忘了去配置aspectj的环境变量;
(2)安装完成后会有类似下面的文件夹:
创建项目导入依赖时我们只需要lib包下的 aspectjrt.jar和aspectjrtweaver.jar
(3) 网络上一些说法是需要去下面区域指定Use compiler 为Ajc,因为javac不能识别aspectj,所以需要Ajc来编译有aspectj关键字的java文件,但实验过程中我并没有用到Ajc。所以如果读者不能正常运行出结果,可以尝试一下这个方向。
下面实验都是基于java 13 + aspect 1.9.4。idea版本为2020.1
AspectJ 允许使用注解定义切面、切入点和增强处理,而Spring 框架则可识别并根据这些注解来生成 AOP 代理,其底层依然使用的是Spring AOP。
为了启用Spring 对@AspectJ 切面配置的支持,并保证Spring 容器中的目标Bean 被一个或多个切
面自动增强,必须在Spring 配置文件中配置如下部分片段:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd"
>
<aop:aspectj-autoproxy/>
</beans>
使用 @Before 增强处理
定义一个切面Bean
package com.bnuz.advice;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeAdviceTest {
//com 前面的*号表示返回值不限
//impl. 后面的*号表示类名不限,也可以把该*号替换成指定类名
//最后一个*号表示方法名不限,也可以把该*号替换成指定方法名
//最后一个*号后面的 (..) 表示指定方法的形参可以是任意个数且类型不限制
@Before("execution(* com.bnuz.service.impl.*.*(..))")
public void authority(){
System.out.println("权限检查:检查一下是自己吧....");
}
}
当启动了@AspectJ 支持后,只要在Spring 容器中配置一个带@Aspect 注解的Bean, Spring 将会自动识别该Bean,并将该Bean 作为切面处理。
切面类(用@Aspect 修饰的类)和其他类一样可以有方法、成员变量定义,还可能包括切入点、增强处理定义。 当使用@Aspect 来修饰一个Java 类之后, Spring 将不会把该Bean 当成组件Bean 处理,因此负责自动增强的后处理Bean 将会略过该Bean,不会对该Bean 进行任何增强处理。
上面程序中使用@Before 注解时,直接指定了切入点表达式,指定匹配com.bnuz.service.impl包下所有类的所有方法的执行作为切入点。@Before括号内可以指定value属性值来指定一个切入点表达式。也可以忽略value,直接指定切入点表达式
创建一个接口类与一个实现类:
package com.bnuz.service;
public interface ITStudent {
public void study();
public void play();
}
GreatITStudent类使用了@Component 注解进行修饰:
package com.bnuz.service.impl;
import com.bnuz.service.ITStudent;
import org.springframework.stereotype.Component;
@Component
public class GreatITStudent implements ITStudent {
@Override
public void study() {
System.out.println("sleep when we study");
}
@Override
public void play() {
System.out.println("Dota?Twitter?Whatever");
}
}
在Spring 配置文件中配置自动搜索Bean 组件、自动搜索切面类, Spring AOP 自动对Bean 组件进行增强。下面是Spring 配置文件完整代码:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<aop:aspectj-autoproxy/>
<context:component-scan base-package="com.bnuz.service,com.bnuz.advice">
<context:include-filter type="annotation" expression="org.aspectj.lang.annotation.Aspect"/>
</context:component-scan>
<bean id="greatITStudent" class="com.bnuz.service.impl.GreatITStudent" />
</beans>
创建BeanTest测试类:
package com.bnuz;
import com.bnuz.service.ITStudent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanTest {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
ITStudent greatITStudent = ctx.getBean("greatITStudent",ITStudent.class);
greatITStudent.study();
greatITStudent.play();
}
}
运行结果:
使用 @AfterReturning 增强处理
AfterReturning 增强处理将在目标方法正常完成后被织入。
使用 @AfterReturning 注解可指定如下两个常用属性:
pointcut/value : 这两个属性的作用是一样的, 它们都用于指定该切入点对应的切入表达式。既可是一个己有的切入点,也可直接定义切入点表达式。当指定了pointcut 属性值后, value属性值将会被覆盖。
returning : 该属性值指定一个形参名,用于表示 Advice 方法中可定义与此同名的形参,该形参可用于访问目标方法的返回值。除此之外,在 Advice 方法中定义该形参( 代表目标方法的返回值) 时指定的类型,会限制目标方法必须返回指定类型的值。
定义一个切面Bean AfterReturningAdviceTest类:
package com.bnuz.advice;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class AfterReturningAdviceTest {
@AfterReturning(returning = "rvt",
pointcut = "execution(* com.bnuz.service.impl.*.*(..))")
public void log(Object rvt){
System.out.println("获取目标返回值:" + rvt);
System.out.println("模拟日志记录功能..");
}
}
程序中使用@AfterReturning 注解时,指定了一个 returning 属性,该属性值为rvt,这表明允许在Advice 方法(即log()方法)中定义名为rvt的形参,程序可通过rvt形参来访问目标方法的返回值。
当目标方法study()或play()执行返回后, 返回值作为相应的参数值rvt传入增强处理方法log()。
使用 @AfterThrowing 增强处理
AfterThrowing 增强处理主要用于处理程序中未处理的异常。
使用 @AfterThrowing 注解时可指定如下两个常用属性:
pointcut/value : 这两个属性的作用是一样的,它们都用于指定该切入点对应的切入表达式。既可是一个己有的切入点,也可直接定义切入点表达式。当指定了pointcut 属性值后, value属性值将会被覆盖。
throwing : 该属性值指定一个形参名,用于表示Advice 方法中可定义与此同名的形参,该形参可用于访问目标方法抛出的异常。除此之外,在Advice 方法中定义该形参(代表目标方法抛出的异常)时指定的类型,会限制目标方法必须抛出指定类型的异常。
定义一个切面Bean AfterThrowingAdviceTest类:
package com.bnuz.advice;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class AfterThrowingAdviceTest {
@AfterThrowing(throwing = "e", pointcut = "execution(* com.bnuz.service.impl.*.*(..))")
public void doRecoveryActions(Throwable e){
System.out.println("目标方法抛出异常:" + e);
System.out.println("模拟抛出异常后的增强处理..");
}
}
修改ITStudent,GreatITStudent和测试类:
ITStudent接口内添加compute()方法:
package com.bnuz.service;
public interface ITStudent {
public void study();
public void play();
public void compute();
}
GreatITStudent类修改如下:
package com.bnuz.service.impl;
import com.bnuz.service.ITStudent;
import org.springframework.stereotype.Component;
@Component
public class GreatITStudent implements ITStudent {
@Override
public void study() {
// TODO Auto-generated method stub
try{
System.out.println("study method 开始执行....");
new java.io.FileInputStream("null.txt");
}
catch (Exception e)
{
System.out.println("目标类的异常处理"+e.getMessage());
}
System.out.println("sleep when we study");
}
@Override
public void play() {
System.out.println("Dota?Twitter?Whatever");
}
@Override
public void compute() {
int k = 10/0;
System.out.println("compute执行完成");
}
}
修改测试类 BeanTest类:
package com.bnuz;
import com.bnuz.service.ITStudent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanTest {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
ITStudent greatITStudent = ctx.getBean("greatITStudent",ITStudent.class);
greatITStudent.study();
greatITStudent.play();
greatITStudent.compute();
}
}
@ AfterThrowing 注解的throwing 属性中指定的参数名必须与增强处理方法内的一个形参对应。当目标方法抛出一个未处理的异常时,该异常将会传给增强处理方法对应的参数。
After 增强处理
After增强处理不管目标方法如何结束(包括成功完成和遇到异常中止两种情况),它都会被织入。
使用@After 注解时需要指定一个value 属性,该属性值用于指定该增强处理被织入的切入点,既可是一个己有的切入点,也可直接指定切入点表达式。
定义一个After增强处理 AfterAdviceTest类:
package com.bnuz.advice;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class AfterAdviceTest {
@After("execution(* com.bnuz.service.impl.*.*(..))")
public void release(){
System.out.println("方法结束后释放资源...");
}
}
Around 增强处理
Around增强处理既可在执行目标方法之前织入增强动作,也可在执行目标方法之后织入增强动作,既可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值。
Around增强处理的功能虽然强大,但通常需要在线程安全的环境下使用。因此,如果使用普通的Before 增强处理、AfterReturning增强处理就能解决的问题,则没有必要使用Around 增强处理了。
当定义一个Around增强处理方法时,该方法的第一个形参必须是ProceedingJoinPoint 类型(至少包含一个形参),在增强处理方法体内,调用P roceedingJoinPoint 参数的proceedO方法才会执行目标方法一一这就是Around 增强处理可以完全控制目标方法的执行时机、执行方式的关键。
如果程序没有调用ProceedingJoinPoint参数的proceed()方法,则目标方法不会被执行。
下面定义一个Around增强处理AroundAdviceTest类:
package com.bnuz.advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class AroundAdviceTest {
@Around("execution(* com.bnuz.service.impl.*.*(..))")
public Object processTest(ProceedingJoinPoint jp) throws java.lang.Throwable{
System.out.println("执行目标方法之前,模拟开始事务");
Object[] args = jp.getArgs();;
if (args != null && args.length > 1){
//修改目标方法的第一个参数
args[0] = "lyq:" + args[0];
}
//以改变后的参数去执行目标方法,并保存目标方法执行后的返回值
Object rvt = jp.proceed(args);
System.out.println("执行目标方法之前,模拟结束事务");
//如果 rvt 的类型是 Integer,将rvt改为它的平方
if (rvt != null && rvt instanceof Integer){
rvt = (Integer)rvt * (Integer)rvt;
}
return rvt;
}
}
上面代码可以看到,调用ProceedingJoinPoint参数的proceed()方法时,还可以传入一个Object[]数组对象作为参数,该数组中的值将被传入目标方法作为执行方法的实参。
由于执行的方法返回为void,所以并未做处理。
访问目标方法的参数
访问目标方法最简单的做法是定义增强处理方法时将第一个参数定义为JoinPoint 类型,当该增强处理方法被调用时,该JoinPoint 参数就代表了织入增强处理的连接点。JoinPoint 里包含了如下几个常用的方法。
Object[] getArgs():返回执行目标方法时的参数。
Signature getSignature():返回被增强的方法的相关信息。
Object getTarget():返回被织入增强处理的目标对象。
Object getThis():返回AOP 框架为目标对象生成的代理对象。
通过使用这些方法就可访问到目标方法的相关信息。
定义切入点
所谓定义切入点,其实质就是为一个切入点表达式起一个名称,从而允许在多个增强处理中重用该名称。
Spring AOP 只支持将Spring Bean的方法执行作为连接点,所以可以把切入点看成所有能和切入点表达式匹配的Bean 方法。切入点定义包含两个部分:
(1)一个切入点表达式:指定该切入点和哪些方法进行匹配,切入点表达式需要使用@Pointcut注解来标注。
(2)一个包含名字和任意参数的方法签名:该切入点的名称。
如下面定义一个切入点anyOldTransfer方法:
@Pointcut("execution(* transfer(..))")
private void anyOldTransfer(){ }
如代码所示,切入点的方法的返回值必须为void,且方法体通常为空。该切入点也可以多次利用,如果修改访问控制符(如private、public等)也可以供其他包的切面类使用。
如果需要使用本切面类中的切入点,则可在使用@Before 、@After 、@Around 等注解定义Advice时,使用pointcut或value属性值引用己有的切入点。
在切面类SystemArchitecture类里仅定义了一个切入点myPointcut()方法:
package com.bnuz.advice;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SystemArchitecture {
@Pointcut("execution(* com.bnuz.service.impl.*.*(..))")
public void myPointcut(){ }
}
下面的切面类中将直接使用上面定义的myPointcut()切入点:
package com.bnuz.advice;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class AfterReturningAdviceTest {
@AfterReturning(returning = "rvt", pointcut = "SystemArchitecture.myPointcut()")
public void log(Object rvt){
System.out.println("获取目标返回值:" + rvt);
System.out.println("模拟日志记录功能..");
}
}
切入点指示符
execution表达式就是一个切入点指示符,SpringAOP还额外支持一个bean切入点指示符。
下面是SpringAOP一共支持如下几种切入点指示符:
(1)execution:用于匹配执行方法的连接点,这是SpringAOP中最主要的切入点指示符。该切入点的用法也相对复杂, execution 表达式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?
name-pattern(param-pattern) throws-pattern?)
上面格式各部分的解释如下:
modifiers-pattern:指定方法的修饰符,支持通配符,该部分可省略。
ret-type-pattem:指定方法的返回值类型,支持通配符,可以使用"*"通配符来匹配所有的返回值类型。
declaring-type-pattern:指定方法所属的类,支持通配符,该部分可省略。
name-pattem:指定匹配指定的方法名,支持通配符,可以使用"*"通配符来匹配所有方法。
param-pattem:指定方法声明中的形参列表,支持两个通配符,即"*"和"..",其中"*"代表一个任意类型的参数,而".."代表零个或多个任意类型的参数。例如,()匹配了一个不接受任何参数的方法,而(..)匹配了一个接受任意数量参数的方法(零个或更多),(*)匹配了一个接受一个任何类型参数的方法,(*,String)匹配了接受两个参数的方法,第一个可以是任意类型,第二个则必须是String 类型。
throws -pattern:指定方法声明抛出的异常,支持通配符,该部分可省略。
//匹配任意public方法的执行
execution(public * * (..))
//匹配任何方法名以"set"开始的方法的执行
execution(* set* (..))
//匹配AccountServicerImpl 中任意方法的执行
execution(* com.bnuz.service.impl.AccountServiceImpl.* (..))
//匹配com.bnuz.service.impl包中任意类的任意方法的执行
execution(* com.bnuz.service.impl.*.*(..))
(2)within:用于限定匹配特定类型的连接点,当使用Spring AOP的时候,只能匹配方法执行的连接点。
//在com.bnuz.service包中的任意连接点(在Spring AOP 中只是方法执行的连接点)
within(com.bnuz.service.*)
//在com.bnuz.service包或其子包中的任意连接点(在Spring AOP中只是方法执行的连接点)
within(com.bnuz.service..*)
(3)this:用于限定AOP代理必须是指定类型的实例,匹配该对象的所有连接点。当使Spring AOP的时候,只能匹配方法执行的连接点。
//匹配实现了com.bnuz.service.AccountService接口的AOP代理的所有连接点
//在Spring AOP 中只是方法执行的连接点
this(com.bnuz.service.AccountService)
(4)target:用于限定目标对象必须是指定类型的实例,匹配该对象的所有连接点。当使用SpringAOP的时候,只能匹配方法执行的连接点。
//匹配实现了com.bnuz.service.AccountService接口的目标对象的所有连接点
//在Spring AOP中只是方法执行的连接点
target(com.bnuz.service.AccountService)
(5)args:用于对连接点的参数类型进行限制,要求参数类型是指定类型的实例。当使用Spring AOP的时候,只能匹配方法执行的连接点。
//匹配只接受一个参数,且传入的参数类型是Serializable的所有连接点
//在Spring AOP中只是方法执行的连接点
args(java.io.Serializable)
(6)bean:用于限定只匹配指定Bean实例内的连接点,实际上只能使用方法执行作为连接点。定义bean 表达式时需要传入Bean 的id或name,表示只匹配该Bean 实例内的连接点。支持使用"*"通配符。
//匹配tradeService Bean 实例内方法执行的连接点
bean(tradeService)
//匹配名字以Service 结尾的Bean 实例内方法执行的连接点
bean(*Service)
组合切入点表达式
Spring 支持使用如下三个逻辑运算符来组合切入点表达式。
&&:要求连接点同时匹配两个切入点表达式。
||:只要连接点匹配任意一个切入点表达式。
!:要求连接点不匹配指定的切入点表达式。
pointcut="execution(* com.bnuz.service.impl.*.*(..)) && args(food,time)"
上面pointcut 属性指定的切入点表达式需要匹配如下两个条件:
(1)匹配com.bnuz.service.impl包下任意类中任意方法的执行。
(2)被匹配的方法的第一个参数类型必须是food的类型,第二个参数类型必须是time的类型(food 、time 的类型由增强处理方法来决定)。
基于XML 配置文件的管理方式
在Spring配置文件中,所有的切面、切入点和增强处理都必须定义在<aop:config.../>元素内部。<beans.../>元素下可以包含多个<aop:config.../>元素,一个<aop:config.../>可以包含pointcut 、advisor 和aspect元素,且这三个元素必须按照此顺序来定义。关于<aop:config.../>元素所包含的子元素如图所示:
先定义两个类:
package com.bnuz.advice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import java.util.Arrays;
public class FourAdviceTest {
public Object processTx(ProceedingJoinPoint jp) throws java.lang.Throwable{
System.out.println("Around增强:执行目标方法之前,模拟开始事务...");
//访问执行目标方法的参数
Object[] args = jp.getArgs();
//当执行目标方法的参数存在,
// 且第一个参数是字符串参数
if (args != null && args.length > 0 && args[0].getClass() == String.class) {
//改变第一个目标方法的第一个参数
args[0] = "被改变的参数";
}
//执行目标方法,并保存目标方法执行后的返回值
Object rvt = jp.proceed(args);
System.out.println("Around增强:执行目标方法之后,模拟结束事务...");
return rvt + " 新增的内容";
}
public void authority(JoinPoint jp){
System.out.println("2.Before增强:模拟执行权限检查");
//返回被织入增强处理的目标方法
System.out.println("2.Before增强:被织入增强处理的目标方法为:"
+ jp.getSignature().getName());
//访问执行目标方法的参数
System.out.println("2.Before增强:目标方法的参数为:"
+ Arrays.toString(jp.getArgs()));
//访问被增强处理的目标对象
System.out.println("2.Before增强:被织入增强处理的目标对象为:"
+ jp.getTarget());
}
public void log(JoinPoint jp , Object rvt) {
System.out.println("AfterReturning增强:获取目标方法返回值:" + rvt);
System.out.println("AfterReturning增强:模拟记录日志功能...");
//返回被织入增强处理的目标方法
System.out.println("AfterReturning增强:被织入增强处理的目标方法为:"
+ jp.getSignature().getName());
//访问执行目标方法的参数
System.out.println("AfterReturning增强:目标方法的参数为:"
+ Arrays.toString(jp.getArgs()));
//访问被增强处理的目标对象
System.out.println("AfterReturning增强:被织入增强处理的目标对象为:"
+ jp.getTarget());
}
public void release(JoinPoint jp) {
System.out.println("After增强:模拟方法结束后的释放资源...");
//返回被织入增强处理的目标方法
System.out.println("After增强:被织入增强处理的目标方法为:"
+ jp.getSignature().getName());
//访问执行目标方法的参数
System.out.println("After增强:目标方法的参数为:"
+ Arrays.toString(jp.getArgs()));
//访问被增强处理的目标对象
System.out.println("After增强:被织入增强处理的目标对象为:"
+ jp.getTarget());
}
}
package com.bnuz.advice;
public class SecondAdviceTest {
public void authority(String aa){
System.out.println("目标方法的参数为:" +aa);
System.out.println("1.Before增强:模拟执行权限检查" +aa);
}
}
再定义一个接口和实现该接口的类:
package com.bnuz.service;
public interface ITStudent {
public void study();
public void play();
}
package com.bnuz.service.impl;
import com.bnuz.service.ITStudent;
import org.springframework.stereotype.Component;
@Component
public class GreatITStudent implements ITStudent {
@Override
public void study() {
System.out.println("study method 开始执行....");
System.out.println("sleep when we study");
}
@Override
public void play() {
System.out.println("Dota?Twitter?Whatever");
}
}
Spring-config.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="fourAdviceAspect" class="com.bnuz.advice.FourAdviceTest"/>
<bean id="secondAdviceTest" class="com.bnuz.advice.SecondAdviceTest"/>
<bean id="greatITStudent" class="com.bnuz.service.impl.GreatITStudent"/>
<aop:config>
<aop:aspect id="fourAdviceAspect" ref="fourAdviceAspect" order="2">
<aop:after method="release" pointcut="execution(* com.bnuz.service.impl.*.*(..))"/>
<aop:before method="authority" pointcut="execution(* com.bnuz.service.impl.*.*(..))"/>
<aop:after-returning method="log"
pointcut="execution(* com.bnuz.service.impl.*.*(..))"
returning="rvt"/>
<aop:around method="processTx" pointcut="execution(* com.bnuz.service.impl.*.*(..))"/>
</aop:aspect>
<aop:aspect id="secondAdviceAspect" ref="secondAdviceTest" order="1">
<aop:before method="authority" pointcut="execution(* com.bnuz.service.impl.*.*(..)) and args(aa)"/>
</aop:aspect>
</aop:config>
</beans>
运行下面代码:
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
ITStudent greatITStudent = ctx.getBean("greatITStudent", ITStudent.class);
greatITStudent.study();
greatITStudent.play();
}
运行结果如下: