1. 添加引用
需要引用一个新的jar包:aspectjweaver,该包是AspectJ的组成部分。可以去http://search.maven.org搜索后下载或直接在maven项目中添加依赖。
示例中使用pom.xml文件如下所示:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zhangguo</groupId>
<artifactId>Spring041</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Spring041</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.0.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
<version>4.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.4</version>
</dependency>
</dependencies>
</project>
2.定义通知
该通知不再需要实现任何接口或继承抽象类,一个普通的bean即可,方法可以带一个JoinPoint连接点参数,用于获得连接点信息,如方法名,参数,代理对象等。
package com.zhangguo.Spring041.aop08;
import org.aspectj.lang.JoinPoint;
/**
* 通知
*/
public class Advices {
//前置通知
public void before(JoinPoint jp)
{
System.out.println("--------------------bofore--------------------");
System.out.println("方法名:"+jp.getSignature()+",参数:"+jp.getArgs().length+",代理对象:"+jp.getTarget());
}
//后置通知
public void after(JoinPoint jp){
System.out.println("--------------------after--------------------");
}
}
通知的类型有多种,有些参数会不一样,特别是环绕通知,通知类型如下:
//前置通知
public void beforeMethod(JoinPoint joinPoint)
//后置通知
public void afterMethod(JoinPoint joinPoint)
//返回值通知
public void afterReturning(JoinPoint joinPoint, Object result)
//抛出异常通知
//在方法出现异常时会执行的代码可以访问到异常对象,可以指定在出现特定异常时在执行通知代码
public void afterThrowing(JoinPoint joinPoint, Exception ex)
//环绕通知
//环绕通知需要携带ProceedingJoinPoint类型的参数
//环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法。
//而且环绕通知必须有返回值,返回值即为目标方法的返回值
public Object aroundMethod(ProceedingJoinPoint pjd)
3.配置IOC容器依赖的XML文件beansOfAOP.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:p="http://www.springframework.org/schema/p"
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-4.3.xsd">
<!--被代理的目标对象 -->
<bean id="math" class="com.zhangguo.Spring041.aop08.Math"></bean>
<!-- 通知 -->
<bean id="advice" class="com.zhangguo.Spring041.aop08.Advices"></bean>
<!-- AOP配置 -->
<!-- proxy-target-class属性表示被代理的类是否为一个没有实现接口的类,Spring会依据实现了接口则使用JDK内置的动态代理,如果未实现接口则使用cblib -->
<aop:config proxy-target-class="true">
<!-- 切面配置 -->
<!--ref表示通知对象的引用 -->
<aop:aspect ref="advice">
<!-- 配置切入点(横切逻辑将注入的精确位置) -->
<aop:pointcut expression="execution(* com.zhangguo.Spring041.aop08.Math.*(..))" id="pointcut1"/>
<!--声明通知,method指定通知类型,pointcut指定切点,就是该通知应该注入那些方法中 -->
<aop:before method="before" pointcut-ref="pointcut1"/>
<aop:after method="after" pointcut-ref="pointcut1"/>
</aop:aspect>
</aop:config>
</beans>
加粗部分的内容是在原IOC内容中新增的,主要是为AOP服务,如果引入失败则没有智能提示。xmlns:是xml namespace的简写。xmlns:xsi:其xsd文件是xml需要遵守的规范,通过URL可以看到,是w3的统一规范,后面通过xsi:schemaLocation来定位所有的解析文件,这里只能成偶数对出现。
<bean id="advice" class="com.zhangguo.Spring041.aop08.Advices"></bean>表示通知bean,也就是横切逻辑bean。<aop:config proxy-target-class="true">用于AOP配置,proxy-target-class属性表示被代理的类是否为一个没有实现接口的类,Spring会依据实现了接口则使用JDK内置的动态代理,如果未实现接口则使用cblib;在Bean配置文件中,所有的Spring AOP配置都必须定义在<aop:config>元素内部。对于每个切面而言,都要创建一个<aop:aspect>元素来为具体的切面实现引用后端Bean实例。因此,切面Bean必须有一个标识符,供<aop:aspect>元素引用。
aop:aspect表示切面配置, ref表示通知对象的引用;aop:pointcut是配置切入点,就是横切逻辑将注入的精确位置,那些包,类,方法需要拦截注入横切逻辑。
aop:before用于声明通知,method指定通知类型,pointcut指定切点,就是该通知应该注入那些方法中。在aop Schema中,每种通知类型都对应一个特定地XML元素。通知元素需要pointcut-ref属性来引用切入点,或者用pointcut属性直接嵌入切入点表达式。method属性指定切面类中通知方法的名称。有如下几种:
<!-- 前置通知 -->
<aop:before method="before" pointcut-ref="pointcut1"/>
<!-- 后置通知 -->
<aop:after method="after" pointcut-ref="pointcut1"/>
<!--环绕通知 -->
<aop:around method="around" pointcut="execution(* com.zhangguo.Spring041.aop08.Math.s*(..))"/>
<!--异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut="execution(* com.zhangguo.Spring041.aop08.Math.d*(..))" throwing="exp"/>
<!-- 返回值通知 -->
<aop:after-returning method="afterReturning" pointcut="execution(* com.zhangguo.Spring041.aop08.Math.m*(..))" returning="result"/>
参数解释:
3.1 表达式类型
标准的Aspectj Aop的pointcut的表达式类型是很丰富的,但是Spring Aop只支持其中的9种,外加Spring Aop自己扩充的一种一共是10种类型的表达式,分别如下。
● execution:一般用于指定方法的执行,用的最多。
● within:指定某些类型的全部方法执行,也可用来指定一个包。
● this:Spring Aop是基于代理的,生成的bean也是一个代理对象,this就是这个代理对象,当这个对象可以转换为指定的类型时,对应的切入点就是它了,Spring Aop将生效。
● target:当被代理的对象可以转换为指定的类型时,对应的切入点就是它了,Spring Aop将生效。
● args:当执行的方法的参数是指定类型时生效。
● @target:当代理的目标对象上拥有指定的注解时生效。
● @args:当执行的方法参数类型上拥有指定的注解时生效。
● @within:与@target类似,看官方文档和网上的说法都是@within只需要目标对象的类或者父类上有指定的注解,则@within会生效,而@target则是必须是目标对象的类上有指定的注解。而根据笔者的测试这两者都是只要目标类或父类上有指定的注解即可。
● @annotation:当执行的方法上拥有指定的注解时生效。
● bean:当调用的方法是指定的bean的方法时生效。
3.2 使用示例
(1) execution
execution是使用的最多的一种Pointcut表达式,表示某个方法的执行,其标准语法如下。
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
modifiers-pattern表示方法的访问类型,public等;
ret-type-pattern表示方法的返回值类型,如String表示返回类型是String,“*”表示所有的返回类型;
declaring-type-pattern表示方法的声明类,如“com.elim..*”表示com.elim包及其子包下面的所有类型;
name-pattern表示方法的名称,如“add*”表示所有以add开头的方法名;
param-pattern表示方法参数的类型,name-pattern(param-pattern)其实是一起的表示的方法集对应的参数类型,如“add()”表示不带参数的add方法,“add(*)”表示带一个任意类型的参数的add方法,“add(*,String)”则表示带两个参数,且第二个参数是String类型的add方法;
throws-pattern表示异常类型;其中以问号结束的部分都是可以省略的。
1、“execution(* add())”匹配所有的不带参数的add()方法。
2、“execution(public * com..*.add*(..))”匹配所有com包及其子包下所有类的以add开头的所有public方法。
3、“execution(* *(..) throws Exception)”匹配所有抛出Exception的方法。
(2) within
within是用来指定类型的,指定类型中的所有方法将被拦截。
1、“within(com.spring.aop.service.UserServiceImpl)”匹配UserServiceImpl类对应对象的所有方法外部调用,而且这个对象只能是UserServiceImpl类型,不能是其子类型。
2、“within(com.elim..*)”匹配com.elim包及其子包下面所有的类的所有方法的外部调用。
(3) this
Spring Aop是基于代理的,this就表示代理对象。this类型的Pointcut表达式的语法是this(type),当生成的代理对象可以转换为type指定的类型时则表示匹配。基于JDK接口的代理和基于CGLIB的代理生成的代理对象是不一样的。
1、“this(com.spring.aop.service.IUserService)”匹配生成的代理对象是IUserService类型的所有方法的外部调用。
(4) target
Spring Aop是基于代理的,target则表示被代理的目标对象。当被代理的目标对象可以被转换为指定的类型时则表示匹配。
1、“target(com.spring.aop.service.IUserService)”则匹配所有被代理的目标对象能够转换为IUserService类型的所有方法的外部调用。
(5) args
args用来匹配方法参数的。
1、“args()”匹配任何不带参数的方法。
2、“args(java.lang.String)”匹配任何只带一个参数,而且这个参数的类型是String的方法。
3、“args(..)”带任意参数的方法。
4、“args(java.lang.String,..)”匹配带任意个参数,但是第一个参数的类型是String的方法。
5、“args(..,java.lang.String)”匹配带任意个参数,但是最后一个参数的类型是String的方法。
(6) @target
@target匹配当被代理的目标对象对应的类型及其父类型上拥有指定的注解时。
1、“@target(com.spring.support.MyAnnotation)”匹配被代理的目标对象对应的类型上拥有MyAnnotation注解时。
(7) @args
@args匹配被调用的方法上含有参数,且对应的参数类型上拥有指定的注解的情况。
1、“@args(com.spring.support.MyAnnotation)”匹配方法参数类型上拥有MyAnnotation注解的方法调用。如我们有一个方法add(MyParam param)接收一个MyParam类型的参数,而MyParam这个类是拥有注解MyAnnotation的,则它可以被Pointcut表达式“@args(com.elim.spring.support.MyAnnotation)”匹配上。
(8) @within
@within用于匹配被代理的目标对象对应的类型或其父类型拥有指定的注解的情况,但只有在调用拥有指定注解的类上的方法时才匹配。
1、“@within(com.spring.support.MyAnnotation)”匹配被调用的方法声明的类上拥有MyAnnotation注解的情况。比如有一个ClassA上使用了注解MyAnnotation标注,并且定义了一个方法a(),那么在调用ClassA.a()方法时将匹配该Pointcut;如果有一个ClassB上没有MyAnnotation注解,但是它继承自ClassA,同时它上面定义了一个方法b(),那么在调用ClassB().b()方法时不会匹配该Pointcut,但是在调用ClassB().a()时将匹配该方法调用,因为a()是定义在父类型ClassA上的,且ClassA上使用了MyAnnotation注解。但是如果子类ClassB覆写了父类ClassA的a()方法,则调用ClassB.a()方法时也不匹配该Pointcut。
(9) @annotation
@annotation用于匹配方法上拥有指定注解的情况。
1、“@annotation(com.spring.support.MyAnnotation)”匹配所有的方法上拥有MyAnnotation注解的方法外部调用。
(10) bean
bean用于匹配当调用的是指定的Spring的某个bean的方法时。
1、“bean(abc)”匹配Spring Bean容器中id或name为abc的bean的方法调用。
2、“bean(user*)”匹配所有id或name为以user开头的bean的方法调用。
3.4 表达式组合
表达式的组合其实就是对应的表达式的逻辑运算,与、或、非。可以通过它们把多个表达式组合在一起。
1、“bean(userService) && args()”匹配id或name为userService的bean的所有无参方法。
2、“bean(userService) || @annotation(MyAnnotation)”匹配id或name为userService的bean的方法调用,或者是方法上使用了MyAnnotation注解的方法调用。
3、“bean(userService) && !args()”匹配id或name为userService的bean的所有有参方法调用。
3.5 基于Aspectj注解的Pointcut表达式应用
在使用基于Aspectj注解的Spring Aop时,我们可以把通过@Pointcut注解定义Pointcut,指定其表达式,然后在需要使用Pointcut表达式的时候直接指定Pointcut。
@Component
@Aspect
public class MyAspect {
@Pointcut("execution(* add(..))")
private void beforeAdd() {}
@Before("beforeAdd()")
public void before() {
System.out.println("-----------before-----------");
}
}
上面的代码中我们就是在@Before()中直接指定使用当前类定义的beforeAdd()方法对应的Pointcut的表达式,如果我们需要指定的Pointcut定义不是在当前类中的,我们需要加上类名称,如下面这个示例中引用的就是定义在MyService中的add()方法上的Pointcut的表达式。
@Before("com.spring.aop.service.MyService.add()")
public void before2() {
System.out.println("-----------before2-----------");
}
当然了,除了通过引用Pointcut定义间接的引用其对应的Pointcut表达式外,我们也可以直接使用Pointcut表达式的,如下面这个示例就直接在@Before中使用了Pointcut表达式。
/**
* 所有的add方法的外部执行时
*/
@Before("execution(* add())")
public void beforeExecution() {
System.out.println("-------------before execution---------------");
}
4. 获得代理对象
package com.zhangguo.Spring041.aop08;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
@org.junit.Test
public void test01()
{
//容器
ApplicationContext ctx=new ClassPathXmlApplicationContext("beansOfAOP.xml");
//从代理工厂中获得代理对象
IMath math=(IMath)ctx.getBean("math");
int n1=100,n2=5;
math.add(n1, n2);
math.sub(n1, n2);
math.mut(n1, n2);
math.div(n1, n2);
}
}
5. 测试运行
6. 环绕通知、异常后通知、返回结果后通知
在配置中我们发现共有5种类型的通知,前面我们试过了前置通知与后置通知,另外几种类型的通知如下代码所示:
package com.zhangguo.Spring041.aop08;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 通知
*/
public class Advices {
//前置通知
public void before(JoinPoint jp)
{
System.out.println("--------------------前置通知--------------------");
System.out.println("方法名:"+jp.getSignature().getName()+",参数:"+jp.getArgs().length+",被代理对象:"+jp.getTarget().getClass().getName());
}
//后置通知
public void after(JoinPoint jp){
System.out.println("--------------------后置通知--------------------");
}
//环绕通知
public Object around(ProceedingJoinPoint pjd) throws Throwable{
System.out.println("--------------------环绕开始--------------------");
Object object=pjd.proceed();
System.out.println("--------------------环绕结束--------------------");
return object;
}
//异常后通知
public void afterThrowing(JoinPoint jp,Exception exp)
{
System.out.println("--------------------异常后通知,发生了异常:"+exp.getMessage()+"--------------------");
}
//返回结果后通知
public void afterReturning(JoinPoint joinPoint, Object result)
{
System.out.println("--------------------返回结果后通知--------------------");
System.out.println("结果是:"+result);
}
}
参数说明:
Spring的AOP中before,afterReturning,afterThrowing参数说明:
1、持行方法之前:public void before(Method method, Object[] args, Object cObj) throws Throwable;
method:调用的方法;
args:调用方法所传的参数数组;
cObj:调用的类对象; 2、持行方法之后:public void afterReturning(Object rObj, Method method, Object[] args, Object cObj) throws Throwable;
rObj:调用方法返回的对象;
method:调用的方法;
args:调用方法所传的参数数组;
cObj:调用的类对象; 3、有异常抛出时:public void afterThrowing(Method method, Object[] args, Object cObj, Exception e);
method:调用的方法;
args:调用方法所传的参数数组;
cObj:调用的类对象;
e:所抛出的异常类型; 在异常抛出时的Exception必须指定为具体的异常对象,如ClassNotFoundException。
容器配置文件beansOfAOP.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:p="http://www.springframework.org/schema/p"
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-4.3.xsd">
<!--被代理的目标对象 -->
<bean id="math" class="com.zhangguo.Spring041.aop08.Math"></bean>
<!-- 通知 -->
<bean id="advice" class="com.zhangguo.Spring041.aop08.Advices"></bean>
<!-- AOP配置 -->
<!-- proxy-target-class属性表示被代理的类是否为一个没有实现接口的类,Spring会依据实现了接口则使用JDK内置的动态代理,如果未实现接口则使用cblib -->
<aop:config proxy-target-class="true">
<!-- 切面配置 -->
<!--ref表示通知对象的引用 -->
<aop:aspect ref="advice">
<!-- 配置切入点(横切逻辑将注入的精确位置) -->
<aop:pointcut expression="execution(* com.zhangguo.Spring041.aop08.Math.a*(..))" id="pointcut1"/>
<!--声明通知,method指定通知类型,pointcut指定切点,就是该通知应该注入那些方法中 -->
<aop:before method="before" pointcut-ref="pointcut1"/>
<aop:after method="after" pointcut-ref="pointcut1"/>
<aop:around method="around" pointcut="execution(* com.zhangguo.Spring041.aop08.Math.s*(..))"/>
<aop:after-throwing method="afterThrowing" pointcut="execution(* com.zhangguo.Spring041.aop08.Math.d*(..))" throwing="exp"/>
<aop:after-returning method="afterReturning" pointcut="execution(* com.zhangguo.Spring041.aop08.Math.m*(..))" returning="result"/>
</aop:aspect>
</aop:config>
</beans>
测试代码:
package com.zhangguo.Spring041.aop08;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
@org.junit.Test
public void test01()
{
//容器
ApplicationContext ctx=new ClassPathXmlApplicationContext("beansOfAOP.xml");
//从代理工厂中获得代理对象
IMath math=(IMath)ctx.getBean("math");
int n1=100,n2=0;
math.add(n1, n2);
math.sub(n1, n2);
math.mut(n1, n2);
math.div(n1, n2);
}
}
运行结果:
小结:不同类型的通知参数可能不相同;aop:after-throwing需要指定通知中参数的名称throwing="exp",则方法中定义应该是这样:afterThrowing(JoinPoint jp,Exception exp);aop:after-returning同样需要设置returning指定方法参数的名称。通过配置切面的方法使AOP变得更加灵活。
7.6、spring中使用Aspect方式进行AOP如何得到method对象
Signature signature = pjp.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
try
{
Object[] arguments = pjp.getArgs();
result = method.invoke(mock, arguments);
s_logger.debug("Mocking " + formatCall(a_joinPoint));
}
catch (InvocationTargetException e)
{
s_logger.debug("Failed to delegate to mock: "
+ formatCall(a_joinPoint), e);
throw e.getTargetException();
}