Spring——AOP(1)之Spring1中的配置

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ggGavin/article/details/79949251

AOP介绍

AOP(Aspect-Oriented Programming),即面向切面编程,作为面向对象(OOP,Object-Oriented Programming)的一种补充,广泛应用于处理一些具有横切性质的系统级服务,比如安全性检查、日志记录、事务管理等。

在OOP中,我们以类(class)作为基本单元,而AOP中的基本单元是Aspect(切面)

定义AOP术语

  • 1.切面(Aspect)

要实现的交叉功能,是系统模块化的一个切面或领域。如日志记录。

Aspectpointcutadvice组成,它既包含了横切逻辑的定义,也包括了连接点的定义。Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。

  • 2.连接点(Join Point)

应用程序执行过程中插入切面的地点,可以是方法调用,异常抛出,或者要修改的字段。

在Spring AOP中,Join Point总是方法的执行点,即只有方法连接点。

  • 3.通知(Advice)

切面的实际实现,他通知系统新的行为。如在日志通知包含了实现日志功能的代码,如向日志文件写日志。通知在连接点插入到应用系统中。

许多AOP框架中,包括Spring AOP,会将Advice模拟为一个拦截器(Interceptor),并且在Join Point上维护多个Advice,进行层层拦截。

  • 4.切入点(Point Cut)

定义了通知应该应用在哪些连接点,通知可以应用到AOP框架支持的任何连接点。Advice是和特定的Point Cut关联的,并且在Point Cut相匹配的Join Point中执行。

Spring中,所有的方法都可以认为是Join Point,但是我们并不希望在所有的方法上都添加Advice,而Point Cut的作用就是提供一组规则(可以使用Aspect pointcut expression language来描述)来匹配Join Point,给满足规则的Join Point添加Advice

  • 5.引入(Introduction)

为类添加新方法和属性。Spring AOP 允许我们为目标对象Adviced Object引入新的接口(和对应的实现)。

  • 6.目标对象(Adviced Object)

被通知的对象。既可以是你编写的类,也可以是第三方的类。

  • 7.代理对象(AOP Proxy)

将通知(Advice)应用到目标对象后创建的对象,应用系统的其他部分不用为了支持代理对象而改变。

Spring AOP使用运行时代理的方式来实现aspect。在Spring AOP中,一个AOP代理是一个JDK动态代理对象或者CGLIB代理对象。

  • 8.织入(Weaving)

将切面(Aspect)应用到目标对象从而创建一个新代理对象的过程。织入发生在目标对象生命周期的多个点上:

  • 编译期:切面在目标对象编译时织入,这需要一个特殊的编译器。

  • 类装载期:切面在目标对象被载入JVM时织入,这需要一个特殊的类载入器。

  • 运行期:切面在应用系统运行时织入。

Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。

创建通知

本文将通过Spring1的配置方式来讲解相关例子,使用ProxyFactoryBean来创建AOP代理,并且使用Advisor指定需要增加的方法。

主要有四大通知类型,如下表:

通知类型 接口 描述
Around org.aopalliance.intercept.MethodInterceptor 拦截对目标方法调用
Before org.springframework.aop.MethodBeforeAdvice 在目标方法调用前调用
After org.springframework.aop.AfterReturningAdvice 在目标方法调用后调用
Throws org.springframework.aop.ThrowsAdvice 当目标方法抛出异常时调用

注意:异常通知ThrowsAdvice是标识性接口,没有任何方法,但实现该接口的类必须要有如下形式的方法:

void afterThrowing(Throwable throwable);
void afterThrowing(Method m, Object[] objects, Object target, Exception throwable);

第一个方法只接受一个参数:需要抛出的异常。

第二个方法接受异常、被调用的方法、参数以及目标对象。

引入通知

引入通知可以自定义切入点。

自定义切入点可以通过正则表达式匹配。

案例

1.定义接口

两个接口ITestServiceITestService2

public interface ITestService {
    void sayHello();
}

public interface ITestService2 {
    void sayBye();
}

2.编写对象(被代理的对象,也即目标对象)

目标对象具有name属性,并且有sayHello()方法和sayBye()方法。

public class TestService1 implements ITestService, ITestService2 {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void sayHello() {
        System.out.println("Hello, " + name);
    }

    @Override
    public void sayBye() {
        System.out.println("Bye, " + name);
        try {
            int i = 1 / 0;
        } catch (Exception e) {
        }
    }
}

3.编写通知

这里我们编写上面介绍的4个通知。

