Spring AOP 概念理解及@AspectJ支持
- public?interface?UserManager?{??
- ??
- ????public?void?add(String?name,?String?password);??
- ????public?void?del(String?id);??
- ????public?void?modify(int?id?,String?name,?String?password);??
- }??
?
Java代码??
- public?class?UserManagerImpl?implements?UserManager?{??
- ??
- ????public?void?add(String?name,?String?password)?{??
- ??
- ????}??
- ??
- ????public?void?del(String?id)?{??
- ??
- ????}??
- ??
- ????public?void?modify(int?id,?String?name,?String?password)?{??
- ??
- ????}??
- ??
- }??
?
Java代码??
- public?class?UserManagerProxy?implements?UserManager?{??
- ??
- ????private?UserManager?userManager?;??
- ????public?void?add(String?name,?String?password)?{??
- ????????check();??
- ????????userManager.add(name,?password);??
- ????}??
- ??
- ????public?void?del(String?id)?{??
- ????????check();??
- ????????userManager.del(id);??
- ????}??
- ??
- ????public?void?modify(int?id,?String?name,?String?password)?{??
- ????????check();??
- ????????userManager.modify(id,?name,?password);??
- ????}??
- ??????
- ????public?void?check(){??
- ????????System.out.println("check?security");??
- ????}??
- ??????
- ????public?void?setUserManager(UserManager?userManager){??
- ????????this.userManager?=?userManager;??
- ????}??
- ??
- ????public?static?void?main(String[]?args)?{??
- ????????UserManagerProxy?proxy?=?new?UserManagerProxy();??
- ????????proxy.setUserManager(new?UserManagerImpl());??
- ????????proxy.add("name","pwd");??
- ????}??
- }??
?
上例中代理类控制在UserManagerImpl进行操作前对用户进行检查即check()方法。
?
那么下面用Spring Aop来实现。
配置步骤:
Java代码??
- 通过示例,理解概念??
- 一、创建普通JAVA项目,加入用户自定义的包:??
- 包里有spring.jar,log4j-1.2.15.jar,commons-logging.jar??
- 二、拷贝log4j.properties和applicationContext.xml到src目录??
- 三、创建代码其中UserManager,UserManagerImpl类是用户管理接口及实现类??
- ????MySecurityManager,MySecurityManagerImpl类是包含安全检查方法的接口及实现类。??
- 四、要启用@AspectJ支持,@AspectJ使用了Java?5的注解,必须是Java?5及后的才能使用。??
- ????在applicationContext.xml加入:<aop:aspectj-autoproxy/>启用@AspectJ支持。??
- ????并在我们的用户自定义包中要加入aspectjrt.jar,aspectjweaver.jar,这两个包可以spring发布包??
- ????的lib\aspectj下找到。??
- 五、声明一个切面:??
- ????在类的定义前加入@Aspect,并引入包?org.aspectj.lang.annotation.Aspect??
- ????@Aspect我们把用Aspect注解的类就叫切面??
?切面如:
Java代码??
- package?com.lwf.aop;??
- ??
- import?org.aspectj.lang.annotation.Aspect;??
- ??
- @Aspect??
- public?class?MySecurityManagerImpl?implements?MySecurityManager?{??
- ??
- ????public?void?checkSecurity()?{??
- ????????System.out.println("User?security?Check");??
- ????}??
- ??
- }??
?
Java代码??
- 六、声明一个切入点(pointcut)??
- 在前面我们提到,切入点决定了连接点关注的内容,使得我们可以控制通知什么时候执行。??
- Spring?AOP只支持Spring?bean的方法执行连接点。所以你可以把切入点看做是Spring?bean上方法执行的匹配。??
- 一个切入点声明有两个部分:一个包含名字和任意参数的签名,还有一个切入点表达式,该表达式决定了我们关注那个方法的执行。??
- 在@AspectJ注解风格的AOP中,一个切入点签名通过一个普通的方法定义来提供,??
- 并且切入点表达式使用@Pointcut注解来表示(作为切入点签名的方法必须返回void?类型)。???
??如:
Java代码??
- package?com.lwf.aop;??
- ??
- import?org.aspectj.lang.annotation.Aspect;??
- import?org.aspectj.lang.annotation.Pointcut;??
- /*?
- ?*?定义切面?
- ?*/??
- @Aspect??
- public?class?MySecurityManagerImpl?implements?MySecurityManager?{??
- ??
- ????/*?
- ?????*?定义切入点,该方法返回值为void,该方法只是一个标识,就象配置文件的id?
- ?????*?切入点的内容是一个表达式,来描述切入哪些对象的哪些方法?
- ?????*?("excute?(*add*(..))")切入点表达表示将要切入所有以add开头的方法,该方法可带任意个数的参数?
- ?????*/??
- ????@Pointcut?("execution?(*?add*(..))")??
- ????public?void?addAllMethod(){};??
- ??????
- ????public?void?checkSecurity()?{??
- ????????System.out.println("User?security?Check");??
- ????}??
- ??
- }??
?
Java代码??
- 七、声明通知??
- 通知是跟一个切入点表达式关联起来的,并且在切入点匹配的方法执行之前或者之后或者前后运行。???
- 切入点表达式可能是指向已命名的切入点的简单引用或者是一个已经声明过的切入点表达式。??
- 通知有:前置通知,后置通知,异常通知,最终通知,环绕通知??
?如:我们声明一个前置通知
Java代码??
- package?com.lwf.aop;??
- ??
- import?org.aspectj.lang.annotation.Aspect;??
- import?org.aspectj.lang.annotation.Before;??
- import?org.aspectj.lang.annotation.Pointcut;??
- /*?
- ?*?定义切面?
- ?*/??
- @Aspect??
- public?class?MySecurityManagerImpl?implements?MySecurityManager?{??
- ??
- ????/*?
- ?????*?定义切入点,该方法返回值为void,该方法只是一个标识,就象配置文件的id?
- ?????*?切入点的内容是一个表达式,来描述切入哪些对象的哪些方法?
- ?????*?("excute?(*add*(..))")切入点表达表示将要切入所有以add开头的方法,该方法可带任意个数的参数?
- ?????*/??
- ????@Pointcut?("execution?(*?add*(..))")??
- ????public?void?addAllMethod(){};??
- ??????
- ??????
- ??????
- ????/*?
- ?????*?前置通知,在addAllMethod切入点所代表的方法前调用checkSecurity方法?
- ?????*??
- ?????*/??
- ????@Before?("addAllMethod()")??
- ????public?void?checkSecurity()?{??
- ????????System.out.println("User?security?Check");??
- ????}??
- ??
- }??
?
上面是分步的配置,下面我把整个配置好的项目代码列出来:
Java代码??
- package?com.lwf.aop;??
- ??
- public?interface?UserManager?{??
- ??
- ????public?void?add(String?name,?String?password);??
- ????public?void?del(String?id);??
- ????public?void?modify(int?id?,String?name,?String?password);??
- }??
?
Java代码??
- package?com.lwf.aop;??
- ??
- public?class?UserManagerImpl?implements?UserManager?{??
- ??
- ????public?void?add(String?name,?String?password)?{??
- ??
- ????????System.out.println("add?method");??
- ????}??
- ??
- ????public?void?del(String?id)?{??
- ????????System.out.println("del?method");??
- ????}??
- ??
- ????public?void?modify(int?id,?String?name,?String?password)?{??
- ????????System.out.println("modify?method");??
- ????}??
- ??
- }??
?
Java代码??
- package?com.lwf.aop;??
- ??
- public?interface?MySecurityManager?{??
- ??
- ????public?void?checkSecurity();??
- }??
?
Java代码??
- package?com.lwf.aop;??
- ??
- import?org.aspectj.lang.annotation.Aspect;??
- import?org.aspectj.lang.annotation.Before;??
- import?org.aspectj.lang.annotation.Pointcut;??
- /*?
- ?*?定义切面?
- ?*/??
- @Aspect??
- public?class?MySecurityManagerImpl?implements?MySecurityManager?{??
- ??
- ????/*?
- ?????*?定义切入点,该方法返回值为void,该方法只是一个标识,就象配置文件的id?
- ?????*?切入点的内容是一个表达式,来描述切入哪些对象的哪些方法?
- ?????*?("excute?(*add*(..))")切入点表达表示将要切入所有以add开头的方法,该方法可带任意个数的参数?
- ?????*/??
- ????@Pointcut?("execution(*?add*(..))")??
- ????public?void?addAllMethod(){};??
- ??????
- ??????
- ????/*?
- ?????*?前置通知,在addAllMethod切入点所代表的方法前调用checkSecurity方法?
- ?????*??
- ?????*/??
- ????@Before("addAllMethod()")??
- ????public?void?checkSecurity()?{??
- ????????System.out.println("User?security?Check");??
- ????}??
- ??
- }??
?
配置文件:
Java代码??
- <?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.5.xsd??
- ????????????http://www.springframework.org/schema/aop?http://www.springframework.org/schema/aop/spring-aop-2.5.xsd??
- ????????????http://www.springframework.org/schema/tx?http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"??
- default-autowire="byType"?????????????
- >??
- ??????
- ??????
- ????<aop:aspectj-autoproxy/>??
- ????<bean?id="userManager"?class="com.lwf.aop.UserManagerImpl"></bean>??
- ????<bean?id="mySecurityManager"?class="com.lwf.aop.MySecurityManagerImpl"></bean>??
- </beans>??
?
下面我们创建一个测试类:
?
Java代码??
- package?com.lwf.aop;??
- ??
- import?junit.framework.TestCase;??
- ??
- import?org.springframework.context.ApplicationContext;??
- import?org.springframework.context.support.ClassPathXmlApplicationContext;??
- ??
- public?class?Client?extends?TestCase{??
- ??
- ????public?void?testAop(){??
- ????????ApplicationContext?ac?=?new?ClassPathXmlApplicationContext("applicationContext.xml");??
- ????????UserManager?userManager?=?(UserManager)ac.getBean("userManager");??
- ????????userManager.add("zhangshang",?"123456");??
- ????}??
- }??
?
好了,上面的测试类,应该输出什么呢?
按照我们的静态代理,在调用add之前要先调用check方法,这里我们是先调用 checkSecurity()方法。
看下面的输出结果:
Java代码??
- 2010-05-20?17:08:44,461?INFO?[org.springframework.context.support.ClassPathXmlApplicationContext]?-?Refreshing?org.springframework.context.support.ClassPathXmlApplicationContext@affc70:?display?name?[org.springframework.context.support.ClassPathXmlApplicationContext@affc70];?startup?date?[Thu?May?20?17:08:44?CST?2010];?root?of?context?hierarchy??
- 2010-05-20?17:08:44,633?INFO?[org.springframework.beans.factory.xml.XmlBeanDefinitionReader]?-?Loading?XML?bean?definitions?from?class?path?resource?[applicationContext.xml]??
- 2010-05-20?17:08:45,055?INFO?[org.springframework.context.support.ClassPathXmlApplicationContext]?-?Bean?factory?for?application?context?[org.springframework.context.support.ClassPathXmlApplicationContext@affc70]:?org.springframework.beans.factory.support.DefaultListableBeanFactory@1c9a690??
- 2010-05-20?17:08:45,243?INFO?[org.springframework.beans.factory.support.DefaultListableBeanFactory]?-?Pre-instantiating?singletons?in?org.springframework.beans.factory.support.DefaultListableBeanFactory@1c9a690:?defining?beans?[org.springframework.aop.config.internalAutoProxyCreator,userManager,mySecurityManager];?root?of?factory?hierarchy??
- User?security?Check??
- add?method??
?
显然调用了 checkSecurity()方法。
?
需要注意的是在切入点:@Pointcut ("execution(* add*(..))")这个地方一定要写对
是execution而不是execute,还有我们设置的是所有以add字符串开头的方法,注意前面的*与add之间要有空隔。因为最前面的*号代表所有的返回类型,而add*(..)中的*表示所有以add开头的方法名。
上面我们只定义了在add开头的方法前执行检查,那么我们也可以在del之前执行检查,使用||操作符,如下:
Java代码??
- @Pointcut?("execution(*?add(..))?||?execution(*?del(..))")??
?
还要注意通知@Before("addAllMethod()"),不要写成@Before("addAllMethod")
还应该注意到切入点addAllMethod()这个方法是不会被执行的,只是起到一个标志作用。
?
?
现在来总结一下:从静态代理到spring aop我们都实现了在操作用户之前调用方法进行用户检查。静态代理我们看成是OOP的处理,它需要代理类通过继承,是树型结构,要实现就要改变原来的树型,即是有侵入性的。而spring aop则是横向的切入。没有改变原来的结构,是没有侵入性的。
AOP的没有侵入性的特性,是对OOP的一个补充。
?
对于常用的切入点表达式有:
Java代码??
- 使用的最频繁的返回类型模式是*,它代表了匹配任意的返回类型。??
- ?一个全限定的类型名将只会匹配返回给定类型的方法。名字模式匹配的是方法名。?你可以使用*通配符作为所有或者部分命名模式。??
- ??参数模式稍微有点复杂:()匹配了一个不接受任何参数的方法,?而(..)匹配了一个接受任意数量参数的方法(零或者更多)。??
- ???模式(*)匹配了一个接受一个任何类型的参数的方法。?模式(*,String)匹配了一个接受两个参数的方法,第一个可以是任意类型,?第二个则必须是String类型。??
?
任意公共方法的执行:
execution(public * *(..))
任何一个名字以“set”开始的方法的执行:
execution(* set*(..))
AccountService接口定义的任意方法的执行:execution(* com.xyz.service.AccountService.*(..))
在service包中定义的任意方法的执行:
execution(* com.xyz.service.*.*(..))
在service包或其子包中定义的任意方法的执行:
execution(* com.xyz.service..*.*(..))
在service包中的任意连接点(在Spring AOP中只是方法执行):
within(com.xyz.service.*)
在service包或其子包中的任意连接点(在Spring AOP中只是方法执行):
within(com.xyz.service..*)
实现了
AccountService接口的代理对象的任意连接点 (在Spring AOP中只是方法执行):this(com.xyz.service.AccountService)
?
'this'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得代理对象在通知体内可用。
实现
AccountService接口的目标对象的任意连接点 (在Spring AOP中只是方法执行):target(com.xyz.service.AccountService)
?
'target'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得目标对象在通知体内可用。
任何一个只接受一个参数,并且运行时所传入的参数是
Serializable?接口的连接点(在Spring AOP中只是方法执行)args(java.io.Serializable)
'args'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得方法参数在通知体内可用。
请注意在例子中给出的切入点不同于?
execution(* *(java.io.Serializable)): args版本只有在动态运行时候传入参数是Serializable时才匹配,而execution版本在方法签名中声明只有一个?Serializable类型的参数时候匹配。目标对象中有一个?
@Transactional?注解的任意连接点 (在Spring AOP中只是方法执行)@target(org.springframework.transaction.annotation.Transactional)
?
'@target'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得注解对象在通知体内可用。
任何一个目标对象声明的类型有一个?
@Transactional?注解的连接点 (在Spring AOP中只是方法执行):@within(org.springframework.transaction.annotation.Transactional)
?
'@within'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得注解对象在通知体内可用。
任何一个执行的方法有一个?
@Transactional?注解的连接点 (在Spring AOP中只是方法执行)@annotation(org.springframework.transaction.annotation.Transactional)
?
'@annotation'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得注解对象在通知体内可用。
任何一个只接受一个参数,并且运行时所传入的参数类型具有
@Classified?注解的连接点(在Spring AOP中只是方法执行)@args(com.xyz.security.Classified)
?
'@args'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得注解对象在通知体内可用。
任何一个在名为'
tradeService'的Spring bean之上的连接点 (在Spring AOP中只是方法执行):bean(tradeService)
任何一个在名字匹配通配符表达式'
*Service'的Spring bean之上的连接点 (在Spring AOP中只是方法执行):bean(*Service)
