菜鸟的成长之路——增强篇

本文详细介绍了Spring AOP中的各种增强类型,包括前置、后置、环绕、异常抛出及引介增强的具体实现方法和使用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  对于spring的aop,我们也通过上面几节课进行了了解,也学习了aop的两种动态代理,但是我们并没有进入springAOP的切面编程,这节课我们就讲一下spring中AOP的增强。Spring中使用增强类来进行横切的逻辑,同时增强类还包括横切逻辑在方法的方位信息。

1、增强有哪些种类,他们的接口有什么关系?

  增强可以分为以下五种:

  • 前置增强:实现MethodBeforeAdvice接口,在方法执行之前进行横切逻辑。
  • 后置增强:实现AfterReturningAdvice接口,在方法执行后进行横切逻辑。
  • 环绕增强:实现MethodInterceptor接口,分别在方法执行前后进行横切逻辑。
  • 异常抛出增强:实现ThrowsAdvice接口,在目标方法抛出异常后进行横切逻辑。
  • 引介增强:实现IntroductionInterceptor接口,表示在目标类中添加一些新的属性和方法。

  这些接口的最终接口都是Advice。

2、前置增强如何使用?

  前置增强就是在目标类方法执行之前进行横切逻辑,我们研究的都是在方法之前进行横切,所以使用MethodBeforeAdvice接口,这个接口的父接口是BeforeAdvice接口,这个父接口也是前置增强的接口,但是不在我们现在研究的范围之内。代码如下:

Lecturer.java(目标类)

package com.spring.test;

public class Lecturer{
    public void ClassBegin() {
        // TODO Auto-generated method stub
        System.out.println("讲师开始上课,同学激烈的讨论!");
    }
}

LecturerAdvice.java(增强类)

package com.spring.test;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

public class LecturerAdvice implements MethodBeforeAdvice {

    public void before(Method method, Object[] args, Object target) throws Throwable {
        // TODO Auto-generated method stub
        if(method.getName().equals("ClassBegin")) {
            System.out.println("讲师开始点名。");
        }

    }

}

  现在我们需要将增强织入到目标类中,我们需要使用ProxyFactory类进行织入。ProxyFactory需要注入目标类,并使用方法addAdVice方法织入增强,结合我们学过的ioc容器知识,可以写出配置文件和测试类如下:
ApplicationContext.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:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    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-4.3.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

    <!-- 目标类 -->
    <bean id="lecturer" class="com.spring.test.Lecturer"></bean>
    <!-- 增强类 -->
    <bean id="advice" class="com.spring.test.LecturerAdvice"></bean>
    <!-- 代理工厂 -->
    <bean id="proxyFactory" class="org.springframework.aop.framework.ProxyFactory">
    <!-- 注入目标类 -->
        <property name="target" ref="lecturer"></property>
    </bean>
</beans>

SpringTest.java

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.spring.test.Lecturer;
import com.spring.test.LecturerAdvice;

public class SpringTest{

    public static void main(String[] args) {
        ApplicationContext content = new ClassPathXmlApplicationContext("applicationcontext.xml");
//      获取增强类
        LecturerAdvice advice = (LecturerAdvice) content.getBean("advice");
//      获取代理工厂
        ProxyFactory pf = (ProxyFactory) content.getBean("proxyFactory");
//      添加增强
        pf.addAdvice(advice);
//      获取代理对象
        Lecturer lecturer = (Lecturer) pf.getProxy();
        lecturer.ClassBegin();
    }
}

  设置目标类,只需要将ProxyFactory对象中的target属性设置成目标类的bean,然后再使用ProxyFactory的bean调用addAdvice方法织入增强,至此,前置增强织入成功,最后控制台打印如下:

讲师开始点名。
讲师开始上课,同学激烈的讨论!

3、后置增强如何使用?

  结合前置增强的使用,后置增强的使用只需要把增强类的接口修改,重写新接口的方法,就变成了后置增强,除增强类其他类不变,增强类代码如下:
LecturerAdvice.java

package com.spring.test;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

public class LecturerAdvice implements AfterReturningAdvice {

    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        // TODO Auto-generated method stub
        if(method.getName().equals("ClassBegin")) {
            System.out.println("讲师开始点名。");
        }

    }


}

  最后控制台输出如下:

讲师开始上课,同学激烈的讨论!
讲师开始点名。

  我们发现横切逻辑已经到了目标方法的后面执行,至此,后置增强完成。

4、环绕增强如何使用?

  后置增强在前置增强的基础上,只是改变了增强类的接口,那环绕增强我们看一下增强类该如何编码?

package com.spring.test;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class LecturerAdvice implements MethodInterceptor {

    public Object invoke(MethodInvocation invocation) throws Throwable {
        // TODO Auto-generated method stub
//      目标方法执行前的横切逻辑
        System.out.println("讲师上课前点名。");
//      使用反射机制调用目标方法
        invocation.proceed();
//      目标方法执行后的横切逻辑
        System.out.println("讲师下课前点名");
        return null;
    }
}

  控制台打印如下:

