Spring AOP

目录

用XMl来创建切面。

用Aspect注解来创建切面

其他通知及环绕通知。

获取通知中的参数

xml的配置

利用@注解方式

通过切面引入新功能


学习Spring Aop,我们就需要先知道几个关键字。

连接点:就是在应用程序流程中的任何连接处。(比如调用方法时,抛出异常时等)

切点:就是我们通过一些条件筛选出的我们需要使用的连接点。

切面:就是我们找到切点后要做的事情。包括切面和通知。

通知:我们进入切点后具体要做的事情。

引入:就是通过切面 向现有的类(切点所匹配的类)做一下操作或引入新的方法或者属性。

织入:就是把切面应用到目标对象,并创建代理对象的过程。

可以看下面的图:见笑见笑。

 

用XMl来创建切面。

我们来模拟一个场景。进入鸟巢看演唱会。正常的流程是   买票进入鸟巢-------看演唱会--------结束出鸟巢。

现在我们想在看演唱会前后加入一些动作。我们现在不改变我们的流程。怎么办呢,通过切面。

首先我们来创建一个演唱会流程

Performance.java  观看演唱会的

package com.tzw.service;

/**
 * 演唱会
 */
public class Performance {


    /**
     * 买票进入鸟巢
     */
    public void inBirdN(){
        System.out.println("买票进入鸟巢");
    }

    /**
     * 看演唱会
     */
    public void WatchConcert(){
        System.out.println("观看演唱会");
    }

    /**
     * 看完出鸟巢
     */
    public void outBirdN(){
        System.out.println("观看完退场");
    }
}

创建一个类将流程串连起来。1.进入现场  2.观看演唱会 3.看完退场。

package com.tzw.service;

import org.springframework.beans.factory.annotation.Autowired;

public class PerformPlus {

    @Autowired
    public Performance performance;

    public void getRun(){
        performance.inBirdN();
        performance.WatchConcert();
        performance.outBirdN();
    }

}

在这个流程中,我们需要用切面加一点东西。在第2步观看演唱会前后,加点动作。在看之前,需要先找到自己的作为,在看之后我们需要及时的鼓掌。

接下来们创建一个切面类,PerAspect.java 这个类中定义了以上的连个动作。

package com.tzw.service;

public class PerAspect {

    public void lookSeat(){
        System.out.println("寻找座位");
    }

    public void applaud(){
        System.out.println("鼓掌");
    }

}

那我们怎么把这个切面加入到我们之前的三步流程里呢。这里我们通过配置文件来配置。

<?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:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


    <context:component-scan base-package="com.tzw.service"/>

    <bean id="performance" class="com.tzw.service.Performance"></bean>

    <bean id="performPlus" class="com.tzw.service.PerformPlus"></bean>
    <!--定义切面bean-->
    <bean id="perAspect" class="com.tzw.service.PerAspect"></bean>

    <!--一般aop的配置都会放在aop:config标签里-->
    <aop:config>
        <!--这里是定义了一个切面类,用aop:aspect -->
        <aop:aspect ref="perAspect">
            <!--定义切点,告诉切面里的通知,在那个点进行通知。-->
            <aop:pointcut id="tzwPointCut" expression="execution(* com.tzw.service.Performance.WatchConcert())"/>
            <!--定义前置通知-->
            <aop:before method="lookSeat" pointcut-ref="tzwPointCut"/>
            <!--定义后置通知-->
            <aop:after method="applaud" pointcut-ref="tzwPointCut"/>
        </aop:aspect>
    </aop:config>

</beans>

在这里,我们定义了切面,切面里定义了两个通知,前置通知和后置通知,同时我们还定义了切点,所以在切点之前会执行前置通知,在切点之后会执行后置通知。

当然,这里出了前置通知,后置通知,还有其他的通知,比如<aop:after-returning>返回通知,<aop:after-throwing>抛出异常通知,<aop:around>环绕通知

所以在这个流程里,在观看演唱会之前会 执行  找座位, 在观看演唱会之后 会鼓掌。

我们进行一下测试

  @Test
    public void test2(){
        ClassPathXmlApplicationContext xmlContext = new ClassPathXmlApplicationContext("springAop.xml");
        PerformPlus performPlus = (PerformPlus) xmlContext.getBean("performPlus");
        performPlus.getRun();
    }

结果

 

用Aspect注解来创建切面

切面类。

package com.tzw.serviceone;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * @Aspect 注解用来声明这个类是一个切面类
 */
@Aspect
//@EnableAspectJAutoProxy
public class PerAspectOne {

