IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring中AOP编程本质(动态代理机制)以及前置通知的开发 -> 正文阅读

[Java知识库]Spring中AOP编程本质(动态代理机制)以及前置通知的开发

1. 引言

我们先通过现有业务层存在的问题来引出代理

定义业务接口

public interface UserService {
    void save(String name);
    void delete(String id);
    void update();
    String findAll(String name);
    String findOne(String id);
}

定义业务接口的实现类

// 原始业务逻辑对象
public class UserServiceImpl implements UserService{
    // 开启事务   处理业务   调用dao
    @Override
    public void save(String name) {
        try {
            System.out.println("开启事务");
            System.out.println("处理事务逻辑,调用DAO~~~");
            System.out.println("提交事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
    }

    @Override
    public void delete(String id) {
        try {
            System.out.println("开启事务");
            System.out.println("处理事务逻辑,调用DAO~~~");
            System.out.println("提交事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
    }

    @Override
    public void update() {
        try {
            System.out.println("开启事务");
            System.out.println("处理事务逻辑,调用DAO~~~");
            System.out.println("提交事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
    }

    @Override
    public String findAll(String name) {
        try {
            System.out.println("开启事务");
            System.out.println("处理事务逻辑,调用DAO~~~");
            System.out.println("提交事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
        return name;
    }

    @Override
    public String findOne(String id) {
        try {
            System.out.println("开启事务");
            System.out.println("处理事务逻辑,调用DAO~~~");
            System.out.println("提交事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
        return id;
    }
}

我们发现业务接口的实现类中的每个方法都有着开启事务、提交事务、回滚事务,也就是存在着代码冗余,那么为了解决这个问题,使业务逻辑对象能够更加专注的处理业务逻辑,我们就需要使用代理这种设计模式。

2. 代理

什么是代理

  • 代理指的是java中的一些设计模式

为什么需要代理

  • 很多时候除了当前类能够提供的功能外,我们还需要补充一些额外功能

代理的作用

  • 代理对象可以在目标对象和客户对象之间起到中介的作用,从而为目标对象增添额外的功能

在这里插入图片描述

2.1 静态代理

目标类|对象(target):被代理类称之为目标类|或者被代理的对象的称之为目标对象

开发代理的原则: 代理类和目标类功能一致且实现相同的接口,同时代理类中依赖于目标类对象(调用目标类对象的方法)

开发静态代理类

//静态代理类
//开发原则:代理类和目标类实现相同接口,依赖于真正的目标类
public class UserServiceStaticProxy implements UserService{

    // 依赖原始业务逻辑对象 // Target:目标对象|被代理对象称之为目标对象 原始业务逻辑对象
    private UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void save(String name) {
        try {
            System.out.println("开启事务");
            // 调用原始业务逻辑对象的方法
            userService.save(name);
            System.out.println("提交事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
    }

    @Override
    public void delete(String id) {
        try {
            System.out.println("开启事务");
            // 调用原始业务逻辑对象的方法
            userService.delete(id);
            System.out.println("提交事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
    }

    @Override
    public void update() {
        try {
            System.out.println("开启事务");
            // 调用原始业务逻辑对象的方法
            userService.update();
            System.out.println("提交事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
    }

    @Override
    public String findAll(String name) {
        try {
            System.out.println("开启事务");
            // 调用原始业务逻辑对象的方法
            String all = userService.findAll(name);
            System.out.println("提交事务");
            return all;
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public String findOne(String id) {
        try {
            System.out.println("开启事务");
            // 调用原始业务逻辑对象的方法
            String one = userService.findOne(id);
            System.out.println("提交事务");
            return one;
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
        return null;
    }
}

更改目标实现类

// 原始业务逻辑对象
public class UserServiceImpl implements UserService{
    // 开启事务   处理业务   调用dao
    @Override
    public void save(String name) {
        System.out.println("处理事务逻辑,调用DAO~~~");
    }

    @Override
    public void delete(String id) {
        System.out.println("处理事务逻辑,调用DAO~~~");
    }

    @Override
    public void update() {
        System.out.println("处理事务逻辑,调用DAO~~~");
    }

    @Override
    public String findAll(String name) {
        System.out.println("处理事务逻辑,调用DAO~~~");
        return name;
    }

    @Override
    public String findOne(String id) {
        System.out.println("处理事务逻辑,调用DAO~~~");
        return id;
    }
}

静态文件的编写

<!--管理Service-->
<bean class="staticproxy.UserServiceImpl" id="userService"></bean>

<!--管理Service中proxy-->
<bean class="staticproxy.UserServiceStaticProxy" id="userServiceStaticProxy">
    <!--依赖于真正的业务逻辑对象-->
    <property name="userService" ref="userService"/>
</bean>

调用代理方法

ApplicationContext context = new ClassPathXmlApplicationContext("staticproxy/spring.xml");

UserServiceStaticProxy userServiceStaticProxy = (UserServiceStaticProxy) 
    
context.getBean("userServiceStaticProxy");

userServiceStaticProxy.findAll("小陈");

总结

  • 代理对象原始业务逻辑对象实现相同的接口代理类和真正的业务逻辑对象实现的目标其实是一致的代理可以在保证原始业务逻辑不变的情况下去额外增加一些操作,而为了达到相同的目标,代理对象一般都要调用原始业务逻辑对象的方法,因此可以在代理类中声明一个类型为原始业务逻辑对象成员变量,使用SET方式注入,在代理类实现接口时调用原始业务逻辑对象的方法,并增加一些额外的操作代理对象依赖原始业务逻辑对象)。

  • 一旦我们开发了静态代理类,之后我们就不能使用业务逻辑对象去调用方法了,而要使用代理对象去调用

  • 使用代理的设计模式可以让原始业务逻辑对象更加专注于处理事务逻辑

# 补充

新的问题:往往在开发我们书写的不仅仅是一个业务层,两个业务层,而我们的业务层会有很多,如果为每一个业务层开发一个静态代理类,不仅没有减轻工作量,甚至让我们的工作量多了一倍不止怎么解决以上这个问题呢?

解决方案: 为业务层在运行过程中动态创建代理类,通过动态代理类去解决我们现有业务层中业务代码冗余的问题 。

2.2 动态代理

我们知道静态代理的开发需要为每一个类都开发一个静态代理类,如果有很多个类,却仍然使用静态代理开发代理类,这样是非常麻烦的。这时候就需要用到动态代理类了,它是在程序运行的过程中通过代码的方式去创建代理对象的,非常方便。

什么是动态代理呢

动态代理就是在程序运行的过程中动态的通过代码底层的方式去创建一些代理类,这些代理对象是通过代码通过jvm底层执行我们的代码去帮我们创建出来的代理类。我们可以通过动态代理对象去解决现有业务层的代码冗余问题或者可以通过动态代理对象执行一些附加的操作。由于动态代理是在程序运行过程中动态生成的,它不需要我们为每一个业务层开发一个动态代理,只需要我们写一段固定的代码,日后它就不断地通过这个固定的代码在程序运行过程中生成动态代理对象就可以了。

下面我们通过一段代码来学习一下动态代理:

  • 首先是我们需要用到的UserService接口

    public interface UserService {
        void save(String name);
        void delete(String id);
        void update();
        String findAll(String name);
        String findOne(String id);
    }
    
  • 之后是我们的原始逻辑对象

    // 原始业务逻辑对象
    public class UserServiceImpl implements UserService{
        // 开启事务   处理业务   调用dao
        @Override
        public void save(String name) {
            try {
                System.out.println("开启事务");
                System.out.println("处理事务逻辑,调用DAO~~~");
                System.out.println("提交事务");
            } catch (Exception e) {
                System.out.println("回滚事务");
                e.printStackTrace();
            }
        }
    
        @Override
        public void delete(String id) {
            try {
                System.out.println("开启事务");
                System.out.println("处理事务逻辑,调用DAO~~~");
                System.out.println("提交事务");
            } catch (Exception e) {
                System.out.println("回滚事务");
                e.printStackTrace();
            }
        }
    
        @Override
        public void update() {
            try {
                System.out.println("开启事务");
                System.out.println("处理事务逻辑,调用DAO~~~");
                System.out.println("提交事务");
            } catch (Exception e) {
                System.out.println("回滚事务");
                e.printStackTrace();
            }
        }
    
        @Override
        public String findAll(String name) {
            try {
                System.out.println("开启事务");
                System.out.println("处理事务逻辑,调用DAO~~~");
                System.out.println("提交事务");
            } catch (Exception e) {
                System.out.println("回滚事务");
                e.printStackTrace();
            }
            return name;
        }
    
        @Override
        public String findOne(String id) {
            try {
                System.out.println("开启事务");
                System.out.println("处理事务逻辑,调用DAO~~~");
                System.out.println("提交事务");
            } catch (Exception e) {
                System.out.println("回滚事务");
                e.printStackTrace();
            }
            return id;
        }
    }
    
  • 上面的原始逻辑对象存在着冗余问题,下面我们通过动态代理来解决一下这个问题。


    首先我们需要知道一个类,叫做Proxy,它有一个静态方法newProxyInstance是用来生成代理对象的,这个newProxyInstance有三个参数:

    • 参数一ClassLoader类型第一个参数是类加载器,我们知道代理类和原始逻辑对象要有相同的接口,对于动态代理来说,因为读接口的信息,因为底层是要操作字节码,而ClassLoader就是用来读.class文件的,读类信息的。
    • 参数二Class[]类型这个Class[]数组需要传原始逻辑对象实现的所有接口,因为代理对象要和原始逻辑对象实现相同的接口,传入这个Class[]数组是为了知道要实现的所有接口是什么。
    • 参数三InvocationHandler接口类型(在传参的时候直接new就可以),这个接口我们需要实现它的invoke()方法,invoke()方法里面用来添加额外功能并且调用原始逻辑对象的方法

    newProxyInstance方法返回类型是一个Object,如果我们知道返回的是原始逻辑的代理类,我们可以使用强制类型转换转换为原始逻辑对象实现的接口类型。


    其次,InvocationHandler接口的invoke()方法也有三个参数

    • 参数1:是一个Object类型的对象,代表当前代理对象。
    • 参数2:传入的是一个Method对象,之后在InvocationHandler接口的invoke()方法内部用Method对象的反射调用原始逻辑对象的对应方法。(Method方法也有一个invoke方法,它的invoke方法有两个参数第一个参数传入原始逻辑对象的类对象(传参的时候可以直接new),因为我们要知道我们现在所创建的动态代理对象是为哪个原始逻辑类创建的,第二个参数代表原始逻辑对象的这个方法需要传入的参数)。
    • 参数3Method需要传入的参数

    下面是我们使用动态代理创建代理的代码:

    
    public class TestDynamicProxy {
        public static void main(String[] args) {
    
            // 使用动态代理对象:指的是在程序运行过程中动态通过代码的方式为指定的类生成动态代理对象
    
    
            // Proxy 用来生成动态代理的类
    
            // 参数1:classLoader 类加载器
            // 因为要读接口的信息,因为底层是要操作字节码,ClassLoader就是用来读.class文件的,读类信息的
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            // 参数2:Class[] 目标对象的接口类型的数组
            // 要基于哪个目标类的(所有)接口去生成代理对象
            // 目标类可能实现多个接口,所以这里用Class[]数组
            Class[] classes = {UserService.class};
            // 参数3:InvocationHandler接口类型 有一个invoke()方法 用来书写额外功能 附加操作
            // 返回值:创建好的动态代理对象
            UserService userServiceDynamicProxy = (UserService) Proxy.newProxyInstance(classLoader, classes, new InvocationHandler() {
                @Override
                // 通过动态代理对象调用自己里面代理方法时会优先执行InvocationHandler类中的invoke方法
                // 参数 1:当前创建好的代理对象
                // 参数 2:当前代理对象执行的方法对象
                // 参数 3:当前代理对象执行方法的参数
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    // 当前执行的方法
                    System.out.println("当前执行的方法:" + method.getName());
                    // 当亲执行的方法的参数
                    System.out.println("当前执行的方法的参数:" + args[0]);
                    try {
                        System.out.println("开启事务");
                        // 调用目标类中业务方法 通过反射机制 调用目标类中的当前方法
                        Object invoke = method.invoke(new UserServiceImpl(), args);
                        System.out.println("提交事务");
                        return invoke;
                    } catch (Exception e) {
                        e.printStackTrace();
                        System.out.println("回滚事务");
                    }
                    return null;
                }
            });
    
            String result = userServiceDynamicProxy.findAll("小陈");
        }
    }
    
    

    在这里插入图片描述

    之后调用动态代理对象的findAll方法的执行流程是这样的:

    执行InvocationHandler类的invoke方法内部是增加一些额外操作之后通过method对象的方法去调用原始逻辑对象的方法


    通过使用动态代理,关于事务的开启、关闭、回滚直接在动态代理中就可以操作了,使原始逻辑对象可以更加专注于处理事务的逻辑。

结果:

在这里插入图片描述

  • 补充

    mybatis中的获取DAO对象的本质就是采用动态代理的方法,之后调用DAO对象的方法时就是让method的namemybatis中标签的id进行对照,这也是mybatis中接口不能重载的原因,如果重载不知道调用的是哪个方法。

3. AOP编程

# spring中AOP编程
   AOP:Aspect(切面)Oriented(面向)Programing(编程)
   	底层原理:java代理设计模式  动态代理
   	好处	 :在保证原始业务功能不变情况下,通过代理对象完成业务中附加操作(事务等),将业务中核心操作放在目标对象中执行,
   			实现了核心操作和附加操作的解耦
   	通知(Advice):在动态代理对象的invoke方法中除了目标对象的方法以外的操作都称之为通知(附加操作就是通知,事务通知、
   				  日志通知、性能通知...)
   		环绕通知:围绕(前后都有的)着目标对象方法的附加操作称为环绕通知
   		前置通知:只想要让附加操作在目标对象方法之前执行 
   		后置通知:附加操作只想在目标对象的方法之后去执行
   		异常通知:出现异常之后执行的附加操作称之为异常通知
   	切入点(Pointcut):指定开发好的通知应用于项目中哪些组件(类)哪些方法 UserService 一般通知多用于业务层
   	切面(Aspect) =  通知(Advice)  +  切入点(Pointcut)
   	
   	
   	AOP 面向切面编程:1. 开发通知类(附加操作) 2. 配置切入点  3. 组装切面

在这里插入图片描述

  • spring中aop编程步骤
# spring aop编程的编程步骤
 1. 引入aop编程相关依赖
 	 spring-aop  spring-expression  spring-aspect(这三个依赖在前面配置pom.xml时已经引入过了)
 2. 项目开发额外功能通知
 	 环绕通知  MethodIntercept
 	 前置通知  MethodBeforeAdvice
     后置通知  AfterReturningAdvice
     异常通知  ThrowsAdvice
 3. 配置切面  spring.xml文件中完成
 	a). 注册通知类
 	    <bean class="xxxAdvice" id="">
 	b). 组装切面  aspect  =  advice +  pointcut
 	
 		<aop:config>
 			<!--切入点-->
 			<aop:pointcut>
 			<!--切面-->
 			<aop:advisort>
 		</aop:config>
 		

3.1 开发前置通知

接下来我们开始aop的编程:

这是我们用到的EmpService接口

public interface EmpService {
    void save(String name);

    String find(String name);
}

这是EmpService接口的实现类EmpServiceImpl

// 原始逻辑对象  目标对象
public class EmpServiceImpl implements EmpService{
    @Override
    public void save(String name) {
        System.out.println("处理业务逻辑调用save DAO~~" + name);
    }

    @Override
    public String find(String name) {
        System.out.println("处理业务逻辑调用find DAO~~" + name);
        return name;
    }
}

现在我们想要让目标对象每次在方法执行之前输出方法的名字,这是额外功能,又因为在每次方法执行之前输出,所以我们需要开发一个前置通知

// 开发的前置通知
// 自定义记录业务方法名称通知 前置通知:目标方法执行之前先执行的额外操作
// 通知封装的其实是动态代理的invoke方法
// 必须实现系统定义的前置通知的接口
public class MyBeforeAdvice implements MethodBeforeAdvice {
    @Override
    // 前置通知把invoke方法换成了before,代表这是一个前置通知
    // 参数1:执行的方法  参数2:当前执行的方法的参数  参数3:目标对象
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("当前执行方法:" + method.getName());
        System.out.println("当前执行方法参数:" + objects[0]);
        System.out.println("目标对象:" + o);
    }
}

配置文件

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


    <!--管理Service组件对象-->
    <bean class="aop.EmpServiceImpl" id="empService"></bean>

    <!--注册通知-->
    <bean class="aop.MyBeforeAdvice" id="myBeforeAdvice"/>

    <!--组装切面-->
    <!--aop标签可能需要额外添加配置,直接 Alt + Enter 就可以了-->
    <aop:config>
        <!--配置切入点pointcut
            id:表示这个切入点在工厂中唯一标识
            expression:用来指定切入项目中哪些组件中哪些方法
                execution(返回值 包.类名.方法名(参数类型))
                execution(返回值 包.类名.*(..))
                                        *代表类中所有方法  ..代表参数任意
                                 aop.*.*(..) 代表aop包下的所有类的所有方法在执行时都要加前置通知
                execution:
                        第一个代表方法的返回值,*代表不关心方法的返回值
                        之后空格写包名(包.类.方法名),切类中所有方法且不关心方法的参数写*(..)
         -->
        <aop:pointcut id="pc" expression="execution(* aop.EmpServiceImpl.*(..))"/>
        <!--配置切面  advice-ref:工厂中通知id  pointcut-ref:工厂中切入点唯一标识-->
        <aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="pc"/>
    </aop:config>
</beans>

在这里插入图片描述

测试类+结果

public class TestSpring {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("aop/spring.xml");

        EmpService empService = (EmpService) context.getBean("empService");

        //empService.find("小陈");

        System.out.println(empService.getClass());
    }
}public class TestSpring {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("aop/spring.xml");

        EmpService empService = (EmpService) context.getBean("empService");

        //empService.find("小陈");

        System.out.println(empService.getClass());
    }
}

在这里插入图片描述

总结:

在这里插入图片描述

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-04 15:22:29  更:2022-03-04 15:26:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:46:51-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码