SpringInAction.4th.面向切面的Spring

来源:互联网 发布:韩版西装品牌 知乎 编辑:程序博客网 时间:2024/05/21 15:04

SpringInAction.4th.面向切面的Spring

@(spring)[AOP]

面向切面编程,按我的理解就是,在执行一个动作的同时执行一些公共的动作。这些公共的动作没必要每个都写在各自的方法里,可以提取出公共的方法。Spring提供了4种类型的AOP支持:
- 基于代理的经典Spring AOP
- 纯POJO切面
- @AspectJ注解驱动的切面
- 注入式AspectJ切面(适用于Spring各版本)
前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截


  • SpringInAction4th面向切面的Spring
    • AOP术语
      • 简单归纳
    • AOP实现
      • 编写切点
        • 1定义简单的切面
        • 2改进切面Pointcut
        • 3在JavaConfig中启用AspectJ注解的自动代理
      • 创建环绕通知切面
        • 使用环绕通知重新实现Audience切面
        • 处理通知中的参数
      • 通过注解引入新功能
    • 在XML中声明切面
      • 为通知传递参数
      • 通过切面引入新的功能
    • 总结

AOP术语


spring AOP有很多伤脑筋的术语,没办法为了理解AOP,还是得稍微看下:
1. 通知(Advice):定义了切面是什么以及何时使用。通俗的说通知就是自己定义的要执行的公共的方法(例如发短信方法前后要执行流水操作方法)
2. 连接点(Join point):我们的应用可能也有数以千计的时机应用通知。这些时机被称为连接点。程序执行过程中能够应用通知的所有点
3. 切点(Poincut):切点就定义了“何处”。通俗的说,切点就是在公共方法执行后要执行的方法(或叫那个操作点例如上面的发短信方法)
4. 切面(Aspect):切面是通知和切点的结合。
5. 引入(Introduction):引入允许我们向现有的类添加新方法或属性。
6. 织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。

简单归纳


1.切面 = 通知+切点:是什么东西在什么时候使用在什么地方使用(说白了切面就类似于代理类吧)
2.切面、连接点都是名词
3.引入和织入是动词,是动作

AOP实现

编写切点


切点的编写格式:

切点的编写格式

AOP简单实例:

1、定义简单的切面


@Aspectpublic class Audience {    @Before("execution(* com.lemontree.spring4.day02.Performance.perform(..))")    public void silence(){        System.out.println("phone silence...");    }    @Before("execution(* com.lemontree.spring4.day02.Performance.perform(..))")    public void taslSeats(){        System.out.println("task seats.");    }    @AfterReturning("execution(* com.lemontree.spring4.day02.Performance.perform(..))")    public void applause(){        System.out.println("Clap...");    }    @AfterThrowing("execution(* com.lemontree.spring4.day02.Performance.perform(..))")    public void demendRefund(){        System.out.println("demend refund");    }}

2、改进切面@Pointcut


通过@Pointcut注解声明频繁使用的切点表达式,perform 方法本身并不重要,它只是一个标识

