Spring整合AspectJ实现Aop

AspectJ:

1.AspectJ是一个基于Java语言的AOP框架。

2.Spring2.0以后新增了对AspectJ切点表达式支持。

3.@AspectJ是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面新版本Spring框架,建议使用AspectJ方式来开发

AspectJ最强大的地方在于他的切入点表达式:

语法:execution(修饰符  返回值  包.类.方法名(参数) throws异常)

  修饰符,一般省略

    public 公共方法

    * 任意

  返回值,不能省略

    void 返回没有值

    String 返回值字符串

    * 任意

  包

    com.zby.service  固定包

    com.zby.oa.*.service oa包下面子包 (例如:com.zby.oa.flow.service)

    com.zby.oa..   oa包下面的所有子包(含自己)

    com.zby.oa.*.service.. oa包下面任意子包,固定目录service,service目录任意包

  类

    UserServiceImpl 指定类

    *Impl 以Impl结尾

    User* 以User开头

    * 任意

  方法名,不能省略

    addUser 固定方法

    add* 以add开头

    *Do 以Do结尾

    * 任意

  (参数)

    () 无参

    (int) 一个整型

    (int ,int) 两个

    (..) 参数任意

  throws ,可省略,一般不写。

当然,execution也是可以变得,但是一般用这个就够了,更详细的表达式用法,当然是查看专业文档。

AspectJ和aopalliance通知的区别:

AOP联盟的通知类型具有特性接口,必须实现,从而确定方法名称,而AspectJ的通知类型只定义了类型名称和方法格式,这意味着,我们的切面不需要实现任何方法!!!。

 AspectJ通知:

 

  before:前置通知(应用:各种校验)

    在方法执行前执行,如果通知抛出异常,阻止方法运行

  afterReturning:后置通知(应用:常规数据处理)

    方法正常返回后执行,如果方法中抛出异常,通知无法执行,必须在方法执行后才执行,所以可以获得方法的返回值。

  around:环绕通知(应用:十分强大,可以做任何事情)

    方法执行前后分别执行,可以阻止方法的执行,必须手动执行目标方法

  afterThrowing:抛出异常通知(应用:包装异常信息)

    方法抛出异常后执行,如果方法没有抛出异常,无法执行

  after:最终通知(应用:清理现场)

    方法执行完毕后执行,无论方法中是否出现异常

当然,最重要也最常用的还是环绕通知,因为环绕通知必须手动执行目标方法,所以,可以代替其他几个通知。

使用XML配置Spring整合AspectJ的AOP:

1)项目整体结构如下:

2)创建maven项目,pom.xml如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<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.zby</groupId>
     <artifactId>aop</artifactId>
     <version> 0.0 . 1 -SNAPSHOT</version>
     <dependencies>
         <!-- https: //mvnrepository.com/artifact/org.springframework/spring-context -->
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-context</artifactId>
             <version> 4.3 . 8 .RELEASE</version>
         </dependency>
         <!-- https: //mvnrepository.com/artifact/org.springframework/spring-aop -->
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-aop</artifactId>
             <version> 4.3 . 8 .RELEASE</version>
         </dependency>
         <!-- https: //mvnrepository.com/artifact/org.springframework/spring-aspects -->
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-aspects</artifactId>
             <version> 4.3 . 8 .RELEASE</version>
         </dependency>
         <!-- https: //mvnrepository.com/artifact/org.springframework/spring-test -->
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-test</artifactId>
             <version> 4.3 . 8 .RELEASE</version>
         </dependency>
         <!-- https: //mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
         <dependency>
             <groupId>org.aspectj</groupId>
             <artifactId>aspectjweaver</artifactId>
             <version> 1.8 . 10 </version>
         </dependency>
     </dependencies>
</project>

3)创建目标类UserService:

1
2
3
4
5
6
7
8
9
package  com.zby.service;
 
public  class  UserService {
 
     public  void  saveUser(String username, String password) {
         System.out.println( "save user[username="  + username +  ",password="  + password +  "]" );
     }
 
}

4)创建切面类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package  com.zby.interceptor;
 
import  org.aspectj.lang.JoinPoint;
import  org.aspectj.lang.ProceedingJoinPoint;
 
public  class  MyAspect {
 