讲师上课前点名。
讲师开始上课,同学激烈的讨论!
讲师下课前点名

5、异常抛出增强如何使用?

  异常抛出增强,只有在目标方法抛出异常的时候才会进行横切逻辑,因此为了达到目标类具有异常抛出的目的,我们简单修改一下目标类,代码如下:
Lecturer.java

package com.spring.test;

import java.util.Scanner;

public class Lecturer{
    public void ClassBegin(){
        // TODO Auto-generated method stub
        Scanner input = new Scanner(System.in);
        int a = 5;
        int b = input.nextInt();
        System.out.println("讲师说"+a+"除以"+b+"的结果是"+a/b);
    }
}

  如果我们在控制台输入的数字是0,则目标方法抛出异常,就可以检测异常抛出增强是否织入成功了,下面我们开始写增强类。
LecturerAdvice.java(增强类)

package com.spring.test;

import org.springframework.aop.ThrowsAdvice;

public class LecturerAdvice implements ThrowsAdvice {
    public void afterThrowing(Exception e) throws Exception{
        // TODO Auto-generated method stub
        System.out.println("方法报错,错误信息是"+e.getMessage()+"\n");
    }
}

  我们为异常抛出增强实现了ThrowsAdvice接口,却发现这个接口没有任何方法可以重写。我们就称这样的接口为标识接口,标识接口没有任何方法,只是为了让我们知道这个类是增强Advice实现类而已。
  虽然接口没有提供方法重写,但是我们需要自己写个方法,让spring的反射机制可以找到横切逻辑代码,这个方法名固定为“afterThrowing”,权限必须是public的,传参方式有两种,如下:

public void afterThrowing(Method method,Object[] args,Object obj,Exception e) throws Exception;

或者

public void afterThrowing(Exception e) throws Exception;

  我们看到控制台打印如下:
这里写图片描述

  发现先进行了横切逻辑,又进行了异常抛出。那这样是不是就证明了横切逻辑会在目标方法之前执行呢?并不是,横切逻辑会在异常代码那一行之前执行,并不会在目标方法的前面执行。

6、异常抛出增强一定要抛出目标方法的的异常吗?

  上个问题中,我们发现目标方法的异常仍然继续抛出,这样我们就捕获不到这个异常,如果在目标方法中使用try捕获异常,横切逻辑又不会执行。
  这时候,我们需要让目标方法向上抛异常,在目标方法调用的地方再使用try捕获异常。这样,我们既可以捕获异常,又可以在异常抛出的时候进行横切逻辑了,代码如下:
Lecturer.java(目标类)

package com.spring.test;

import java.util.Scanner;

public class Lecturer{
    public void ClassBegin() throws Exception{
        // TODO Auto-generated method stub
        Scanner input = new Scanner(System.in);
        int a = 5;
        int b = input.nextInt();
        System.out.println("讲师说"+a+"除以"+b+"的结果是"+a/b);
    }
}

LecturerAdvice.java(增强类)

package com.spring.test;

import java.lang.reflect.Method;

import org.springframework.aop.ThrowsAdvice;

public class LecturerAdvice implements ThrowsAdvice {
    public void afterThrowing(Method method,Object[] args,Object obj,Exception e) throws Exception{
        // TODO Auto-generated method stub
        System.out.println("方法报错,错误信息是"+e.getMessage()+"\n");
    }
}

ApplicationContext.xml(spring配置文件)

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

    <!-- 目标类 -->
    <bean id="lecturer" class="com.spring.test.Lecturer"></bean>
    <!-- 增强类 -->
    <bean id="advice" class="com.spring.test.LecturerAdvice"></bean>
    <!-- 代理工厂 -->
    <bean id="proxyFactory" class="org.springframework.aop.framework.ProxyFactory">
    <!-- 设置目标类 -->
        <property name="target" ref="lecturer"></property>
    </bean>
</beans>

SpringTest.java(测试类)

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.spring.test.Lecturer;
import com.spring.test.LecturerAdvice;

public class SpringTest{

    public static void main(String[] args) {
        ApplicationContext content = new ClassPathXmlApplicationContext("applicationcontext.xml");
//      获取增强类
        LecturerAdvice advice = (LecturerAdvice) content.getBean("advice");
//      获取代理工厂
        ProxyFactory pf = (ProxyFactory) content.getBean("proxyFactory");
//      添加增强
        pf.addAdvice(advice);
//      获取代理对象
        Lecturer lecturer = (Lecturer) pf.getProxy();
        try {
            lecturer.ClassBegin();
        } catch (Exception e) {
            // TODO Auto-generated catch block
        }
    }
}

  这样,控制台就不会抛出异常了。使用异常抛出增强后,Lecturer类只需要负责他自己的方法就可以了,不需要再为事务管理而编写臃肿的代码,也就从事务管理中脱离出来了,java的历史揭开了新的一页。

