SpringInAction.4th.面向切面的Spring

SpringInAction.4th.面向切面的Spring

@(spring)[AOP]

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


AOP术语


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

简单归纳


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

AOP实现

编写切点


切点的编写格式:

切点的编写格式

AOP简单实例:

1、定义简单的切面

@Aspect
public 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 方法本身并不重要,它只是一个标识

@Aspect
public 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()方法。如果不调这个方法的话,那么你的通知实际上会阻塞对被通知方法的调用(意思就是只会执行上下文方法,而不会去执行通知方法,有可能就是要这种效果)

/**
 * 定义环绕通知
 */
@Aspect
public 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);
}

@Aspect
public 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....");
    }
}

@Aspect
public 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代理理解

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值