小工具知识网

当前位置: 首页 > 行业知识 > 4 Spring AOP

行业知识

4 Spring AOP

2026-11-03 09:07:14 来源:互联网转载

目录

AOP 简介

传统开发模式

先来看一个需求

解决方案

AOP 图示

Spring 启用 AspectJ 基于 xml 配置

创建 pom.xml

创建 UserService 借口和 UserServiceImpl实现类

创建 LogAdvice 日志通知

创建 log4j.properties

重点:创建 spring-context-xml.xml 配置

创建 AopTest_Xml 测试类

Spring 启用 AspectJ 基于注解配置

改造实现类 UserServiceImpl ,加上 @Service 注解

改造 LogAdvice 日志通知,加上 @Component 和 @Aspect 注解

创建 spring-context-anno.xml 注解配置

创建 AopTest_Anno 测试类


AOP 简介

  1. AOP(Aspect-Oriented Programming, 面向切面编程): 是一种新的方法论,是对传统 OOP(Object-Oriented Programming,面向对象编程) 的补充。
  2. AOP 的主要编程对象是切面(aspect),而切面是模块化的横切关注点。
  3. 在应用 AOP 编程时, 仍然需要定义公共功能,但可以明确的定义这个功能在哪里, 以什么方式应用, 并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的对象(切面)里。
  4. AOP 的好处:(1) 每个事物逻辑位于一个位置, 代码不分散,便于维护和升级;(2) 业务模块更简洁,只包含核心业务代码。

传统开发模式

先来看一个需求

下面是分别定义了一个接口和它的实现类,功能非常简单,实现正整数的加、减、乘、除操作,代码如下:

CalculateService.java

package com.springdemo.service;/** * Created by qfxl on 2024/05/03. */public interface CalculateService {    int add(int a, int b);    int subtract(int a, int b);    int times(int a, int b);    int pide(int a, int b);}

CalculateServiceImpl.java

package com.springdemo.service.impl;import com.springdemo.service.CalculateService;import org.springframework.stereotype.Component;/** * Created by qfxl on 2024/05/03. */@Componentpublic class CalculateServiceImpl implements CalculateService {    @Override    public int add(int a, int b) {        return a + b;    }    @Override    public int subtract(int a, int b) {        return a - b;    }    @Override    public int times(int a, int b) {        return a * b;    }    @Override    public int pide(int a, int b) {        return a / b;    }}

我们的额外需求是:

需求1-做日志:在程序执行期间追踪正在发生的活动

需求2-做验证:希望计算器只能处理正数的运算

如果我们采用传统的方式,只能在每个方法里面先做参数的验证,然后再进行日志记录,这样不仅代码,冗余,而且不利于后期的维护,每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。

以日志需求为例, 只是为了满足这个单一需求, 就不得不在多个模块(方法)里多次重复相同的日志代码。 如果日志需求发生变化, 必须修改所有模块。

类似于这样:

@Componentpublic class CalculateServiceImpl implements CalculateService {    private static final Logger LOGGER = Logger.getLogger(CalculateServiceImpl.class);    @Override    public int add(int a, int b) {    //先进行参数有效性的验证    if(a  

可以看出,在 add 方法中插入了验证和日志的功能,但是,此方法的核心只是做加法操作,我们在业务实现时还要考虑一些共性的东西,这显然是不好的一种方式,那如何在不改变业务类实现的基础上,动态添加验证、日志功能呢?

解决方案

一种比较好的方式是:使用动态代理解决上述问题

代理设计模式的原理:

使用一个代理将对象包装起来,然后用该代理对象取代原始对象。

任何对原始对象的调用都要通过代理。 代理对象决定是否以及何时将方法调用转到原始对象上。

关于使用程序编写动态代理代码,自己可以去查看相关文档,我们这里不做介绍。

实际上,Spring 中的 AOP 就是基于动态代理技术来实现的,下面我们来详细讲解 AOP 。

AOP 图示

我们把上面的需求以图的方式呈现出来,可以清晰地看到共性的操作,把共性的地方抽取出来,模块化成切面,然后让这个切面按我们的预期进行工作,这就是 AOP 的作用。

上面的图示中,我们抽取出来共性,也就是验证和日志功能,但是,光有这个共性,还是不能够模块化成切面的,它还需要其它的辅助元素才能成为一个切面。具体如何使用 AOP 完成上述的需求,Spring 给出了具体的方案,下面让我们来看看详细的案例。

Spring 启用 AspectJ 基于 xml 配置

在演示案例前我们先来了解些 AOP 术语:

  • Aspect 中文意思:切面,它只是一个统称,一种名词,它由 Advice 和 PointCut 组成;
  • Advice 中文意思:通知,它是切面的代码载体,可以想象成一个封装好的 JAVA 类,把共性代码封装在这里;
  • PointCut 中文意思:切点,它是用来匹配目标方法的一段配置信息,可以使用注解也可以使用 XML 来配置,它由多个JoinPoint 组成;
  • JoinPoint 中文意思:连接点,它是指匹配到目标方法之后执行 Advice 的时机,共有如下5种:
    • before: 在目标方法执行之前
    • after:在目标方法之后
    • afterThrowing: 在目标方法的 catch 块中
    • afterReturn: 在目标方法的 finally 块中
    • around: 环绕,相当于是上面4种的一个综合体
  • Weave 中文织入:它是一个过程,就是切面织入到目标对象方法的过程,它的原理是基于 代理 来实现的,Spring 目前支持两种代理:
    • 基于 JDK 自带的动态代理类,java.lang.reflect.Proxy,这是默认的情况,但是这个实现有一个前提条件是目标对象必需要有实现的接口。
    • 基于第三方组件 CGLIB 来生成我们的代理类,它不需要目标对象实现任何接口。

    创建 pom.xml

        4.0.0    com.example    spring-aop-demo    1.0.0-SNAPSHOT            UTF-8        1.8        1.8        5.1.5.RELEASE        1.16.20        4.12        1.2.17        1.7.25                                    org.springframework            spring-context            ${spring.version}                            org.springframework            spring-test            ${spring.version}            test                            org.springframework            spring-aspects            ${spring.version}                                    junit            junit            ${junit.version}                                    org.projectlombok            lombok            ${lombok.version}            provided                                            org.slf4j            slf4j-api            ${slf4j.version}                            org.slf4j            slf4j-log4j12            ${slf4j.version}                            org.slf4j            jcl-over-slf4j            ${slf4j.version}                            org.slf4j            jul-to-slf4j            ${slf4j.version}                            log4j            log4j            ${log4j.version}                                                                        org.apache.maven.plugins                maven-compiler-plugin                3.7.0                                    ${java.version}                    ${java.version}                    ${project.build.sourceEncoding}                    true                                        

    创建 UserService 借口和 UserServiceImpl实现类

    package com.example.spring.aop.demo.xml.service;public interface UserService {    public void addUser();    public boolean updateUser();    public int deleteUser(int id);}
    package com.example.spring.aop.demo.xml.service.impl;import com.example.spring.aop.demo.xml.service.UserService;public class UserServiceImpl implements UserService {    @Override    public void addUser() {        System.out.println("addUser:添加用户!!!");    }    @Override    public boolean updateUser() {        System.out.println("updateUser:修改用户!!!");        return true;    }    @Override    public int deleteUser(int id) {        System.out.println("deleteUser:删除用户!!!");        return id;    }}

    创建 LogAdvice 日志通知