7、引介增强如何使用?

  引介增强有些特殊,上面提到的几个增强都是在方法的不同位置进行横切逻辑,都是方法级别的。而引介增强为类增加属性和方法,属于类级别的,而引介增强给目标类增加属性和方法,是通过接口实现的。
  换句话说,如果引介增强可以给目标类添加了一些目标接口,就意味着给目标类添加了若干属性和方法,下面我们来举个例子。现有如下需求:

学校规定讲师在上课前要点名,但是如果学校有活动必须暂停授课

Lecturer.java(目标类)

package com.spring.test;

/**
 * 讲师类,具有上课方法
 * @author beijingduanluo
 *
 */
public class Lecturer {
    public void classBegin() {
        System.out.println("讲师传授新技术,同学们讨论很激烈!");
    }
}

School.java(目标接口)

package com.spring.test;
/**
 * 学校的接口,具有举办活动的方法
 * @author beijingduanluo
 *
 */
public interface School {
    public void setActive(boolean active);
}

  现在需要将School接口添加到Lecturer类中。
LecturerAdvice.java(引介增强类)

package com.spring.test;

import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
/**
 * 本类继承IntroductionInterceptor接口的支持类DelegatingIntroductionInterceptor,成为引介增强
 * 本增强类实现需要添加的目标接口
 * @author beijingduanluo
 *
 */
public class LecturerAdvice extends DelegatingIntroductionInterceptor implements School{

    private boolean active;
    /**
     * 代理目标类实现接口的方法
     */
    public void setActive(boolean active) {
        // TODO Auto-generated method stub
        this.active = active;
    }

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        // TODO Auto-generated method stub
//      如果目标类调用的不是classBegin方法,不进行横切逻辑
        if(!mi.getMethod().getName().equals("classBegin")) {
            super.invoke(mi);
            return null;
        }
//      判断学校是否有活动
        if(active) {
//          如果有活动,则停止讲师上课
            System.out.println("学校有活动,讲师暂停上课。");
        }else {
//          学校没有活动,讲师点名
            System.out.println("讲师开始点名。");
//          讲师正常上课
            super.invoke(mi);
        }
        return null;
    }

}

applicationContext.xml(spring的配置文件)

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

    <!-- 配置目标类 -->
    <bean id="lecturer" class="com.spring.test.Lecturer"></bean>
    <!-- 配置增强类 -->
    <bean id="advice" class="com.spring.test.LecturerAdvice"></bean>
    <!-- 配置代理工厂 -->
    <bean id="proxyFactory" class="org.springframework.aop.framework.ProxyFactory">
        <!-- 注入目标类 -->
        <property name="target" ref="lecturer"></property>
        <!-- 因为引介增强是继承关系,所以使用CGLib,进行下面修改 -->
        <property name="ProxyTargetClass" value="true"></property>
        <!-- 注入需要添加的目标接口 -->
        <property name="interfaces" value="com.spring.test.School"></property>
    </bean>
</beans>

SpringTest.java(测试类)

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.spring.test.Lecturer;
import com.spring.test.LecturerAdvice;
import com.spring.test.School;

public class SpringTest{

    public static void main(String[] args) {
        ApplicationContext content = new ClassPathXmlApplicationContext("applicationcontext.xml");
        LecturerAdvice advice = (LecturerAdvice) content.getBean("advice");
        ProxyFactory pf = (ProxyFactory) content.getBean("proxyFactory");
//      将增强加入代理工厂
        pf.addAdvice(advice);
//      获取讲师的代理对象
        Lecturer lecturer = (Lecturer) pf.getProxy();
//      没有举办活动时候讲师上课
        lecturer.classBegin();
        System.out.println("-------------学校举办活动-------------");
//      此时已经将接口加到目标类Lecturer中,如果调用接口方法,需要将目标对象强转
        School school = (School) lecturer;
//      学校举办活动
        school.setActive(true);
//      举办活动的时候讲师上课
        lecturer.classBegin();
    }
}

控制台

讲师开始点名。
讲师传授新技术,同学们讨论很激烈!
————-学校举办活动————-
学校有活动,讲师暂停上课。

  这样,就可以了。有的同学会问,你为什么说接口给了目标类呢?我们看School school = (School) lecturer;这行代码,将目标类强转成了School接口,并且强转成功了,这足以说明我们给目标类添加了School接口。

8、这样是不是就是spring的AOP了呢?

  这节课,我们讲的是增强,默认会把增强织入到目标类的所有方法上。而对于spring的AOP而言,不仅仅是需要添加哪些逻辑,还有将横切逻辑添加到哪些切面上。想知道切面是怎么回事,就继续我们下节课的内容吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值