    /**
     * @Pointcut 用来定义切点
     */
    @Pointcut(value = "execution(* com.tzw.serviceone.PerformanceOne.WatchConcert())")
    public void pointCut(){

    }

    /**
     * @Before 用来定义一个前置通知。同时引用 pointCut切点
     */
    @Before(value = "pointCut()")
    public void lookSeat(){
        System.out.println("寻找座位");
    }

    /**
     * @After 用来定义一个前置通知。同时引用 pointCut切点
     * 当然也可以不引用,直接在方式上声明一个切点。
     */
//    @After(value = "pointCut()")
    @After(value="execution(* com.tzw.serviceone.PerformanceOne.WatchConcert())")
    public void applaud(){
        System.out.println("鼓掌");
    }

}

spring配置。如果用@Aspect注解定义切面的话,在spring中就不需要用<aop:config><aop:Aspect>等标签。只需要开启@Aspect注解就行,在xml中用 <aop:aspectj-autoproxy>,用来启动@Aspect注解,当然也可以直接在切面类,就是上面那个类,用该注解注释在类上,@EnableAspectJAutoProxy。这个两个选一种配置即可。

<?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:context="http://www.springframework.org/schema/context"
       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/context
       http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


    <context:component-scan base-package="com.tzw.serviceone"/>

    <bean id="performance" class="com.tzw.serviceone.PerformanceOne"></bean>

    <bean id="performPlusOne" class="com.tzw.serviceone.PerformPlusOne"></bean>

    <!--声明切面bean-->
    <bean id="perAspect" class="com.tzw.serviceone.PerAspectOne"></bean>

    <aop:aspectj-autoproxy/>

</beans>

值得注意的是,我们还是需要在spring中声明一下切面bean。

<!--声明切面bean-->
    <bean id="perAspect" class="com.tzw.serviceone.PerAspectOne"></bean>

当然也可用javaConfig 或这注解的形式都行,但一定要加载到spring。否则切面类步器作用。

其他通知及环绕通知。

除了上述通知之外,还有,还有其他的通知,比如<aop:after-returning>返回通知,<aop:after-throwing>抛出异常通知,<aop:around>环绕通知。

<aop:after-returning>返回后通知,<aop:after-throwing>这两个通知呢,用法和前置后置一样,比较简单,

<aop:after-returning>返回后通知,从字面意思讲,就是方法方法成功retrun之后通知。

<aop:after-throwing>抛出异常通知,就是这抛出异常时进行通知。

上述两个就不说了,说一个环绕那个通知。这个通知也是通知的一种,只是说这个通知可以实现 以上的所以通知,包括前置,后置,返回,抛异常。所以可以说它一个通知可以全部搞定。

 public void arountOne(ProceedingJoinPoint jp){
        try {
            System.out.println("前置通知");
            jp.proceed();
            System.out.println("后置通知");
            System.out.println("返回通知");
        } catch (Throwable throwable) {
            System.out.println("异常通知");
            throwable.printStackTrace();
        }
    }

这个就是一个环绕通知的一个方法,这里包含了所以的通知,前置后置等等。

这里引入了一个ProceedingJoinPoint 类,它的proceed()方法我们可以理解为切点所指向的具体方法,在这个方法执行前后,我们做了一些通知。

获取通知中的参数

在程序流程中,如果我们先在切面中获取正常流程中的参数,我们应该怎么做呢。

xml的配置

主要是用了args表达式。同时切点对应的通知方法参数名称也要和args表达式中的字段值相同。

 expression="execution(* com.tzw.service.Performance.WatchConcert(..)) and args(name,a)"
<?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:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


    <context:component-scan base-package="com.tzw.service"/>

    <bean id="performance" class="com.tzw.service.Performance"></bean>

    <bean id="performPlus" class="com.tzw.service.PerformPlus"></bean>
    <!--定义切面bean-->
    <bean id="perAspect" class="com.tzw.service.PerAspect"></bean>

    <!--一般aop的配置都会放在aop:config标签里-->
    <aop:config>
        <!--这里是定义了一个切面类,用aop:aspect -->
        <aop:aspect ref="perAspect">
            <!--定义切点,告诉切面里的通知,在那个点进行通知。-->
            <aop:pointcut id="tzwPointCut" expression="execution(* com.tzw.service.Performance.WatchConcert(..)) and args(name,a)"/>
            <!--定义前置通知-->
            <aop:before method="lookSeat" pointcut-ref="tzwPointCut" arg-names="name,a"/>
            <!--定义后置通知-->
            <aop:after method="applaud" pointcut-ref="tzwPointCut" arg-names="name,a"/>

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

