java设计模式之代理设计模式(Spring核心思想AOP的底层设计模式)

简介: 代理设计模式🍅 作者:程序员小王🍅 程序员小王的博客:程序员小王的博客🍅 扫描主页左侧二维码,加我微信 一起学习、一起进步🍅 欢迎点赞 👍 收藏 ⭐留言 📝🍅 如有编辑错误联系作者,如果有比较好的文章欢迎分享给我,我会取其精华去其糟粕🍅java自学的学习路线:java自学的学习路线

代理设计模式文章目录:

一、结构型模式

结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。


由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。


结构型模式分为以下 7 种:


  • 代理模式
  • 适配器模式
  • 装饰者模式
  • 桥接模式
  • 外观模式
  • 组合模式
  • 享元模式


二、现有开发中存在的问题

问题:在JavaEE分层开发开发中,那个层次对于我们来讲最重要?

DAO ---> Service --> Controller 
JavaEE分层开发中,最为重要的是Service层

1、定义业务层接口

public interface EmpService {
    /**
     * 分页
     * @return 当前页的员工
     */
    public List<Emp> selectEmpByPage(int pageNumber);
    /**
     * 总的页数
     * @return 数据库有多少条员工数据
     */
    public int selectTotalPage();
    /**
     * 添加员工
     * @param emp
     */
    public void addEmp(Emp emp);
    /**
     * 根据id删除员工
     * @param id
     */
    public void dropEmp(Integer id);
    /**
     * 根据id修改员工信息
     * @param emp
     */
    public void update(Emp emp);
    /**
     * 批量删除员工
     * @param ids
     */
    public void plDeleteEmp(List<Integer> ids);
    /**
     * 更具id查询员工,用于数据回显
     * @param id
     * @return
     */
    public Emp queryEmp(Integer id);
}

2、实现业务接口

public class EmpServiceImpl implements EmpService {
    @Override
    public List<Emp> selectEmpByPage(int pageNumber) {
        EmpDao mapper = (EmpDao) MybatisUtil.getMapper(EmpDao.class);
        //设置每页展示的页数
        int rows = 3;
        //设置第几页开始的起始条数
        int begin = (pageNumber - 1) * rows;
        List<Emp> emps = mapper.selectEmpByPage(begin, rows);
        MybatisUtil.close();
        return emps;
    }
    @Override
    public int selectTotalPage() {
        EmpDao mapper = (EmpDao) MybatisUtil.getMapper(EmpDao.class);
        int i = mapper.selectTotalCount();
        int rows = 3;
        int page = 0;
        if (i % rows == 0) {
            page = i / rows;
        } else {
            page = i / rows + 1;
        }
        MybatisUtil.close();
        return page;
    }
    @Override
    public void addEmp(Emp emp) {
        EmpDao mapper = (EmpDao) MybatisUtil.getMapper(EmpDao.class);
        mapper.insertEmp(emp);
        MybatisUtil.commit();
    }
    @Override
    public void dropEmp(Integer id) {
        EmpDao mapper = (EmpDao) MybatisUtil.getMapper(EmpDao.class);
        mapper.deleteEmp(id);
        MybatisUtil.commit();
    }
    @Override
    public void update(Emp emp) {
        EmpDao mapper = (EmpDao) MybatisUtil.getMapper(EmpDao.class);
        mapper.update(emp);
        MybatisUtil.commit();
    }
    @Override
    public void plDeleteEmp(List<Integer> ids) {
        EmpDao mapper = (EmpDao) MybatisUtil.getMapper(EmpDao.class);
        mapper.plDeleteEmp(ids);
        MybatisUtil.commit();
    }
    @Override
    public Emp queryEmp(Integer id) {
        EmpDao mapper = (EmpDao) MybatisUtil.getMapper(EmpDao.class);
        Emp emp = mapper.queryEmp(id);
        MybatisUtil.close();
        return emp;
    }
}

3、Service层中包含了哪些代码?

