Spring5源码--Spring AOP使用接口方式实现

简介: Spring5源码--Spring AOP使用接口方式实现

Spring 提供了很多的实现AOP的方式:Spring 接口方式,schema配置方式和注解.

本文重点介绍Spring使用接口方式实现AOP. 研究使用接口方式实现AOP, 以了解为目的. 更好地理解spring使用动态代理实现AOP. 通常我们使用的更多的是使用注解的方式实现AOP


下面来看看如何实现接口方式的AOP


一. 环境准备



要在项目中使用Spring AOP 则需要在项目中导入除了spring jar包之外, 还需要引入aspectjrt.jar,aspectjweaver.jar,aopalliance.jar ,spring-aop-3.2.0.M2.jar和cglib.jar


二、Spring接口方式实现AOP步骤



使用Spring aop接口方式实现aop, 可以通过自定义通知来供Spring AOP识别. 常见的自己定义通知有:前置通知, 后置通知, 返回通知, 异常通知, 环绕通知. 对应实现的接口是:


  • 前置通知: MethodBeforeAdvice
  • 后置通知: AfterAdvice
  • 返回通知:AfterReturningAdvice
  • 异常通知:ThrowsAdvice
  • 环绕通知:MethodInterceptor


实现步骤如下:


1. 业务接口实现


package com.lxl.www.aop.interfaceAop;
/**
 * 使用接口方式实现AOP, 默认通过JDK的动态代理来实现. 非接口方式, 使用的是cglib实现动态代理
 *
 * 业务接口类-- 计算器接口类
 *
 * 定义三个业务逻辑方法
 */
public interface IBaseCalculate {
    int add(int numA, int numB);
    int sub(int numA, int numB);
    int div(int numA, int numB);
    int multi(int numA, int numB);
    int mod(int numA, int numB);
}

2. 业务类

package com.lxl.www.aop.interfaceAop;//业务类,也是目标对象
import com.lxl.www.aop.Calculate;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
/**
 * 业务实现类 -- 基础计算器
 */
@Service
public class BaseCalculate implements IBaseCalculate {
    @Override
    public int add(int numA, int numB) {
        System.out.println("执行目标方法: add");
        return numA + numB;
    }
    @Override
    public int sub(int numA, int numB) {
        System.out.println("执行目标方法: sub");
        return numA - numB;
    }
    @Override
    public int multi(int numA, int numB) {
        System.out.println("执行目标方法: multi");
        return numA * numB;
    }
    @Override
    public int div(int numA, int numB) {
        System.out.println("执行目标方法: div");
        return numA / numB;
    }
    @Override
    public int mod(int numA, int numB) {
        System.out.println("执行目标方法: mod");
        int retVal = ((Calculate) AopContext.currentProxy()).add(numA, numB);
        return retVal % numA;
    }
}

3. 通知类


前置通知

package com.lxl.www.aop.interfaceAop;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
 * 定义前置通知
 */
@Component
public class BaseBeforeAdvice implements MethodBeforeAdvice {
    /**
     *
     * @param method 切入的方法
     * @param args 切入方法的参数
     * @param target 目标对象
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("===========进入beforeAdvice()============");
        System.out.println("目标对象:" + target);
        System.out.println("方法名: "+method);
        System.out.println("即将进入切入点方法");
    }
}

后置通知

package com.lxl.www.aop.interfaceAop;
import org.aspectj.lang.annotation.AfterReturning;
import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
public class BaseAfterReturnAdvice implements AfterReturningAdvice {
    /**
     *
     * @param returnValue 切入点执行完方法的返回值,但不能修改
     * @param method 切入点方法
     * @param args 切入点方法的参数数组
     * @param target 目标对象
     * @throws Throwable
     */
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("==========进入afterReturning()=========== \n");
        System.out.println("切入点方法执行完成");
        System.out.println("后置通知--目标对象:" + target);
        System.out.println("后置通知--方法名: "+method);
        System.out.println("后置通知--方法入参: "+ args.toString());
        System.out.println("后置通知--方法返回值: "+ returnValue);
    }
}


异常通知