</beans>

 

package com.tzw.service;

public class PerAspect {

    public void lookSeat(String name,int a){
        System.out.println(name+"寻找座位");
    }

    public void applaud(String name,int a){
        System.out.println("鼓掌");
    }
}

注意:如果我们引用同一个切点,那么引用该切点的所有方法都需要配置该参数。否则无法执行。

例如:前置通知和后置通知都引用同一个切点,那么前置通知和后置通知都需要配置切点里的参数。如上。

如果只有前置通知需要,而后置通知不需要,那就不要引用同一个切点,可以新建一个,获者也可以不要引用pointcut-ref

,而是用pointcut表达式重新写一个。

<aop:config>
        <!--这里是定义了一个切面类,用aop:aspect -->
        <aop:aspect ref="perAspect">
            <!--定义切点,告诉切面里的通知,在那个点进行通知。-->
            <aop:pointcut id="tzwPointCut" expression="execution(* com.tzw.service.Performance.WatchConcert(..)) and args(name,a)"/>
            <!--定义前置通知-->
            <aop:before method="lookSeat" pointcut-ref="tzwPointCut" arg-names="name,a"/>
            <!--定义后置通知-->
            <aop:after method="applaud"   pointcut="execution(* com.tzw.service.Performance.WatchConcert(..))"/>

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

 

利用@注解方式

 

package com.tzw.serviceone;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

/**
 * @Aspect 注解用来声明这个类是一个切面类
 */
@Aspect
@EnableAspectJAutoProxy
public class PerAspectOne {

    /**
     * @Pointcut 用来定义切点
     */
    @Pointcut(value = "execution(* com.tzw.serviceone.PerformanceOne.WatchConcert(..))")
    public void pointCut(){

    }


    @Around(value = "execution(* com.tzw.serviceone.PerformanceOne.WatchConcert())")
    public void arountOne(ProceedingJoinPoint jp){
        try {
            System.out.println("前置通知");
            jp.proceed();
            System.out.println("后置通知");
            System.out.println("返回通知");
        } catch (Throwable throwable) {
            System.out.println("异常通知");
            throwable.printStackTrace();
        }
    }

    /**
     * @Before 用来定义一个前置通知。同时引用 pointCut切点
     */
    @Before(value = "pointCut() && args(name,a)")
    public void lookSeat(String name,int a){
        System.out.println(name+"寻找座位"+a);
    }

    /**
     * @After 用来定义一个前置通知。同时引用 pointCut切点
     * 当然也可以不引用,直接在方式上声明一个切点。
     */
    @After(value = "pointCut()&&args(name,a)")
//    @After(value="execution(* com.tzw.serviceone.PerformanceOne.WatchConcert())")
    public void applaud(String name ,int a){
        System.out.println("鼓掌"+a);
    }


}

 

这里注意,在xml文件中 用args表达式是要用 and 连接,因为在xml中 && 有特殊含义。

而在 java中就可以直接用&&。

 

通过切面引入新功能

映入新功能可以理解为为原对象新加了一个接口父类,所以我们在注入原对象的时候可以声明这个父类接口。而原对象做为这个接口的实现,然后调用这个接口父类的方法。而真正执行方法的行为是有 引入的接口的真正实现类来实现的。

 

原对象和原接口

package com.tzw.servicetwo;

/**
 * 演唱会
 */
public class PerformanceTwo implements  PerformanceInter{


    /**
     * 买票进入鸟巢
     */
    public void inBirdN(){
        System.out.println("买票进入鸟巢");
    }

    /**
     * 看演唱会
     */
    public void WatchConcert(String name,int a){
        System.out.println("观看演唱会");
    }

    /**
     * 看完出鸟巢
     */
    public void outBirdN(){
        System.out.println("观看完退场");
    }
}
package com.tzw.servicetwo;

public interface PerformanceInter {


    /**
     * 买票进入鸟巢
     */
    public void inBirdN();

    /**
     * 看演唱会
     */
    public void WatchConcert(String name,int a);

    /**
     * 看完出鸟巢
     */
    public void outBirdN();
}

 

需要引入的对象和接口

package com.tzw.servicetwo.add;

public interface EncoreableTwo {

    public void test();
}

 

package com.tzw.servicetwo.add;

public class EncoreableTowImp implements EncoreableTwo {


    public void test(){
        System.out.println("引入新的方法");
    }
}

 

1.通过注解切面类配置