Service层中 = 核心功能(几十行 上百代码) + 额外功能(附加功能)
1. 核心功能
   业务运算
   DAO调用
2. 额外功能 
   1. 不属于业务
   2. 可有可无
   3. 代码量很小 
   事务、日志、性能...

从下图中可以看出,现有业务层中控制事务代码出现了大量的冗余,如何解决现有业务层出现的冗余问题?

0.png



4、额外功能书写在Service层中好不好?

Service层的调用者的角度(Controller):需要在Service层书写额外功能。
                         软件设计者:Service层不需要额外功能


5、现实生活中的解决方式

1.住酒店不一定需要亲自到酒店去,还可以通过微信支付下的同程艺龙来订酒店。


2.我们可以通过中介去找房子,不用直接跟房东沟通(现实生活中,我们更希望直接跟房东沟通)


3.春运买票买不到,我们可以找黄牛替我们抢票


4.想访问国外的网站,可以使用代理服务器进行访问。


三、代理设计模式

单词 proxy [ˈprɑːksi] 代理


1、概述

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。


1.png


Java中的代理按照代理类生成时机不同 又分为静态代理和动态代理。


1."静态代理"代理类在编译期就生成


2."动态代理"代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。


2、结构

代理(Proxy)模式分为三种角色:


抽象主题(Subject)类: 通过接口或抽象类 声明真实主题和代理对象实现的业务方法。


真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。


代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。


3.png


1、什么是代理? 中介

代理是java中的一种设计模式:代理设计模式


2、为什么需要代理?

有没有代理都可以完成当前的功能,但是存在代理之后,更加专注于核心的业务功能,额外功能可以交给代理去做,实现功能的解耦合


生活案例:小红正在做饭,但是男朋友小李给他发消息,他如果边做饭边发消息,影响做饭的效率,他就可以口述给她的闺蜜让她闺蜜和男朋友聊天,自己认真做饭,闺蜜就是代理,"如花"就是一个接口

4.png


3、代理的好处?

  保证核心功能完成的前提下,同时兼顾额外功能


4、怎么开发代理对象?

1、代理对象一定依赖于核心业务对象

2、代理对象和核心业务对象一定要实现相同的接口


5.png


四、静态代理

1、什么是静态代理

静态代理:手工为某个类开发一个代理类,一个类对应一个代理类


通过代理类,为原始类(目标)增加额外的功能


好处:利于原始类(目标)的维护


2、名词解释

目标类 原始类 被代理的对象

指的是 业务类 (核心功能 --> 业务运算 DAO调用)


目标方法,原始方法

目标类(原始类)中的方法 就是目标方法(原始方法)


额外功能 (附加功能)

日志,事务,性能,数据清洗


3、代理开发的核心要素

代理类 = 目标类(原始类) + 额外功能 + 原始类(目标类)实现相同的接口

房东 ---> public interface UserService{
               m1
               m2
          }
          UserServiceImpl implements UserService{
               m1 ---> 业务运算 DAO调用
               m2 
          }
          UserServiceProxy implements UserService
               m1
               m2


4、编码(静态代理的开发)

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

(1)UserService接口

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

(2)开发静态代理类

/**
 * @author 王恒杰
 * @version 1.0
 * @date 2021/11/11 11:14
 * @email 1078993387@qq.com
 * @Address 天津
 * @Description:开发原则:代理类和目标类实现相同接口,依赖于真正的目标类
 */
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) {
        String all=null;
        try {
            System.out.println("开启事务");
           all = userService.findAll(name);
            System.out.println("提交事务");
        }catch (Exception e){
            System.out.println("回滚事务");
            e.printStackTrace();
        }
        return all;
    }
    @Override
    public String findOne(String id) {
        String one=null;
        try {
            System.out.println("开启事务");
            one= userService.findOne(id);
            System.out.println("提交事务");
        }catch (Exception e){
            System.out.println("回滚事务");
            e.printStackTrace();
        }
        return one;
    }
}

(3)修改目标实现类

public class UserServiceImpl implements UserService {
    @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;
    }
}

