一、面向对象之多态

多态是同一个行为具有多个不同表现形式或形态的能力

1.1 实现多态的关键是接口

  • 接口是一个抽象的类型,是提供相应方法的定义
  • 实现类是一个接口得具体实现,要实现每一个接口方法的功能

1.2 具体实现

sample1.system 功能封装的包

//Lanuague.interface
/**
 * 接口类,实现不同的动作
 */
public interface Language {
    //模拟各国语言的方法定义
    public void voice();
}
//Chinese.class
//实现类
public class Chinese implements Language {
    @Override
    public void voice() {
        System.out.println("中国");
    }
}
//French.class
//实现类
public class French implements Language{
    @Override
    public void voice() {
        System.out.println("法国");
    }
}
//English.class
//实现类
public class English implements Language{
    @Override
    public void voice() {
        System.out.println("英语");
    }
}
//CustomerService.class
public class CustomerService {
    /**
     * 封装实现类
     * 多态的实现,不同的参数,返回不同的动作
     * @param areaCode
     * @return
     */
    public Language contact(int areaCode){
        if(areaCode == 86){
            return new Chinese();
        }
        else if(areaCode == 33){
            return new French();
        }
        else{
            return new English();
        }
    }
}

sample1 包外调用封装包,实现多态

//Customer.class
public class Customer {
    /**
     * 在系统外去调用系统中的方法,不需要了解实现细节
     * 只需要传入不同的参数,就能实现不同的功能
     * 多态:通过调用同一个动作,传入不同的参数,产生不同的行为
     * @param args
     */
    public static void main(String[] args) {
        CustomerService customerService = new CustomerService();
        //接口本身不能被实例化,需要指向相应的实现类
        //而不同的参数就能返回不同的实现类,从而实现多态
        Language language = customerService.contact(86);
        language.voice();
    }
}

以上就是多态的基本实现

1.3 多态的优势

扩展性强,父类的变量可以赋值不同的子类对象,而调用不同的子类重写的方法

二、代理模式概念及分类

2.1 代理模式基本概念

为其他对象提供一种代理,以控制对这个对象的访问。代理对象起到中介的作用,可去掉功能服务或增加额外的服务。

常见的几种代理模式:

  • 远程代理(remote proxy)

为不同地理的对象,提供局域网代表对象

简单应用:通过远程代理可以监控各个商铺,使之能直观的了解店内信息

  • 虚拟代理(virtual proxy)

根据需要将资源消耗很大的对象进行延迟,真正需要的时候进行创建

简单应用:网页上浏览新闻时,若其中的图片过大,会导致整篇文章无法阅读;使用虚拟代理,就可以在图片加载时使用虚拟的图片代替,加载完后再使用原来的图片,期间可以正常阅读

  • 保护代理

控制对一个对象的访问权限

简单应用:一个网站需要登录后才能使用一些功能,未登录前只能使用有限的功能

  • 智能引用代理

提供对目标对象额外的服务

例如:火车站售票站,提供了火车站额外的一些服务

三、常见代理模式原理

3.1 以智能引用代理为例(静态代理)

两种实现方式:

  • 静态代理

代理和被代理对象在代理之前是确定的。它们都实现相同的接口或继承相同的抽象类

  • 动态代理

(1)未提供代理模式的实现:

//Moveable.class
/**
 * 汽车行驶接口,提供方法
 */
public interface Moveable {
    void move();
}
//Car1.class
/**
 * 汽车行驶实现类
 */
