Spring-AOP【总结】

原文地址:http://blog.psjay.com/posts/summary-of-spring-3-aop/


概念

AOP(Aspect Oriented Programming),即面向切面编程(也叫面向方面编程,面向方法编程)。其主要作用是,在不修改源代码的情况下给某个或者一组操作添加额外的功能。像日志记录,事务处理,权限控制等功能,都可以用AOP来“优雅”地实现,使这些额外功能和真正的业务逻辑分离开来,软件的结构将更加清晰。AOP是OOP的一个强有力的补充。

术语

AOP的术语不太直观,Spring文档中也没有给一个确切的定义,所以重在理解。

  • Join Point: Spring AOP中,join point就是一个方法。(通俗来讲就是起作用的那个方法)。

  • Pointcut: 用来指定join point(通俗来讲就是描述的一组符合某个条件的join point)。通常使用pointcut表达式来限定joint point,Spring默认使用AspectJ pointcut expression language。

  • Advice: 在join point上特定的时刻执行的操作,Advice有几种不同类型,下文将会讨论(通俗地来讲就是起作用的内容和时间点)。

  • Introduction:给对象增加方法或者属性。

  • Target object: Advice起作用的那个对象。

  • AOP proxy: 为实现AOP所生成的代理。在Spring中有两种方式生成代理:JDK代理和CGLIB代理。

  • Aspect: 组合了Pointcut与Advice,在Spring中有时候也称为Advisor。某些资料说Advisor是一种特殊的Aspect,其区别是Advisor只能包含一对pointcut和advice,但是aspect可以包含多对。AOP中的aspect可以类比于OOP中的class。

  • Weaving:将Advice织入join point的这个过程。


Advice的类型
  • Before advice:  执行在join point之前的advice,但是它不能阻止joint point的执行流程,除非抛出了一个异常(exception)。

  • After returning advice: 执行在join point这个方法返回之后的advice。

  • After throwing advice: 执行在join point抛出异常之后的advice。

  • After(finally) advice: 执行在join point返回之后或者抛出异常之后的advice,通常用来释放所使用的资源。

  • Around advice: 执行在join point这个方法执行之前与之后的advice。


两种代理

Spring AOP是基于代理机制的。上文说到,Spring AOP通过JDK Proxy和CGLIB Proxy两种方法实现代理。

如果target object没有实现任何接口,那么Spring将使用CGLIB来实现代理。CGLIB是一个开源项目,它是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。

如果target object实现了一个以上的接口,那么Spring将使用JDK Proxy来实现代理,因为Spring默认使用的就是JDK Proxy,并且JDK Proxy是基于接口的。这也是Spring提倡的面向接口编程。当然,你也可以强制使用CGLIB来进行代理,但是这样可能会造成性能上的下降。


Pointcut expression

Pointcut通过pointcut expression来描述,有若干种限定词。由于Pointcut的定义在Spring文档7.2.3 Declaring a pointcut中写得比较详细,所以在此不再赘述。

Spring AOP的使用

我们可以通过三种方式来使用Spring AOP,它们分别是:@Aspect-based(Annotation),Schema-based(XML),以及底层的Spring AOP API。

@Aspect-based (Annotation)

Annotaion是最常用的方式。

配置

首先,我们应该在配置文件中增加对Annotation的支持。

假设我们的配置文件是classpath下的applicationContext.xml,添加如下片段:

1
<aop:aspectj-autoproxy />

业务逻辑类

假设我们有一个UserManager类,这个类负责处理业务逻辑。类的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class UserManager {
  /*这个方法需要一个参数*/
  public void addUser(String user) {
      System.out.println("addUser(String str) method is executed!");
  }
  
  public void deleteUser() {
      System.out.println("deleteUser() method is executed!");
  }
  /*这个方法返回一个字符串*/
  public String getUser() {
      System.out.println("getUser() method is executed!");
      return "Hello";
  }
  /*这个方法抛出一个异常*/
  public void editUser() throws Exception {
      throw new Exception("something is wrong.");
  }    
}

