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

news2024/11/23 7:17:07

一、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>
    
  • 在数据库中创建一张表 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 +
                    '}';
        }
    }
    
  • 这个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;
        }
    }
    
  • 我们需要将 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>
    
  • 我们在测试方法中通过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);
}

通过运行结果,我们可以知道 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);
        }
    

$ 更新一条数据

@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);

    }

$ 删除一条数据

@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);
    }

在这里插入图片描述

$ 查询一条数据

@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);
    }

在这里插入图片描述

$ 查询全部数据

@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);
    }

在这里插入图片描述

$ 查询数据总量

@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);

    }

在这里插入图片描述

$ 批量添加

@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));
    }

在这里插入图片描述

$ 批量更新

@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));
    }

在这里插入图片描述

$ 批量删除

@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));
    }

在这里插入图片描述

$ 函数回调

@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);
    }

在这里插入图片描述

$ 使用德鲁伊连接池

  • 前面使用的是我们自己写的简易版的连接池,接下来我们配置一下德鲁伊数据库连接池
    • 第一步,需要导入相关的依赖
      <!--使用德鲁伊连接池-->
      <dependency>
      	<groupId>com.alibaba</groupId>
      	<artifactId>druid</artifactId>
      	<version>1.1.8</version>
      </dependency>
      
    • 第二步,在配置文件中进行配置
      <?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>
      
    • 第三步,可以使用了,我们以上面回调函数的测试代码为例进行测试

在这里插入图片描述

二、代理模式

  • 什么是代理模式?

用一个类代表另一个类的功能。属于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();
}

编写我们的接口实现类 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("订单已修改");
    }
}

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

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

public class Test {
    public static void main(String[] args) {
		// 创建实现类的对象
        OrderService orderService = new OrderServiceImpl();
        // 调用业务方法
        orderService.generate();
        orderService.detail();
        orderoService.modify();
    }
}

在这里插入图片描述

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

    • 方法一:我们直接在实现类的方法中的前后添加系统时间的获取,然后计算输出
      • 违背了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)+"毫秒");
    }
}

编写我们的测试程序

// 创建目标对象
OrderService target = new OrderServiceImpl();
// 创建代理对象
OrderService proxy = new OrderServiceProxy(target);
// 调用代理对象的方法
proxy.generate();
proxy.detail();
proxy.modify();

在这里插入图片描述

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

$ 动态代理

  • 什么是动态代理?

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

    • 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();
}

编写我们新的实现类

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";
    }
}

编写我们的调用处理器 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;
    }
}

编写我们的测试程序

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);
    }
}

在这里插入图片描述

  • 为了在客户端程序创建代理对象看起来简单一点,我们可以封装一个工具类 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));
    }
}

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

$$ CGLIB 动态代理

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

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

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>

第二步,准备一个目标类 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("系统正在退出...");
    }
}

第三步,编写我们的方法拦截器 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;
    }
}
  • 要实现 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();
    }
}
  • 因为我们使用的是高版本的JDK,如果JDK版本超过1.8 需要进行额外的配置,否则会出现下面这样的报错
    在这里插入图片描述
  • 解决方法:
    在这里插入图片描述
  • –add-opens java.base/java.lang=ALL-UNNAMED
  • –add-opens java.base/sun.net.util=ALL-UNNAMED

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/21165.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

livekit 简单上手教程

什么是livekit&#xff1f; livekit是一个开源的webrtc项目&#xff0c;基于pion实现了SFU架构的go程序。 它主要有以下几个特性&#xff1a; 可以水平扩展的sfu架构。有完整的sdk程序接口供开发人员使用。基于JWT进行身份验证&#xff0c;可与大部分系统进行权限集成。内置Tur…

SpringCloud学习笔记 - 分布式配置中心 - config

1. 简介 Spring Cloud Config是用来为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持的。 它由服务端和客户端构成&#xff1a; 其中&#xff0c;服务端也称为分布式配置中心&#xff0c;是一个独立的微服务应用&#xff0c;用来连接配置仓库、为客户端提供支持…

32.nacos配置文件中心,使用实例(springcloud)

1.为什么使用Nacos 配置文件中心1.如果在生产情况下&#xff0c;修改了配置文件&#xff0c;项目需要下架&#xff0c;重新打包&#xff0c;重新发布。这在单机模式下影响不会太大&#xff0c;但如果过在一个集群项目中就会照成很大的影响&#xff0c;需要停掉多个项目。因此&a…

字节研发之道

前言 字节的迅速崛起缔造了一个互联网的神话&#xff0c;堪称火箭般的蹿升。当阿里来到第十八个年头时&#xff0c;员工人数还不足6万。老牌互联网腾讯现在也不过11万左右人数。但字节发展到七个年头时&#xff0c;人数就突破了10万。曾经入职阿里是一票难求&#xff0c;但现在…

基于51单片机的音乐盒播放器proteus仿真

资料编号&#xff1a;092 下面是相关功能视频演示&#xff1a; 92-基于51单片机的音乐盒播放器proteus仿真&#xff08;源码仿真全套资料&#xff09;功能介绍&#xff1a;使用51单片机&#xff0c;采用蜂鸣器进行音乐播放&#xff0c;提供了音乐代码生成器软件&#xff0c;可…

JavaWeb运行环境安装教程以及各个安装包