@Aspectpublic class Audience {    // perform 本身并不重要,它只是一个标识    @Pointcut("execution(* com.lemontree.spring4.day02.Performance.perform(..))")    public void perform(){}    @Before("perform()")    public void silence(){        System.out.println("phone silence...");    }    @Before("perform()")    public void taslSeats(){        System.out.println("task seats.");    }    @AfterReturning("perform()")    public void applause(){        System.out.println("Clap...");    }    @AfterThrowing("perform()")    public void demendRefund(){        System.out.println("demend refund");    }}

3、在JavaConfig中启用AspectJ注解的自动代理


如果只是单单的只是作为一个@bean注入就来,那么Spring无法将audience 识别为一个切面,所以使用@EnableAspectJAutoProxy开启自动代理,让audience注册为一个切面{@see AspectJAutoProxyRegistrar.class}。
AspectJ自动代理都会为使用@Aspect注解的bean创建一个代理,这个代理会围绕着所有该切面的切点所匹配的bean。在这种情况下,将会为{basePackge}下的类创建一个代理,Audience类中的通知方法将会在perform()调用前后执行。

/** * spring 配置类 */@Configuration@ComponentScan@EnableAspectJAutoProxy // 启用AspectJ注解的自动代理,让audience注册为一个切面{@see AspectJAutoProxyRegistrar.class}public class SpringConfig {    @Bean    public Audience audience(){        return new Audience();    }}

注意:测试类中切点中的方法必须是接口方法(涉及到代理的知识点)

创建环绕通知切面

使用环绕通知重新实现Audience切面


需要注意的是,别忘记调用proceed()方法。如果不调这个方法的话,那么你的通知实际上会阻塞对被通知方法的调用(意思就是只会执行上下文方法,而不会去执行通知方法,有可能就是要这种效果)

/** * 定义环绕通知 */@Aspectpublic class Audience02 {    //切点    @Pointcut("execution(* com.lemontree.spring4.day02.Performance.perform(..))")    public void perform(){}    @Around("perform()")    public void warchperform(ProceedingJoinPoint proceedingJoinPoint){        try {            System.out.println("silencing cell phone");            proceedingJoinPoint.proceed(); //通知            System.out.println("Clap ...");        } catch (Throwable throwable) {            System.out.println("Demanding a refund");        }    }}

处理通知中的参数


在切点表达式中声明参数,这个参数传入到通知方法中
在切点表达式中声明参数,这个参数传入到通知方法中

public interface Performance {    void perform();    void needArg(int num);}@Aspectpublic class TrackAop {    @Pointcut("execution(* com.lemontree.spring4.day02.Performance.needArg(int))"     + "&&args(num)")    public void needArg(int num) {};    @Before("needArg(num)")    public void count(int num) {        System.out.println("num -> do"+num);    }}

调用者传入的参数会入到cont里面。

通过注解引入新功能


利用被称为引入的AOP概念,切面可以为Spring bean添加新方法。
引入

引入其实就是:一个接口a,一个实现类c,一个AOP引入,引入里面定义一个接口b(+号的意思就是子类的意思)子类和前面的实现类c,意思就是说,b的子类可以使用c里面的方法(当转换为接口a类型时才可以)。

/** * @Author: YLBG-YCY-1325 * @Description: * @Date: 2017/8/21 */public interface Encoreable {    void doSomething();}public class DefaultEncoreableImpl implements Encoreable{    @Override    public void doSomething() {        System.out.println("do something....");    }}@Aspectpublic class Encoreableroducer {    @DeclareParents(value = "com.lemontree.spring4.day02.Performance+",            defaultImpl = DefaultEncoreableImpl.class)    public static Encoreable encoreable;}

这么说可能不好理解,看下测试类:

@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = {SpringConfig.class})public class Spring4Test02 {    @Autowired    private Performance audiencePerform;    @Test    public void test(){       Encoreable en = (Encoreable) audiencePerform;       en.doSomething();    }}

在XML中声明切面


有这样一种原则,那就是基于注解的配置要优于基于Java的配置,基于Java的配置要优于基于XML的配置。但是,如果你需要声明切面,但是又不能为通知类添加注解的时候,那么就必须转向XML配置了。
在Spring的aop命名空间中,提供了多个元素用来在XML中声明切面:
这里写图片描述这里写图片描述

与java 配置类似,切点可以单独提出来进行配置,<\aop:aspectj-autoproxy/> xml配置默认开启aop,所以不需要这个标签也是可以的

<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.xsd">    <!--委托类-->    <bean class="com.lemontree.spring3.Aop.AudiencePerform" id="perform"/>    <!--切面1-->    <bean class="com.lemontree.spring3.Aop.Audience" id="audience"/>    <!--切面2-->    <bean class="com.lemontree.spring3.Aop.Audience02" id="audience02"/>    <!--开启aop-->    <aop:aspectj-autoproxy/>    <!--前置、后置-->    <aop:config>        <aop:aspect id="audience" ref="audience">            <!--定义切点-->            <aop:pointcut id="ap" expression="execution(* com.lemontree.spring3.Aop.Performance.perform(..))"/>            <aop:before method="silence" pointcut-ref="ap"/>            <aop:before method="taslSeats" pointcut-ref="ap"/>            <aop:after method="applause" pointcut-ref="ap"/>            <aop:after-throwing method="demendRefund" pointcut-ref="ap"/>        </aop:aspect>    </aop:config>    <!--环绕-->    <aop:config>        <aop:aspect id="audience2" ref="audience02">            <!--定义切点-->            <aop:pointcut id="ap" expression="execution(* com.lemontree.spring3.Aop.Performance.perform(..))"/>            <aop:around method="warchperform" pointcut-ref="ap"/>        </aop:aspect>    </aop:config></beans>

来看看切面的定义:

public class Audience {    public void silence(){        System.out.println("phone silence...");    }    public void taslSeats(){        System.out.println("task seats.");    }    public void applause(){        System.out.println("Clap...");    }    public void demendRefund(){        System.out.println("demend refund");    }}

为通知传递参数


代码演示就去掉了,不过要注意一点,那就是xml中&&会被解析为实体的开始,所以要用and来代替。

通过切面引入新的功能


使用default-impl来直接标识委托和间接使用delegate-ref的区别在于后者是Spring bean,它本身可以被注入、通知或使用其他的Spring配置。(大致和java的配置一样)

    <aop:config>        <aop:aspect>            <aop:declare-parents                types-matching="com.lemontree.spring3.Aop.Performance+"                implement-interface="com.lemontree.spring3.Aop.Encoreable"                   delegate-ref="defaultEncoreable"/>                 <!--default-impl="com.lemontree.spring3.Aop.DefaultEncoreableImpl"/>-->        </aop:aspect>    </aop:config>

测试类:

    <aop:config>        <aop:aspect>            <aop:declare-parents                types-matching="com.lemontree.spring3.Aop.Performance+"                implement-interface="com.lemontree.spring3.Aop.Encoreable"                 default-impl="com.lemontree.spring3.Aop.DefaultEncoreableImpl"/>        </aop:aspect>    </aop:config>

总结


其实AOP代理本质就是,让别人代你做事情,别人可以在你要做的事情的前后做一些其他的事情。Spring里面就是说,调用者实际调用的不是实际的类(或说是方法),调用者调用的是代理类,代理类方法做完后再去处理实际类的方法。
AOP代理理解

而引入的本质就是,当你用另外一个接口来声明(或者叫强转)时,你可以用这个接口实现类(引入了的实现类)的方法。