package com.lxl.www.aop.interfaceAop;
import org.springframework.aop.ThrowsAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
public class BaseAfterThrowsAdvice implements ThrowsAdvice {
    /**
     * @param method    可选:切入的方法
     * @param args      可选:切入的方法的参数
     * @param target    可选:目标对象
     * @param throwable 必填 : 异常子类,出现这个异常类的子类,则会进入这个通知。
     */
    public void afterThrowing(Method method, Object[] args, Object target, Throwable throwable) {
        System.out.println("出错啦");
    }
}

环绕通知

package com.lxl.www.aop.interfaceAop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
 * 环绕通知
 */
@Component
public class BaseAroundAdvice implements MethodInterceptor {
    /**
     * invocation :连接点
     */
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("===========around环绕通知方法 开始===========");
        // 调用目标方法之前执行的动作
        System.out.println("环绕通知--调用方法之前: 执行");
        // 调用方法的参数
        Object[] args = invocation.getArguments();
        // 调用的方法
        Method method = invocation.getMethod();
        // 获取目标对象
        Object target = invocation.getThis();
        System.out.println("输入参数:" + args[0] + ";" + method + ";" + target);
        // 执行完方法的返回值:调用proceed()方法,就会触发切入点方法执行
        Object returnValue = invocation.proceed();
        System.out.println("环绕通知--调用方法之后: 执行");
        System.out.println("输出参数:" + args[0] + ";" + method + ";" + target + ";" + returnValue);
        System.out.println("===========around环绕通知方法  结束===========");
        return returnValue;
    }
}

4. 自定义切点

package com.lxl.www.aop.interfaceAop;
/**
 * 切点
 *
 * 继承NameMatchMethodPointcut类,来用方法名匹配
 */
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
public class Pointcut extends NameMatchMethodPointcut {
    private static final long serialVersionUID = 3990456017285944475L;
    @SuppressWarnings("rawtypes")
    @Override
    public boolean matches(Method method, Class targetClass) {
        // 设置单个方法匹配
        this.setMappedName("add");
        // 设置多个方法匹配
        String[] methods = { "add", "div" };
        //也可以用“ * ” 来做匹配符号
        // this.setMappedName("get*");
        this.setMappedNames(methods);
        return super.matches(method, targetClass);
    }
}

5. 配置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: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-3.0.xsd
          http://www.springframework.org/schema/context
          http://www.springframework.org/schema/context/spring-context-3.0.xsd
         http://www.springframework.org/schema/aop
          http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
>
    <!-- ==============================aop配置================================ -->
    <!-- 声明一个业务类 -->
    <bean id="baseCalculate" class="com.lxl.www.aop.interfaceAop.BaseCalculate"/>
    <!-- 声明通知类 -->
    <bean id="baseBefore" class="com.lxl.www.aop.interfaceAop.BaseBeforeAdvice"/>
    <bean id="baseAfterReturn" class="com.lxl.www.aop.interfaceAop.BaseAfterReturnAdvice"/>
    <bean id="baseAfterThrows" class="com.lxl.www.aop.interfaceAop.BaseAfterThrowsAdvice"/>
    <bean id="baseAround" class="com.lxl.www.aop.interfaceAop.BaseAroundAdvice"/>
    <!-- 指定切点匹配类 -->
    <bean id="pointcut" class="com.lxl.www.aop.interfaceAop.Pointcut"/>
    <!-- 包装通知,指定切点 -->
    <bean id="matchBeforeAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="pointcut">
            <ref bean="pointcut"/>
        </property>
        <property name="advice">
            <ref bean="baseBefore"/>
        </property>
    </bean>
    <!-- 使用ProxyFactoryBean 产生代理对象 -->
    <bean id="businessProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 代理对象所实现的接口 ,如果有接口可以这样设置 -->
        <property name="proxyInterfaces">
            <value>com.lxl.www.aop.interfaceAop.IBaseCalculate</value>
        </property>
        <!-- 设置目标对象 -->
        <property name="target">
            <ref bean="baseCalculate"/>
        </property>
        <!-- 代理对象所使用的拦截器 -->
        <property name="interceptorNames">
            <list>
                <value>matchBeforeAdvisor</value>
                <value>baseAfterReturn</value>
                <value>baseAround</value>
            </list>
        </property>
    </bean>
</beans>

6. 方法入口

package com.lxl.www.aop.interfaceAop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class InterfaceMainClass{
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("aop/aop.xml");
        BaseCalculate calculate = (BaseCalculate) context.getBean("baseCalculate");
        calculate.add(1, 3);
    }
}


