spring5:AOP操作

Posted _GGBond_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring5:AOP操作相关的知识,希望对你有一定的参考价值。

spring5(五):AOP操作



前言

本博主将用CSDN记录软件开发求学之路上亲身所得与所学的心得与知识,有兴趣的小伙伴可以关注博主!也许一个人独行,可以走的很快,但是一群人结伴而行,才能走的更远!让我们在成长的道路上互相学习,欢迎关注!

一、代理模式

1、场景模拟

(1)声明计算器接口Calculator,包含加减乘除的抽象方法

public interface Calculator 
	int add(int i, int j);
	int sub(int i, int j);
	int mul(int i, int j);
	int div(int i, int j);

(2)创建实现类

public class CalculatorPureImpl implements Calculator 
	@Override
	public int add(int i, int j) 
		int result = i + j;
		System.out.println("方法内部 result = " + result);
		return result;
	
	@Override
	public int sub(int i, int j) 
		int result = i - j;
		System.out.println("方法内部 result = " + result);
		return result;
	
	@Override
	public int mul(int i, int j) 
		int result = i * j;
		System.out.println("方法内部 result = " + result);
		return result;
	
	@Override
	public int div(int i, int j) 
		int result = i / j;
		System.out.println("方法内部 result = " + result);
		return result;
	

(3)创建带日志功能的实现类

public class CalculatorLogImpl implements Calculator 
	@Override
	public int add(int i, int j) 
		System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
		int result = i + j;
		System.out.println("方法内部 result = " + result);
		System.out.println("[日志] add 方法结束了,结果是:" + result);
		return result;
	
	@Override
	public int sub(int i, int j) 
		System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j);
		int result = i - j;
		System.out.println("方法内部 result = " + result);
		System.out.println("[日志] sub 方法结束了,结果是:" + result);
		return result;
	
	@Override
	public int mul(int i, int j) 
		System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j);
		int result = i * j;
		System.out.println("方法内部 result = " + result);
		System.out.println("[日志] mul 方法结束了,结果是:" + result);
		return result;
	
	@Override
	public int div(int i, int j) 
		System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j);
		int result = i / j;
		System.out.println("方法内部 result = " + result);
		System.out.println("[日志] div 方法结束了,结果是:" + result);
		return result;
	

(4)提出问题

现有代码缺陷:
针对带日志功能的实现类,我们发现有如下缺陷: 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力
附加功能分散在各个业务功能方法中,不利于统一维护
解决思路:
解决这两个问题,核心就是:解耦。我们需要把附加功能从业务功能代码中抽取出来。
困难:
解决问题的困难:要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决。 所以需要引入新的技术。

2、代理模式

2.1 概念

①介绍

二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。

⭕ 使用代理前:

⭕ 使用代理后:

②生活中的代理

● 广告商找大明星拍广告需要经过经纪人
● 合作伙伴找大老板谈合作要约见面时间需要经过秘书
● 房产中介是买卖双方的代理

③相关术语

● 代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法。
● 目标:被代理“套用”了非核心逻辑代码的类、对象、方法。

2.2 静态代理

public class CalculatorStaticProxy implements Calculator 
	// 将被代理的目标对象声明为成员变量
	private Calculator target;
	public CalculatorStaticProxy(Calculator target) 
	this.target = target;
	
	@Override
	public int add(int i, int j) 
		// 附加功能由代理类中的代理方法来实现
		System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
		// 通过目标对象来实现核心业务逻辑
		int addResult = target.add(i, j);
		System.out.println("[日志] add 方法结束了,结果是:" + addResult);
		return addResult;
	

静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来
说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代
码,日志功能还是分散的,没有统一管理。
提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理
类来实现。这就需要使用动态代理技术了

2.3 动态代理


生产代理对象的工厂类:

public class ProxyFactory 
    private Object target;
    public ProxyFactory(Object target) 
        this.target = target;
    
    public Object getProxy()
        /**
         * newProxyInstance():创建一个代理实例
         * 其中有三个参数:
         * 1、classLoader:加载动态生成的代理类的类加载器
         * 2、interfaces:目标对象实现的所有接口的class对象所组成的数组
         * 3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接
         口中的抽象方法
         */
        ClassLoader classLoader = target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler invocationHandler = new InvocationHandler() 
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
                /**
                 * proxy:代理对象
                 * method:代理对象需要实现的方法,即其中需要重写的方法
                 * args:method所对应方法的参数
                 */
                Object result = null;
                try 
                    System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args));
                    result = method.invoke(target, args);
                    System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result);
                 catch (Exception e) 
                    e.printStackTrace();
                    System.out.println("[动态代理][日志] "+method.getName()+",异常:"+e.getMessage());
                 finally 
                    System.out.println("[动态代理][日志] "+method.getName()+",方法执行完毕");
                
                return result;
            
        ;
        return Proxy.newProxyInstance(classLoader, interfaces,invocationHandler);
    

测试

@Test
public void testDynamicProxy()
	ProxyFactory factory = new ProxyFactory(new CalculatorLogImpl());
	Calculator proxy = (Calculator) factory.getProxy();
	proxy.div(1,0);
	//proxy.div(1,1);

二、AOP概述

1、什么是 AOP?

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。

⭕ 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能

2、相关术语

⭕ 横切关注点

