代理模式Demo

一、前情提要

1.目的

主要是实现一遍静态代理动态代理的简易代码
加深对Mybatis中的代理模式的理解

2.代理模式概念

  • 代理模式的最大特点就是,核心功能是由被代理对象自己去执行的,代理对象只是增加了代理功能,在被代理对象的核心功能的基础上进一步封装,以实现更强大的功能
  • 代理模式的编程风格是面向接口编程: 成员变量、方法入参、初始化构造器的参数、调用核心功能的方法等,都是通过接口
  • 代理分为静态代理动态代理
    • 静态代理可以自由切换被代理对象,实现某个被代理功能的强化,在这一点上比较灵活,但是对被代理功能的强化和封装方面是固定的,无法实现动态强化被代理的功能
      • 如果业务功能增加了,那么代理对象对被代理对象功能的封装也需要重新编写,在这一点上非常局限,由此引出了动态代理
    • 动态代理分为JDK动态代理GCLib动态代理
      • JDK动态代理使用的是JDK提供的Proxy工具类,基于反射,实现动态代理
        • 特点是代理对象不需要实现业务功能接口,就能够去代理其他对象,但只能使用被代理对象实现了接口中的业务功能,被代理对象自己的方法无法调用
      • GCLib动态代理使用的是字节码框架ASM,对字节码文件进行代理
        • 又称为子类代理,通过动态的在内存中构建子类对象,重写父类的方法进行代理功能的强化
        • 如果被代理对象没有实现接口,则只能通过CGLib动态代理实现功能强化
        • 特点是代理对象不需要实现业务功能接口,就能够去代理其他对象,并且可以代理目标所有的功能方法
        • 被代理对象的类不能是final,否则报错
        • 目标对象的方法不能是final/static修饰,那么方法不会被拦截。静态没有多态,final不能修改
        • Cglib采用底层的字节码技术,在子类中采用方法拦截的技术,拦截父类指定方法的调用,并顺势植入代理功能的代码

      二、项目相关

    1.项目目录的层次结构

    在这里插入图片描述

2.项目相关的依赖:pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>cn.kbkwwk.mybatis</groupId>
  <artifactId>dynamic-demo</artifactId>
  <version>1.0-SNAPSHOT</version>


  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>

<!--    CGLib动态代理功能的依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.3.18</version>
    </dependency>

  </dependencies>

</project>

3.代理功能实现的接口:Service.java

package cn.kbkwwk.mybatis.service.api;

/**
 * ClassName: Service
 * Package: cn.kbkwwk.mybatis.service.api
 * Description:
 *
 * @Date: 2022-05-30 3:06 星期一
 * @Author: Kevin
 */
public interface Service {
    void sing();
    void show();
}

4.被代理的目标一:SuperStarLiu.java

注意:不论是静态代理还是动态代理,被代理的目标都需要去实现核心功能接口

package cn.kbkwwk.mybatis.service.impl;

import cn.kbkwwk.mybatis.service.api.Service;

/**
 * ClassName: SuperStarLiu
 * Package: cn.kbkwwk.mybatis.dynamic
 * Description:
 *
 * @Date: 2022-05-30 3:03 星期一
 * @Author: Kevin
 */
public class SuperStarLiu implements Service {

    @Override
    public void sing() {
        System.out.println("我是刘德华,我在唱歌...");
    }

    @Override
    public void show() {
        System.out.println("我是刘德华,我在表演...");
    }
}

5.被代理的目标二:SuperStarZhou.java

注意:不论是静态代理还是动态代理,被代理的目标都需要去实现核心功能接口

package cn.kbkwwk.mybatis.service.impl;

import cn.kbkwwk.mybatis.service.api.Service;

/**
 * ClassName: SuperStarZhou
 * Package: cn.kbkwwk.mybatis.dynamic
 * Description:
 *
 * @Date: 2022-05-30 3:03 星期一
 * @Author: Kevin
 */
public class SuperStarZhou implements Service {

    @Override
    public void sing() {
        System.out.println("我是周杰伦,我在唱歌...");
    }

    @Override
    public void show() {
        System.out.println("我是周杰伦,我在表演...");
    }
    
     /**
     * 非接口方法,即被代理对象自身的方法
     */
    public void dance() {
        System.out.println("我是周杰伦,我在跳舞...");
    }
}

6.静态代理对象的类:StaticAgent.java

注意:静态代理对象需要实现与被代理对象一样的接口,通过接口的引用,在被代理对象核心功能的基础上进一步封装强化功能,再去调用,实现功能的强化
优点是:切换被代理对象容易,但是当被代理对象的功能需要拓展,则代理对象的代码也需要同步修改,在这一点上比较局限,由此引申出动态代理

package cn.kbkwwk.mybatis.dynamic;

import cn.kbkwwk.mybatis.service.api.Service;

