一个 Spring AOP 例子

Posted 菜鸟日常

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个 Spring AOP 例子相关的知识,希望对你有一定的参考价值。


阅读本文大概需要 10 分钟



1. AOP 介绍

  • 全称面向切面编程(Aspect Oriented Programming,AOP)

  • 通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,通过少量的配置,减少项目中不必要的重复编写工作

  • 应用场景:日志记录,事物处理,权限管理等


2. AOP 里面的术语

  • 切点(PointCut):执行操作的点,即我需要在这个地方执行各种「增强」操作

  • 通知(Advice):又有人称为「增强」,就是对目标方法进行各种增强操作,比如说在这个方法执行前,我先打印一句话,方法执行完成之后,再打印一句话,这就是方法增强。它可以分为以下几类:

    • 前置通知:目标方法执行前执行

    • 后置返回通知:目标方法执行返回时执行

    • 后置通知:目标方法执行完成后执行

    • 后置异常通知:目标方法执行异常返回时执行

    • 环绕通知:方法执行的环绕操作,可以替换即将传入的参数

  • 切面(Aspect): 切点 + 通知,一般来说,我们会定义一个切面类,然后里面定义了切点和通知方法。比如日志切面类,切点就是定义在哪些地方进行日志记录,通知就是定义日志记录是怎么样的形式进行


3. 基于 XML 配置文件实现 AOP


  • 首先定义接口 Service.java 及其实现类 ServiceImpl.java,增强操作都是在这些方法上

public interface Service {
   // 验证前置通知、后置返回通知、后置通知
   boolean sayHello(String serviceParam);
   // 验证异常返回通知
   void afterThrowing();
   // 验证环绕通知
   void sayAround(String aroundParam);
}
public class ServiceImpl implements Service {  
   public boolean sayHello(String serviceParam) {  
       System.out.println("service sayHello "+ serviceParam);
       return false;
   }  
   public void afterThrowing(){
          System.out.println("service throwing ");
       throw new RuntimeException();
   }  
public void sayAround(String aroundParam) {
       System.out.println("around param " + aroundParam);
   }
}


  • 定义配置文件 springcofig-schema.xml

    • <aop:pointcut/> 定义的是切点,expression 表达式的意思即对 sayHello(String s) 这个方法进行增强

    • <aop:aspect ref="aspectSchema"> 定义的就是一个切面,里面设置了对方法进行增强操作,看英文单词意思很好理解

    • 切面里面还可以自定义切点,不引用上面那个,例如后置异常通知使用自定义的切点

<bean id="serviceSchema" class="ServiceImpl"/>
<bean id="aspectSchema" class="schema.Aspect"/>
<aop:config>
 <aop:pointcut id="pointcut" expression="execution(* *.sayHello(..)) and args(serviceParam1)"/>
 <aop:aspect ref="aspectSchema">
 <aop:before method="beforeAdvice" pointcut-ref="pointcut" />
 <aop:after method="afterAdvice" pointcut-ref="pointcut" />
 <aop:after-returning method="afterReturnAdvice" returning="serviceParam1" pointcut="execution(* *.sayHello(..))"/>
 <aop:after-throwing method="afterThrowingAdvice" pointcut="execution(* *.afterThrowing())"/>
 <aop:around method="aroundAdvice" pointcut="execution(* *.sayAround(..))"/>
 <aop:declare-parents
 types-matching="Service+"
 implement-interface="introductionService"
 default-impl="introductionServiceImpl"/>

 </aop:aspect>
</aop:config>


  • 配置文件里面指明了增强方法的名称,定义切面类 Aspect.java,里面定义了增强方法的具体实现

public class Aspect {
 // 前置通知
 public void beforeAdvice(String serviceParam1){    
     System.out.println("前置通知 "+ serviceParam1);
 }  
 // 后置通知
 public void afterAdvice(String serviceParam1){
     System.out.println("后置通知 "+ serviceParam1);
 }  
 // 后置返回通知
 public void afterReturnAdvice(Object serviceParam1){
     System.out.println("后置返回通知 "+ serviceParam1);
 }  
 // 后置异常通知
 public void afterThrowingAdvice(){
     System.out.println("后置异常通知 ");
 }  
 // 环绕通知
 public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
     System.out.println("环绕通知前 ");
     Object resultVal = proceedingJoinPoint.proceed(new Object[]{"replace"});
     System.out.println("环绕通知后 ");
     return resultVal;
 }

}


  • 测试类 AOPTest.java

