Spring2.0提供了一种更简便也更强大的方式来编写切面,可以通过基于schema的方式,也可以通过@AspectJ注解的方式,这两种方式都提供了完整的AspectJ切入点语言中的通知和使用方法,但是依然使用的是Spring AOP的织入方式,也就是通过代理的方式进行织入(不同于AspectJ在编译期织入)。
Spring 2.0 AOP完全兼容Spring 1.2 AOP。
@AspectJ注解方式是AspectJ项目在第5个版本中引进的。Spring使用了AspectJ5中的相同的注解,通过使用AspectJ提供的一个库来进行切入点的解析和匹配。但AOP仍然是采用代理的方式进行织入的,也就是Spring AOP不依赖于AspectJ的编译器和织入器。
要在Spring中使用@AspectJ注解,需要在Spring中添加基于@AspectJ切面的支持,并且配置自动代理。自动代理意味着如果Spring判断出一个Bean被一个或者更多的切面通知了,它就会自动生成该Bean的代理,来拦截方法的执行,确保通知按照我们所需被执行。
为了添加@AspectJ支持,我们需要将AspectJ中的Jar包aspectjweaver.jar
引入项目,该Jar包在Aspect项目目录下的lib
目录下,可以在Maven中央仓库中找到。
配置自动代理一般通过在Spring的配置文件中进行配置,添加如下元素:
<aop:aspectj-autoproxy/>
当然,添加这个配置,需要导入支持aop
命名空间的标签。
下面通过一个示例来介绍如何在Spring中通过注解的方式来进行AOP开发。
首先我们创建一个简单的Maven项目,引入Spring依赖,并且引入上面所说的aspectjweaver.jar
依赖。这些基本的库文件已经满足我们所需了。如下是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.0modelVersion>
<groupId>com.gavingroupId>
<artifactId>SpringAopartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>5.0.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-beansartifactId>
<version>5.0.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.0.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.1version>
dependency>
dependencies>
project>
然后我们添加Spring的配置文件spring-config.xml
,并且导入支持aop
命名空间的标签,用该标签配置自动代理:
接下来我们创建业务类com.gavin.service.LoginService
,如下:
package com.gavin.service;
public class LoginService {
public String login(String username, String password) {
System.out.println(username + "正在登录,他的密码是:" + password);
return "success";
}
}
可以看到我们在LoginService
中写了一个login
方法,该方法有两个String
参数,并且有一个String
返回值。
然后我们在spring-config.xml
注入该Bean,此时的配置文件为:
<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/>
<bean id="loginService" class="com.gavin.service.LoginService"/>
beans>
接着创建一个测试类com.gavin.test.Main
,进行简单的测试:
package com.gavin.test;
import com.gavin.service.LoginService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
LoginService loginService = applicationContext.getBean("loginService", LoginService.class);
loginService.login("Gavin", "123");
}
}
运行结果为:
运行结果符合我们的预期。接下来我们使用注解的方法来创建一个切面com.gavin.aspect.LoginAdvice
,如下:
package com.gavin.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class LoginAdvice {
@Pointcut("execution(* com.gavin.service.LoginService.login(..))")
public void loginPointcut(){}
@Before("loginPointcut() && args(username, password)")
public void beforeLogin(JoinPoint thisJoinPoint, String username, String password) {
System.out.println("thisJoinPoint : " + thisJoinPoint);
System.out.println("在登录之前记录的日志...");
System.out.println("username = " + username);
System.out.println("password = " + password);
System.out.println();
}
@AfterReturning(pointcut = "loginPointcut()", returning = "returnVal")
public void afterLogin(String returnVal) {
System.out.println();
System.out.println("返回值是:" + returnVal);
}
}
我们使用@Aspect
注解该类,表示其是一个切面。在切面中,我们定义了名字为loginPointcut
的切入点,并为该切入点定义了前置通知和后置通知。在前置通知中,通过args
原生切入点获取了方法的参数。在后置通知中,我们获取了方法的返回值。
最后我们需要在Spring配置文件中注入该切面,此时的配置文件为:
<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/>
<bean id="loginService" class="com.gavin.service.LoginService"/>
<bean class="com.gavin.aspect.LoginAdvice"/>
beans>
再次运行程序,此时的运行结果为:
通过上例可以看出,Spring AOP使用注解的语法与前面所介绍的AspectJ中的注解语法是一模一样的。
Spring AOP 支持AspectJ中的大部分语法,也支持一些原生的切入点,比如this
和args
等,但是有一些是不支持的,比如call
、get
、set
、preinitialization
、staticinitialization
、initialization
、handler
、withincode
、cflow
、cflowbelow
、if
、@this
和@withincode
等。在Spring AOP中使用这些原生切入点将会抛出IllegalArgumentException
异常。
那么总结一下,Spring AOP支持的切入点标识符有:execution
、within
、this
、target
、args
、@annotation
以及@target
、@args
和@within
。这些都是AspectJ中所具有的,除此之外,Spring AOP加入了一个额外的切入点标识符:bean(idOrNameOfBean)
,它用来匹配特定名称的Bean对象的执行方法。
对于这些标识符,我们只要关心一些常用的即可。
上例我们使用常规的execution
表达式来声明切入点,除此之外,也可以使用自定义注解,通过注解的方法来声明切入点,只有添加了注解的方法才能被增强。
我们在上个例子的基础上进行修改,首先我们自定义注解UserLogin
,如下:
package com.gavin.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLogin {
}
接着我们对业务类需要增强的方法login
添加我们自定义的注解:
package com.gavin.service;
import com.gavin.annotation.UserLogin;
public class LoginService {
@UserLogin
public String login(String username, String password) {
System.out.println(username + "正在登录,他的密码是:" + password);
return "success";
}
}
此时修改切面,我们只需要修改切入点表达式,使用@annotation
,如下:
package com.gavin.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class LoginAdvice {
@Pointcut("@annotation(com.gavin.annotation.UserLogin)")
public void loginPointcut(){}
@Before("loginPointcut() && args(username, password)")
public void beforeLogin(JoinPoint thisJoinPoint, String username, String password) {
System.out.println("thisJoinPoint : " + thisJoinPoint);
System.out.println("在登录之前记录的日志...");
System.out.println("username = " + username);
System.out.println("password = " + password);
System.out.println();
}
@AfterReturning(pointcut = "loginPointcut()", returning = "returnVal")
public void afterLogin(String returnVal) {
System.out.println();
System.out.println("返回值是:" + returnVal);
}
}
可以看到,我们只修改了切入点声明,其他的都没有更改。此时运行程序会得到一样的结果。
Spring 2.0 也支持通过schema的方式进行AOP的配置。我们仍然通过上述例子来进行介绍。
这里我们在test2
包下进行开发:
代码与上面的例子基本类似,业务类LoginService
如下:
package com.gavin.test2;
public class LoginService {
public String login(String username, String password) {
System.out.println(username + "正在登录,他的密码是:" + password);
return "success";
}
}
切面类也是一样的,只不过我们删除配置AOP的注解:
package com.gavin.test2;
import org.aspectj.lang.JoinPoint;
public class LoginAdvice {
public void beforeLogin(JoinPoint thisJoinPoint, String username, String password) {
System.out.println("thisJoinPoint : " + thisJoinPoint);
System.out.println("Before 登录 记录的日志...");
System.out.println("username = " + username);
System.out.println("password = " + password);
System.out.println();
}
public void afterLogin(String returnVal) {
System.out.println();
System.out.println("返回值是:" + returnVal);
}
}
此时我们在Spring的配置文件spring-config.xml
中进行AOP的配置,如下:
<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="loginService" class="com.gavin.test2.LoginService"/>
<bean id="loginAdvice" class="com.gavin.test2.LoginAdvice"/>
<aop:config>
<aop:aspect id="loginAdvice" ref="loginAdvice">
<aop:pointcut id="loginPointcut" expression="execution(* com.gavin.test2.LoginService.login(..)) and args(username, password)"/>
<aop:before method="beforeLogin" arg-names="thisJoinPoint, username, password" pointcut-ref="loginPointcut"/>
<aop:after-returning method="afterLogin" arg-names="returnVal" returning="returnVal" pointcut="execution(* com.gavin.test2.LoginService.login(..))"/>
aop:aspect>
aop:config>
beans>
其实与通过注解的方式配置是一样的,只不过我们把注解里面的东西都放进了XML文件中。这种方式没有注解的方法简洁。
主方法也没有改变,如下:
package com.gavin.test2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
LoginService loginService = applicationContext.getBean("loginService", LoginService.class);
loginService.login("Gavin", "123");
}
}
运行之后得到运行结果,可以看到运行结果与上面通过注解的方式是完全一样的: