Spring AOP面向切面编程

来源:互联网 发布:scp基金会知乎 编辑:程序博客网 时间:2024/06/05 04:19

重点内容

(一).AOP编程的目的:分离横切关注(日志、安全、事务)点和业务逻辑,使得业务模块更简洁,便于日后维护和升级。
(二).AOP中的术语
1.切面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的Advisor或拦截器实现。
2.连接点(Joinpoint):程序执行过程中明确的点,如方法的调用或特定的异常被抛出。
3.通知(Advice):在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。
4.切入点(Pointcut):指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点,例如,使用正则表达式。
5.引入(Introduction):添加方法或字段到被通知的类。Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现IsModified接口,来简化缓存。
6.目标对象(Target Object):包含连接点的对象,也被称作被通知或被代理对象。
7.AOP代理(AOP Proxy):AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。
8.编织(Weaving):组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。


(三)Spring AOP支持5种类型的通知

  • 前置通知(Before):在目标方法被调用之前,调用通知功能。
  • 后置通知(After):在目标方法调用之后,调用通知。
  • 返回通知(After-returning):在目标方法成功执行之后,调用通知。
  • 异常通知(After-thowing):在目标方法抛出异常之后,调用通知。
  • 环绕通知(Around):通知包裹着被通知的方法,在被通知方法调用之前和调用之后,执行自定义的行为。

(四)Spring提供4种类型的AOP支持(选择自己喜欢的一种就ok)

  • 基于代理的经典Spring AOP
  • 纯POJO切面
  • @AspectJ注解驱动切面
  • 注入式AspectJ切面(各个版本都可以使用)

(五) @AspectJ注解驱动切面

Spring借助Aspect的切点表达式语言来定义Spring切面

如表所示:

这里写图片描述

Spring使用AspectJ注解来声明通知方法

这里写图片描述

这里写图片描述

这里写图片描述

1.execution(public * *(..)):执行所有共有方法。

2.execution(* set*(..)):执行所有set方法。

3.execution(* com.xyz.service.AccountService.*(..)):执行AccountService类中的所有方法。

4.execution(* com.xyz.service..(..)):执行 com.xyz.service包下的所有方法。

5.execution(* com.xyz.service...(..)):执行com.xyz.service包下或其子包的所有方法。

代码演示:

1.定义一个接口package concert;public interface Performance {    public void perform();}2.定义一个接口实现类package concert;public class JeckSon implements Performance {    @Override    public void perform() {     System.out.println("演唱dangerous");    }}3.定义一个切面package concert;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;//@Component@Aspect//定义一个切面//使用AspectJ注解,必须下载其jar包public class Audience {    //定义切点,前面的*后一定是空格号,否则报错。    @Before("execution(* concert.JeckSon.*(..))")    public void silenceCellPhones(){        System.out.println("歌舞表演前,请大家关闭手机!");    }    @Before("execution(* concert.JeckSon.*(..))")    public void  takeSeats(){        System.out.println("歌舞表演前,请大家坐好自己的位置!");    }    @AfterReturning("execution(* concert.JeckSon.*(..))")     public void applause(){        System.out.println("非常棒,太精彩了");    }    @AfterReturning("execution(* concert.JeckSon.*(..))")     public void demandRefund(){        System.out.println("垃圾表演,退款!");    }}4.在JavaCobfig中启用AspectJ注解的自动代理package concert;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration@EnableAspectJAutoProxy@ComponentScanpublic class concertConfig {    //此处给Bean取个名字,在应用上下文好调用该对象    @Bean(name="audience1")    public Audience audience(){        return new Audience() ;    }    @Bean(name="jeckson1")    public JeckSon jeckjson(){        return new JeckSon();    }}5.测试package concert;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class TestDemo {    @SuppressWarnings("unused")    @Test    public void testAspectJDemo(){        @SuppressWarnings("resource")        ApplicationContext context = new AnnotationConfigApplicationContext(concertConfig.class);    //JeckSon类实现了Performance接口,因此转换类型也必须是Performance类型,否则报错        Performance js=  (Performance) context.getBean("jeckson1");        js.perform();    }}控制台输出结果为:歌舞表演前,请大家关闭手机!歌舞表演前,请大家坐好自己的位置!马上演唱dangerous非常棒,太精彩了垃圾表演,退款!6.以上切面的切点重复使用,因此我们可以使用@Pointcut代替package concert;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;//@Component@Aspect//定义一个切面//使用AspectJ注解,必须下载其jar包public class Audience {    //定义切点,前面的*后一定是空格号,否则报错。    @Pointcut("execution(* concert.JeckSon.*(..))")    public  void perform(){         System.out.println("马上演唱dangerous");    }    //value属性值添加的是切点     @Before(value ="perform()")    public void silenceCellPhones(){        System.out.println("歌舞表演前,请大家关闭手机!");    }    @Before(value ="perform()")    public void  takeSeats(){        System.out.println("歌舞表演前,请大家坐好自己的位置!");    }    @AfterReturning(value ="perform()")    public void applause(){        System.out.println("非常棒,太精彩了");    }    @AfterReturning(value ="perform()")    public void demandRefund(){        System.out.println("垃圾表演,退款!");    }}7.在xml文件中通过Spring的aop命名空间启用AspectJ自动代理<?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/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          http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">      <context:component-scan base-package="concert"></context:component-scan>    <!-- 使 AspectJ 的注解起作用 -->      <aop:aspectj-autoproxy></aop:aspectj-autoproxy>      <bean id="audience" class="concert.Audience"/>        <bean id="jeckson" class="concert.JeckSon"/>   </beans>  

总结:
1.使用AspectJ注解定义切面需要导入aspectjrt-1.6.11.jar、aspectjweaver-1.8.10.jar。
2.定义切点,前面的*后一定是空格号,否则报错。
3.如果子类实现接口时,从ApplicationContext容器获取的对象类型一定要是父类类型,否则报无法转化对象的错误。
4.如果一个类中切点表达式重复使用,这种情况可以使用@Pointcut代替。
5.启用AspectJ自动代理有两种方法:第一种是在JavaConfig中启用,第二种在xml文件中启用。

(四) 创建环绕通知

环绕通知是最强大的通知类型,让你编写的逻辑被通知的目标方法包装起来。但是要环绕通知必须创建ProceedingJoinPoint 实例。

代码演示:

1.定义接口package concert;public interface Performance {    public void perform();}2.定义接口实现类package concert;public class JeckSon implements Performance {    @Override    public void perform() {     System.out.println("马上演唱dangerous");    }}3.定义切面package concert;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;//@Component@Aspect//定义一个切面//使用AspectJ注解,必须下载其jar包public class Audience {    //定义切点,前面的*后一定是空格号,否则报错。    @Pointcut("execution(* concert.JeckSon.*(..))")    public  void perform(){    }    @Around(value = "perform()")    public void watchPerformance(ProceedingJoinPoint jp){        try {            System.out.println("关闭手机或调静音!");            System.out.println("坐好自己的位置");            jp.proceed();            System.out.println("演唱的非常棒!");        } catch (Throwable e) {            System.out.println("垃圾演唱,退款!");        }    }}4.启用AspectJ注解自动代理package concert;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration@EnableAspectJAutoProxy@ComponentScanpublic class concertConfig {@Bean(name="audience1")    public Audience audience(){        return new Audience() ;    }    @Bean(name="jeckson1")    public JeckSon jeckjson(){        return new JeckSon();    }}5.测试package concert;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class TestDemo {    @SuppressWarnings("unused")    @Test    public void testAspectJDemo() {        @SuppressWarnings("resource")        ApplicationContext context = new AnnotationConfigApplicationContext(concertConfig.class);        // JeckSon类实现了Performance接口,因此转换类型也必须是Performance类型,否则报错        Performance js = (Performance) context.getBean("jeckson1");        js.perform();    }}6.控制台输出结果关闭手机或调静音!坐好自己的位置马上演唱dangerous演唱的非常棒!总结:因为没有异常,所有不会执行异常代码。

(五)Spring AOP处理通知中的参数

这里写图片描述

package com.learnSpring09;public interface Performance {     public void perform();     public void plackTrack(String number);}package com.learnSpring09;//实现接口类public class Actor implements Performance {    @Override    public void perform() {        System.out.println("表演中...");    }    @Override    public void plackTrack(String number) {        System.out.println(number + " number actor");    }}package com.learnSpring09;import java.util.HashMap;import java.util.Map;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;@Aspect@Componentpublic class TrackCountAnnotation {    private Map<String,Integer> trackCounts = new HashMap<String,Integer>();    @Pointcut("execution(** com.learnSpring09.Performance.plackTrack(String)) && args(trackNumber)")    public void trackPlayed(String trackNumber){}    @Before("trackPlayed(trackNumber)")    public void countTrack(String trackNumber){        System.out.println("i am record..." + trackNumber + " people");        int currentCount = getPlayCount(trackNumber);        trackCounts.put(trackNumber, ++currentCount);    }    public int getPlayCount(String trackNumber) {        return trackCounts.get(trackNumber)!=null?trackCounts.get(trackNumber):0;    }}package com.learnSpring09;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration@EnableAspectJAutoProxy@ComponentScan//配置以下两个Beanpublic class AppConfig {    @Bean(name="actor")    public Actor actorDemo(){        return new Actor();    }    @Bean(name="trackCountAnnotation")    public TrackCountAnnotation  TrackCountAnnotationDemo(){        return new TrackCountAnnotation();    }}package com.learnSpring09;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class testDemo {    @Test    public void testDemo() {        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);        //实现接口的类,获得实例时,必须是父类类型,否则报错        Performance actor = (Performance) context.getBean("actor");        TrackCountAnnotation tc = (TrackCountAnnotation) context.getBean("trackCountAnnotation");        actor.plackTrack("A");        actor.plackTrack("B");        actor.plackTrack("B");        actor.plackTrack("C");        actor.plackTrack("C");        actor.plackTrack("C");        actor.plackTrack("D");        actor.plackTrack("D");        System.out.println("A======= " + tc.getPlayCount("A"));        System.out.println("B======= " + tc.getPlayCount("B"));        System.out.println("C======= " + tc.getPlayCount("C"));        System.out.println("D======= " + tc.getPlayCount("D"));        System.out.println("E======= " + tc.getPlayCount("E"));        System.out.println("F======= " + tc.getPlayCount("F"));        System.out.println("G======= " + tc.getPlayCount("G"));    }}控制台输出结果:i am record...A peopleA number actori am record...B peopleB number actori am record...B peopleB number actori am record...C peopleC number actori am record...C peopleC number actori am record...C peopleC number actori am record...D peopleD number actori am record...D peopleD number actorA======= 1B======= 2C======= 3D======= 2E======= 0F======= 0G======= 0

(六) 基于xml文件配置切面

1.声明一个接口package com.learnSpring10;public interface Person {    public void sleep();}2.实现一个接口package com.learnSpring10;public class Student implements Person{    @Override    public void sleep() {         System.out.println("晚安,睡觉");    }}3.编写一个Java类,在xml文件中声明为切面package com.learnSpring10;public class DoSomethings {    public void BeforeSleepDothing(){        System.out.println("关灯");    }    public void AfterSleepDothing(){        System.out.println("做梦");    }}4.在xml文件中声明切面<?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/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.3.xsd">    <aop:config>         <aop:aspect ref="dosomethings">            <aop:pointcut expression="execution(* com.learnSpring10.Student.*(..))" id="aa" />            <!-- before advice -->            <aop:before pointcut="execution(* com.learnSpring10.Student.*(..))"  method="BeforeSleepDothing"  />             <!-- after advice -->            <aop:after  pointcut="execution(* com.learnSpring10.Student.*(..))"  method="AfterSleepDothing"  />        </aop:aspect>     </aop:config>     <bean id="student01" class="com.learnSpring10.Student" />    <!-- DoSomethings  Aspect -->    <bean id="dosomethings" class="com.learnSpring10.DoSomethings" /></beans>/////////////////////////////////////////////////////////////////<?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/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.3.xsd">    <aop:config>         <aop:aspect ref="dosomethings">            <aop:pointcut expression="execution(* com.learnSpring10.Student.*(..))" id="aa" />            <!-- before advice -->            <aop:before   method="BeforeSleepDothing" pointcut-ref="aa" />             <!-- after advice -->            <aop:after    method="AfterSleepDothing" pointcut-ref="aa"  />        </aop:aspect>     </aop:config>     <bean id="student01" class="com.learnSpring10.Student" />    <!-- DoSomethings  Aspect -->    <bean id="dosomethings" class="com.learnSpring10.DoSomethings" /></beans>///////////////////////////////////////////////////////////////5.测试package com.learnSpring10;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class testDemo {    @Test    public void testDemo(){        ApplicationContext context=new ClassPathXmlApplicationContext("AOP03.xml");        Person stu=(Person) context.getBean("student01");        stu.sleep();    }}控制台输出结果为:关灯晚安,睡觉做梦总结:当sleep()方法执行时,声明为前置通知和后置通知的方法,嵌入该方法的前后。