     public  void  myBefore(JoinPoint joinPoint) {
         System.out.println( "前置通知 : "  + joinPoint.getSignature().getName());
     }
 
 
 
     public  void  myAfterReturning(JoinPoint joinPoint, Object ret) {
         System.out.println( "后置通知 : "  + joinPoint.getSignature().getName() +  " , -->"  + ret);
     }
 
 
 
     public  Object myAround(ProceedingJoinPoint joinPoint)  throws  Throwable {
         System.out.println( "环绕通知执行方法前" );
         // 手动执行目标方法
         Object obj = joinPoint.proceed();
 
         System.out.println( "环绕通知执行方法后" );
         return  obj;
     }
 
 
 
     public  void  myAfterThrowing(JoinPoint joinPoint, Throwable e) {
         System.out.println( "抛出异常通知 : "  + e.getMessage());
     }
 
 
 
     public  void  myAfter(JoinPoint joinPoint) {
         System.out.println( "最终通知" );
     }
}

切面类没有实现接口,但是有几种方法参数,这些不是必须的。这些传入的对象是什么?当然是我们在切面点需要的信息!用脑壳想,在给一个方法进行增强的时候,前置方法,或者后置方法,或者环绕方法,有可能需要得到原方法的哪些信息,这里面都有。

5)编写配置文件applicationContext.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
         http: //www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
 
     <!-- 目标类 -->
     <bean id= "userService"  class = "com.zby.service.UserService" ></bean>
 
     <!-- 切面类 -->
     <bean id= "myInterceptor"  class = "com.zby.interceptor.MyAspect" ></bean>
 
     <aop:config>
         <aop:aspect ref= "myInterceptor" >
             <aop:pointcut expression= "execution(* com.zby.service.UserService.*(..))"
                 id= "myPointcut"  />
             <!--环绕通知
                 <aop:around method= ""  pointcut-ref= "" />
                 通知方法格式: public  Object myAround(ProceedingJoinPoint joinPoint)  throws  Throwable{ }
                 返回值类型:Object 方法名:任意
                 参数:org.aspectj.lang.ProceedingJoinPoint
                 抛出异常
                 执行目标方法:Object obj = joinPoint.proceed();
                 例如: <aop:around method= "myAround"  pointcut-ref= "myPointCut" /> -->
             <aop:around method= "myAround"  pointcut-ref= "myPointcut"  />
             <!-- 最终通知 -->
 
             <aop:after method= "myAfter"  pointcut-ref= "myPointcut"  />
             <!--后置通知 ,目标方法后执行,获得返回值
                 <aop:after-returning method= ""  pointcut-ref= ""  returning= "" />
                 returning 通知方法第二个参数的名称 通知方法格式:
                 public  void  myAfterReturning(JoinPoint joinPoint,Object ret){}
                  参数 1 :连接点描述
                  参数 2 :类型Object,参数名 returning= "ret"  配置的 例如:
                 <aop:after-returning method= "myAfterReturning"  pointcut-ref= "myPointCut"  returning= "ret"  /> -->
             <aop:after-returning method= "myAfterReturning"
                 pointcut-ref= "myPointcut"  returning= "ret"  />
             <!--抛出异常 <aop:after-throwing method= ""  pointcut-ref= ""  throwing= "" />
                 throwing :通知方法的第二个参数名称
                 通知方法格式: public  void  myAfterThrowing(JoinPoint joinPoint,Throwable e){ }
                 参数 1 :连接点描述对象
                 参数 2 :获得异常信息,类型Throwable ,参数名由throwing= "e"  配置 例如: <aop:after-throwing method= "myAfterThrowing"  pointcut-ref= "myPointCut"  throwing= "e" /> -->
             <aop:after-throwing method= "myAfterThrowing"
                 pointcut-ref= "myPointcut"  throwing= "e"  />
             <!--前置通知
                 <aop:before method= ""  pointcut= ""  pointcut-ref= "" />
                 method : 通知,及方法名
                 pointcut :切入点表达式,此表达式只能当前通知使用。
                 pointcut-ref : 切入点引用,可以与其他通知共享切入点。
                 通知方法格式: public  void  myBefore(JoinPoint joinPoint){}
                 参数 1 :org.aspectj.lang.JoinPoint 用于描述连接点(目标方法),获得目标方法名等
                 例如: <aop:before method= "myBefore"  pointcut-ref= "myPointCut" /> -->
             <aop:before method= "myBefore"  pointcut-ref= "myPointcut"  />
         </aop:aspect>
     </aop:config>
