现在的位置: 首页 > 综合 > 正文

Spring AOP 概念理解及@AspectJ支持

2013年12月14日 ⁄ 综合 ⁄ 共 15704字 ⁄ 字号 评论关闭

首先要理解代理模式:有静态代理和动态代理
下面先给出静态代理的代码。


  1. public interface UserManager {  
  2.     public void add(String name, String password);  
  3.     public void del(String id);  
  4.     public void modify(int id ,String name, String password);  
  5. }  
  6. public class UserManagerImpl
    implements UserManager {  
  7.     public void add(String name, String password) {  
  8.     }  
  9.     public void del(String id) {  
  10.     }  
  11.     public void modify(int id, String name, String password) {  
  12.     }  
  13. }  
  14. public class UserManagerProxy
    implements UserManager {  
  15.     private UserManager userManager ;  
  16.     public void add(String name, String password) {  
  17.         check();  
  18.         userManager.add(name, password);  
  19.     }  
  20.     public void del(String id) {  
  21.         check();  
  22.         userManager.del(id);  
  23.     }  
  24.     public void modify(int id, String name, String password) {  
  25.         check();  
  26.         userManager.modify(id, name, password);  
  27.     }  
  28.     public void check(){  
  29.         System.out.println("check security");  
  30.     }  
  31.     public void setUserManager(UserManager userManager){  
  32.         this.userManager = userManager;  
  33.     }  
  34.     public static
    void main(String[] args) {  
  35.         UserManagerProxy proxy = new UserManagerProxy();  
  36.         proxy.setUserManager(new UserManagerImpl());  
  37.         proxy.add("name","pwd");  
  38.     }  
  39. }  


上例中代理类控制在UserManagerImpl进行操作前对用户进行检查即check()方法。
那么下面用Spring Aop来实现。
配置步骤:
通过示例,理解概念
一、创建普通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注解的类就叫切面
切面如:

  1. package com.lwf.aop;  
  2. import org.aspectj.lang.annotation.Aspect;  
  3. @Aspect  
  4. public class MySecurityManagerImpl
    implements MySecurityManager {  
  5.     public void checkSecurity() {  
  6.         System.out.println("User security Check");  
  7.     }  
  8. }  


六、声明一个切入点(pointcut)
在前面我们提到,切入点决定了连接点关注的内容,使得我们可以控制通知什么时候执行。
Spring AOP只支持Spring bean的方法执行连接点。所以你可以把切入点看做是Spring bean上方法执行的匹配。
一个切入点声明有两个部分:一个包含名字和任意参数的签名,还有一个切入点表达式,该表达式决定了我们关注那个方法的执行。
在@AspectJ注解风格的AOP中,一个切入点签名通过一个普通的方法定义来提供,
并且切入点表达式使用@Pointcut注解来表示(作为切入点签名的方法必须返回void 类型)。
如:

  1. package com.lwf.aop;  
  2. import org.aspectj.lang.annotation.Aspect;  
  3. import org.aspectj.lang.annotation.Pointcut;  
  4. /* 
  5. * 定义切面 
  6. */  
  7. @Aspect  
  8. public class MySecurityManagerImpl
    implements MySecurityManager {  
  9.     /* 
  10.      * 定义切入点,该方法返回值为void,该方法只是一个标识,就象配置文件的id 
  11.      * 切入点的内容是一个表达式,来描述切入哪些对象的哪些方法 
  12.      * ("excute (*add*(..))")切入点表达表示将要切入所有以add开头的方法,该方法可带任意个数的参数 
  13.      */  
  14.     @Pointcut ("execution (* add*(..))")  
  15.     public void addAllMethod(){};  
  16.     public void checkSecurity() {  
  17.         System.out.println("User security Check");  
  18.     }  
  19. }  


七、声明通知
通知是跟一个切入点表达式关联起来的,并且在切入点匹配的方法执行之前或者之后或者前后运行。
切入点表达式可能是指向已命名的切入点的简单引用或者是一个已经声明过的切入点表达式。
通知有:前置通知,后置通知,异常通知,最终通知,环绕通知
如:我们声明一个前置通知

  1. package com.lwf.aop;  
  2. import org.aspectj.lang.annotation.Aspect;  
  3. import org.aspectj.lang.annotation.Before;  
  4. import org.aspectj.lang.annotation.Pointcut;  
  5. /* 
  6. * 定义切面 
  7. */  
  8. @Aspect  
  9. public class MySecurityManagerImpl
    implements MySecurityManager {  
  10.     /* 
  11.      * 定义切入点,该方法返回值为void,该方法只是一个标识,就象配置文件的id 
  12.      * 切入点的内容是一个表达式,来描述切入哪些对象的哪些方法 
  13.      * ("excute (*add*(..))")切入点表达表示将要切入所有以add开头的方法,该方法可带任意个数的参数 
  14.      */  
  15.     @Pointcut ("execution (* add*(..))")  
  16.     public void addAllMethod(){};  
  17.     /* 
  18.      * 前置通知,在addAllMethod切入点所代表的方法前调用checkSecurity方法 
  19.      *  
  20.      */  
  21.     @Before ("addAllMethod()")  
  22.     public void checkSecurity() {  
  23.         System.out.println("User security Check");  
  24.     }  
  25. }  