从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。
这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点,比如上例中CalculatorImpl类中的日志代码是非核心代码,我们把这些非核心代码抽取出来,作为一些附加功能,即横切关注点。
附加功能相对于目标类来说是横切关注点,相对于目标类来说是通知。


⭕ 通知

每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。

  1. 前置通知:在被代理的目标方法前执行
  2. 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)
  3. 异常通知:在被代理的目标方法异常结束后执行(死于非命)
  4. 后置通知:在被代理的目标方法最终结束后执行(盖棺定论)
  5. 环绕通知:使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

⭕ 切面

封装通知方法的类。


⭕目标

被代理的目标对象。

⭕代理

向目标对象应用通知之后创建的代理对象。

⭕连接点

这也是一个纯逻辑概念,不是语法定义的。
把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。


⭕切入点

定位连接点的方式。
每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。
如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句。
SpringAOP 技术可以通过切入点定位到特定的连接点。
切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

3、作用

  1. 简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。
  2. 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。

三、AOP底层原理

1、AOP 底层使用动态代理

有两种情况动态代理:

⭕ 第一种 有接口情况,使用 JDK 动态代理

创建接口实现类代理对象,增强类的方法

⭕ 第二种 没有接口情况,使用 cglib 动态代理
创建子类的代理对象,增强类的方法

2、AOP(JDK 动态代理)

① 使用 JDK 动态代理,使用 Proxy 类里面的方法创建代理对象

② 调用 newProxyInstance() 方法
方法有三个参数:

  1. 第一参数,类加载器
  2. 第二参数,增强方法所在的类,这个类实现的接口,支持多个接口
  3. 第三参数,实现这个接口InvocationHandler,创建代理对象,写增强的部分

2.1 编写 JDK 动态代理代码

① 创建接口,定义方法


public interface UserDao 
    public int add(int a,int b);
    public String update(String id);

② 创建接口实现类,实现方法

public class UserDaoImpl implements UserDao 
    @Override
    public int add(int a, int b) 
        System.out.println("add方法执行了.....");
        return a+b;
    

    @Override
    public String update(String id) 
        System.out.println("update方法执行了.....");
        return id;
    

③ 使用 Proxy 类创建接口代理对象

public class JDKProxy 

    public static void main(String[] args) 
        //创建接口实现类代理对象
        Class[] interfaces = UserDao.class;
//        Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() 
//            @Override
//            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
//                return null;
//            
//        );
        UserDaoImpl userDao = new UserDaoImpl();
        UserDao dao = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
        int result = dao.add(1, 2);
        System.out.println("result:"+result);
    


//创建代理对象代码
class UserDaoProxy implements InvocationHandler 

    //1 把创建的是谁的代理对象,把谁传递过来
    //有参数构造传递
    private Object obj;
    public UserDaoProxy(Object obj) 
        this.obj = obj;
    

    //增强的逻辑
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
        //方法之前
        System.out.println("方法之前执行...."+method.getName()+" :传递的参数..."+ Arrays.toString(args));

        //被增强的方法执行
        Object res = method.invoke(obj, args);

        //方法之后
        System.out.println("方法之后执行...."+obj);
        return res;
    

四、AOP 操作的准备工作

1、AspectJ概述

Spring 框架一般都是基于 AspectJ 实现 AOP 操作
AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJSpirng 框架一起使用,进行 AOP操作

⭕ 基于 AspectJ 实现 AOP 操作

  1. 基于 xml 配置文件实现
  2. 基于注解方式实现

⭕ 在项目工程里面引入 AOP 相关依赖

2、依赖的引入

五、AOP操作(注解版)

1、添加依赖

IOC所需依赖基础上再加入下面依赖即可:

<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aspects</artifactId>
	<version>5.3.1</version>
</dependency>

2、准备被代理的目标资源

接口:

public interface Calculator 
	int add(int i, int j);
	int sub(int i, int j);
	int mul(int i, int j);
	int div(int i, int j);

实现类:

@Component
public class CalculatorPureImpl implements Calculator 
	@Override
	public int add(int i, int j) 
		int result = i + j;
		System.out.println("方法内部 result = " + result);
		return result;
	
	@Override
	public int sub(int i, int j) 
		int result = i - j;
		System.out.println("方法内部 result = " + result);
		return result;
	
	@Override
		public int mul(int i, int j) 
		int result = i * j;
		System.out.println("方法内部 result = " + result);
		return result;
	
	@Override
	public int div(int i, int j) 
		int result = i / j;
		System.out.println("方法内部 result = " + result);
		return result;
	

3、在Spring的配置文件中配置

<!--
基于注解的AOP的实现:
1、将目标对象和切面交给IOC容器管理(注解+扫描)
2、开启AspectJ的自动代理,为目标对象自动生成代理
3、将切面类通过注解@Aspect标识
-->
<context:component-scan base-package="com.atguigu.aop.annotation">
</context:component-scan>
<aop:aspectj-autoproxy />

4、创建切面类并配置

package com.reds.AOPTest;

import org.aspectj.lang.JoinPoint;
import org.aspectj以上是关于spring5:AOP操作的主要内容,如果未能解决你的问题,请参考以下文章

Day381.AOP编程 -Spring5

Spring_11-Spring5总结

[Spring5]AOP底层原理

Spring | Spring5学习笔记 #yyds干货盘点#

Spring5学习——4AOP概念

Spring5 AOP 学习笔记

(c)2006-2024 SYSTEM All Rights Reserved IT常识