(4)配置静态代理类

    <!--配置目标类-->
    <bean id="userService" class="StaticProxy.UserServiceImpl"></bean>
    <!--配置代理类-->
    <bean id="userServiceStaticProxy" class="StaticProxy.UserServiceStaticProxy">
        <!--setter入-->
        <property name="userService" ref="userService"></property>
     </bean>

(5)调用代理方法

 

    @Test
    public void StaticProxyText() {
        ClassPathXmlApplicationContext context =
                 new ClassPathXmlApplicationContext("ApplicationContext.xml");
         UserService userService = 
                     (UserService) context.getBean("userServiceStaticProxy");
        userService.delete("1");
    }
}


a.开发静态代理类


5、静态代理存在的问题

存在问题:造成程序中存在过多的代理类,不利于后期额外功能的维护


静态类文件数量过多,不利于项目管理

   UserServiceImpl——》 UserServiceProxy


   OrderServiceImpl——》 OrderServiceProxy


额外功能维护性差

   代理类中 额外功能修改复杂(麻烦)


五、动态代理

1、什么动态态代理

动态代理:在程序运行的过程中,JVM动态的为某个类生成代理对象


概念:通过代理类为原始类(目标类)增加额外功能


好处:利于原始类(目标类)的维护


常用的动态代理技术


1.JDK 代理 : 基于接口的动态代理技术


2.cglib 代理:基于父类的动态代理技术


6.png


DK动态代理是利用反射机制生成一个实现代理接口的匿名类 ,在调用具体方法前调用InvokeHandler (调用处理程序的意思)来处理。


而cglib动态代理是利用asm开源包 ,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。


使用场景


   1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP

   2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP

   3、如果目标对象没有实现了接口,必须采用CGLIB库 ,spring会自动在JDK动态代理和CGLIB之间转换