public class Car1 implements Moveable{
    @Override
    public void move() {
        long start = System.currentTimeMillis();
        System.out.println("汽车开始行驶...");
        try {
            Thread.sleep(new Random().nextInt(1000));  //1000毫秒==1秒
            System.out.println("汽车行驶中...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("汽车结束行驶... 行驶时间为:"+(end-start) + "毫秒");

    }
}
//Test.class
/**
 * 汽车行驶测试类
 */
public class Test {
    public static void main(String[] args) {
        Car1 car = new Car1();
        car.move();
    }
}

image-20220411170551791

(2)使用继承方式来实现:

//Moveable.class
//同样的接口类
//Car2.class
/**
 * 为了体现出静态代理的区别,只保留汽车行驶核心的部分
 */
public class Car2 implements Moveable{
    @Override
    public void move() {
        try {
            Thread.sleep(new Random().nextInt(1000));  //1000毫秒==1秒
            System.out.println("汽车行驶中...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
//Test2.class
/**
 * 继承了Car2类
 */
public class Test2 extends Car2{
    /**
     * 调用父类的方法,并添加一些功能
     */
    @Override
    public void move() {
        long start = System.currentTimeMillis();
        System.out.println("汽车开始行驶...");
        super.move(); //super关键字,引用父类中的成员
        long end = System.currentTimeMillis();
        System.out.println("汽车结束行驶... 行驶时间为:"+(end-start) + "毫秒");
    }
}
//Test.class
/**
 * 汽车行驶测试类,最终的效果一致
 */
public class Test {
    public static void main(String[] args) {
        //继承方式
        Moveable m = new Test2();
        m.move();
    }
}

(3)使用聚合方式实现

简而言之就是在一个类中调用另外一个对象

//Moveable.class
//还是相同的接口
//Car2.class
//一样的实现类
//Car3.class
/**
 * 聚合方式,实现静态代理
 * 在类中调用另一个对象的move方法,结果一致
 */
public class Car3 implements Moveable{
    private Moveable car;
    public Car3(Moveable m) {
        super(); //调用父类的构造方法
        this.car = m;
    }

	//增加新的功能
    @Override
    public void move() {
        long start = System.currentTimeMillis();
        System.out.println("汽车开始行驶...");
        this.car.move();
        long end = System.currentTimeMillis();
        System.out.println("汽车结束行驶... 行驶时间为:"+(end-start) + "毫秒");
    }
}

3.2 聚合比继承更适合代理模式

使用继承的方式:

​ 假设我们需要添加新的功能或者修改功能的实现顺序:

​ 每次都需要重新新建一个类,去重新构造这些方法,所以很麻烦

例如:

我先构造一个时间代理类,实现了记录时间的功能

接下来我就只能将时间代理类放在最里层

而其他的代理需要继承时间代理后,再去实现其他的功能

//Car2.class
//和前面一样,实现的是汽车行驶的功能
//Test2.class
public class Test2 extends Car2{
    /**
     * 调用父类的方法,并添加了记录时间的功能
     * 继承方式
     */
    @Override
    public void move() {
        long start = System.currentTimeMillis();
        System.out.println("汽车开始行驶...");
        super.move();  //汽车核心功能
        long end = System.currentTimeMillis();
        System.out.println("汽车结束行驶... 行驶时间为:"+(end-start) + "毫秒");
    }
}
//Car3.class
//接下来我想要实现日志和时间的代理
//就需要再次继承,并且两者顺序已经不能改变
//如果改变就需要重写Test.class 类
public class Car3 extends Test2{
    @Override
    public void move() {
        System.out.println("日志记录开始...");
        super.move();
        System.out.println("日志记录结束...");
    }
}

使用聚合的方式:

​ 这种方法相比继承更加灵活、方便

//CarTimeProxy.class
//汽车行驶时间的代理
public class CarTimeProxy implements Moveable{
    private Moveable car;
    public CarTimeProxy(Moveable m) {
        this.car = m;
    }


    @Override
    public void move() {
        long start = System.currentTimeMillis();
        System.out.println("汽车开始行驶...");
        this.car.move();
        long end = System.currentTimeMillis();
        System.out.println("汽车结束行驶... 行驶时间为:"+(end-start) + "毫秒");
    }
}
//CarLogProxy.class
//汽车行驶日志的代理
public class CarLogProxy implements Moveable{
    private Moveable car;
    public CarLogProxy(Moveable car) {
        this.car=car;
    }

    @Override
    public void move() {
        System.out.println("日志记录开始...");
        car.move();
        System.out.println("日志记录结束...");
    }
}
//Test.class
//实现类
//不同的代理类分开实现,**最后实现的顺序根据传入的顺序调整**
public class Test {
    public static void main(String[] args) {
        //先实现日志 -> 时间 -> 行驶
        //调整传入参数的顺序,就能调整代理的顺序
       Car2 car = new Car2(); //汽车行驶类
       CarTimeProxy carTimeProxy = new CarTimeProxy(car); //汽车时间代理
       CarLogProxy carLogProxy = new CarLogProxy(carTimeProxy); //汽车日志代理
       carLogProxy.move();
    }
}

image-20220413174542205

3.3 JDK动态代理

根据前面的汽车行驶代理,看上去更方便了,但是它只能实现汽车的代理

那么不同的对象就需要不同的代理类来实现,这样代理类就越来越多了

那么引出一个问题:

​ 能不能动态产生代理,实现对不同对象、方法的代理?(火车、飞机等)

Java动态代理类,位于java.lang.reflect包下,一般涉及到一下两个类:

  • Interface InvocationHandler

该接口仅定义了一个方法 public Object invoke(Object obj,Method method,Object[] args)

在实际使用时,第一个参数obj指的是代理类,method是被代理的方法,args为该方法的参数数组

这个抽象方法在代理类中动态实现。

  • Proxy

该类即为动态代理类

static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)

返回代理类的一个实例,返回后的代理类可以当做被代理类来使用(可使用被代理类的在接口中声明的方法)

//TimeHandler.class
//事件处理器
public class TimeHandler implements InvocationHandler {

    private Object target;  //我们传入的代理类,一切类的父类都是Object

    public TimeHandler(Object target) {
        super();
        this.target = target;
    }

    /**
     *
     * @param proxy 被代理的对象
     * @param method 被代理对象的方法
     * @param args 方法的参数
     * @return Object 方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println("汽车开始行驶...");
        method.invoke(target); //动态调用方法
        long end = System.currentTimeMillis();
        System.out.println("汽车结束行驶... 行驶时间为:"+(end-start) + "毫秒");

        return null;  //car2.move()方法没有返回值,return null
    }
}
//Test.class
/**
 * JDK动态代理测试类
 */
public class Test {
    public static void main(String[] args) {
        Car2 car2 = new Car2();
        InvocationHandler h = new TimeHandler(car2); //事件处理器
        Class<?> cls = car2.getClass(); //Class<?> 

        /**
         * loader 被代理类的类加载器
         * interfaces 实现的接口
         * h 事件处理器 (invocationHandler)
         * 返回的是一个动态代理对象
         */
        Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(),h);
        m.move();
    }
}

总结:

所谓动态代理(Dynamic Proxy)是这样一种 Class:

它是在运行时生成的Class(Proxy类)

该Class需要实现一组interface

使用动态代理类时,必须实现InvocationHandler接口(由它来实现我们需要的功能)

3.4 CGLIB动态代理

JDK动态代理与CGLIB动态代理的区别

​ JDK动态代理:

​ 1、只能代理实现了接口的类

​ 2、没有实现接口的类不能使用JDK动态代理

​ CGLIB动态代理:

​ 1、针对类来实现代理

​ 2、对指定目标类产生一个子类,通过方法拦截技术拦截所有父类方法调用

可能由于高版本JDK的原因,基于子类的动态代理会产生报错:(还未解决)

module java.base does not “opens java.lang” to unnamed module @722c41f4

//Train.class
//被代理类
public class Train {
    public void move(){
        System.out.println("火车行驶中");
    }
}
//CglibProxy.class
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 代理类
 */
public class CglibProxy implements MethodInterceptor {

    //传入的目标类
    private Object o ;

    //返回被代理对象的子类
    public Object getProxy(Object o){
        this.o = o;

        Enhancer enhancer = new Enhancer(); //用来生成代理的工厂类

        //生成目标类的子类,来增强(所以需要知道目标类)
        //设置被代理类的类型type
        enhancer.setSuperclass(o.getClass());

        //回调,应该 是回调调用函数,调用的函数被intercept函数拦截重写
        enhancer.setCallback(this);

        //动态生成代理类
        return enhancer.create();

    }

    /**
     *描述如何增强父类
     * @param o 代理对象 引用
     * @param method 被代理对象的方法描述
     * @param objects 方法的参数
     * @param methodProxy 代理对象对目标对象的方法的描述
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //增强代码

        //1. 利用代理类来实现增强代码
        System.out.println("日志记录开始...");

        //代理类的方法对象调用父类的方法
        Object i = methodProxy.invokeSuper(o,objects);

        System.out.println("日志记录结束...");
//        Train t = new Train();
//        method.invoke(t);

        /**
         * 2. 利用被代理类实现增强代码
         * System.out.println("日志记录开始...");
         * Train t = new Train();
         * method.invoke(t);
         * System.out.println("日志记录结束...");
         */

        return i;
    }
}
//Client.class
//测试类
public class Client {
    public static void main(String[] args) {

        CglibProxy cglibProxy = new CglibProxy();

        //返回的是代理类的对象
        Train t = new Train();
        Train train = (Train) cglibProxy.getProxy(t);
        train.move();
    }
}

四、自定义模拟JDK动态代理的实现

4.1 JDK动态代理的基本实现

首先理一下JDK动态代理的逻辑:

  • 实现功能:通过Proxy类的newProxyInstance方法返回代理对象
  • 首先,声明一段源码(动态产生代理)
  • 然后,编译源码(JDK Compiler API),产生新的类(代理类)
  • 其次,将这个类load到内存当中,产生一个新的对象(代理对象)
  • 最后,返回这个代理对象
//Proxy.java
/**
 * 自定义Proxy类的newProxyInstance方法,返回一个代理对象
 */
public class Proxy {
    //传入infce 接口,动态产生代理类
    public static Object newProxyInstance(Class infce)throws Exception{

        //由接口传入方法,动态产生代理类
        String methodStr ="";
        for (Method m:infce.getMethods()
             ) {
            methodStr +="    @Override\r\n" +
                    "    public void "+m.getName()+"() {\r\n" +
                    "        long start = System.currentTimeMillis();\r\n" +
                    "        System.out.println(\"汽车开始行驶...\");\r\n" +
                    "        this.car."+m.getName()+"();\r\n" +
                    "        long end = System.currentTimeMillis();\r\n" +
                    "        System.out.println(\"汽车结束行驶... 行驶时间为:\"+(end-start) + \"毫秒\");\r\n" +
                    "    }\r\n";
        }

        String str = "package com.my.jdkproxy;\r\n" +
                "public class $Proxy0 implements "+infce.getName()+"{\r\n" +
                "    private "+infce.getName()+" car;\r\n" +
                "    public $Proxy0("+infce.getName()+" m) {\r\n" +
                "        super();\r\n" +
                "        this.car = m;\r\n" +
                "    }\r\n" +
                methodStr +
                "}";

        //将动态产生的代理类写入文件
        String filename=System.getProperty("user.dir")+"/src/com/my/jdkproxy/$Proxy0.java";
        File file = new File(filename);
        FileUtils.writeStringToFile(file,str);

        //为了编译上述文件
        // 拿到编译器
        JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
        //文件管理者
        StandardJavaFileManager fileMgr =
                javaCompiler.getStandardFileManager(null,null,null);
        //获取文件
        Iterable units = fileMgr.getJavaFileObjects(filename);
        //编译任务
        /**
         * out 文件输出的位置
         * fileManeger 文件管理者
         * diagnosticListener 监听器
         * options
         * classes
         * compilationUnits 需要编译的文件
         * return 返回一个编译任务
         */
        JavaCompiler.CompilationTask t = javaCompiler.getTask(null,fileMgr,null,null,null,units);
        //进行编译
        t.call();
        fileMgr.close();

        //将编译好的文件 load 到内存当中
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        Class c = cl.loadClass("com.my.jdkproxy.$Proxy0");

        //产生代理类
        Constructor ctr = c.getConstructor(infce);

        //返回代理对象
        return ctr.newInstance(new CarService());
    }
}
//Client.java
//测试类
public class Client {
    public static void main(String[] args) throws Exception {
        Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class);
        m.move();
    }
}

到这基本实现了动态代理(任意对象的任意方法),但是还无法修改业务逻辑,那么如何做呢?

image-20220416164604865

4.2 动态代理实现添加 InvocationHandler

//interface InvocationHandler.java
public interface InvocationHandler {
    //对某个对象的方法进行处理

    /**
     *
     * @param o 传递的是代理对象
     * @param m 传递方法(还可以传递方法参数)
     */
    public void invoke(Object o, Method m) throws InvocationTargetException, IllegalAccessException;
}
//Proxy.java
**
 * 自定义Proxy类的newProxyInstance方法,返回一个代理对象
 */
public class Proxy {
    //传入infce 接口,动态产生代理类
    public static Object newProxyInstance(Class infce,InvocationHandler h)throws Exception{

        //由接口传入方法,动态产生代理类
        String methodStr ="";
        for (Method m:infce.getMethods()
             ) {
            methodStr +="    @Override\r\n" +
                    "    public void "+m.getName()+"() {\r\n" +
                    "try{\r\n"+
                    "Method md = "+infce.getName()+".class.getMethod(\""+
                                    m.getName()+"\");\r\n"+
                    " h.invoke(this,md);\r\n"+
                    "}catch(Exception e){e.printStackTrace();}\r\n"+
                    "    }\r\n";
        }

        String str = "package com.my.jdkproxy;\r\n" +
                "import java.lang.reflect.Method;\r\n"+
                "import com.my.jdkproxy.InvocationHandler;\r\n"+
                "public class $Proxy0 implements "+infce.getName()+"{\r\n" +
                "private InvocationHandler h;\r\n"+
                "    public $Proxy0(InvocationHandler h) {\r\n" +
                "        super();\r\n" +
                "        this.h = h;\r\n" +
                "    }\r\n" +
                methodStr +
                "}";

        //将动态产生的代理类写入文件
        String filename=System.getProperty("user.dir")+"/src/com/my/jdkproxy/$Proxy0.java";
        File file = new File(filename);
        FileUtils.writeStringToFile(file,str);

        //为了编译上述文件
        // 拿到编译器
        JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
        //文件管理者
        StandardJavaFileManager fileMgr =
                javaCompiler.getStandardFileManager(null,null,null);
        //获取文件
        Iterable units = fileMgr.getJavaFileObjects(filename);
        //编译任务
        /**
         * out 文件输出的位置
         * fileManeger 文件管理者
         * diagnosticListener 监听器
         * options
         * classes
         * compilationUnits 需要编译的文件
         * return 返回一个编译任务
         */
        JavaCompiler.CompilationTask t = javaCompiler.getTask(null,fileMgr,null,null,null,units);
        //进行编译
        t.call();
        fileMgr.close();

        //将编译好的文件 load 到内存当中
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        Class c = cl.loadClass("com.my.jdkproxy.$Proxy0");

        //产生代理类
        Constructor ctr = c.getConstructor(InvocationHandler.class);

        //返回代理对象
        return ctr.newInstance(h);
    }
}
//Client.java
//测试类
public class Client {
    public static void main(String[] args) throws Exception {
        CarService c = new CarService();
        InvocationHandler h =new TimeHandler(c);
        Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class,h);
        m.move();
    }
}

这里实现了InvocationHandler接口(也就是事务处理器),相比较前一个未实现事务处理器的Proxy类,这一个也就能实现业务代码的增加

那么到这里,JDK动态代理的大致流程我们都知道了

1、我们需要动态代理产生代理类,就需要实现Proxy类,其返回了一个代理类

2、而为了实现业务逻辑,我们需要一个类来实现InvocationHandler,这个类在实现被代理类的功能下,增加了业务逻辑

3、我们将事务处理器以及被代理类的接口传入Proxy类中,最后就会产生并编译代理类,最后返回给我们一个代理对象

五、总结

总结一下本篇文章所学到的知识:

  • 代理概念、分类及应用场景

  • 静态代理(继承、聚合)

  • JDK动态代理(动态代理不仅可以使用JDK动态代理,还有其他的,如:cglib动态代理)

    JDK动态代理需要被代理类实现接口类,cglib动态代理是基于一个普通的类来实现动态代理

  • 模拟JDK动态代理的实现过程

    动态代理是有实际意义的:当我们使用一个包中的类的某个方法(无法修改),就需要使用代理模式

    这种方式也叫做AOP(面向切面编程):

    ​ 在不改变原有类的基础上,增加一些额外的业务逻辑