这是一个很普通的Java对象,看不出任何Spring AOP的痕迹,这也是Spring低侵入式设计的体现。

切面(Aspect)类

为了给业务逻辑增加额外功能,我们需要定义一个切面类,切面类里包含了pointcut和advice。假设我们的切面类是ExampleAspect,代码如下:

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
@Aspect
public class ExampleAspect {
  
  @Pointcut("execution(* com.psjay.example.spring.aop.*.*(..))")
  public void aPointcut() {
  }
  
  @Before("aPointcut()")
  public void beforeAdvice() {
      System.out.println("before advice is executed!");
  }
  
  @AfterReturning(pointcut = "aPointcut()", returning="r")
  public void afterReturningAdvice(String r) {
      if (r != null)
          System.out.println("after returning advice is executed! returning String is : " + r);
  }
  
  @After("aPointcut()")
  public void AfterAdvice() {
      System.out.println("after advice is executed!");
  }
  
  @After("aPointcut() && args(str)")
  public void AfterAdviceWithArg(String str) {
      System.out.println("after advice with arg is executed!arg is : " + str);
  }
  
  @AfterThrowing(pointcut="aPointcut()",throwing="e")
  public void afterThrowingAdvice(Exception e) {
      System.out.println("after throwing advice is executed!exception msg is : " + e.getMessage());
  }
  
}

在基于annotation的Spring AOP中,@Aspect用来标注切面类。@Pointcut标注一个空的方法,用来代表一个pointcut,这个方法必须是public的。@Pointcut注解括号内是pointcut expression,例子中的表达式表示com.psjay.example.spring.aop的所有方法都是join point。而@Before,@After等注解对应着几种不同类型的Advice。被标注的方法就是一个Advice。@Advice注解括号内是一个pointcut。例子中的@afterReturningAdvice(),AfterAdviceWithArg()和afterThrowingAdvice()分别演示了Advice得到join point的返回值,Advice使用join point的参数,Advice使用join point抛出的异常对象几种操作。

不要忘了在Spring配置文件中配置以上两个类的“Bean”,这里就不贴出具体代码了。

测试类

测试类相对简单,就是从Spring中拿出bean演示AOP的结果。测试类代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {

  public static void main(String[] args) {
      ApplicationContext ctx  = new ClassPathXmlApplicationContext("applicationContext.xml");
      UserManager um =  ctx.getBean("userManager",UserManager.class);
      System.out.println("------ Case 1 --------");
      um.addUser("hey");
      System.out.println("------ Case 2 --------");
      try {
          um.editUser();
      } catch (Exception e) {
          
      }
      System.out.println("------ Case 3 --------");
      um.getUser();
      
  }
}

测试结果:

------ Case 1 --------
before advice is executed!
addUser(String str) method is executed!
after advice is executed!
after advice with arg is executed!arg is : hey
------ Case 2 --------
before advice is executed!
after advice is executed!
after throwing advice is executed!exception msg is : something is wrong.
------ Case 3 --------
before advice is executed!
getUser() method is executed!
after returning advice is executed! returning String is : Hello
after advice is executed!

可以看到,Advice已经在对应的join point上起作用了。

Schema-based(XML)

除了使用Annotation,我们还可以使用XML来实现Spring AOP。使用XML来实现AOP只是将AOP的配置信息移到XML配置文件里,其他地方与annotation实现AOP并无太大区别。所以这里就不贴出相关代码了。具体配置方法,见Spring文档7.3. Schema-based AOP support


以下为博主补充】

Before Advice:前置通知

After Return Advice:返回通知

代码示例:

业务逻辑类

public class AspectBiz {
public void biz(){
System.out.println("AspectBiz---biz");
}
}


切面类

public class MyAspect {
public void before(){
System.out.println("Before");
}

public void afterReturning(){
System.out.println("afterReturning");
}
}



xml文件

<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/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd  
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
        
        <bean id="myAspect" class="aop.test1.MyAspect"></bean>
        <bean id="aspectBiz" class="aop.test1.AspectBiz"></bean>
        