上面是分步的配置,下面我把整个配置好的项目代码列出来:

  1. package com.lwf.aop;  
  2. public interface UserManager {  
  3.     public void add(String name, String password);  
  4.     public void del(String id);  
  5.     public void modify(int id ,String name, String password);  
  6. }  
  7. package com.lwf.aop;  
  8. public class UserManagerImpl
    implements UserManager {  
  9.     public void add(String name, String password) {  
  10.         System.out.println("add method");  
  11.     }  
  12.     public void del(String id) {  
  13.         System.out.println("del method");  
  14.     }  
  15.     public void modify(int id, String name, String password) {  
  16.         System.out.println("modify method");  
  17.     }  
  18. }  

  1. package com.lwf.aop;  
  2. public interface MySecurityManager {  
  3.     public void checkSecurity();  
  4. }  
  5. package com.lwf.aop;  
  6. import org.aspectj.lang.annotation.Aspect;  
  7. import org.aspectj.lang.annotation.Before;  
  8. import org.aspectj.lang.annotation.Pointcut;  
  9. /* 
  10. * 定义切面 
  11. */  
  12. @Aspect  
  13. public class MySecurityManagerImpl
    implements MySecurityManager {  
  14.     /* 
  15.      * 定义切入点,该方法返回值为void,该方法只是一个标识,就象配置文件的id 
  16.      * 切入点的内容是一个表达式,来描述切入哪些对象的哪些方法 
  17.      * ("excute (*add*(..))")切入点表达表示将要切入所有以add开头的方法,该方法可带任意个数的参数 
  18.      */  
  19.     @Pointcut ("execution(* add*(..))")  
  20.     public void addAllMethod(){};  
  21.     /* 
  22.      * 前置通知,在addAllMethod切入点所代表的方法前调用checkSecurity方法 
  23.      *  
  24.      */  
  25.     @Before("addAllMethod()")  
  26.     public void checkSecurity() {  
  27.         System.out.println("User security Check");  
  28.     }  
  29. }  


配置文件:

  1. < xml version="1.0" encoding="UTF-8" >  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.         xmlns:aop="http://www.springframework.org/schema/aop"  
  5.         xmlns:tx="http://www.springframework.org/schema/tx"  
  6.         xsi:schemaLocation="  
  7.             http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  
  8.             http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd  
  9.             http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"  
  10. default-autowire="byType"             
  11. >  
  12.     <aop:aspectj-autoproxy/>  
  13.     <bean id="userManager"
    class="com.lwf.aop.UserManagerImpl"></bean>  
  14.     <bean id="mySecurityManager"
    class="com.lwf.aop.MySecurityManagerImpl"></bean>  
  15. </beans>  


下面我们创建一个测试类:

  1. package com.lwf.aop;  
  2. import junit.framework.TestCase;  
  3. import org.springframework.context.ApplicationContext;  
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;  
  5. public class Client
    extends TestCase{  
  6.     public void testAop(){  
  7.         ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");  
  8.         UserManager userManager = (UserManager)ac.getBean("userManager");  
  9.         userManager.add("zhangshang",
    "123456");  
  10.     }  
  11. }  


好了,上面的测试类,应该输出什么呢?
按照我们的静态代理,在调用add之前要先调用check方法,这里我们是先调用 checkSecurity()方法。
看下面的输出结果:

  1. 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  
  2. 2010-05-20
    17:08:44,633 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - Loading XML
    bean definitions from
    class path resource [applicationContext.xml]  
  3. 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  
  4. 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  
  5. User security Check  
  6. add method  


显然调用了 checkSecurity()方法。
需要注意的是在切入点:@Pointcut ("execution(* add*(..))")这个地方一定要写对
是execution而不是execute,还有我们设置的是所有以add字符串开头的方法,注意前面的*与add之间要有空隔。因为最前面的*号代表所有的返回类型,而add*(..)中的*表示所有以add开头的方法名。

上面我们只定义了在add开头的方法前执行检查,那么我们也可以在del之前执行检查,使用||操作符,如下:
@Pointcut ("execution(* add(..)) || execution(* del(..))")
还要注意通知@Before("addAllMethod()"),不要写成@Before("addAllMethod")
还应该注意到切入点addAllMethod()这个方法是不会被执行的,只是起到一个标志作用。
现在来总结一下:从静态代理到spring aop我们都实现了在操作用户之前调用方法进行用户检查。静态代理我们看成是OOP的处理,它需要代理类通过继承,是树型结构,要实现就要改变原来的树型,即是有侵入性的。而spring aop则是横向的切入。没有改变原来的结构,是没有侵入性的。

AOP的没有侵入性的特性,是对OOP的一个补充。
对于常用的切入点表达式有:
使用的最频繁的返回类型模式是*,它代表了匹配任意的返回类型。
一个全限定的类型名将只会匹配返回给定类型的方法。名字模式匹配的是方法名。 你可以使用*通配符作为所有或者部分命名模式。
参数模式稍微有点复杂:()匹配了一个不接受任何参数的方法, 而(..)匹配了一个接受任意数量参数的方法(零或者更多)。
模式(*)匹配了一个接受一个任何类型的参数的方法。 模式(*,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)

抱歉!评论已关闭.