上篇博客中讲解了一些AOP的基础概念和Spring AOP的基础知识点。现在来具体实践一下,一探究竟。
在spring中,常用的AOP实现方式有两种:一种是基于xml配置文件方式的实现,另一种是基于注解的实现。接下来以日志服务类为例,来看看这两种方式的具体实现。
业务类准备
用户服务接口
package com.tgb.spring;
/**
* @ClassName: UserManager
* @Description: 用户业务类接口
* @author 牛迁迁
* @date 2016-4-27 下午8:09:07
*/
public interface UserManager {
public void addUser(String username, String password);
public void delUser(int userId);
public String findUserById(int userId);
public void modifyUser(int userId, String username, String password);
}
用户服务实现类
package com.tgb.spring;
public class UserManagerImpl implements UserManager {
public void addUser(String username, String password) {
System.out.println("---------UserManagerImpl.add()--------");
}
public void delUser(int userId) {
System.out.println("---------UserManagerImpl.delUser()--------");
}
public String findUserById(int userId) {
System.out.println("---------UserManagerImpl.findUserById()--------");
return "张三";
}
public void modifyUser(int userId, String username, String password) {
System.out.println("---------UserManagerImpl.modifyUser()--------");
}
}
配置文件方式实现AOP
日志切面类
package com.tgb.spring;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 日志切面类
*
* @author 牛迁迁
*/
public class LogAspect {
// 任何通知方法都可以将第一个参数定义为 org.aspectj.lang.JoinPoint类型
public void before(JoinPoint call) {
// 获取目标对象对应的类名
String className = call.getTarget().getClass().getName();
// 获取目标对象上正在执行的方法名
String methodName = call.getSignature().getName();
System.out.println("前置通知:" + className + "类的" + methodName + "方法开始了");
}
public void afterReturn() {
System.out.println("后置通知:方法正常结束");
}
public void after() {
System.out.println("最终通知:不管方法有没有正常执行完成,一定会返回的");
}
public void afterThrowing() {
System.out.println("异常抛出后通知:方法执行时出异常了");
}
// 用来做环绕通知的方法可以第一个参数定义为org.aspectj.lang.ProceedingJoinPoint类型
public Object doAround(ProceedingJoinPoint call) throws Throwable {
Object result = null;
this.before(call);// 相当于前置通知
try {
result = call.proceed();
this.afterReturn(); // 相当于后置通知
} catch (Throwable e) {
this.afterThrowing(); // 相当于异常抛出后通知
throw e;
} finally {
this.after(); // 相当于最终通知
}
return result;
}
}
这个类就是日志服务类,是Spring中的切面类Aspect,它定义了许多通知:Before()、afterReturn()、after()和afterThrowing()这些方法都是通知。通知可以看做是原始方法的增强
applicationContext.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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<bean id="userManager" class="com.tgb.spring.UserManagerImpl" />
<!-- 日志切面类 -->
<bean id="logAspectBean" class="com.tgb.spring.LogAspect" />
<!-- 第1步: AOP的配置 -->
<aop:config>
<!-- 第2步:配置一个切面 -->
<aop:aspect id="logAspect" ref="logAspectBean">
<!-- 第3步:定义切入点,指定切入点表达式 -->
<aop:pointcut id="allMethod" expression="execution(* com.tgb.spring.*.*(..))" />
<!-- 第4步:应用前置通知 -->
<!-- <aop:before method="before" pointcut-ref="allMethod" /> -->
<!-- 第4步:应用后置通知 -->
<!-- <aop:after-returning method="afterReturn" pointcut-ref="allMethod" /> -->
<!-- 第4步:应用最终通知 -->
<!-- <aop:after method="after" pointcut-ref="allMethod"/> -->
<!-- 第4步:应用抛出异常后通知 -->
<!-- <aop:after-throwing method="afterThrowing" pointcut-ref="allMethod"/> -->
<!-- 第4步:应用环绕通知 -->
<aop:around method="doAround" pointcut-ref="allMethod" />
</aop:aspect>
</aop:config>
</beans>
注意:上面配置了前置,后置,最终,环绕等各种通知,但是启动时只能配置一个通知,其他的应该注释掉。
客户端测试
测试环绕通知:
public class Client {
public static void main(String[] args) {
BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
UserManager userManager = (UserManager)factory.getBean("userManager");
userManager.delUser(1);
}
}
打印:
前置通知:com.tgb.spring.UserManagerImpl类的delUser方法开始了
---------UserManagerImpl.delUser()--------
后置通知:方法正常结束
最终通知:不管方法有没有正常执行完成,一定会返回的
基于注解的AOP的实现
Spring提供了基于注解的AOP支持,大大简化AOP的配置。具体的步骤有两步。
开发一个基于注解的切面类LogAspect
package com.tgb.spring;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
* 日志切面类
*
* @author 牛迁迁
*/
@Aspect
// 定义切面类
public class LogAspect {
// 定义切入点,提供一个方法,这个方法的名字就是改切入点的id
@Pointcut("execution(* com.tgb.spring.*.*(..))")
//@Pointcut("execution(* del*(..))")
private void allMethod() {
}
// 针对指定的切入点表达式选择的切入点应用前置通知
//@Before("allMethod()")
public void before(JoinPoint call) {
String className = call.getTarget().getClass().getName();
String methodName = call.getSignature().getName();
System.out.println("【注解-前置通知】:" + className + "类的" + methodName
+ "方法开始了");
}
// 访问命名切入点来应用后置通知
//@AfterReturning("allMethod()")
public void afterReturn() {
System.out.println("【注解-后置通知】:方法正常结束了");
}
// 应用最终通知
//@After("allMethod()")
public void after() {
System.out.println("【注解-最终通知】:不管方法有没有正常执行完成," + "一定会返回的");
}
// 应用异常抛出后通知
//@AfterThrowing("allMethod()")
public void afterThrowing() {
System.out.println("【注解-异常抛出后通知】:方法执行时出异常了");
}
// 应用周围通知
@Around("allMethod()")
public Object doAround(ProceedingJoinPoint call) throws Throwable {
Object result = null;
this.before(call);// 相当于前置通知
try {
result = call.proceed();
this.afterReturn(); // 相当于后置通知
} catch (Throwable e) {
this.afterThrowing(); // 相当于异常抛出后通知
throw e;
} finally {
this.after(); // 相当于最终通知
}
return result;
}
}
@Aspect 表示该类是一个切面类
@Pointcut 定义切入点,并且需要提供一个方法,这个方法的名字就是改切入点的id,这个方法仅作为一个标识,没有实际代码。allMethod()
@Before,@AfterReturning,@Around定义通知,方法执行的时机
applicationContext.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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<bean id="userManager" class="com.tgb.spring.UserManagerImpl" />
<!-- 日志切面类 -->
<bean id="logAspectBean" class="com.tgb.spring.LogAspect" />
<!-- 启用spring对AspectJ注解的支持 -->
<aop:aspectj-autoproxy/>
</beans>
客户端测试
public class Client {
public static void main(String[] args) {
BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
UserManager userManager = (UserManager)factory.getBean("userManager");
userManager.delUser(1);
}
}
输出:
【注解-前置通知】:com.tgb.spring.UserManagerImpl类的delUser方法开始了
---------UserManagerImpl.delUser()--------
【注解-后置通知】:方法正常结束了
【注解-最终通知】:不管方法有没有正常执行完成,一定会返回的
在做注解开发的时候遇到了一个错误:
错误:
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userManager' defined in class path resource [applicationContext.xml]: Initialization of bean failed; nested exception is java.lang.IllegalArgumentException: error at ::0 can't find referenced pointcut allMethod
Caused by: java.lang.IllegalArgumentException: error at ::0 can't find referenced pointcut allMethod
at org.aspectj.weaver.tools.PointcutParser.parsePointcutExpression(PointcutParser.java:315)
at org.springframework.aop.aspectj.AspectJExpressionPointcut.buildPointcutExpression(AspectJExpressionPointcut.java:172)
at org.springframework.aop.aspectj.AspectJExpressionPointcut.checkReadyToMatch(AspectJExpressionPointcut.java:162)
at org.springframework.aop.aspectj.AspectJExpressionPointcut.getClassFilter(AspectJExpressionPointcut.java:103)
at org.springframework.aop.support.AopUtils.canApply(AopUtils.java:171)
at org.springframework.aop.support.AopUtils.canApply(AopUtils.java:231)
at org.springframework.aop.support.AopUtils.findAdvisorsThatCanApply(AopUtils.java:256)
详细检查了代码和applicationContext.xml配置文件,发现并没有错!而其他Spring项目能运行,因此出错原因可以肯定是aspectj两个jar包的问题。网上很多说法是spring 2.0的版本中的的aspectjrt.jar和jdk不兼容,我的JDK是1.7的,于是尝试使用不同版本的aspectjrt.jar文件,发现错误仍然存在!所以不是aspectjrt.jar的问题。
于是下载了最新版本的aspectjweaver.jar并替换了原来的版本,错误成功解决了,程序正常运行。
小结
文章讲解了Spring AOP的两种实现方式,xml配置和注解方式,具体选择哪种看实际情况。要想工作量小,开发效率高就用注解方式,要想更易于扩展就选择配置方式。本文仅仅讲解了Spring的AOP是怎么回事,AOP作为一种思想一种编程方式,实现的方式是多种多样的,比如之前的拦截器,过滤器,动态代理等都是AOP的思想的实现“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”