/**
 * ClassName: StaticAgent
 * Package: cn.kbkwwk.mybatis.dynamic
 * Description:
 *
 * @Date: 2022-05-30 2:52 星期一
 * @Author: Kevin
 */
public class StaticAgent implements Service{
//    被代理的接口
    private Service target;

    public StaticAgent() {
    }

    public StaticAgent(Service target) {
        this.target = target;
    }

    public Service getTarget() {
        return target;
    }

    public void setTarget(Service target) {
        this.target = target;
    }

    @Override
    public void sing() {
        System.out.println("我是静态代理对象");
        System.out.println("我在做前置的协调工作");
//        核心功能还是由接口引用被代理对象,被代理对象去执行核心功能
        target.sing();
        System.out.println("我在做收尾的协调工作");
    }

    @Override
    public void show() {
        System.out.println("我是静态代理对象");
        System.out.println("我在做前置的协调工作");
//        核心功能还是由接口引用被代理对象,被代理对象去执行核心功能
        target.show();
        System.out.println("我在做收尾的协调工作");
    }
}

7.JDK动态代理对象的类:JDKDynamicAgentFactory.java

JDK动态代理,不需要实现功能接口。用Proxy工具类,指定被代理的对象,在创建代理实例时,获取被代理对象调用的方法,进行封装强化。
优点是:实现了动态代理,当代理对象功能拓展后,不需要再修改代码
缺点是:实现被代理功能强化,是通过接口去引用根据被代理对象创建的实例,调用方法实现的,因此,只能代理那些被代理对象实现了接口的功能,无法代理被代理对象自己的一些方法(非接口中的方法)

package cn.kbkwwk.mybatis.dynamic;

import cn.kbkwwk.mybatis.service.api.Service;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * ClassName: JDKDynamicAgentFactory
 * Package: cn.kbkwwk.mybatis.dynamic
 * Description:
 *
 * @Date: 2022-05-30 11:30 星期一
 * @Author: Kevin
 */
public class JDKDynamicAgentFactory {

    //    被代理的接口
    private Service target;

    public JDKDynamicAgentFactory() {
    }

    public JDKDynamicAgentFactory(Service target) {
        this.target = target;
    }

    public Service getTarget() {
        return target;
    }

    public void setTarget(Service target) {
        this.target = target;
    }

    /**
     * 获取JDK代理对象
     */
    public Object getJDKProxyAgent(){
        Class<? extends Service> clazz = target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("我是JDK动态代理对象");
                System.out.println("我在做前置的协调工作");
//                Object proxy为被代理的对象本身,如果这里填proxy,会递归
                Object methodValue =  method.invoke(target,args);
                System.out.println("我在做收尾的协调工作");
                return methodValue;
            }
        });
    }
}

8.CGLib动态代理对象的类:CGLibDynamicAgentFactory.java

CGBLib动态代理,引入了spring-core依赖,里面包含了需要的cglib-jar
又称为子类代理,可以简单大致理解为继承重写父类方法,以此实现功能的强化
相比于JDK动态代理,cglib动态代理需要导入依赖
优点是:可以代理被代理对象的所有非final/static方法

package cn.kbkwwk.mybatis.dynamic;

import cn.kbkwwk.mybatis.service.api.Service;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * ClassName: CGLibDynamicAgentFactory
 * Package: cn.kbkwwk.mybatis.dynamic
 * Description:
 * 需要引入spring-core依赖
 *
 * @Date: 2022-05-30 12:10 星期一
 * @Author: Kevin
 */
public class CGLibDynamicAgentFactory implements MethodInterceptor {
    private Service target;

    public CGLibDynamicAgentFactory(Service target) {
        this.target = target;
    }

    public Service getTarget() {
        return target;
    }

    public void setTarget(Service target) {
        this.target = target;
    }

    public CGLibDynamicAgentFactory() {
    }

    /**
     * 拦截并重写父类的方法,强化代理功能
     *
     * @param proxy
     * @param method
     * @param args
     * @param methodProxy
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("我是JDK动态代理对象");
        System.out.println("我在做前置的协调工作");
//                Object proxy为被代理的对象本身,如果这里填proxy,会递归
        Object methodValue = method.invoke(target, args);
        System.out.println("我在做收尾的协调工作");
        return methodValue;
    }

    /**
     * 创建并返回子类代理对象
     *
     * @return
     */
    public Object getCGLibDynamicAgent() {
//        使用工具类
        Enhancer en = new Enhancer();
//        设置父类
        en.setSuperclass(target.getClass());
//        设置回调函数,调用的是intercept拦截并强化后的方法
        en.setCallback(this);
//        创建并返回子类代理对象
        return en.create();
    }
}

9.测试类:TestStaticStaticAgent.java

package cn.kbkwwk.mybatis;