<aop:config>
  <aop:aspect id="myAspectAOP" ref="myAspect">
<aop:pointcut expression="execution(* aop.test1.*Biz.*(..))" id="myPointcut"/>

<aop:before method="before" pointcut-ref="myPointcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
  </aop:aspect>
</aop:config>
 </beans>



测试类

@RunWith(BlockJUnit4ClassRunner.class)
public class TestAOPSchemaAdvice extends UnitTestBase{
public TestAOPSchemaAdvice(){
super("classpath:spring-aop-schema.xml");
}

@Test
public void test(){
AspectBiz aspectBiz=super.getBean("aspectBiz");
aspectBiz.biz();
}
}


输出结果:


Before
AspectBiz---biz
afterReturning




After Throwing Advice:当方法中有异常抛出时,执行


在xml文件中添加:

<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>

切面类添加该方法:

public void afterThrowing(){
System.out.println("afterThrowing");
}

在AspectBiz类中biz方法抛出异常:

throw new RuntimeException();

输出结果:

Before
AspectBiz biz
afterThrowing



After(Finally)Advice:

在xml文件中添加:

<aop:after method="after" pointcut-ref="myPointcut"/>

切面类添加该方法:

public void after(){
System.out.println("after");
}

输出结果:

Before
AspectBiz biz
afterThrowing
after

注意:无论是否抛出异常,都会执行after advice,当after-return和after-throw同时存在时,只会执行throw,不会执行return,这是因为方法中抛出异常后无法正常返回



Around Advice:有种包围方法的感觉,在方法执行前后都执行

方法的第一个参数必须为ProceedingJoinPoint类型

在xml文件中添加:

<aop:around method="around" pointcut-ref="myPointcut"/>

切面类添加该方法:

public Object around(ProceedingJoinPoint pjp){
Object obj=null;
try {
System.out.println("Around 1");
obj = pjp.proceed();
System.out.println("Around 2");
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return obj;
}

因为在代码中对异常进行了TRY-CATCH,所以需要把biz方法中手动抛出的异常删掉

输出结果:

Before
Around 1
AspectBiz biz
Around 2
after
afterReturning



Advice Parameters:在通知中使用参数

在xml文件中添加:(重新设置了一个pointcut)

<aop:around method="aroundInit" pointcut="execution(* aop.test1.AspectBiz.init(String,int))
and args(bizName,times)"></aop:around>

切面类添加该方法:

public Object aroundInit(ProceedingJoinPoint pjp,String bizName,int times){
System.out.println(bizName+" "+times);
Object obj=null;
try {
System.out.println("Around 1");
obj = pjp.proceed();
System.out.println("Around 2");
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return obj;
}

在AspectBiz类中添加方法:

public void init(String bizName,int times){
System.out.println("AspectBiz init:"+bizName+" "+times);
}


在测试中添加一个方法:

@Test
public void testInit(){
AspectBiz aspectBiz=super.getBean("aspectBiz");
aspectBiz.init("hello",123);
}

输出结果:

Before
Around 1
hello 123
Around 1
AspectBiz init:hello 123
Around 2
Around 2
after
afterReturning


分析一下这个输出结果,before和after先不说,around就像把你真正要执行的方法包围起来,

around 1 输出以后,obj = pjp.proceed(); 这才开始执行biz方法,然后再输出Around 2

但是在有两个around的情况下,那就是aroundA(aroundB(biz))

那么是A包围B还是B包围A是看AB在xml文件中的位置,在前面的那个包围在后面的那个

在没设置Around时,after和after-return是谁在xml文件前面谁先执行,但是加上around以后就反着来


Spring AOP API

在Spring1.2中使用底层的Spring AOP API来实现AOP。当然,Spring3也是完全与其兼容的。我们可以借其窥探一下底层实现。由于比较复杂,我将单独写一篇文章来描述这种实现方式。

总结

Spring AOP是基于代理的,是运行时绑定的。合理的运用AOP,将使软件的开发更加便捷,清晰。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值