• 【深入浅出Spring6】第七期——使用JDBC模板与代理模式


    一、JDBCTemplate

    • JdbcTemplateSpring提供的一个JDBC模板类,是对JDBC的封装,简化JDBC代码
    • Spring也继承了其他持久化的框架,比如 MyBatis
    • 本篇我们从简单的增删改查角度介绍如何使用Spring提供的这个模板类

    $ 准备工作

    • 创建一个新的模块 spring6-009-jdbc

    • 导入我们需要的依赖 + 打Jar

      <packaging>jar</packaging>
          <!--配置多个仓库repositories标签-->
          <repositories>
              <repository>
                  <id>repository.spring.milestone</id>
                  <name>Spring Milestone Repository</name>
                  <url>https://repo.spring.io/milestone</url>
              </repository>
          </repositories>
          <!--依赖配置 spring-context、mysql数据驱动、junit单元测试、jdbc相关依赖-->
          <dependencies>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-context</artifactId>
                  <version>6.0.0-M2</version>
              </dependency>
              <dependency>
                  <groupId>mysql</groupId>
                  <artifactId>mysql-connector-java</artifactId>
                  <version>8.0.31</version>
              </dependency>
              <dependency>
                  <groupId>junit</groupId>
                  <artifactId>junit</artifactId>
                  <version>4.13.2</version>
                  <scope>test</scope>
              </dependency>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-jdbc</artifactId>
                  <version>6.0.0-M2</version>
              </dependency>
              <!--使用德鲁伊连接池-->
              <dependency>
                  <groupId>com.alibaba</groupId>
                  <artifactId>druid</artifactId>
                  <version>1.1.8</version>
              </dependency>
      </dependencies>
      
      • 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
      • 38
      • 39
    • 在数据库中创建一张表 t_user,表的结构如下:【可以初始化几条数据】
      在这里插入图片描述

    • 我们创建一个pojocom.powernode.spring.bean.User 用来封装表中字段

      package com.powernode.spring6.bean;
      
      /**
       * pojo类对应我们的 t_user 表
       * @author Bonbons
       * @version 1.0
       */
      public class User {
          private Integer id;
          private String realName;
          private Integer age;
      
          public User() {
          }
      
          public User(Integer id, String realName, Integer age) {
              this.id = id;
              this.realName = realName;
              this.age = age;
          }
      
          public Integer getId() {
              return id;
          }
      
          public void setId(Integer id) {
              this.id = id;
          }
      
          public String getRealName() {
              return realName;
          }
      
          public void setRealName(String realName) {
              this.realName = realName;
          }
      
          public Integer getAge() {
              return age;
          }
      
          public void setAge(Integer age) {
              this.age = age;
          }
      
          @Override
          public String toString() {
              return "User{" +
                      "id=" + id +
                      ", realName='" + realName + '\'' +
                      ", age=" + age +
                      '}';
          }
      }
      
      • 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
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
    • 这个JdbcTemplate 有一个属性,dataSource,所以我们还需要给他提供数据源的 Bean

    • 我们可以自己定义一个数据源【只要实现了DataSource接口的类都可以充当数据源】

      package com.powernode.spring6.bean;
      
      import javax.sql.DataSource;
      import java.io.PrintWriter;
      import java.sql.Connection;
      import java.sql.DriverManager;
      import java.sql.SQLException;
      import java.sql.SQLFeatureNotSupportedException;
      import java.util.logging.Logger;
      
      /**
       * @author Bonbons
       * @version 1.0
       */
      public class MyDataSource implements DataSource {
          private String driver;
          private String url;
          private String username;
          private String password;
      
          public void setDriver(String driver) {
              this.driver = driver;
          }
      
          public void setUrl(String url) {
              this.url = url;
          }
      
          public void setUsername(String username) {
              this.username = username;
          }
      
          public void setPassword(String password) {
              this.password = password;
          }
      
          @Override
          public String toString() {
              return "MyDataSource{" +
                      "driver='" + driver + '\'' +
                      ", url='" + url + '\'' +
                      ", username='" + username + '\'' +
                      ", password='" + password + '\'' +
                      '}';
          }
      
          @Override
          public Connection getConnection() throws SQLException {
              try {
                  // 注册驱动
                  Class.forName(driver);
                  // 返回创建的连接对象
                  return DriverManager.getConnection(url, username, password);
      
              } catch (ClassNotFoundException e) {
                  throw new RuntimeException(e);
              }
              // 这里有个 return null; 被我注释掉了
          }
      
          @Override
          public Connection getConnection(String username, String password) throws SQLException {
              return null;
          }
      
          @Override
          public PrintWriter getLogWriter() throws SQLException {
              return null;
          }
      
          @Override
          public void setLogWriter(PrintWriter out) throws SQLException {
      
          }
      
          @Override
          public void setLoginTimeout(int seconds) throws SQLException {
      
          }
      
          @Override
          public int getLoginTimeout() throws SQLException {
              return 0;
          }
      
          @Override
          public Logger getParentLogger() throws SQLFeatureNotSupportedException {
              return null;
          }
      
          @Override
          public <T> T unwrap(Class<T> iface) throws SQLException {
              return null;
          }
      
          @Override
          public boolean isWrapperFor(Class<?> iface) throws SQLException {
              return false;
          }
      }
      
      • 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
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 98
      • 99
      • 100
    • 我们需要将 JdbcTemplate 和 我们自定义的数据源在配置文件中声明一下,交给Spring容器管理

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
      
          <!--JDBC模板类放到Spring容器中-->
          <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
              <property name="dataSource" ref="myDataSource" />
          </bean>
          <!--配置我们自己定义的数据源 >> 只要实现了DataSource接口我们都可以称之为数据源-->
          <bean id="myDataSource" class="com.powernode.spring6.bean.MyDataSource">
              <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
              <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
              <property name="username" value="root"/>
              <property name="password" value="111111"/>
          </bean>
      </beans>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
    • 我们在测试方法中通过getBean获取到 JdbcTemplate 的对象,然后就可以调用方法操作数据库了

    • 所以下面演示那些功能,直接通过测试方法实现

    • 对于功能的解释我都写在了注释里面

    $ 获取JdbcTemplate实例

    @Test
    public void testJdbc() {
    	ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    	JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    	// 成功获得jdbcTemplate的对象,之后就可以用这个对象操作数据库
    	System.out.println(jdbcTemplate);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    通过运行结果,我们可以知道 JDBCTemplate 可以成功托管到Spring中,接下来我们就可以调用它的方法了

    在这里插入图片描述

    $ 插入一条数据

    • 先获取到JdbcTemplate的对象,然后编写sql语句调用方法,查看结果
      @Test
          public void testInsert(){
              ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
              JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
              // 现在我们想插入数据,insert语句
              String sql = "insert into t_user (real_name, age) values (?,?)";
              // 在jdbcTemplate中,增删改都使用 update 方法
              int count = jdbcTemplate.update(sql, "吃不饱三战士", 25);
              System.out.println(count);
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

    $ 更新一条数据

    @Test
        public void testUpdate(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
            // 根据id更新我们数据库表中的数据
            String sql = "update t_user set real_name = ?, age = ? where id = ?";
            // 调用我们的update方法,传递sql语句以及与我们 ? 对应的参数值
            int count = jdbcTemplate.update(sql, "哪吒三太子", 15, 1);
            System.out.println(count);
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    $ 删除一条数据

    @Test
        public void testDelete(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
            // 删除SQL
            String sql = "delete from t_user where id = ?";
            // 调用我们的 update 方法
            jdbcTemplate.update(sql, 2);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    $ 查询一条数据

    @Test
        public void testQueryOne(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
            // 根据id查询一条记录
            String sql = "select id, real_name, age from t_user where id = ?";
            // 查询语句调用的是 QueryForObject 方法,参数:sql语句、new BeanPropertyRowMapper<>(类型)查询结果映射对象、?参数传值
            User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 1);
            System.out.println(user);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    $ 查询全部数据

    @Test
        public void testQueryAll(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
            // 查询数据库表中的全部数据
            String sql = "select id, real_name, age from t_user";
            // 查询全部数据我们调用的是 query 方法
            List<User> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
            System.out.println(query);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    $ 查询数据总量

    @Test
        public void testQueryOneValue(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
            // 查询一个值 >> 此处以查询数据库表中记录条数为例[count(具体的列)只会本列不为空的记录条数]
            String sql = "select count(*) from t_user";
            // 一条记录,我们调用的还是 queryForObject
            Integer count = jdbcTemplate.queryForObject(sql, int.class);
            System.out.println(count);
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    $ 批量添加

    @Test
        public void testBatchInsert(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
            // 插入数据的SQL
            String sql = "insert into t_user (real_name, age) value(?, ?)";
            // 利用数组封装我们要插入的一条数据
            Object [] obj1 = {"张三", 22};
            Object [] obj2 = {"李四", 25};
            Object [] obj3 = {"王五", 30};
            // 利用一个集合对象存储这些待插入的数据
            List<Object[]> list = new ArrayList<>();
            list.add(obj1);
            list.add(obj2);
            list.add(obj3);
            // 调用我们的 batchUpdate 方法插入多条数据,参数1为sql、参数2为集合对象
            int[] counts = jdbcTemplate.batchUpdate(sql, list);
            System.out.println(Arrays.toString(counts));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    $ 批量更新

    @Test
        public void testBatchUpdate(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
            // 插入数据的SQL
            String sql = "update t_user set real_name = ?, age = ? where id = ?";
            // 利用数组封装我们要更新的数据和对应的id
            Object [] obj1 = {"杨戬", 23, 4};
            Object [] obj2 = {"李靖", 25, 5};
            Object [] obj3 = {"孙悟空", 22, 6};
            // 利用一个集合对象存储数据
            List<Object[]> list = new ArrayList<>();
            list.add(obj1);
            list.add(obj2);
            list.add(obj3);
            // 调用我们的 batchUpdate 方法更新多条数据,参数1为sql、参数2为集合对象
            int[] counts = jdbcTemplate.batchUpdate(sql, list);
            System.out.println(Arrays.toString(counts));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    $ 批量删除

    @Test
        public void testBeachDelete(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
            // 删除语句
            String sql = "delete from t_user where id = ?";
            // 准备数据,只有一项也得用数组
            Object [] obj1 = {4};
            Object [] obj2 = {5};
            Object [] obj3 = {6};
            // 利用一个集合对象存储数据
            List<Object[]> list = new ArrayList<>();
            list.add(obj1);
            list.add(obj2);
            list.add(obj3);
            // 调用我们的 batchUpdate 方法删除多条数据,参数1为sql、参数2为集合对象
            int[] counts = jdbcTemplate.batchUpdate(sql, list);
            System.out.println(Arrays.toString(counts));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    $ 函数回调

    @Test
        public void testCallback(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
            // 编写根据id查询数据的SQL
            String sql = "select id, real_name, age from t_user where id = ?";
            // 注册回调函数,execute执行之后,会调用我们的doInPreparedStatement方法
            User user = jdbcTemplate.execute(sql, new PreparedStatementCallback<User>() {
    
                @Override
                public User doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
                    User user = null;
                    // 参数代表对应第1个 ?, 参数值为3
                    ps.setInt(1, 3);
                    //
                    ResultSet rs = ps.executeQuery();
                    if (rs.next()) {
                        int id = rs.getInt("id");
                        String realName = rs.getString("real_name");
                        int age = rs.getInt("age");
                        // 将获取到的数据封装到我们的User对象中
                        user = new User(id, realName, age);
                    }
                    return user;
                }
            });
            System.out.println(user);
        }
    
    • 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

    在这里插入图片描述

    $ 使用德鲁伊连接池

    • 前面使用的是我们自己写的简易版的连接池,接下来我们配置一下德鲁伊数据库连接池
      • 第一步,需要导入相关的依赖
        <!--使用德鲁伊连接池-->
        <dependency>
        	<groupId>com.alibaba</groupId>
        	<artifactId>druid</artifactId>
        	<version>1.1.8</version>
        </dependency>
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
      • 第二步,在配置文件中进行配置
        <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        
            <!--JDBC模板类放到Spring容器中-->
            <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
                <property name="dataSource" ref="myDataSource" />
            </bean>
        
            <!--配置德鲁伊连接池-->
            <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource">
                <!--此处这个driver的名字有变化-->
                <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
                <property name="username" value="root"/>
                <property name="password" value="111111"/>
            </bean>
        </beans>
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
      • 第三步,可以使用了,我们以上面回调函数的测试代码为例进行测试

    在这里插入图片描述

    二、代理模式

    • 什么是代理模式?

    用一个类代表另一个类的功能。属于GoF23种设计模式中的结构型模式

    • 使用代理模式有什么用?

      • 通过代理实现间接引用
      • 增强功能
      • 目标对象需要保护
    • 代理模式有哪些角色?

      • 目标对象:我们被代理的对象
      • 代理对象:实际去执行操作的对象
      • 公共接口:为了让代理类和目标类具有共同的行为
    • 代理模式的实现方式?

      • 静态代理
      • 动态代理
        • JDK 动态代理
        • CGLIB 动态代理
    • 本篇内容概述:我们结合实例来演示什么是静态代理,以及如何实现基于JDK的动态代理(重点内容),还有涉及如何使用CGLIB的动态代理

    $ 静态代理

    • 需求:我们设计一个订单的接口,然后实现这个接口,主要包括生成订单、查看订单、修改订单

    编写我们的 OrderService 接口

    package com.powernode.proxy.service;
    
    /**
     * 代理机制的公共接口
     * @author Bonbons
     * @version 1.0
     */
    public interface OrderService {
        // 生成订单
        void generate();
        // 查看订单
        void detail();
        // 修改订单
        void modify();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    编写我们的接口实现类 OrderServiceImpl

    package com.powernode.proxy.service;
    
    /**
     * 代理机制的目标对象
     * @author Bonbons
     * @version 1.0
     */
    public class OrderServiceImpl implements OrderService {
        @Override
        public void generate() {
            try {
                Thread.sleep(1234);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("订单已生成");
        }
    
        @Override
        public void detail() {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("查看订单详情");
        }
    
        @Override
        public void modify() {
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("订单已修改");
        }
    }
    
    • 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
    • 38

    此处我们通过 Thread.sleep(number);线程睡眠来模拟实际操作的等待时间

    编写测试程序,查看是否可以正常进行业务操作

    public class Test {
        public static void main(String[] args) {
    		// 创建实现类的对象
            OrderService orderService = new OrderServiceImpl();
            // 调用业务方法
            orderService.generate();
            orderService.detail();
            orderoService.modify();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    • 现在我们产生了新的需求:要计算每个方法的执行时长,那我们如何解决呢?

      • 方法一:我们直接在实现类的方法中的前后添加系统时间的获取,然后计算输出
        • 违背了OCP原则,修改了当初运行正常的代码
        • 而且每个方法都要添加相同的代码块,没有实现代码复用
      • 方法二:我们创建目标类的子类,通过继承这个类来代替父类进行操作
        • 解决了OCP问题,但是继承导致代码耦合度很高
        • 也没有解决代码不能复用的问题
      • 方法三:采用静态代理的方式 ,关联属性–将目标对象作为代理对象的属性使用【展开论述】
        • 符合OCP开闭原则,同时采用的是关联关系,所以程序的耦合度较低
        • 如果接口特别多,那么就导致我们要创建很多代理类,不方便维护
        • 也没有解决代码不能复用的问题
    • 类和类之间的关系:【此处说明泛化关系和关联关系】
      在这里插入图片描述

    基于上面的接口和接口实现类,我们需要编写代理对象类 OrderServiceProxy 并实现接口

    package com.powernode.proxy.service;
    
    /**
     * 代理对象
     * @author Bonbons
     * @version 1.0
     */
    public class OrderServiceProxy implements OrderService{
        // 我们将公共接口作为代理对象的属性 [关联关系--耦合度更低]
        private OrderService target;
        // 通过构造方法将目标对象传递给代理对象
        public OrderServiceProxy(OrderService orderService) {
            this.target = orderService;
        }
    
        @Override
        public void generate() {
            long start = System.currentTimeMillis();
            target.generate();
            long end = System.currentTimeMillis();
            System.out.println("耗时"+(end - start)+"毫秒");
        }
    
        @Override
        public void detail() {
            long start = System.currentTimeMillis();
            target.detail();
            long end = System.currentTimeMillis();
            System.out.println("耗时"+(end - start)+"毫秒");
        }
    
        @Override
        public void modify() {
            long start = System.currentTimeMillis();
            target.modify();
            long end = System.currentTimeMillis();
            System.out.println("耗时"+(end - start)+"毫秒");
        }
    }
    
    • 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
    • 38
    • 39

    编写我们的测试程序

    // 创建目标对象
    OrderService target = new OrderServiceImpl();
    // 创建代理对象
    OrderService proxy = new OrderServiceProxy(target);
    // 调用代理对象的方法
    proxy.generate();
    proxy.detail();
    proxy.modify();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    • 静态代理需要的成员如下:
      在这里插入图片描述
    • 上面我们说使用静态代理依旧可能存在着不方便维护和计算耗时的代码不能复用的问题,那么我们如何解决呢?
      • 我们可以选择使用动态代理机制,可以为我们在内存中动态生成代理类的字节码文件

    $ 动态代理

    • 什么是动态代理?

      • 在程序运行阶段,在内存中动态生成代理类
      • 目的是为了减少代理类的数量。解决代码复用的问题。
    • 在内存当中动态生成类的技术常见的包括:

      • JDK动态代理技术:只能代理接口
      • CGLIB动态代理技术:
        • 它既可以代理接口,又可以代理类
        • 底层是通过继承的方式实现的,性能比JDK动态代理要好【底层使用了字节码处理框架ASM
      • Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库

    $$ JDK 动态代理

    • 只能代理接口
    • 具体的说明都在代码块的注释里
    • 需求:我们创建一个新的模块 dynamic-jdk,演示如何使用JDK来完成动态代理

    编写修改后的接口,添加了一个拥有返回值的方法 getName()

    package com.powernode.proxy.service;
    
    /**
     * 代理机制的公共接口
     * @author Bonbons
     * @version 1.0
     */
    public interface OrderService {
        // 生成订单
        void generate();
        // 查看订单
        void detail();
        // 修改订单
        void modify();
        // 为了研究invoke返回值问题,我们写一个有返回值的方法
        String getName();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    编写我们新的实现类

    package com.powernode.proxy.service;
    
    /**
     * 代理机制的目标对象
     * @author Bonbons
     * @version 1.0
     */
    public class OrderServiceImpl implements OrderService {
        @Override
        public void generate() {
            try {
                Thread.sleep(1234);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("订单已生成");
        }
    
        @Override
        public void detail() {
            try {
                Thread.sleep(1234);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("查看订单详情");
        }
    
        @Override
        public void modify() {
            try {
                Thread.sleep(1234);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("订单已修改");
        }
    
        @Override
        public String getName() {
            System.out.println("getName方法执行!");
            return "Bonbons";
        }
    }
    
    • 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
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    编写我们的调用处理器 TimeInvocationHandler,负责调用我们目标对象的目标方法

    package com.powernode.proxy.service;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    /**
     * 关于计时的调用处理器对象,不需要重复编写,在内部编写与计时相关的代码
     * @author Bonbons
     * @version 1.0
     */
    public class TimeInvocationHandler implements InvocationHandler {
        // 因为我们调用方式的时候需要目标对象,所以我们通过处理器的构造方法传递目标对象
        private Object target;
    
        public TimeInvocationHandler(Object target) {
            this.target = target;
        }
    
        /*
                当代理对象调用代理方法时,注册在处理器中的invoke方法才会被JDK调用
                invoke 的三个参数:
                    (1) Object proxy 代理对象
                    (2) Method method 目标对象的目标方法
                    (3) Object[] args 方法的参数列表
             */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            long start = System.currentTimeMillis();
            // 通过反射机制调用我们目标对象的方法
            Object o = method.invoke(target, args);
            long end = System.currentTimeMillis();
            System.out.println("耗时"+(end - start)+"毫秒");
            // 如果我们调用的方法有返回值,需要我们invoke方法传递一下返回值[继续返回]
            return o;
        }
    }
    
    • 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

    编写我们的测试程序

    package com.powernode.proxy.client;
    
    import com.powernode.proxy.service.OrderService;
    import com.powernode.proxy.service.OrderServiceImpl;
    
    import com.powernode.proxy.util.ProxyUtil;
    
    
    /**
     * @author Bonbons
     * @version 1.0
     */
    public class Client {
        public static void main(String[] args) {
            // 创建目标对象
            OrderService target = new OrderServiceImpl();
            // 创建代理对象
            /*
                我们通过newProxyInstance在内存中创建字节码文件并生成代理类对象
                    (1)ClassLoader loader 代表目标对象的类加载器
                    (2)Class[] interfaces 公共接口
                    (3)InvocationHandler h 处理调用器,包含增强代码的接口
             */
            Object proxyObj = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimeInvocationHandler(target));
            // 代理对象调用方法
            OrderService obj = (OrderService) proxyObj;
            obj.generate();
            obj.detail();
            obj.modify();
            // 调用有返回值的方法
            String name = obj.getName();
            System.out.println(name);
        }
    }
    
    • 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

    在这里插入图片描述

    • 为了在客户端程序创建代理对象看起来简单一点,我们可以封装一个工具类 ProxyUtil
    package com.powernode.proxy.util;
    
    import com.powernode.proxy.service.TimeInvocationHandler;
    
    import java.lang.reflect.Proxy;
    
    /**
     * @author Bonbons
     * @version 1.0
     */
    public class ProxyUtil {
        // 通过静态方法封装我们启用动态代理的那段代码,底层还是由JDK进行调用
        public static Object newProxyInstance(Object target){
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(), new TimeInvocationHandler(target));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    此处客户端创建代理对象的代码可以替换为: Object proxyObj = ProxyUtil.newProxyInstance(target);

    $$ CGLIB 动态代理

    • 既可以代理接口,也可以代理类,底层是由继承的方式实现的
    • 所以被代理的类不能被 final 修饰
    • 需求:演示如何使用CGLIB完成动态代理

    第一步,需要我们导入相关依赖

    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.3.0</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    第二步,准备一个目标类 UserService

    package com.powernode.proxy.service;
    
    /**
     * @author Bonbons
     * @version 1.0
     */
    public class UserService {
        // 登录验证方法
        public boolean login(String username, String password){
            if ("admin".equals(username) && "admin".equals(password)) {
                return true;
            }else{
                return false;
            }
        }
        // 退出登录
        public void logout(){
            System.out.println("系统正在退出...");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    第三步,编写我们的方法拦截器 TimeMethodInterceptor,类似于JDK中的调用处理器

    package com.powernode.proxy.service;
    
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    /**
     * @author Bonbons
     * @version 1.0
     */
    public class TimeMethodInterceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            long start = System.currentTimeMillis();
            // 此处执行的是代理对象的方法
            Object value = methodProxy.invokeSuper(o, objects);
            long end = System.currentTimeMillis();
            System.out.println("耗时"+(end - start)+"毫秒");
            return value;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 要实现 CGLIB 提供的方法拦截器的接口,在接口方法中去调用我们的目标方法以及实现其他功能
    • 接口方法有四个参数:
      • Object o 目标对象
      • Method method 目标方法
      • Object[] objects 目标方法调用传递的实参
      • MethodProxy methodProxy 代理方法

    第四步,编写我们的客户端程序 Client

    package com.powernode.proxy.client;
    
    import com.powernode.proxy.service.TimeMethodInterceptor;
    import com.powernode.proxy.service.UserService;
    import net.sf.cglib.proxy.Enhancer;
    
    /**
     * @author Bonbons
     * @version 1.0
     */
    public class Client {
        public static void main(String[] args) {
            // 创建字节码增强器[CGLIB 的核心对象,用来创建代理类]
            Enhancer enhancer = new Enhancer();
            // 目标对象是谁
            enhancer.setSuperclass(UserService.class);
            // 通过回调设置调用处理器 >> 方法拦截器接口 MethodInterceptor
            enhancer.setCallback(new TimeMethodInterceptor());
            // 创建代理对象
            UserService userServiceProxy = (UserService) enhancer.create();
            System.out.println("查看我们的代理对象: " + userServiceProxy);
            // 调用代理方法 --登录 --退出
            boolean login = userServiceProxy.login("admin", "admin");
            System.out.println(login ? "登录成功" : "登录失败");
            userServiceProxy.logout();
        }
    }
    
    • 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
    • 因为我们使用的是高版本的JDK,如果JDK版本超过1.8 需要进行额外的配置,否则会出现下面这样的报错
      在这里插入图片描述
    • 解决方法:
      在这里插入图片描述
    • –add-opens java.base/java.lang=ALL-UNNAMED
    • –add-opens java.base/sun.net.util=ALL-UNNAMED
  • 相关阅读:
    VL2 异步复位的串联T触发器
    一级缓存 + ORM + 持久化
    Request Body Search
    vue 在线预览PDF
    Spring中的面试题01
    腾讯mini项目-【指标监控服务重构】2023-08-11
    Design for failure常见的12种设计思想
    解决vue项目导出当前页Table为Excel
    ABAP 导入Excel表示例程序
    pychon假消息生产器
  • 原文地址:https://blog.csdn.net/qq_61323055/article/details/127909387