spring——(7)Spring中的AOP

在java中oop是面向对象编程,这个从我们刚接触java的时候就知道了,今天我们要说的是aop面向切面的编程,其实关于aop的具体思想我也不能完整的表达出来,对于初学者来说只需要知道他是代理模式的一种实现。

AOP核心概念

1、横切关注点

对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点

2、切面(aspect)

类是对物体特征的抽象,切面就是对横切关注点的抽象

3、连接点(joinpoint)

被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器

4、切入点(pointcut)

对连接点进行拦截的定义

5、通知(advice)

所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类

6、目标对象

代理的目标对象

7、织入(weave)

将切面应用到目标对象并导致代理对象创建的过程

8、引入(introduction)

在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段

对于程序员来说使用Spring AOP只需要关注下面几个点就可以了。

  1. 配置切面aspect
  2. 配置切入点pointcut
  3. 配置Advice

配置切面aspect

aspect就是定义在代理模式中添加到方法前或方法后的代码。
Spring中所有关于aop的配置都必须放在< aop:config>标签内

    <bean id="aBean" class=""></bean>
    <aop:config>
        <aop:aspect id="myAspect" ref="aBean">

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

上面的代码首先声明一个bean,然后在< aop:config>标签下声明< aop:aspect>标签,Id代表切面的名称,ref是这个切面引用的资源,也就是一个类(我们在上面声明过的bean),这个类里有需要在代理中添加的方法,现在说有一点抽象,看了后面的例子之后就能理解。

首先创建两个类:

public class AspectBiz {

}
public class MoocAspect {

}

然后在配置文件中添加:

<?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">

    <bean id="moocAspect" class="com.mss.aop.MoocAspect"></bean>

    <bean id="aspectBiz" class="com.mss.aop.AspectBiz"></bean>

    <aop:config>
        <aop:aspect id="moocAspectAOP" ref="moocAspect">
        </aop:aspect>
    </aop:config>

</beans>

配置切入点Pointcut

切入点Pointcut其实就是一个过滤器,过滤需要代理的方法。具体的过滤规则有很多中,这里就不详细讲了,大家可以查看官方文档或者直接百度。
接着上面的例子,我们接着写:

<?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">

    <bean id="moocAspect" class="com.mss.aop.MoocAspect"></bean>

    <bean id="aspectBiz" class="com.mss.aop.AspectBiz"></bean>

    <aop:config>
        <aop:aspect id="moocAspectAOP" ref="moocAspect">
            <aop:pointcut expression="execution( *com.mss.aop.AspectBiz.*(..))"
                id="moocPiontcut" />
        </aop:aspect>
    </aop:config>

</beans>

这里我们只是修改了一下配置文件添加了一个切入点,这个切入点代表(过滤)所有com.mss.aop.AspectBiz包下的方法。

配置Advice

当一个方法执行,Spring AOP 可以劫持一个执行的方法,在方法执行之前或之后添加额外的功能,Advice就是配置添加的功能的位置。
在Spring AOP中,有 5 种类型通知(advices)的支持:

  1. 通知(Advice)之前 - 方法执行前运行< aop:before />
  2. 通知(Advice)返回之后 – 方法执行后返回一个结果后运行< aop:after-returning />
  3. 通知(Advice)之后 – 该方法执行后运行< aop:after />(在aop:after-returning之后运行
  4. 通知(Advice)抛出之后 – 运行方法抛出异常后< aop:after-throwing />
  5. 环绕通知 – 环绕方法执行运行,结合以上这三个通知< aop:around />
<?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">

    <bean id="moocAspect" class="com.mss.aop.MoocAspect"></bean>

    <bean id="aspectBiz" class="com.mss.aop.AspectBiz"></bean>

    <aop:config>
        <aop:aspect id="moocAspectAOP" ref="moocAspect">
            <aop:pointcut expression="execution(* com.mss.aop.AspectBiz.*(..))"
                id="moocPiontcut" />
            //引用pointcut-ref
            <aop:before method="before" pointcut-ref="moocPiontcut" />
            //直接定义pointcut
            <aop:before method="before"
                pointcut="execution(* com.mss.aop.AspectBiz.*(..))" />
        </aop:aspect>
    </aop:config>

</beans>

配置Advice需要指定pointcut,我们可以引用上面声明的pointcut,或者直接在标签内定义一个pointcut。method是指定一个方法名,这个方法必须是在aspect中配置的类中的方法。由于我们使用的是

public class MoocAspect {
    //切面,也就是插入的代码
    public void before() {
        System.out.println("MoocAspect before.");
    }

}
public class AspectBiz {

    public void biz() {
        System.out.println("AspectBiz biz.");
    }

}

写个测试类测试一下:

@RunWith(BlockJUnit4ClassRunner.class)
public class TestOneInterface extends UnitTestBase{

    public TestOneInterface() {
        super("classpath*:spring-ioc.xml");
    }

    @Test
    public void test() {
        AspectBiz aspectBiz=super.getBean("aspectBiz");
        aspectBiz.biz();
    }
}

运行结果:

MoocAspect before.
AspectBiz biz.

可以看到,我们成功把before() 中的代码插入到 biz() 之中,并且在biz()之前运行。
上面我们使用了< aop:before />,其他通知的使用方法是一样的,大家可以自己去测试一下,但是环绕通知有点特殊,下面我们讲一讲环绕通知:
环绕通知就是其他四种通知的结合,环绕通知需要在切面方法中添加一个ProceedingJoinPoint类型的参数,并且在切面方法中添加一行代码,看下面的方法:

    public Object around(ProceedingJoinPoint pjp) {
        Object obj = null;
        try {
            System.out.println("MoocAspect around 1.");
            obj = pjp.proceed();
            System.out.println("MoocAspect around 2.");
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return obj;
    }

可以看到方法中有一个ProceedingJoinPoint 类型的参数,并且多了一行obj = pjp.proceed();这行代码就是调用业务方法,这下就清晰了,我们可以在调用业务方法之前、之后、抛出异常中添加代码。这个方法有一个返回值,这个返回值就是业务方法的返回值,最后我们需要把这个返回值返回,如果你愿意甚至可以返回一个与业务方法完全无关的返回值。下面我们直接上例子:
首先在配置文件中添加环绕通知:

<?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">

    <bean id="moocAspect" class="com.mss.aop.MoocAspect"></bean>

    <bean id="aspectBiz" class="com.mss.aop.AspectBiz"></bean>

    <aop:config>
        <aop:aspect id="moocAspectAOP" ref="moocAspect">
            <aop:pointcut expression="execution(* com.mss.aop.AspectBiz.*(..))"
                id="moocPiontcut" />
            <aop:around method="around" pointcut-ref="moocPiontcut"/>
        </aop:aspect>
    </aop:config>

</beans>

修改MoocAspect中的代码:

public class MoocAspect {

    public Object around(ProceedingJoinPoint pjp) {
        Object obj = null;
        try {
            //方法运行前
            System.out.println("MoocAspect around 1.");
            obj = pjp.proceed();
            //方法运行后
            System.out.println("MoocAspect around 2.");
        } catch (Throwable e) {
            //方法抛出异常后
            System.out.println("MoocAspect afterThrowing.");
        }
        return obj;
    }

}

修改AspectBiz中的代码:

public class AspectBiz {

    public void biz() {
        System.out.println("AspectBiz biz.");
    }
}

测试代码:

@RunWith(BlockJUnit4ClassRunner.class)
public class TestOneInterface extends UnitTestBase{

    public TestOneInterface() {
        super("classpath*:spring-ioc.xml");
    }

    @Test
    public void test() {
        AspectBiz aspectBiz=super.getBean("aspectBiz");
        aspectBiz.biz();
    }
}

运行结果:

MoocAspect around 1.
AspectBiz biz.
MoocAspect around 2.

配置带参数的Advice

我们的业务方法大部分都是有参数的,如果想要在切面方法中使用这些方法怎么办呢?下面介绍一下带参数的Advice:
其实带参数和不带参数的Advice只是切入点Pointcut配置不一样,下面看一个例子:

<?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">

    <bean id="moocAspect" class="com.mss.aop.MoocAspect"></bean>

    <bean id="aspectBiz" class="com.mss.aop.AspectBiz"></bean>

    <aop:config>
        <aop:aspect id="moocAspectAOP" ref="moocAspect">
            <aop:pointcut expression="execution(* com.mss.aop.AspectBiz.*(..))"
                id="moocPiontcut" />
            <aop:around method="aroundInit"
                pointcut="execution(* com.mss.aop.AspectBiz.init(String, int)) and args(bizName, times)" />
        </aop:aspect>
    </aop:config>

</beans>

可以看到我们在Advice内配置了单独的pointcut,这个pointcut指定了具体的方法,以及方法的参数类型。下面在看一下这个方法:

public class AspectBiz {

    public void init(String bizName, int times) {
        System.out.println("AspectBiz init : " + bizName + "   " + times);
    }

}
public class MoocAspect {

    public Object aroundInit(ProceedingJoinPoint pjp, String bizName, int times) {
        System.out.println(bizName + "   " + times);
        Object obj = null;
        try {
            System.out.println("MoocAspect aroundInit 1.");
            obj = pjp.proceed();
            System.out.println("MoocAspect aroundInit 2.");
        } catch (Throwable e) {
            System.out.println("MoocAspect afterThrowing.");
        }
        return obj;
    }

}

切面方法除了必须的ProceedingJoinPoint 类型的参数之外,还添加了业务方法中的两个参数。下面看一下测试方法:

@RunWith(BlockJUnit4ClassRunner.class)
public class TestOneInterface extends UnitTestBase{

    public TestOneInterface() {
        super("classpath*:spring-ioc.xml");
    }

    @Test
    public void test() {
        AspectBiz aspectBiz=super.getBean("aspectBiz");
        aspectBiz.init("mss", 1);
    }
}

运行结果:

mss   1
MoocAspect aroundInit 1.
AspectBiz init : mss   1
MoocAspect aroundInit 2.

introduction

一个完整的introduction配置:

            <aop:declare-parents types-matching=""
                implement-interface="" 
                default-impl="" />

关于introduction的意思,我举一个例子:

有个类叫小明(biz),小明隔壁住着老王(FitImpl),老王实现了一个技能叫开豪车(Fit)

现在上帝声明了一个切面,这个切面给小明指定一个新的爹叫老王,于是小明每次叫爸爸的时候就能开豪车了~

类匹配(小明):type-matching

接口(开豪车):implement-interface

接口的实现类(老王):default-impl

introduction的作用:小明干自己的事的时候(叫爸爸)能莫名其妙地开上豪车而不用做多余的工作,这些工作由上帝(AOP)帮他完成,这叫“解耦”

例子:
创建一个接口以及一个接口实现类:

public interface Fit {

    void filter();

}
public class FitImpl implements Fit {

    public void filter() {
        System.out.println("FitImpl filter.");
    }

}

在配置文件中配置introduction:

<?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">

    <bean id="moocAspect" class="com.mss.aop.MoocAspect"></bean>

    <bean id="aspectBiz" class="com.mss.aop.AspectBiz"></bean>

    <aop:config>
        <aop:aspect id="moocAspectAOP" ref="moocAspect">

            <aop:declare-parents 
                //匹配所有com.mss.aop包下的类
                types-matching="com.mss.aop.*(+)"
                //定义的接口
                implement-interface="com.mss.aop.Fit" 
                //定义的接口实现类
                default-impl="com.mss.aop.FitImpl" />

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

</beans>

测试类(将上一节中的类强转):

@RunWith(BlockJUnit4ClassRunner.class)
public class TestOneInterface extends UnitTestBase{

    public TestOneInterface() {
        super("classpath*:spring-ioc.xml");
    }

    @Test
    public void test() {
        Fit fit=(Fit)super.getBean("aspectBiz");
        fit.filter();
    }
}

运行结果:

FitImpl filter.

重点内容:所有基于配置文件的aspect只支持singleton

发布了65 篇原创文章 · 获赞 24 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/shanshui911587154/article/details/79123177
今日推荐