上一篇博客介绍了AOP思想,并且解释了使用JDK的动态代理实现AOP思想,下面介绍使用Spring 来实现AOP。首先我们对AOP思想的基本术语必须要了解,基本术语:
* 横切性关注点(cross cutting conern): 使用AOP思想所能解决的问题,比如,在调用目的请求前进行字符集转换、在进行业务逻辑前进行权限控制、在关键方法前进行日志记录等都是对问题的抽象。
*切面(Aspect):对横切性关注点的模块化实现,也就是字符转换逻辑,事物控制逻辑,在JDK动态代理中就是继承了InvocationHandler的代理类。
*连接点(JoinPoint):表示横切性关注点逻辑通过切入点最终作用的目标对象的那个点的抽象。通俗点讲就是AOP作用的具体的点的抽象,例如method、attribute、object,在spring中是方法。
*切入点(Pointcut):横切性关注点适用的范围的抽象,AOP思想最终要起作用的范围。切入点有一个表达式语言,spring aop可以限定到方法层次,例如 execution(“* com.wj.service.*.*Service(..)”) 表示 com.wj.service包下所有的类的所有方法名以Service结尾的方法
*通知(Advice):对切入点所匹配的连接点上触发的aop操作类型。通知分为很多种,前置通知、后置通知、环绕通知、异常通知、返回通知等。适用于不同的场景。
*代理(proxy):spring会给切入点适用范围的类生成代理,spring 默认适用JDK的动态代理,但JDK动态代理要求目标对象不需要有实现接口,对于没有接口的内容,spring会适用CGLIB库生成目标类的子类代理。
*织入(weave):通过切面、切入点、连接点来对目标对象进行代理的过程称为织入。
基本概念基本就那么多。直接看文字解释理解起来比较费劲,下面看看例子,适用AspectJ注解实现Aop;
假设我们要对操作添加日志和参数验证,那么我们的横切性关注点就是添加日志、参数验证,基本代码如下:
基本类如下:
package com.wj.aop;
public class User { private String username; private String password; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User [username=" + username + ", password=" + password + "]"; } }
package com.wj.aop;
public interface IUserService { int addService(String username,String password); User findUserService(String username); void printlInfo(); }
package com.wj.aop;
import java.util.*; public class UserService implements IUserService { private static final List<User> userList = new ArrayList<User>(); public int addService(String username, String password) { User user = new User(); user.setPassword(password); user.setUsername(username); System.out.println("add User user = " + user); userList.add(user); return 1; } public User findUserService(String username) { User result = null; for(User user : userList){ if(user.getUsername().equals(username)){ result = user; break; } } return result; } public static List<User> getUserlist() { return userList; } public void printlInfo() { System.out.println(this.userList); } }
上面是基本的数据操作,下面是对日志添加、参数验证的实现,也就是切面、切入点、通知的实现:
package com.wj.aop; import java.text.DateFormat; import java.util.Date; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; @Aspect public class AopAspect { private static Logger logger = LoggerFactory.getLogger(AopAspect.class ); @Pointcut("execution(* com.wj.aop.*.*Service(..))") private void processorRange(){} @Before("processorRange()") public void checkArg(JoinPoint joinpoint){ System.out.println("=====================================AopAspect.checkArg()"); Object[] args = joinpoint.getArgs(); for(Object obj : args){ if(obj == null || obj.toString().length() == 0){ System.out.println("=====================================参数内容为空,程序异常不能处理"); throw new NullPointerException("the args is null"); } } } @Before("processorRange()") public void logProcessor(JoinPoint joinpoint){ System.out.println("=====================================AopAspect.logProcessor()"); String argStr =""; Object[] args = joinpoint.getArgs(); for(Object obj : args){ argStr+=obj.toString(); } String methodName = joinpoint.getSignature().getName(); String time = DateFormat.getDateInstance().format(new Date()); System.out.println(time+" 调用方法是 "+ methodName +" 参数是 "+argStr); } }
代码中使用@Aspect定义了切面,使用@PointCut定义了切入点,使用@Before定义了前置通知,在方法中有个JoinPoint连接点参数,这个参数spring会自动注入,它代表了目标对象连接点的抽象,可以得到代理对象对方方法的签名、参数等数据,spring支持多种切入点指定者,其中我们用的最多的是execution匹配特定方法的制定者,其他指定者分别是call, initialization, preinitialization, staticinitialization, get, set, handler, adviceexecution, withincode, cflow, cflowbelow, if, @this
和 @withincode:
-
execution - 匹配方法执行的连接点,这是你将会用到的Spring的最主要的切入点指定者。
-
within - 限定匹配特定类型的连接点(在使用Spring AOP的时候,在匹配的类型中定义的方法的执行)。
-
this - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中bean reference(Spring AOP 代理)是指定类型的实例。
-
target - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中目标对象(被代理的appolication object)是指定类型的实例。
-
args - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中参数是指定类型的实例。
-
@target
- 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中执行的对象的类已经有指定类型的注解。 -
@args
- 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中实际传入参数的运行时类型有指定类型的注解。 -
@within
- 限定匹配特定的连接点,其中连接点所在类型已指定注解(在使用Spring AOP的时候,所执行的方法所在类型已指定注解)。 -
@annotation - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中连接点的主题有某种给定的注解
解释是翻译spring2.0 官方文档。下面给出一些常见切入点表达式的例子。
-
任意公共方法的执行:
execution(public * *(..))
-
任何一个以“set”开始的方法的执行:
execution(* set*(..))
-
AccountService
接口的任意方法的执行:execution(* com.xyz.service.AccountService.*(..))
-
定义在service包里的任意方法的执行:
execution(* com.xyz.service.*.*(..))
-
定义在service包或者子包里的任意方法的执行:
execution(* com.xyz.service..*.*(..))
-
最后需要将上述类添加到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: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"> <aop:aspectj-autoproxy /> <bean id="User" class="com.wj.aop.User" /> <bean id="UserService" class="com.wj.aop.UserService" /> <bean id="AopAspect" class="com.wj.aop.AopAspect" /> </beans>
下面是测试的客服端代码:
package com.wj.test;
import org.springframework.beans.factory.BeanFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.wj.aop.*; public class client { public static void main(String[] args) { BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml"); IUserService service = (IUserService) factory.getBean("UserService"); service.addService("zhangsan", "123"); service.addService("lisi", "234"); service.printlInfo(); service.addService("AAA", ""); User user = service.findUserService("zhangsan"); System.out.println("result=" + user); } }
运行结果:
[manager] 2012-10-22 15:41:23,125 - org.springframework.beans.factory.support.DefaultListableBeanFactory -2219 [main] DEBUG - Returning cached instance of singleton bean 'UserService'
=====================================AopAspect.checkArg() =====================================AopAspect.logProcessor() 2012-10-22 调用方法是 addService 参数是 zhangsan123 add User user = User [username=zhangsan, password=123] [User [username=zhangsan, password=123]] =====================================AopAspect.checkArg() =====================================参数内容为空,程序异常不能处理 Exception in thread "main" java.lang.NullPointerException: the args is null at com.wj.aop.AopAspect.checkArg(AopAspect.java:29) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597)
上面是使用AspectJ注解方式来实现AOP,其实就是定义切面、切入点、通知,在将切入点和通知关联起来。下面我们适用spring的xml配置方式实现,将AopAspect里的所有注解去掉,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: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"> <aop:aspectj-autoproxy /> <bean id="User" class="com.wj.aop.User" /> <bean id="UserService" class="com.wj.aop.UserService" /> <bean id="AopAspect" class="com.wj.aop.AopAspect" /> <aop:config> <aop:aspect id="aspect" ref="AopAspect"> <aop:pointcut expression="execution(* com.wj.aop.*.*Service(..))" id="pointcut"/> <aop:before method="checkArg" pointcut-ref="pointcut"/> <aop:before method="logProcessor" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
核心配置如下:
<aop:config> <aop:aspect id="aspect" ref="AopAspect"> <aop:pointcut expression="execution(* com.wj.aop.*.*Service(..))" id="pointcut"/> <aop:before method="checkArg" pointcut-ref="pointcut"/> <aop:before method="logProcessor" pointcut-ref="pointcut"/> </aop:aspect> </aop:config>
实际就是定义切入点、切面、通知以及他们的关系。