文章目录安装包下载安装教程一、JDK安装1、下载2、安装3、配置环境4、验证环境配置二、IDEA安装下载链接三、MySQL安装1、下载2、安装四、Navicat安装五、Tomcat安装1、下载2、环境配置3、修改编码4、启动六、Maven安装1、下载2、环境配置3、新建本地仓库4、修改镜像5、添加IDE…

Design Compiler工具学习笔记(1)

本人做过FPGA设计的项目&#xff0c;后面想转 IC 设计方向。现在从 DC 工具的使用开始学起&#xff0c;DC 是新思科技的EDA软件&#xff0c;具体的安装见下面的文章&#xff1a; Synopsys EDA Tools 安装问题记录https://blog.csdn.net/qq_43045275/article/details/127630241…

5G无线技术基础自学系列 | CloudRAN架构

素材来源&#xff1a;《5G无线网络规划与优化》 一边学习一边整理内容&#xff0c;并与大家分享&#xff0c;侵权即删&#xff0c;谢谢支持&#xff01; 附上汇总贴&#xff1a;5G无线技术基础自学系列 | 汇总_COCOgsta的博客-CSDN博客 5G在核心网实现云化之后&#xff0c;更…

2.旋转的骰子(1)

1.动画——旋转的骰子 我们想用纯html 和CSS, 做一个旋转的筛子,骰子要有立体感,每个面上要有圆点,表示点数,并且骰子可以原地旋转。 2.分析需求——庖丁解牛

【DELM回归预测】基于matlab多元宇宙优化算法改进深度学习极限学习机数据回归预测【含Matlab源码 2230期】

⛄一、多元宇宙优化算法 MVO是Seyedali Mirjalili受到多元宇宙理论的启发提出来的元启发式优化算法。主要根据多元宇宙理论的3个主要概念-白洞、黑洞和虫洞&#xff0c;来建立数学模型。 MVO算法中的可行解对应宇宙&#xff0c;解的适应度对应该宇宙的膨胀率&#xff0c;在每…

【第三部分 | 移动端开发】2:流式布局

目录 | 移动端布局 | 流式布局&#xff08;百分比布局&#xff09; | 案例&#xff1a;京东搜索页相关知识点 | 移动端布局 | 流式布局&#xff08;百分比布局&#xff09; 介绍 核心思想&#xff1a;不使用固定布局&#xff0c;而是使用百分比 为了保护盒子的内容不因拉伸…

Spring AOP使用与原理

AOP介绍 SpringAOP核心概念 上述中已经出现的关键词有Advice(顶级的通知类/拦截器)、MethodInvocation(方法连接点)、MethodInterceptor(方法拦截器) SpringAOP在此基础上又增加了几个类&#xff0c;丰富了AOP定义及使用概念&#xff0c;包括 Advisor&#xff1a;包含通知(拦截…

基于密度的划分、DBSCAN(机器学习)

目录 居于密度的划分 DBSCAN算法 居于密度的划分 基于划分聚类和基于层次聚类的方法在聚类过程中根据距离来划分类簇&#xff0c;因此只能够用于挖掘球状簇。 为了解决这一缺陷&#xff0c;基于密度聚类算法利用密度思想&#xff0c;将样本中的高密度区域(即样本点分布稠密的…

图的概念(1)

图是什么&#xff1f; 首先&#xff0c;我们导入需要的包&#xff1f; import numpy as np import random import networkx as nx from IPython.display import Image import matplotlib.pyplot as plt 图的定义&#xff1f; 图表示物件与物件之间关系的数学对象&#xff0c;…

Metasploit入门用法

靶机介绍 Difficult&#xff1a; As always, it’s a very easy box for beginners. Goal&#xff1a; Get flag Download&#xff1a;https://www.vulnhub.com/entry/funbox-scriptkiddie,725/ 解题过程&#xff1a; 1.使用nmap进行端口扫描:Nmap -sV IP 2.使用search命令查…

yolov5加入分割头,多任务头

Yolov5同时进行目标检测和分割分割_MidasKing的博客-CSDN博客_yolov5分割 用YOLOv5ds训练自己的数据集,注意点!_用猪头过日子.的博客-CSDN博客 基于pytorch用yolov5算法实现目标检测与分割_无损检测小白白的博客-CSDN博客 原理 代码:

一种三自由度机器人的设计(CAD+Solidworks+文档)

目 录 摘 要 I Abstract II 第1章 绪论 1 1.1课题研究背景及其意义 1 1.2三自由度机器人的发展与现状 1 1.3三自由度机器人的原理和介绍 3 1.4三自由度机器人目前存在问题 4 1.5三自由度机器人的力控制问题 4 1.6三自由度机器人的发展趋势 5 第2章 总体技术方案及系统组成 7 2.…

[附源码]SSM计算机毕业设计电子病历信息管理系统JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

AVL的单旋和双旋—附图超详细

文章目录前言&#xff1a;AVL 的插入寻找插入位置更新平衡因子调整AVL右旋左旋左右双旋右左双旋完整代码前言&#xff1a; 我们知道二叉排序树的搜索效率很高&#xff0c;能达到(logn)的时间复杂度&#xff0c;但是当元素有序导致二叉搜索树变成了一个长条&#xff08;图左&am…

游戏优化之空间划分

使用的动机 定义&#xff1a;将对象根据它们的位置存储在数据结构中&#xff0c;来高效地定位对象。 在游戏中&#xff0c;AI向最近的敌人攻击是很常见的&#xff0c;但如果有很多单位的话&#xff0c;他们AI需要频繁的查找单位&#xff0c;然后在检测是不是距离最近的单位&a…