Spring系列(二)之AOP的特性

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Spring系列(二)之AOP的特性

一. AOP简介

AOP是Aspect-Oriented Programming的缩写,即面向切面编程。利用oop思想,可以很好的处理业务流程,但是不能把系统中某些特定的重复性行为封装到模块中。例如,在很多业务中都需要记录操作日志,结果我们不得不在业务流程中嵌入大量的日志记录代码。无论是对业务代码还是对日志记录代码来说,维护都是相当复杂的。由于系统中嵌入了这种大量的与业务无关的其他重复性代码,系统的复杂性、代码的重复性增加了。维护起来会更加复杂。

AOP可以很好解决这个问题,AOP关注的是系统的“截面”,在适当的时候“拦截”程序的执行流程,把程序的预处理和后期处理交给某个拦截器来完成。比如,访问数据库时需要记录日志,如果使用AOP的编程思想,那么在处理业务流程时不必在去考虑记录日志,而是把它交给一个专门的例子记录模块去完成。这样,程序员就可以集中精力去处理业务流程,而不是在实现业务代码时嵌入日志记录代码,实现业务代码与非业务代码的分别维护。在AOP术语中,这称为关注点分离。AOP的常见应用有日志拦截、授权认证、数据库的事务拦截和数据审计等。

当一个方法,对不同的用户的功能要求不满足时,那么需要在此方法的地方就可以出现变化;在这个变化点进行封转,留下一个可扩展的接口,便于后期的维护;

简单来说就是, 将非业务核心代码进行封装

二. AOP中的关键性概念

连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或者异常的抛出.

目标(Target):被通知(被代理)的对象

注1:完成具体的业务逻辑

通知(Advice):在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理)

注2:完成切面编程

代理(Proxy):将通知应用到目标对象后创建的对象(代理=目标+通知),

例子:外科医生+护士

注3:只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的

切入点(Pointcut):多个连接点的集合,定义了通知应该应用到那些连接点。

(也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序)

适配器(Advisor):适配器=通知(Advice)+切入点(Pointcut)

在 Spring 中 AOP 代理使用 JDK 动态代理和 CGLIB 代理来实现,默认如果目标对象是接口,则使用 JDK 动态代理,否则使用 CGLIB 来生成代理类。

三. 前置通知

前置通知(org.springframework.aop.MethodBeforeAdvice):在连接点之前执行的通知()

目标接口

package com.xissl.aop.biz;
public interface IBookBiz {
  // 购书
  public boolean buy(String userName, String bookName, Double price);
  // 发表书评
  public void comment(String userName, String comments);
}

接口实现类

package com.xissl.aop.biz.impl;
import com.xissl.aop.biz.IBookBiz;
import com.xissl.aop.exception.PriceException;
public class BookBizImpl implements IBookBiz {
  public BookBizImpl() {
    super();
  }
  public boolean buy(String userName, String bookName, Double price) {
    // 通过控制台的输出方式模拟购书
    if (null == price || price <= 0) {
      throw new PriceException("book price exception");
    }
    System.out.println(userName + " buy " + bookName + ", spend " + price);
    return true;
  }
  public void comment(String userName, String comments) {
    // 通过控制台的输出方式模拟发表书评
    System.out.println(userName + " say:" + comments);
  }
}

通知

package com.xissl.aop.advice;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.springframework.aop.MethodBeforeAdvice;
/**
 * 买书、评论前加系统日志
 * @author xissl
 *
 */
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
  @Override
  public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
//    在这里,可以获取到目标类的全路径及方法及方法参数,然后就可以将他们写到日志表里去
    String target = arg2.getClass().getName();
    String methodName = arg0.getName();
    String args = Arrays.toString(arg1);
    System.out.println("【前置通知:系统日志】:"+target+"."+methodName+"("+args+")被调用了");
  }
}

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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- aop-->
    <!-- 目标对象-->
    <bean class="com.xissl.aop.biz.impl.BookBizImpl" id="bookBiz"></bean>
    <!-- 通知-->
    <bean class="com.xissl.aop.advice.MyMethodBeforeAdvice" id="methodBeforeAdvice"></bean>
    <!-- 代理-->
    <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy">
        <!-- 配置目标对象-->
        <property name="target" ref="bookBiz"></property>
