• Spring之AOP


    一、AOP关键名词

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

    目标(Target):被通知(被代理)的对象
    注1:完成具体的业务逻辑

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

    代理(Proxy):将通知应用到目标对象后创建的对象(代理=目标+通知),
    例子:外科医生+护士
    注3:只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的

    切入点(Pointcut):多个连接点的集合,定义了通知应该应用到那些连接点。
    (也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序)

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

    二、AOP六大工具类

    1、工具类准备

    工具类org.springframework.aop.framework.ProxyFactoryBean用来创建一个代理对象,在一般情况下它需要注入以下三个属性:
    proxyInterfaces:代理应该实现的接口列表(List)
    interceptorNames:需要应用到目标对象上的通知Bean的名字。(List)
    target:目标对象 (Object)

    PriceException.java

    package com.xlb.aop.exception;
    
    public class PriceException extends RuntimeException {
    
    	public PriceException() {
    		super();
    	}
    
    	public PriceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
    		super(message, cause, enableSuppression, writableStackTrace);
    	}
    
    	public PriceException(String message, Throwable cause) {
    		super(message, cause);
    	}
    
    	public PriceException(String message) {
    		super(message);
    	}
    
    	public PriceException(Throwable cause) {
    		super(cause);
    	}
    	
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    一个接口用来测试BookBiz.java

    package com.xlb.aop.biz;
    
    public interface BookBiz {
    	// 购书
    	public boolean buy(String userName, String bookName, Double price);
    
    	// 发表书评
    	public void comment(String userName, String comments);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    实现接口类(目标)
    BookBizImpl.java

    package com.xlb.aop.biz.impl;
    
    import com.xlb.aop.biz.BookBiz;
    import com.xlb.aop.exception.PriceException;
    
    public class BookBizImpl implements BookBiz {
    
    	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);
    	}
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    2、前置通知

    实现MethodBeforeAdvice接口在买书评论前加入系统日志

    首先我们建一个类实现前置通知的接口,然后我们可以拿到目标对象的类名,还可以拿到当前调用的方法,还可以拿到当期调用方法传递的参数,然后我们在配置到spring-context.xml里面,首先配置目标对象,在配置前置通知对象,然后配置代理对象(代理对象=目标+通知)在配置代理对象时,我们需要在里面配置目标,还要配置实现目标对象的接口列表,还要配置实现指定通知对象,最后写测试类

    实现接口类 MyMethodBeforeAdvice.java

    
    ```java
    package com.xlb.aop.advice;
    
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    import org.springframework.aop.MethodBeforeAdvice;
    
    /**
     * 前置通知
     * 	买书、评论前添加系统日志
     * @author Administrator
     *
     */
    public class MyMethodBeforeAdvice implements MethodBeforeAdvice{
    
    	
    	@Override
    	public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
    		//目标的对象的类名
    		String clzName = arg2.getClass().getName();
    		//点前调用的方法是
    		String methodName = arg0.getName();
    		//点前调用方法所用的参数
    		String args = Arrays.toString(arg1);
    		System.out.println("【系统日志】:"+clzName+"."+methodName+"被调用,传递的参数"+args);
    	}
    	
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    配置文件

    <!-- 目标对象 -->
    	<bean class="com.xlb.aop.biz.impl.BookBizImpl" id="bookBiz"></bean>
    	<!-- 前置通知 -->
    	<bean class="com.xlb.aop.advice.MyMethodBeforeAdvice" id="myBefore"></bean>
    	<!-- 代理对象=目标+通知 -->
    	<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy">
    		<!-- 目标 -->
    		<property name="target" ref="bookBiz"></property>
    		<!-- 目标对象所实现的接口列表 -->
    		<property  name="proxyInterfaces">
    			<!-- 接口多实现 -->
    			<list>
    				<value>com.xlb.aop.biz.BookBiz</value>
    			</list>
    		</property>
    		<!-- 通知:正式调用目标方法的时候,会调用下面指定的通知 -->
    		<property name="interceptorNames">
    			<list>
    				<value>myBefore</value>
    			</list>
    		</property>
    	</bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    测试类Demo1.java
    首先我们测试目标对象

    package com.xlb.aop.text;
    
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.xlb.aop.biz.BookBiz;
    
    public class Demo1 {
    
    	@SuppressWarnings("resource")
    	public static void main(String[] args) {
    		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
    		//拿到目标对象
    		BookBiz bean =(BookBiz)context.getBean("bookBiz");
    		bean.buy("老王", "jpm", 6.6d);
    		bean.comment("老王", "ABC....");
    	}
    	
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    结果
    在这里插入图片描述
    然后我们在实现业务类

    package com.xlb.aop.text;
    
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.xlb.aop.biz.BookBiz;
    
    public class Demo1 {
    
    	@SuppressWarnings("resource")
    	public static void main(String[] args) {
    		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
    		//调用业务类
    		BookBiz bean =(BookBiz)context.getBean("bookProxy");
    		bean.buy("老王", "jpm", 6.6d);
    		bean.comment("老王", "ABC....");
    	}
    	
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    结果
    在这里插入图片描述

    3、后置通知

    实现AfterReturningAdvice接口然后实现买书返利功能

    后置通知跟前置通知差不多,不过多了一个返回值。
    接口类
    MyAfterReturningAdvice.java

    package com.xlb.aop.advice;
    
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    import org.springframework.aop.AfterReturningAdvice;
    
    public class MyAfterReturningAdvice implements AfterReturningAdvice{
    
    	@Override
    	public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
    		//目标的对象的类名
    		String clzName = arg3.getClass().getName();
    		//点前调用的方法是
    		String methodName = arg1.getName();
    		//点前调用方法所用的参数
    		String args = Arrays.toString(arg2);
    		//
    		System.out.println("【买书返利日志】:"+clzName+"."+methodName+"被调用,传递的参数"+args+";目标对象方法返回值为:"+arg0);
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    配置文件:

    <!-- 目标对象 -->
    	<bean class="com.xlb.aop.biz.impl.BookBizImpl" id="bookBiz"></bean>
    	<!-- 前置通知 -->
    	<bean class="com.xlb.aop.advice.MyMethodBeforeAdvice" id="myBefore"></bean>
    	<!-- 后置通知 -->
    	<bean class="com.xlb.aop.advice.MyAfterReturningAdvice" id="myAfter"></bean>
    	<!-- 代理对象=目标+通知 -->
    	<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy">
    		<!-- 目标 -->
    		<property name="target" ref="bookBiz"></property>
    		<!-- 目标对象所实现的接口列表 -->
    		<property  name="proxyInterfaces">
    			<!-- 接口多实现 -->
    			<list>
    				<value>com.xlb.aop.biz.BookBiz</value>
    			</list>
    		</property>
    		<!-- 通知:正式调用目标方法的时候,会调用下面指定的通知 -->
    		<property name="interceptorNames">
    			<list>
    				<value>myBefore</value>
    				<value>myAfter</value>
    			</list>
    		</property>
    	</bean>
    	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    测试类不变测试结果:
    在这里插入图片描述

    4、环绕通知(前置+后置)

    实现MethodInterceptor接口,类似拦截器,会包括切入点,目标类前后都会执行代码

    因为我们的环绕通知=前置通知+后置通知,所以理应然我们可以拿到其两个通知所有对象

    接口类:MyMethodInterceptor.java

    package com.xlb.aop.advice;
    
    import java.util.Arrays;
    
    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
    
    /**
     * 环绕通知=前置通知+后置通知
     * @author Administrator
     *
     */
    public class MyMethodInterceptor implements MethodInterceptor{
    
    	@Override
    	public Object invoke(MethodInvocation arg0) throws Throwable {
    		//目标的对象的类名
    		String clzName = arg0.getThis().getClass().getName();
    		//点前调用的方法是
    		String methodName = arg0.getMethod().getName();
    		//点前调用方法所用的参数
    		String args = Arrays.toString(arg0.getArguments());
    		//方法的返回值  执行目标方法   指调用bookBiz。buy(张三,jpm,6.6)
    		Object proceed = arg0.proceed();
    		System.out.println("【环绕通知日志】:"+clzName+"."+methodName+"被调用,传递的参数"+args+";目标对象方法返回值为:"+proceed);
    		return proceed;
    	}
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    配置

    <!-- 目标对象 -->
    	<bean class="com.xlb.aop.biz.impl.BookBizImpl" id="bookBiz"></bean>
    	<!-- 前置通知 -->
    	<bean class="com.xlb.aop.advice.MyMethodBeforeAdvice" id="myBefore"></bean>
    	<!-- 后置通知 -->
    	<bean class="com.xlb.aop.advice.MyAfterReturningAdvice" id="myAfter"></bean>
    	<!-- 环绕通知 -->
    	<bean class="com.xlb.aop.advice.MyMethodInterceptor" id="myMethod"></bean>
    	<!-- 代理对象=目标+通知 -->
    	<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy">
    		<!-- 目标 -->
    		<property name="target" ref="bookBiz"></property>
    		<!-- 目标对象所实现的接口列表 -->
    		<property  name="proxyInterfaces">
    			<!-- 接口多实现 -->
    			<list>
    				<value>com.xlb.aop.biz.BookBiz</value>
    			</list>
    		</property>
    		<!-- 通知:正式调用目标方法的时候,会调用下面指定的通知 -->
    		<property name="interceptorNames">
    			<list>
    				<value>myBefore</value>
    				<value>myAfter</value>
    				<value>myMethod</value>
    			</list>
    		</property>
    	</bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    结果
    在这里插入图片描述

    5、异常通知

    实现接口,出现异常执行系统提示,然后进行处理。价格异常为例
    关于过滤通知
    相较于前置通知、后置通知、环绕通知有一盒非常大的区别
    前面三大通知都需要实现其中的方法
    环绕通知则不需要,但是,它的方法名就是固定的;

    接口类MyThrowsAdvice.java

    **package com.xlb.aop.advice;
    
    import org.springframework.aop.ThrowsAdvice;
    
    import com.xlb.aop.exception.PriceException;
    
    /**
     * 关于过滤通知
     * 	相较于前置通知、后置通知、环绕通知有一盒非常大的区别
     * 	前面三大通知都需要实现其中的方法
     * 	环绕通知则不需要,但是,它的方法名就是固定的;
     * @author Administrator
     */
    public class MyThrowsAdvice implements ThrowsAdvice{
    
    	public void afterThrowing(PriceException p) {
    		System.out.println("【异常通知】:当前价格发生异常,那么执行此处代码块!!!");
    	}
    	
    }
    **
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    配置文件

    <!-- 目标对象 -->
    	<bean class="com.xlb.aop.biz.impl.BookBizImpl" id="bookBiz"></bean>
    	<!-- 前置通知 -->
    	<bean class="com.xlb.aop.advice.MyMethodBeforeAdvice" id="myBefore"></bean>
    	<!-- 后置通知 -->
    	<bean class="com.xlb.aop.advice.MyAfterReturningAdvice" id="myAfter"></bean>
    	<!-- 环绕通知 -->
    	<bean class="com.xlb.aop.advice.MyMethodInterceptor" id="myMethod"></bean>
    	<!-- 异常通知 -->
    	<bean class="com.xlb.aop.advice.MyThrowsAdvice" id="myThrows"></bean>
    	<!-- 代理对象=目标+通知 -->
    	<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy">
    		<!-- 目标 -->
    		<property name="target" ref="bookBiz"></property>
    		<!-- 目标对象所实现的接口列表 -->
    		<property  name="proxyInterfaces">
    			<!-- 接口多实现 -->
    			<list>
    				<value>com.xlb.aop.biz.BookBiz</value>
    			</list>
    		</property>
    		<!-- 通知:正式调用目标方法的时候,会调用下面指定的通知 -->
    		<property name="interceptorNames">
    			<list>
    				<value>myBefore</value>
    				<value>myAfter</value>
    				<value>myMethod</value>
    				<value>myThrows</value>
    			</list>
    		</property>
    	</bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    测试类改一下价格

    package com.xlb.aop.text;
    
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.xlb.aop.biz.BookBiz;
    
    public class Demo1 {
    
    	@SuppressWarnings("resource")
    	public static void main(String[] args) {
    		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
    		//拿到目标对象
    		//BookBiz bean =(BookBiz)context.getBean("bookBiz");
    		//调用业务类
    		BookBiz bean =(BookBiz)context.getBean("bookProxy");
    		bean.buy("老王", "jpm", -6.6d);
    		bean.comment("老王", "ABC....");
    	}
    	
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    结果
    在这里插入图片描述

    6、过滤通知(适配器)

    配置Spring自带的实现类,处理买书返利bug
    给后置通知添加一个过滤功能,只有买书返利,评论不返利

    配置

    <!-- 目标对象 -->
    	<bean class="com.xlb.aop.biz.impl.BookBizImpl" id="bookBiz"></bean>
    	<!-- 前置通知 -->
    	<bean class="com.xlb.aop.advice.MyMethodBeforeAdvice" id="myBefore"></bean>
    	<!-- 后置通知 -->
    	<bean class="com.xlb.aop.advice.MyAfterReturningAdvice" id="myAfter"></bean>
    	<!-- 环绕通知 -->
    	<bean class="com.xlb.aop.advice.MyMethodInterceptor" id="myMethod"></bean>
    	<!-- 异常通知 -->
    	<bean class="com.xlb.aop.advice.MyThrowsAdvice" id="myThrows"></bean>
    	<!-- 过滤通知:给后置通知添加一个过滤功能,只有买书返利,评论不返利 -->
    	<bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="myafterPlus">
    		<property name="advice" ref="myAfter"></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.xlb.aop.biz.BookBiz</value>
    			</list>
    		</property>
    		<!-- 通知:正式调用目标方法的时候,会调用下面指定的通知 -->
    		<property name="interceptorNames">
    			<list>
    				<value>myBefore</value>
    				<!-- <value>myAfter</value> -->
    				<value>myafterPlus</value>
    				<value>myMethod</value>
    				<value>myThrows</value>
    			</list>
    		</property>
    	</bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    结果
    在这里插入图片描述

    三、AOP总结

    AOP面向切面编程
    主要作用:将核心的业务功能与非核心的业务功能进行分离,将核心的业务功能写到目标对象中,将非核心的业务功能写到通知中
    核心业务功能指:增删改查
    非核心:日志等
    专业名称:通知、连接点、目标对象、切入点、代理、适配器
    日常开发通常应用场景:
    事物管理、日志
    事物的开启:前置通知
    事物的提交:后置通知
    事物的回滚:异常通知

  • 相关阅读:
    ESP32 ESP-IDF TFT-LCD(ST7735 128x160) LVGL演示
    专业综合课程设计 - 优阅书城项目(第一版)
    windows升级新版本mysql
    创新趋势 | SaaS增长新趋势:产品驱动增长PLG(上)
    Java-递归算法简要概括总结
    Iceberg Flink FLIP-27实现
    “淘宝拍立淘图片搜索接口:轻松找到同款商品!
    【DETR 论文解读】End-to-End Object Detection with Transformer
    BACnet/IP网关如何采集楼宇集中控制系统数据
    客户端负载均衡_什么是负载均衡
  • 原文地址:https://blog.csdn.net/qq_63531917/article/details/126216011