</beans>

  

6)编写测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package  com.zby.test;
 
import  org.junit.Test;
import  org.junit.runner.RunWith;
import  org.springframework.beans.factory.annotation.Autowired;
import  org.springframework.test.context.ContextConfiguration;
import  org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
import  com.zby.service.UserService;
 
@RunWith (SpringJUnit4ClassRunner. class )
@ContextConfiguration (locations = {  "classpath:applicationContext.xml"  })
public  class  AOPTest {
 
     @Autowired
     private  UserService userService;
 
 
 
     @Test
     public  void  testProxy() {
         System.out.println( "After Proxy......" );
         userService.saveUser( "zby" "1234567890" );
     }
}

7)控制台打印结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
六月  09 2017  2 : 07 : 56  下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getDefaultTestExecutionListenerClassNames
信息: Loaded  default  TestExecutionListener  class  names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
六月  09 2017  2 : 07 : 56  下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]. Specify custom listener classes or make the  default  listener classes (and their required dependencies) available. Offending  class : [org/springframework/transaction/interceptor/TransactionAttribute]
六月  09 2017  2 : 07 : 56  下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener]. Specify custom listener classes or make the  default  listener classes (and their required dependencies) available. Offending  class : [org/springframework/transaction/interceptor/TransactionAttributeSource]
六月  09 2017  2 : 07 : 56  下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the  default  listener classes (and their required dependencies) available. Offending  class : [javax/servlet/ServletContext]
六月  09 2017  2 : 07 : 56  下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getTestExecutionListeners
信息: Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener @6e983d8d , org.springframework.test.context.support.DependencyInjectionTestExecutionListener @4cf12cb4 , org.springframework.test.context.support.DirtiesContextTestExecutionListener @6dae04e2 ]
六月  09 2017  2 : 07 : 56  下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from  class  path resource [applicationContext.xml]
六月  09 2017  2 : 07 : 56  下午 org.springframework.context.support.GenericApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.GenericApplicationContext @24024ad8 : startup date [Fri Jun  09  14 : 07 : 56  CST  2017 ]; root of context hierarchy
After Proxy......
环绕通知执行方法前
前置通知 : saveUser
save user[username=zby,password= 1234567890 ]
后置通知 : saveUser , --> null
最终通知
环绕通知执行方法后

这个DEMO就是一个大杂烩,其实使用时使用一个环绕通知就够了。再环绕通知里面必须手动执行方法,因此我们用try-catch把方法执行包裹起来,然后在执行前和执行后写增强代码即可。

使用注解配置Spring整合AspectJ的AOP:

1)上面的一二步骤不变。

2)编写目标类UserService:

1
2
3
4
5
6
7
8
9
10
11
12
package  com.zby.service;
 
import  org.springframework.stereotype.Service;
 
@Service
public  class  UserService {
 
     public  void  saveUser(String username, String password) {
         System.out.println( "save user[username="  + username +  ",password="  + password +  "]" );
     }
 
}

3)编写切面类,使用注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package  com.zby.interceptor;
 
import  org.aspectj.lang.JoinPoint;
import  org.aspectj.lang.ProceedingJoinPoint;
import  org.aspectj.lang.annotation.After;
import  org.aspectj.lang.annotation.AfterReturning;
import  org.aspectj.lang.annotation.AfterThrowing;
import  org.aspectj.lang.annotation.Around;
import  org.aspectj.lang.annotation.Aspect;
import  org.aspectj.lang.annotation.Before;
import  org.aspectj.lang.annotation.Pointcut;
import  org.springframework.stereotype.Component;
 
@Component
@Aspect
public  class  MyAspect {
     // 多个方法需要使用这个切入点表达式,定义为一个公用的
     @Pointcut ( "execution(* com.zby.service..*(..))" )
     public  void  myPointCut() {
 
     }
 
 
 