DK动态代理和CGLIB字节码生成的区别?

   (1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类

   (2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法


   因为是继承,所以该类或方法最好不要声明成final


2、名词解释

目标对象:被代理的对象,称之为目标对象


目标方法: 核心业务对象中实现的核心方法


额外功能 (附加功能)

日志处理,事务,性能


3、搭建开发环境

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>5.1.14.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.8.8</version>
</dependency>
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.3</version>
</dependency>


4、JDK动态代理的开发

(1)目标类接口

public interface EmpService {
    public  void method();
}

(2)目标类

public class EmpServiceImpl implements EmpService{
    @Override
    public void method() {
        System.out.println("Service方法被执行了");
    }
}


(3)JDK动态代理代码并调用代理对象的方法测试

    @Test
    public void JDKDynamicProxyTest() {
        //生成动态代理对象   1、目标对象和代理对象实现相同的接口  2、代理对象一定依赖目标对象
        final EmpService empService = new EmpServiceImpl();
        //获取动态代理前的对象
        System.out.println(empService.getClass());
        //   创建代理对象
        InvocationHandler invocationHandler;
        //动态代理 o就是静态代理对象
        /**
         * 参数1: 类加载器(class.getClassLoad()) 动态代理JVM依据字节码技术完成 加载.class文件
         * Thread.currentThread():当前的线程
         * Thread.currentThread().getContextClassLoader()等价于DynamicProxyTest.class.getClassLoader(),
         * 参数2:目标对象实现的接口的类对象 数组 目标对象实现的所有的接口类型的数组
         * 参数3:额外功能 目标对象中的目标方法需要执行的额外方法
         */
        EmpService empService1 = (EmpService) Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(),
                new Class[]{EmpService.class},
                new InvocationHandler() {
                    /**
                     * 参数1 proxy: 当前的动态代理对象
                     * 参数2 method: 当前代理对象执行的方法的对象
                     * 参数3 args: 当前代理对象执行的方法的参数
                     *
                     * @param proxy
                     * @param method
                     * @param args
                     * @return
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("这是EmpServiceImpl目标对象的动态代理");
                        //调用目标方法  参数1:目标对象  参数2:目标方法执行时传递的参数
                        Object invoke = method.invoke(empService, args);
                        return null;
                    }
                }
        );
        empService1.method();
        //动态代理后的对象
        System.out.println(empService1.getClass());
    }

7.png


5、cglib 动态代理

需要导入两个jar(maven依赖)包,asm.jar,cglib.jar。版本自行选择·


所需相关依赖

 

   <!--asmasm开源包-->
    <dependency>
      <groupId>asm</groupId>
      <artifactId>asm</artifactId>
      <version>3.3.1</version>
    </dependency>
    <!--cglib包-->
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.3.0</version>
    </dependency>


(1)目标类

  public void method() {
            System.out.println("Target 运行了....");
        }


(2)动态代理代码及调用代理对象的方法测试

8.png

  /**
     * CjLib动态代理
     * 如果目标对象没有实现了接口,
     * 必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
     */
    @Test
    public void CjLibTest() {
        //创建目标对象
        final Target target = new Target();
        //创建增强器
        Enhancer enhancer = new Enhancer();
        // 设置父类
        enhancer.setSuperclass(Target.class);
        //设置回调
        enhancer.setCallback(new MethodInterceptor() {
            /**
             * 参数1 proxy: 当前的动态代理对象
             * 参数2 method: 当前代理对象执行的方法的对象
             * 参数3 args: 当前代理对象执行的方法的参数
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("这是Target目标对象的动态代理");
                //调用目标方法  参数1:目标对象  参数2:目标方法执行时传递的参数
                Object invoke = method.invoke(target, objects);
                return invoke;
            }
        });
        //创建代理对象
        Target t = (Target) enhancer.create();
        t.method();
    }


六、三种代理的对比

1、jdk代理和CGLIB代理的区别

   使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类 。


   在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率 ,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理 。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。


2、动态代理和静态代理的区别?

   动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。


   如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题


七、代理模式的优缺点

1、代理模式的优点:

代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;


代理对象可以扩展目标对象的功能;


代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;


2、代理模式的缺点:

增加了系统的复杂度;

八、代理模式使用场景

1、远程(Remote)代理

   本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。


2、防火墙(Firewall)代理

   当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。


3、保护(Protect or Access)代理

   控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。


相关文章
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
87 2
|
1月前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
58 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
2月前
|
XML Java 数据安全/隐私保护
Spring Aop该如何使用
本文介绍了AOP(面向切面编程)的基本概念和术语,并通过具体业务场景演示了如何在Spring框架中使用Spring AOP。文章详细解释了切面、连接点、通知、切点等关键术语,并提供了完整的示例代码,帮助读者轻松理解和应用Spring AOP。
Spring Aop该如何使用
|
2月前
|
监控 安全 Java
什么是AOP?如何与Spring Boot一起使用?
什么是AOP?如何与Spring Boot一起使用?
69 5
|
2月前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
76 8
|
2月前
|
Java 开发者 Spring
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。
|
2月前
|
XML 监控 安全
深入调查研究Spring AOP
【11月更文挑战第15天】
49 5
|
2月前
|
Java 开发者 Spring
Spring AOP深度解析:探秘动态代理与增强逻辑
Spring框架中的AOP(Aspect-Oriented Programming,面向切面编程)功能为开发者提供了一种强大的工具,用以将横切关注点(如日志、事务管理等)与业务逻辑分离。本文将深入探讨Spring AOP的底层原理,包括动态代理机制和增强逻辑的实现。
49 4
|
2月前
|
Java 数据库连接 API
Spring 框架的介绍(Java EE 学习笔记02)
Spring是一个由Rod Johnson开发的轻量级Java SE/EE一站式开源框架,旨在解决Java EE应用中的多种问题。它采用非侵入式设计,通过IoC和AOP技术简化了Java应用的开发流程,降低了组件间的耦合度,支持事务管理和多种框架的无缝集成,极大提升了开发效率和代码质量。Spring 5引入了响应式编程等新特性,进一步增强了框架的功能性和灵活性。
54 0
|
2月前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
45 0