三. 分析



各种类型通知的执行顺序: 前置方法会在切入点方法之前执行,后置会在切入点方法执行之后执行,环绕则会在切入点方法执行前执行同事方法结束也会执行对应的部分。主要是调用proceed()方法来执行切入点方法。来作为环绕通知前后方法的分水岭

在xml 配置 businessProxy这个bean的时候,ProxyFactoryBean类中指定了,proxyInterfaces参数。这里把他配置了IBaseCalculate接口。因为在项目开发过程中,往往业务类都会有对应的接口,以方便利用IOC解耦。但Spring AOP却也能支持没有接口的代理。这就是为什么需要导入cglib.jar包了。看过spring的源码,知道在目标切入对象如果有实现接口,spring会默认使用jdk动态代理来实现代理类。如果没有接口,则会通过cglib来实现代理类。


  这个业务类现在有 前置通知,后置通知,环绕三个通知同时作用,可能以及更多的通知进行作用。那么这些通知的执行顺序是怎么样的?就这个例子而言,同时实现了三个通知。在例 子xml中,则显示执行before通知,然后执行around的前处理,执行切点方法,再执行return处理。最后执行around的后处理。经过测 试,知道spring 处理顺序是按照xml配置顺序依次处理通知,以队列的方式存放前通知,以压栈的方式存放后通知。所以是前通知依次执行,后通知到切入点执行完之后,从栈里 在后进先出的形式把后通知执行。


  在实现过程中发现通知执行对应目标对象的整个类中的方法,如何精确到某个方法,则需要定义一个切点匹配的方式:spring提供了方法名匹配或正则方式来匹配。然后通过DefaultPointcutAdvisor来包装通知,指定切点。


 使用接口方式配置起来,可见代码还是非常的厚重的,定义一个切面就要定义一个切面类,然而切面类中,就一个通知方法,着实没有必要。所以Spring提供了,依赖aspectj的schema配置和基于aspectj 注解方式。这两种方式非常简单方便使用,也是项目中普遍的使用方式。

相关文章
|
24天前
|
XML Java 数据格式
探索Spring之利剑:ApplicationContext接口
本文深入介绍了Spring框架中的核心接口ApplicationContext,解释了其作为应用容器的功能,包括事件发布、国际化支持等,并通过基于XML和注解的配置示例展示了如何使用ApplicationContext管理Bean实例。
48 6
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
87 2
|
2月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
1月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
54 2
|
2月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
71 9
|
3月前
|
存储 安全 Java
|
3月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
194 5
|
4月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
AOP(面向切面编程)能够帮助我们在不修改现有代码的前提下,为应用程序添加新的功能或行为。Micronaut框架中的AOP模块通过动态代理机制实现了这一目标。AOP将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,提高模块化程度。在Micronaut中,带有特定注解的类会在启动时生成代理对象,在运行时拦截方法调用并执行额外逻辑。例如,可以通过创建切面类并在目标类上添加注解来记录方法调用信息,从而在不侵入原有代码的情况下增强应用功能,提高代码的可维护性和可扩展性。
84 1
|
2月前
|
安全 Java 编译器
什么是AOP面向切面编程?怎么简单理解?
本文介绍了面向切面编程(AOP)的基本概念和原理,解释了如何通过分离横切关注点(如日志、事务管理等)来增强代码的模块化和可维护性。AOP的核心概念包括切面、连接点、切入点、通知和织入。文章还提供了一个使用Spring AOP的简单示例,展示了如何定义和应用切面。
191 1
什么是AOP面向切面编程?怎么简单理解?
|
2月前
|
XML Java 开发者
论面向方面的编程技术及其应用(AOP)
【11月更文挑战第2天】随着软件系统的规模和复杂度不断增加,传统的面向过程编程和面向对象编程(OOP)在应对横切关注点(如日志记录、事务管理、安全性检查等)时显得力不从心。面向方面的编程(Aspect-Oriented Programming,简称AOP)作为一种新的编程范式,通过将横切关注点与业务逻辑分离,提高了代码的可维护性、可重用性和可读性。本文首先概述了AOP的基本概念和技术原理,然后结合一个实际项目,详细阐述了在项目实践中使用AOP技术开发的具体步骤,最后分析了使用AOP的原因、开发过程中存在的问题及所使用的技术带来的实际应用效果。
72 5