<!--      配置代理的接口,目标对象的接口  -->
        <property name="proxyInterfaces">
            <list>
                <value>com.xissl.aop.biz.IBookBiz</value>
            </list>
        </property>
<!--        配置通知-->
        <property name="interceptorNames">
            <list>
                <value>methodBeforeAdvice</value>
            </list>
        </property>
    </bean>
</beans>

工具类org.springframework.aop.framework.ProxyFactoryBean用来创建一个代理对象,在一般情况下它需要注入以下三个属性:

proxyInterfaces:代理应该实现的接口列表(List)

interceptorNames:需要应用到目标对象上的通知Bean的名字。(List)

target:目标对象 (Object)

测试

package com.xissl.aop.demo;
import com.xissl.aop.biz.IBookBiz;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Demo01 {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
//        IBookBiz bookBiz = (IBookBiz) context.getBean("bookBiz");
        IBookBiz bookBiz = (IBookBiz) context.getBean("bookProxy");
        bookBiz.buy("yhsb","《大话西游》",39.9d);
        bookBiz.comment("yhsb","尊嘟好看");
    }
}

运行结果:

四. 后置通知

后置通知(org.springframework.aop.AfterReturningAdvice):在连接点正常完成后执行的通知

通知

package com.xissl.aop.advice;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.springframework.aop.AfterReturningAdvice;
/**
 * 买书返利
 * @author xissl
 *
 */
public class MyAfterReturningAdvice implements AfterReturningAdvice {
  @Override
  public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
    String target = arg3.getClass().getName();
    String methodName = arg1.getName();
    String args = Arrays.toString(arg2);
    System.out.println("【后置通知:买书返利】:"+target+"."+methodName+"("+args+")被调用了,"+"该方法被调用后的返回值为:"+arg0);
  }
}

配置到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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- aop-->
    <!-- 目标对象-->
    <bean class="com.xissl.aop.biz.impl.BookBizImpl" id="bookBiz"></bean>
    <!-- 前置通知-->
    <bean class="com.xissl.aop.advice.MyMethodBeforeAdvice" id="methodBeforeAdvice"></bean>
<!--    后置通知-->
    <bean class="com.xissl.aop.advice.MyAfterReturningAdvice" id="myAfterReturningAdvice"></bean>
    <!-- 代理-->
    <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy">
        <!-- 配置目标对象-->
        <property name="target" ref="bookBiz"></property>
<!--      配置代理的接口,目标对象的接口  -->
        <property name="proxyInterfaces">
            <list>
                <value>com.xissl.aop.biz.IBookBiz</value>
            </list>
        </property>
<!--        配置通知-->
        <property name="interceptorNames">
            <list>
                <value>methodBeforeAdvice</value><!-- 前置通知-->
                <value>myAfterReturningAdvice</value><!-- 后置通知-->
            </list>
        </property>
    </bean>
</beans>

运行结果:

五. 环绕通知

环绕通知(org.aopalliance.intercept.MethodInterceptor):包围一个连接点的通知,最大特点是可以修改返回值,由于它在方法前后都加入了自己的逻辑代码,因此功能异常强大。它通过MethodInvocation.proceed()来调用目标方法(甚至可以不调用,这样目标方法就不会执行)

通知

package com.xissl.aop.advice;
import java.util.Arrays;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
 * 环绕通知
 *  包含了前置和后置通知
 * 
 * @author xissl
 *
 */
public class MyMethodInterceptor implements MethodInterceptor {
  @Override
  public Object invoke(MethodInvocation arg0) throws Throwable {
    String target = arg0.getThis().getClass().getName();
    String methodName = arg0.getMethod().getName();
    String args = Arrays.toString(arg0.getArguments());
    System.out.println("【环绕通知调用前:】:"+target+"."+methodName+"("+args+")被调用了");
//    arg0.proceed()就是目标对象的方法
    Object proceed = arg0.proceed();
    System.out.println("【环绕通知调用后:】:该方法被调用后的返回值为:"+proceed);
    return proceed;
  }
}

配置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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- aop-->
    <!-- 目标对象-->
    <bean class="com.xissl.aop.biz.impl.BookBizImpl" id="bookBiz"></bean>
    <!-- 前置通知-->
    <bean class="com.xissl.aop.advice.MyMethodBeforeAdvice" id="methodBeforeAdvice"></bean>
<!--    后置通知-->
    <bean class="com.xissl.aop.advice.MyAfterReturningAdvice" id="myAfterReturningAdvice"></bean>