主要是通过@DeclareParents注解。

 /**
     * 引入新功能
     */
    @DeclareParents(value = "com.tzw.servicetwo.PerformanceInter+" ,defaultImpl = EncoreableTowImp.class)
    public static EncoreableTwo encoreableTwo;

 以下是完整的代码。

package com.tzw.servicetwo;

import com.tzw.servicetwo.add.EncoreableTowImp;
import com.tzw.servicetwo.add.EncoreableTwo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @Aspect 注解用来声明这个类是一个切面类
 */
@Aspect
@EnableAspectJAutoProxy
public class PerAspectTwo {

    /**
     * 引入新功能
     */
    @DeclareParents(value = "com.tzw.servicetwo.PerformanceInter+" ,defaultImpl = EncoreableTowImp.class)
    public static EncoreableTwo encoreableTwo;

    /**
     * @Pointcut 用来定义切点
     */
    @Pointcut(value = "execution(* com.tzw.servicetwo.PerformanceTwo.WatchConcert(..))")
    public void pointCut(){

    }


    @Around(value = "execution(* com.tzw.servicetwo.PerformanceTwo.WatchConcert())")
    public void arountOne(ProceedingJoinPoint jp){
        try {
            System.out.println("前置通知");
            jp.proceed();
            System.out.println("后置通知");
            System.out.println("返回通知");
        } catch (Throwable throwable) {
            System.out.println("异常通知");
            throwable.printStackTrace();
        }
    }

    /**
     * @Before 用来定义一个前置通知。同时引用 pointCut切点
     */
    @Before(value = "pointCut() && args(name,a)")
    public void lookSeat(String name,int a){
        System.out.println(name+"寻找座位"+a);
    }

    /**
     * @After 用来定义一个前置通知。同时引用 pointCut切点
     * 当然也可以不引用,直接在方式上声明一个切点。
     */
    @After(value = "pointCut()&&args(name,a)")
//    @After(value="execution(* com.tzw.serviceone.PerformanceTwo.WatchConcert())")
    public void applaud(String name ,int a){
        System.out.println("鼓掌"+a);
    }



}

 

2.通过xml配置。

通过<aop:declare-parents>标签。

<?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:context="http://www.springframework.org/schema/context"
       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/context
       http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


    <context:component-scan base-package="com.tzw.servicetwo"/>

    <bean id="performance" class="com.tzw.servicetwo.PerformanceTwo"></bean>

    <bean id="performPlusTwo" class="com.tzw.servicetwo.PerformPlusTwo"></bean>

    <!--声明切面bean-->
    <bean id="perAspect" class="com.tzw.servicetwo.PerAspectTwo"></bean>

    <!--<aop:aspectj-autoproxy/>-->

    <aop:config>
        <aop:aspect ref="perAspect">

            <aop:declare-parents types-matching="com.tzw.servicetwo.PerformanceInter+"
                                 implement-interface="com.tzw.servicetwo.add.EncoreableTwo" default-impl="com.tzw.servicetwo.add.EncoreableTowImp"/>
        </aop:aspect>
    </aop:config>

</beans>

这两种是一样的,只是实现方式不一样,一个是通过注解实现的,另一个是通过xml配置<aop>标签实现的。两种方式都可以。

 

使用引入的新功能。

 

package com.tzw.servicetwo;

import com.tzw.servicetwo.add.EncoreableTwo;
import org.springframework.beans.factory.annotation.Autowired;

public class PerformPlusTwo {

    @Autowired
    public PerformanceInter performance;

    public void getRun(){
        performance.inBirdN();
        performance.WatchConcert("张三",1);
        performance.outBirdN();
        //加入新方法。将
        EncoreableTwo encoreableTwo = (EncoreableTwo)performance;
        encoreableTwo.test();
    }

}

 

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:context="http://www.springframework.org/schema/context"
       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/context
       http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


    <context:component-scan base-package="com.tzw.servicetwo"/>

    <bean id="performance" class="com.tzw.servicetwo.PerformanceTwo"></bean>

    <bean id="performPlusTwo" class="com.tzw.servicetwo.PerformPlusTwo"></bean>

    <!--声明切面bean-->
    <bean id="perAspect" class="com.tzw.servicetwo.PerAspectTwo"></bean>


</beans>

 

测试类

@Test
    public void test4(){
        ClassPathXmlApplicationContext xmlContext = new ClassPathXmlApplicationContext("springAopTwo.xml");
        PerformPlusTwo performPlusTwo = (PerformPlusTwo) xmlContext.getBean("performPlusTwo");
        performPlusTwo.getRun();
    }

 

运行结果

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值