    package com.example.spring.aop.demo.xml.advice;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.Arrays;public class LogAdvice {    private static final Logger LOGGER = LoggerFactory.getLogger(LogAdvice.class);    public void beforeMethod(JoinPoint joinPoint) {        LOGGER.trace("前置通知...");        System.out.println("本次要调用的目标对象:" + joinPoint.getTarget());        System.out.println("本次要调用的目标方法名:" + joinPoint.getSignature().getName());        System.out.println("本次要调用的目标方法参数:" + Arrays.toString(joinPoint.getArgs()));    }    public void afterReturn(Object obj) {        LOGGER.trace("目标方法返回后, 返回对象结果是:" + obj);    }    public void afterMethod() {        LOGGER.trace("后置通知...");    }    public Object aroundMethod(ProceedingJoinPoint pjp){        Object result = null;        LOGGER.warn("前置通知!!!");        try {            result = pjp.proceed();        } catch (Throwable e) {//            e.printStackTrace();            LOGGER.warn("异常通知!!!");            throw new RuntimeException(e);        }        LOGGER.warn("后置通知!!!");        return result;    }}

    创建 log4j.properties

    log4j.rootLogger=INFO, console, filelog4j.appender.console=org.apache.log4j.ConsoleAppenderlog4j.appender.console.layout=org.apache.log4j.PatternLayoutlog4j.appender.console.layout.ConversionPattern=%d %p [%c] - %m%nlog4j.appender.file=org.apache.log4j.DailyRollingFileAppenderlog4j.appender.file.File=logs/log.loglog4j.appender.file.layout=org.apache.log4j.PatternLayoutlog4j.appender.A3.MaxFileSize=1024KBlog4j.appender.A3.MaxBackupIndex=10log4j.appender.file.layout.ConversionPattern=%d %p [%c] - %m%n# 便于控制台日志的识别,所以用TRACE日志级别# 基于 xml 配置的日志打印log4j.logger.com.example.spring.aop.demo.xml.advice =TRACE# 基于注解配置的日志打印log4j.logger.com.example.spring.aop.demo.anno.advice =TRACE

    重点:创建 spring-context-xml.xml 配置

                                                                                                                                                                                    

    创建 AopTest_Xml 测试类

    package com.example.spring.aop.demo;import com.example.spring.aop.demo.xml.service.UserService;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class AopTest_Xml {    @Test    public void xmlTest(){        ApplicationContext ac = new ClassPathXmlApplicationContext("spring-context-xml.xml");        UserService userService = (UserService) ac.getBean("userService");        userService.addUser();        System.out.println();        boolean b = userService.updateUser();        System.out.println();        int i = userService.deleteUser(10);        System.out.println();    }}

    测试结果:

    Spring 启用 AspectJ 基于注解配置

    改造实现类 UserServiceImpl ,加上 @Service 注解

    package com.example.spring.aop.demo.anno.service.impl;import com.example.spring.aop.demo.xml.service.UserService;import org.springframework.stereotype.Service;@Service("userService")public class UserServiceImpl implements UserService {    @Override    public void addUser() {        System.out.println("addUser:添加用户!!!");    }    @Override    public boolean updateUser() {        System.out.println("updateUser:修改用户!!!");        return true;    }    @Override    public int deleteUser(int id) {        System.out.println("deleteUser:删除用户!!!");        return id;    }}

    改造 LogAdvice 日志通知,加上 @Component 和 @Aspect 注解

    package com.example.spring.aop.demo.anno.advice;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import java.util.Arrays;@Component@Aspectpublic class LogAdvice {    private static final Logger LOGGER = LoggerFactory.getLogger(LogAdvice.class);    /*     * 声明一个公共切入点,所有的通知都可以使用     *      @Pointcut(value = "execution(* com.example.spring.aop.demo.anno.service.impl.UserServiceImpl.*(..))")     *      等同于 xml 配置里的:     *           */    @Pointcut(value = "execution(* com.example.spring.aop.demo.anno.service.impl.UserServiceImpl.*(..))")    public void log_pc() {    }    @Before("log_pc()")    public void beforeMethod(JoinPoint joinPoint) {        LOGGER.trace("前置通知...");        System.out.println("本次要调用的目标对象:" + joinPoint.getTarget());        System.out.println("本次要调用的目标方法名:" + joinPoint.getSignature().getName());        System.out.println("本次要调用的目标方法参数:" + Arrays.toString(joinPoint.getArgs()));    }    @AfterReturning(value = "log_pc()", returning = "obj")    public void afterReturn(Object obj) {        LOGGER.trace("目标方法返回后, 返回对象结果是:" + obj);    }    @After("log_pc()")    public void afterMethod() {        LOGGER.trace("后置通知...");    }    /*     * 声明一个切入点表达式,此表达式只能当前通知使用     *      @Around("execution(* com.example.spring.aop.demo.anno.service.impl.UserServiceImpl.*(..))")     *      等同于 xml 配置里的:     *           */    @Around("execution(* com.example.spring.aop.demo.anno.service.impl.UserServiceImpl.*(..))")    public Object aroundMethod(ProceedingJoinPoint pjp) {        Object result = null;        LOGGER.warn("前置通知!!!");        try {            result = pjp.proceed();        } catch (Throwable e) {//            e.printStackTrace();            LOGGER.warn("异常通知!!!");            throw new RuntimeException(e);        }        LOGGER.warn("后置通知!!!");        return result;    }}

    创建 spring-context-anno.xml 注解配置

                    

    创建 AopTest_Anno 测试类

    package com.example.spring.aop.demo;import com.example.spring.aop.demo.xml.service.UserService;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:spring-context-anno.xml")public class AopTest_Anno {    @Autowired    private UserService userService;    @Test    public void annoTest() {        userService.addUser();        System.out.println();        boolean b = userService.updateUser();        System.out.println();        int i = userService.deleteUser(10);        System.out.println();    }}

    测试结果:

上一篇:英语发音宝典,How to正确发音there发音指南

下一篇:宝塔面板低版本安装全攻略,轻松上手,一键搞定,助你快速部署!