@Test
public void schemaMethod()
{
   ApplicationContext context = new ClassPathXmlApplicationContext("springcofig-schema.xml");  Service service = (Service) context.getBean("serviceSchema");
   service.sayHello(" hello");
   System.out.println("----------------");  // 后置异常通知,捕捉得到的异常
   try {
      service.afterThrowing();
   } catch (Exception e){
      System.out.println(e.toString());
   }  
   System.out.println("----------------");  // 环绕通知 方法执行前进行替换
   service.sayAround("123");  System.out.println("----------------");
   introductionService service1 = (introductionService) context.getBean("serviceSchema");
   service1.induct();
}


  • 输出结果,第一个横线上面结果是对 sayHello 方法进行增强,最后一行是 「引入」,使得目标方法实现新的接口,实现新的功能

前置通知  hello
service sayHello  hello
后置返回通知 false后置通知  hello----------------service throwing
后置异常通知
java.lang.RuntimeException----------------环绕通知前
around param replace
环绕通知后
----------------introduction impl


4. 基于注解实现 AOP


  • xml 文件配置,开启注解、定义切面、定义 Bean

<aop:aspectj-autoproxy/>

<bean id="aspect" class="aspect.Aspect"/>

<bean id="service" class="ServiceImpl"/>


  • Aspect.java 切面类定义

package aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

@org.aspectj.lang.annotation.Aspect
public class Aspect {
//切点@Pointcut(value = "execution(* *.sayHello(..)) && args(param)")
public void PointCut(String param){}

//前置通知
@Before(value = "PointCut(beforeparam)",argNames = "beforeparam")
public void beforeAdvice(String beforeparam){
 System.out.println("前置通知 "+ beforeparam);
}

//后置通知
@After(value = "PointCut(afterParam)",argNames = "afterParam")
public void afterAdvice(String afterParam){
 System.out.println("后置通知 "+ afterParam);
}

//后置返回通知
@AfterReturning(pointcut = "execution(* *.sayHello(..))",argNames = "returnvalue",returning = "returnvalue")
public void afterReturningAdvice(Boolean returnvalue){
 System.out.println("后置返回通知 "+ returnvalue);
}

//后置异常通知
@AfterThrowing(pointcut = "execution(* *.afterThrowing(..))",argNames = "exception",throwing = "exception")
public void afterThrowingAdvice(Exception exception){
 System.out.println("后置异常通知 "+ exception);
}
//环绕通知@Around(value = "execution(* *.sayAround(..))")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
 System.out.println("环绕通知前 ");  
 Object resultVal = proceedingJoinPoint.proceed(new Object[]{"replace"});
 System.out.println("环绕通知后 ");
 return resultVal;
}

@DeclareParents(value = "Service+",defaultImpl = introductionServiceImpl1.class)
public introductionService1 introductionService1;
}


  • 测试类,输出结果和上面基于 XML 方式一样

@Test
public void aspectMethod()
{
 ApplicationContext context = new ClassPathXmlApplicationContext("springconfig-aspect.xml");
 Service service = (Service) context.getBean("service");  // 已经设定了前置通知 后置通知 环绕通知
 service.sayHello(" hello");
 System.out.println("----------------");  // 环绕通知 方法执行前进行替换
 service.sayAround("123");
 System.out.println("----------------");  // 后置异常通知,捕捉得到的异常
 try {
 service.afterThrowing();
 } catch (Exception e){
   System.out.println(e.toString());
 }  //引入:为原目标类添加新的接口
 System.out.println("----------------");
 introductionService1 introductionService1 = (introductionService1) context.getBean("service");;
 introductionService1.induct();



5. 小结

   

  • 点击「阅读原文」即可获取源码

  • 样例参考  http://jinnianshilongnian.iteye.com/blog/1482071

  • 这个 AOP 实例里面还有一个「引入」没说,篇幅有限,以后再说

  • 写这个 Demo 是为了以后自己方便,有机会写切面时可以照着样例



推荐阅读:




菜鸟日常

关注技术,乐于分享

用脑力解放劳力


回复“电子书”,查看编程类电子资源

回复“科学上网”,教你正确上网姿势

以上是关于一个 Spring AOP 例子的主要内容,如果未能解决你的问题,请参考以下文章

用spring aop实现动态代理的例子

spring aop中this和target区别

一个 Spring AOP 例子

使用springAPI以及自定义类 实现AOP的一个例子-aop编程

详解 spring AOP 动态代理

阿里四面:你知道Spring AOP创建Proxy的过程吗?