import cn.kbkwwk.mybatis.dynamic.CGLibDynamicAgentFactory;
import cn.kbkwwk.mybatis.dynamic.JDKDynamicAgentFactory;
import cn.kbkwwk.mybatis.dynamic.StaticAgent;
import cn.kbkwwk.mybatis.service.api.Service;
import cn.kbkwwk.mybatis.service.impl.SuperStarLiu;
import cn.kbkwwk.mybatis.service.impl.SuperStarZhou;
import org.junit.Test;

/**
 * ClassName: TestStaticStaticAgent
 * Package: cn.kbkwwk.mybatis
 * Description:
 *
 * @Date: 2022-05-30 3:04 星期一
 * @Author: Kevin
 */
public class TestStaticStaticAgent {
    @Test
    public void testStaticAgent(){
        System.out.println("测试一:");
//        创建接口的实现类对象,即被代理对象
        Service liu = new SuperStarLiu();
//        因为代理对象实现了Service接口,所以new出来的代理对象不需要再强转类型
        Service agent =  new StaticAgent(liu);
//        调用代理对象封装后的代理功能
        agent.sing();

        Service zhou = new SuperStarZhou();
//        当需要被代理的对象换了,不再需要修改代理功能封装的方法,只需要将被代理对象传给代理对象
        Service agent1 =  new StaticAgent(zhou);
        agent1.sing();
    }

    @Test
    public void testStaticAgent1(){
        System.out.println("测试二:");
//        接口类型引用实现的子类对象,只能调用实现了接口中指定的方法,子类自己的方法(没有在接口中声明),无法通过接口引用调用
//        当代理对象不使用接口类型引用时,功能会更加强大
        StaticAgent agent = new StaticAgent();
        agent.setTarget(new SuperStarLiu());
        agent.sing();
        agent.setTarget(new SuperStarZhou());
        agent.show();
    }


    @Test
    public void testJDKDynamicAgent(){
        System.out.println("测试三:");
//        创建代理对象工厂
        JDKDynamicAgentFactory agentFactory = new JDKDynamicAgentFactory();
//        创建被代理对象
        SuperStarLiu liu = new SuperStarLiu();
//        设置需要代理的对象
        agentFactory.setTarget(liu);
//        用工厂对象创建创建代理对象
//        注意返回的是Object类型,因此如果要使用被代理对象的方法,只需要强转成实现的接口类型,就可以使用被代理对象中的功能
         Service jdkProxyAgent = (Service)agentFactory.getJDKProxyAgent();
        jdkProxyAgent.sing();

        SuperStarZhou zhou = new SuperStarZhou();
        agentFactory.setTarget(zhou);
        Service jdkProxyAgent1 = (Service) agentFactory.getJDKProxyAgent();
        jdkProxyAgent1.show();
    }


    @Test
    public void testCGLibDynamicAgent(){
        System.out.println("测试四:");
//        创建CGLib代理工程类
         CGLibDynamicAgentFactory cgLibDynamicAgentFactory = new CGLibDynamicAgentFactory();
//         设置需要被代理的对象
        cgLibDynamicAgentFactory.setTarget(new SuperStarLiu());
//        根据工厂对象获取CGLib代理对象,并强转为被代理对象的类类型
        SuperStarLiu agent = (SuperStarLiu) cgLibDynamicAgentFactory.getCGLibDynamicAgent();
        agent.show();


        cgLibDynamicAgentFactory.setTarget(new SuperStarZhou());
        SuperStarZhou agent1 = (SuperStarZhou) cgLibDynamicAgentFactory.getCGLibDynamicAgent();
        agent1.show();
//
        agent1.dance();
    }
}

10.运行测试程序的结果

测试一:
测试一:
我是静态代理对象
我在做前置的协调工作
我是刘德华,我在唱歌...
我在做收尾的协调工作
--------------------------------
我是静态代理对象
我在做前置的协调工作
我是周杰伦,我在唱歌...
我在做收尾的协调工作
--------------------------------
测试二:
测试二:
我是静态代理对象
我在做前置的协调工作
我是刘德华,我在唱歌...
我在做收尾的协调工作
--------------------------------
我是静态代理对象
我在做前置的协调工作
我是周杰伦,我在表演...
我在做收尾的协调工作
--------------------------------
测试三:
测试三:
我是JDK动态代理对象
我在做前置的协调工作
我是刘德华,我在唱歌...
我在做收尾的协调工作
--------------------------------
我是JDK动态代理对象
我在做前置的协调工作
我是周杰伦,我在表演...
我在做收尾的协调工作
--------------------------------
测试四:
测试四:
我是CGLib动态代理对象
我在做前置的协调工作
我是刘德华,我在表演...
我在做收尾的协调工作
--------------------------------
我是CGLib动态代理对象
我在做前置的协调工作
我是周杰伦,我在表演...
我在做收尾的协调工作
--------------------------------
我是CGLib动态代理对象
我在做前置的协调工作
我是周杰伦,我在跳舞...
我在做收尾的协调工作
--------------------------------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值