public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
    /**
     *
     * @param method 被调用的方法
     * @param objects 给方法传递的参数
     * @param o 被代理的对象,目标对象
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("=============");
        System.out.println("记录日志:" + method.getName());
    }
}

public class MyAfterReturningAdvice implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("关闭资源....");
    }
}

public class myMethodInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("调用方法前执行!");
        // proceed()真正执行方法,object为返回的对象
        Object object = methodInvocation.proceed();
        System.out.println("方法调用后!");
        return object;
    }
}

public class MyThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(Method m, Object[] objects, Object target, Exception throwable){
        System.out.println("出大事了!" + throwable.getMessage());
    }
}

4.在beans.xml文件中配置

4.1 先配置被代理对象

4.2 配置通知

4.3 配置代理对象,是ProxyFactoryBean的实例

4.3.1 配置代理接口集

4.3.2 把通知织入到代理对象

4.3.3 指定被代理对象

  • 配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--配置被代理的对象-->
    <bean id="testService1" class="com.gavin.aop.TestService1">
        <property name="name" value="Gavin"/>
    </bean>

    <!--配置前置通知-->
    <bean id="myMethodBeforeAdvice" class="com.gavin.aop.MyMethodBeforeAdvice"/>
    <!--配置后置通知-->
    <bean id="myAfterReturningAdvice" class="com.gavin.aop.MyAfterReturningAdvice"/>
    <!--配置环绕通知-->
    <bean id="myMethodInterceptor" class="com.gavin.aop.myMethodInterceptor"/>
    <!--配置异常通知-->
    <bean id="myThrowsAdvice" class="com.gavin.aop.MyThrowsAdvice"/>

    <!--使用Advisor来定义前置通知的切入点,该定义只允许sayHello使用前置通知,sayBye不使用前置通知-->
    <bean id="myMethodBeforeAdviceFilter" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
        <property name="advice" ref="myMethodBeforeAdvice"/>
        <property name="mappedNames">
            <list>
                <value>sayHello</value>
            </list>
        </property>
    </bean>

    <!--配置代理对象-->
    <bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--代理接口集-->
        <property name="proxyInterfaces">
            <list>
                <value>com.gavin.aop.ITestService</value>
                <value>com.gavin.aop.ITestService2</value>
            </list>
        </property>
        <!--把通知织入到代理对象-->
        <property name="interceptorNames">
            <!--相当于把前置通知和代理对象关联起来-->
            <!--我们也可以把通知看成拦截器-->
            <list>
                <!--使用自定义切入点,来控制前置通知-->
                <value>myMethodBeforeAdviceFilter</value>
                <value>myAfterReturningAdvice</value>
                <value>myMethodInterceptor</value>
                <value>myThrowsAdvice</value>
            </list>
        </property>
        <!--指定被代理对象-->
        <property name="target">
            <ref bean="testService1"/>
        </property>
    </bean>
</beans>

在这里,如果只使用ProxyFactoryBean来配置代理对象的话,会把代理接口中的所有方法都代理了,也就是每个方法都被增强了,如果不想被增强,我们可以通过配置Advisor来指定切入点。这里我们使用了NameMatchMethodPointcutAdvisor,通过指定具体方法名称来指定需要被增强的方法。另外如果方法很多,也可以通过RegexpMethodPointcutAdvisor,该Advisor可以通过正则表达式来匹配要增强的方法。

5.测试类:

public class App1 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("com/gavin/aop/beans.xml");
        ITestService iTestService = (ITestService) applicationContext.getBean("proxyFactoryBean");
        iTestService.sayHello();
        System.out.println();
        ((ITestService2)iTestService).sayBye();
    }
}

6.输出

这里写图片描述

可以看到,TestService中的sayHello()sayBye()方法都被增强了。其中由于我们对前置通知配置了Advisor指定了只有sayHello()方法织入了前置通知,所以sayHello()方法记录了日志,而sayBye()方法没有。

sayBye()方法中,由于我们捕获并处理了异常,所以程序的执行过程中并没有遇到异常报错,异常通知也就没有被调用。如果在sayBye()方法中,我们对int i = 1 / 0;这句话不处理异常,异常在执行的时候就会被抛出,异常通知就会被调用。


本文使用了Spring1中配置AOP的方式进行配置,但是这里有一个问题,当我们的业务增多了之后,ProxyFactoryBean的配置也会增多,导致xml迅速变得很臃肿。

后面会介绍AspectJ框架,以及Spring2以后的版本中加入的使用@AspectJ的方式进行配置的方式。要知道的是Spring只是引入了@AspectJ方式配置,但其底层实现AOP的方式仍是通过代理的方式,不同于AspectJ框架。

阅读更多

扫码向博主提问

KLeonard

非学,无以致疑;非问,无以广识
  • 擅长领域:
  • Java
  • Python
去开通我的Chat快问
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页