<!--    环绕通知-->
    <bean class="com.xissl.aop.advice.MyMethodInterceptor" id="myMethodInterceptor"></bean>
    <!-- 代理-->
    <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy">
        <!-- 配置目标对象-->
        <property name="target" ref="bookBiz"></property>
<!--      配置代理的接口,目标对象的接口  -->
        <property name="proxyInterfaces">
            <list>
                <value>com.xissl.aop.biz.IBookBiz</value>
            </list>
        </property>
<!--        配置通知-->
        <property name="interceptorNames">
            <list>
                <value>methodBeforeAdvice</value><!-- 前置通知-->
                <value>myAfterReturningAdvice</value><!-- 后置通知-->
                <value>myMethodInterceptor</value><!-- 环绕通知-->
            </list>
        </property>
    </bean>
</beans>

运行结果:

六. 异常通知

异常通知(org.springframework.aop.ThrowsAdvice):这个通知会在方法抛出异常退出时执行

通知

package com.xissl.aop.advice;
import org.springframework.aop.ThrowsAdvice;
import com.xissl.aop.exception.PriceException;
/**
 * 出现异常执行系统提示,然后进行处理。价格异常为例
 * @author xissl
 *
 */
public class MyThrowsAdvice implements ThrowsAdvice {
  public void afterThrowing(PriceException ex) {
    System.out.println("【异常通知】:当价格发生异常,那么执行此处代码块!!!");
  }
}

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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- aop-->
    <!-- 目标对象-->
    <bean class="com.xissl.aop.biz.impl.BookBizImpl" id="bookBiz"></bean>
    <!-- 前置通知-->
    <bean class="com.xissl.aop.advice.MyMethodBeforeAdvice" id="methodBeforeAdvice"></bean>
<!--    后置通知-->
    <bean class="com.xissl.aop.advice.MyAfterReturningAdvice" id="myAfterReturningAdvice"></bean>
<!--    环绕通知-->
    <bean class="com.xissl.aop.advice.MyMethodInterceptor" id="myMethodInterceptor"></bean>
<!--    异常通知-->
    <bean class="com.xissl.aop.advice.MyThrowsAdvice" id="myThrowsAdvice"></bean>
    <!-- 代理-->
    <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy">
        <!-- 配置目标对象-->
        <property name="target" ref="bookBiz"></property>
<!--      配置代理的接口,目标对象的接口  -->
        <property name="proxyInterfaces">
            <list>
                <value>com.xissl.aop.biz.IBookBiz</value>
            </list>
        </property>
<!--        配置通知-->
        <property name="interceptorNames">
            <list>
                <value>methodBeforeAdvice</value><!-- 前置通知-->
                <value>myAfterReturningAdvice</value><!-- 后置通知-->
                <value>myMethodInterceptor</value><!-- 环绕通知-->
                <value>myThrowsAdvice</value><!-- 异常通知-->
            </list>
        </property>
    </bean>
</beans>

测试

package com.xissl.aop.demo;
        import com.xissl.aop.biz.IBookBiz;
        import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Demo01 {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
//        IBookBiz bookBiz = (IBookBiz) context.getBean("bookBiz");
        IBookBiz bookBiz = (IBookBiz) context.getBean("bookProxy");
        bookBiz.buy("yhsb","《大话西游》",-39.9d);
        bookBiz.comment("yhsb","尊嘟好看");
    }
}

运行结果:

七. 过滤通知

过滤后置通知的重复

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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- aop-->
    <!-- 目标对象-->
    <bean class="com.xissl.aop.biz.impl.BookBizImpl" id="bookBiz"></bean>
    <!-- 前置通知-->
    <bean class="com.xissl.aop.advice.MyMethodBeforeAdvice" id="methodBeforeAdvice"></bean>
<!--    后置通知-->
    <bean class="com.xissl.aop.advice.MyAfterReturningAdvice" id="myAfterReturningAdvice"></bean>
<!--    环绕通知-->
    <bean class="com.xissl.aop.advice.MyMethodInterceptor" id="myMethodInterceptor"></bean>
<!--    异常通知-->
    <bean class="com.xissl.aop.advice.MyThrowsAdvice" id="myThrowsAdvice"></bean>
