Spring 2.0 AOP
Spring2.0提供了一种更简便也更强大的方式来编写切面,可以通过基于schema的方式,也可以通过@AspectJ注解的方式,这两种方式都提供了完整的AspectJ切入点语言中的通知和使用方法,但是依然使用的是Spring AOP的织入方式,也就是通过代理的方式进行织入(不同于AspectJ在编译期织入)。
Spring 2.0 AOP完全兼容Spring 1.2 AOP。
0.使用@AspectJ注解方式进行AOP开发
@AspectJ注解方式是AspectJ项目在第5个版本中引进的。Spring使用了AspectJ5中的相同的注解,通过使用AspectJ提供的一个库来进行切入点的解析和匹配。但AOP仍然是采用代理的方式进行织入的,也就是Spring AOP不依赖于AspectJ的编译器和织入器。
0.0 添加@AspectJ支持
要在Spring中使用@AspectJ注解,需要在Spring中添加基于@AspectJ切面的支持,并且配置自动代理。自动代理意味着如果Spring判断出一个Bean被一个或者更多的切面通知了,它就会自动生成该Bean的代理,来拦截方法的执行,确保通知按照我们所需被执行。
为了添加@AspectJ支持,我们需要将AspectJ中的Jar包aspectjweaver.jar引入项目,该Jar包在Aspect项目目录下的lib目录下,可以在Maven中央仓库中找到。
配置自动代理一般通过在Spring的配置文件中进行配置,添加如下元素:
<aop:aspectj-autoproxy/>
当然,添加这个配置,需要导入支持aop命名空间的标签。
0.1 一个示例
下面通过一个示例来介绍如何在Spring中通过注解的方式来进行AOP开发。
首先我们创建一个简单的Maven项目,引入Spring依赖,并且引入上面所说的aspectjweaver.jar依赖。这些基本的库文件已经满足我们所需了。如下是pom.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<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.gavin</groupId>
<artifactId>SpringAop</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.1</version>
</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,此时的配置文件为:
<?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/>
<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配置文件中注入该切面,此时的配置文件为:
<?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/>
<bean id="loginService" class="com.gavin.service.LoginService"/>
<bean class="com.gavin.aspect.LoginAdvice"/>
</beans>
再次运行程序,此时的运行结果为:

0.2 需要注意的地方
通过上例可以看出,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对象的执行方法。
对于这些标识符,我们只要关心一些常用的即可。
0.3 使用自定义注解作为execution的表达式
上例我们使用常规的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);
}
}
可以看到,我们只修改了切入点声明,其他的都没有更改。此时运行程序会得到一样的结果。
1.使用基于schema的方法进行AOP开发
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的配置,如下:
<?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="loginService" class="com.gavin.test2.LoginService"/>
<!--切面类-->
<bean id="loginAdvice" class="com.gavin.test2.LoginAdvice"/>
<!--AOP配置-->
<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");
}
}
运行之后得到运行结果,可以看到运行结果与上面通过注解的方式是完全一样的:


本文介绍了如何使用Spring2.0的AOP功能,包括@AspectJ注解方式和基于schema的配置方法。通过实例展示了切入点、前置通知及后置通知的应用。

732

被折叠的 条评论
为什么被折叠?