     // 这里注解里面的值为上面的方法名
     @Before ( "myPointCut()" )
     public  void  myBefore(JoinPoint joinPoint) {
         System.out.println( "前置通知 : "  + joinPoint.getSignature().getName());
     }
 
 
 
     // 当你只有一个方法,或者只在这儿用,可以直接写切入点表达式
     @AfterReturning (value =  "execution(* com.zby.service..*(..))" , returning =  "ret" )
     public  void  myAfterReturning(JoinPoint joinPoint, Object ret) {
         System.out.println( "后置通知 : "  + joinPoint.getSignature().getName() +  " , -->"  + ret);
     }
 
 
 
     //
     @Around ( "myPointCut()" )
     public  Object myAround(ProceedingJoinPoint joinPoint)  throws  Throwable {
         System.out.println( "环绕通知执行方法前" );
         // 手动执行目标方法
         Object obj = joinPoint.proceed();
 
         System.out.println( "环绕通知执行方法后" );
         return  obj;
     }
 
 
 
     @AfterThrowing (value =  "myPointCut()" , throwing =  "e" )
     public  void  myAfterThrowing(JoinPoint joinPoint, Throwable e) {
         System.out.println( "抛出异常通知 : "  + e.getMessage());
     }
 
 
 
     @After ( "myPointCut()" )
     public  void  myAfter(JoinPoint joinPoint) {
         System.out.println( "最终通知" );
     }
}

4)编写配置文件applicationContext.xml:

1
2
3
4
5
6
7
8
9
10
11
12
<?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:context= "http://www.springframework.org/schema/context"
     xsi:schemaLocation="http: //www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
         http: //www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
         http: //www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
 
<context:component-scan base- package = "com.zby" ></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

5)编写测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package  com.zby.test;
 
import  org.junit.Test;
import  org.junit.runner.RunWith;
import  org.springframework.beans.factory.annotation.Autowired;
import  org.springframework.test.context.ContextConfiguration;
import  org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
import  com.zby.service.UserService;
 
@RunWith (SpringJUnit4ClassRunner. class )
@ContextConfiguration (locations = {  "classpath:applicationContext.xml"  })
public  class  AOPTest {
 
     @Autowired
     private  UserService userService;
 
 
 
     @Test
     public  void  testProxy() {
         System.out.println( "After Proxy......" );
         userService.saveUser( "zby" "1234567890" );
     }
}

  6)控制台打印结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
六月  09 2017  2 : 29 : 21  下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getDefaultTestExecutionListenerClassNames
信息: Loaded  default  TestExecutionListener  class  names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
六月  09 2017  2 : 29 : 21  下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]. Specify custom listener classes or make the  default  listener classes (and their required dependencies) available. Offending  class : [org/springframework/transaction/interceptor/TransactionAttribute]
六月  09 2017  2 : 29 : 21  下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the  default  listener classes (and their required dependencies) available. Offending  class : [javax/servlet/ServletContext]
六月  09 2017  2 : 29 : 21  下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener]. Specify custom listener classes or make the  default  listener classes (and their required dependencies) available. Offending  class : [org/springframework/transaction/interceptor/TransactionAttributeSource]
六月  09 2017  2 : 29 : 21  下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getTestExecutionListeners
信息: Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener @6dae04e2 , org.springframework.test.context.support.DependencyInjectionTestExecutionListener @3bc2c9af , org.springframework.test.context.support.DirtiesContextTestExecutionListener @71471ecf ]
六月  09 2017  2 : 29 : 21  下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from  class  path resource [applicationContext.xml]
六月  09 2017  2 : 29 : 21  下午 org.springframework.context.support.GenericApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.GenericApplicationContext @69f31d : startup date [Fri Jun  09  14 : 29 : 21  CST  2017 ]; root of context hierarchy
After Proxy......
环绕通知执行方法前
前置通知 : saveUser
save user[username=zby,password= 1234567890 ]
环绕通知执行方法后
最终通知
后置通知 : saveUser , --> null

总结:对比起来,可以看出来使用最后一种方式开发AOP很方便,这也是我们最常用的,至于spring原生的AOP,大多在一些框架里面看到。使用整合AspectJ的方式,最主要的是要注意切面表达式的书写和方法参数传入,以及怎么使用这些参数。

猜你喜欢

转载自blog.csdn.net/liangwenmail/article/details/80421242