<!--    过滤通知-->
    <bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="methodPointcutAdvisor">
        <property name="advice" ref="myAfterReturningAdvice"></property>
        <property name="pattern" value=".*buy"></property>
    </bean>
    <!-- 代理-->
    <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy">
        <!-- 配置目标对象-->
        <property name="target" ref="bookBiz"></property>
<!--      配置代理的接口,目标对象的接口  -->
        <property name="proxyInterfaces">
            <list>
                <value>com.xissl.aop.biz.IBookBiz</value>
            </list>
        </property>
<!--        配置通知-->
        <property name="interceptorNames">
            <list>
                <value>methodBeforeAdvice</value><!-- 前置通知-->
<!--                <value>myAfterReturningAdvice</value>&lt;!&ndash; 后置通知&ndash;&gt;-->
                <value>myMethodInterceptor</value><!-- 环绕通知-->
                <value>myThrowsAdvice</value><!-- 异常通知-->
                <value>methodPointcutAdvisor</value><!-- 过滤通知-->
            </list>
        </property>
    </bean>
</beans>

运行结果:

相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
相关文章
|
3月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
448 0
|
2月前
|
XML Java 数据格式
《深入理解Spring》:AOP面向切面编程深度解析
Spring AOP通过代理模式实现面向切面编程,将日志、事务等横切关注点与业务逻辑分离。支持注解、XML和编程式配置,提供五种通知类型及丰富切点表达式,助力构建高内聚、低耦合的可维护系统。
|
7月前
|
监控 安全 Java
Spring AOP实现原理
本内容主要介绍了Spring AOP的核心概念、实现机制及代理生成流程。涵盖切面(Aspect)、连接点(Join Point)、通知(Advice)、切点(Pointcut)等关键概念,解析了JDK动态代理与CGLIB代理的原理及对比,并深入探讨了通知执行链路和责任链模式的应用。同时,详细分析了AspectJ注解驱动的AOP解析过程,包括切面识别、切点表达式匹配及通知适配为Advice的机制,帮助理解Spring AOP的工作原理与实现细节。
1141 13
|
4月前
|
人工智能 监控 安全
如何快速上手【Spring AOP】?核心应用实战(上篇)
哈喽大家好吖~欢迎来到Spring AOP系列教程的上篇 - 应用篇。在本篇,我们将专注于Spring AOP的实际应用,通过具体的代码示例和场景分析,帮助大家掌握AOP的使用方法和技巧。而在后续的下篇中,我们将深入探讨Spring AOP的实现原理和底层机制。 AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的核心特性之一,它能够帮助我们解决横切关注点(如日志记录、性能统计、安全控制、事务管理等)的问题,提高代码的模块化程度和复用性。
|
10月前
|
负载均衡 Java API
Spring Cloud是什么及基本特性都有哪些?
Spring Cloud 是用于构建健壮云应用的框架,包含多个子项目。其核心组件如Eureka(服务注册与发现)、Hystrix(熔断器)、Ribbon(负载均衡)等,帮助开发者快速实现微服务架构。Spring Cloud 提供了服务注册与发现、分布式配置、路由、断路器等功能,简化了微服务开发与管理。本文将重点介绍服务注册与发现及分布式配置两大特性。
551 5
|
4月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
11月前
|
XML Java 开发者
Spring Boot中的AOP实现
Spring AOP(面向切面编程)允许开发者在不修改原有业务逻辑的情况下增强功能,基于代理模式拦截和增强方法调用。Spring Boot通过集成Spring AOP和AspectJ简化了AOP的使用,只需添加依赖并定义切面类。关键概念包括切面、通知和切点。切面类使用`@Aspect`和`@Component`注解标注,通知定义切面行为,切点定义应用位置。Spring Boot自动检测并创建代理对象,支持JDK动态代理和CGLIB代理。通过源码分析可深入了解其实现细节,优化应用功能。
532 6
|
5月前
|
Java 数据库连接 API
Java 8 + 特性及 Spring Boot 与 Hibernate 等最新技术的实操内容详解
本内容涵盖Java 8+核心语法、Spring Boot与Hibernate实操,按考试考点分类整理,含技术详解与代码示例,助力掌握最新Java技术与应用。
174 2
|
10月前
|
XML Java 测试技术
Spring AOP—通知类型 和 切入点表达式 万字详解(通俗易懂)
Spring 第五节 AOP——切入点表达式 万字详解!
750 25