勇者为了吃块肉竟然学了《JDK动态代理》

目录

一、静态代理

    继承:

    聚合

二、jdk动态代理

    手写小jdk动态代理

    你猜勇者最后吃到肉了没?



今天要学习的是:

java开发者必会的设计模式之一:代理模式

首先代理这个词很容易理解,日常生活我们也总是寻求代理去解决问题。先看一看两个名词:

代理对象:增强后的对象

目标对象:被增强的对象


一、静态代理

     继承:

我们可以使用继承来实现一种代理。实现方式:用代理对象继承我们的目标对象,然后重写目标对象的方法。

现在我们来写一个《苦B勇者吃肉记》(为了简化内容,代码规范格式就不管了)

public class Person {
    public String name;
    public String gender;
    public int money;
    public Map <String ,Integer>  AttributeValue;
    public List<String> smallBag;
    public Person(){};
  public Person(String name, String gender){
        this.name = name;
        this.gender = gender;
        this.money = 500;
        this.smallBag = new ArrayList<>(5);
        this.AttributeValueMap = new HashMap<>();
        this.AttributeValueMap.put("HP",100);
        this.AttributeValueMap.put("饱腹感",20);
    }
    public void eat(String food) {
        System.out.println("吃了"+food+"。");
        eated(food);
    }
     public void eated(String food){
      if(food.equals("生肉")){
          System.out.println("勇者吃了生肉之后感觉不太好,就去世了。");
      }else if(food.equals("熟肉")){
          System.out.println("味道棒极了,勇者神清气爽,活蹦乱跳,饱腹感+10 开心度+10");
      }
    }
}

上面就是我们的主角:苦B勇者,的各个属性;上帝赐给了勇者一块只卖34块5毛一斤的猪腿肉。

勇者看着这块肉,陷入了沉思:

public class Main {
    public static void main(String[] args) throws Exception {
         Person person = new Person("苦B勇者","男");
         person.eat("生肉");
    }
}
/*
吃了生肉。
勇者吃了生肉之后感觉不太好,就去世了。
*/

勇者想到自己直接吃了这块生肉可能就活不过这篇博客了,幸运的是他在垃圾桶旁发现了一个(锅)!于是他制作了一个简单的炊具(代理对象):

public class MakingFoodProxy extends Person {
    public void eat(String food) {
        System.out.println("烹饪"+food+"...");
        food = "熟肉";
        System.out.println("吃了"+food+"。");
        eated(food);
    }
}

这个时候,神奇的事情发生了。

public class Main {
    public static void main(String[] args) throws Exception {
         Person makingFoodProxy = new MakingFoodProxy();
           makingFoodProxy.eat("生肉");
    }
}
/*
烹饪生肉...
吃了熟肉。
味道棒极了,勇者神清气爽,活蹦乱跳,饱腹感+10 开心度+10
*/

好了,相信看完的大家已经知道什么是静态代理的继承方式了。

但是这种方式是由明显的缺陷的。首先我们必须去继承目标对象,这点就很让人不爽了。不可能我来一个代理就继承一下吧。

而且java是单继承的,这个方法就难以扩展。

接下来我们使用一个好一点方式:

聚合

这里勇者写了一个接口,并且让勇者自己的Person类去实现这个接口,勇者从Person进化成了PersonDaoImpl。

public interface PersonDao {
    public void eat(String food);
}
public class PersonDaoImpl  implements PersonDao{
  //***重复代码不写了
}

勇者使用魔法:聚合--将代理类通过PersonDao依赖自己。实现eat方法中去写代理逻辑完了调用personDao的eat方法。

public class MakingFoodProxy implements PersonDao {

    private PersonDao personDao;

    public MakingFoodProxy(PersonDao personDao){
        this.personDao = personDao;
    }

    @Override
    public void eat(String food) {

        System.out.println("烹饪"+food+"...");
        food = "熟肉";
        personDao.eat(food);
    }
}

接着同样可以完成代理:

   public static void main(String[] args) throws Exception {

        PersonDao makingFoodProxy = new MakingFoodProxy(
                                new PersonDaoImpl("苦B勇者","男"));
           makingFoodProxy.eat("生肉");
    }

来回顾下勇者是怎么实现的:我们发现他的的目标对象和代理对象都实现了目标对象的接口。然后代理类将目标接口传进来,再实现代理逻辑,调用传入的接口的后续方法。

但是这样还是会有问题,勇者还是使用了很多类,类变多了管理起来很烦。那么勇者想能不能使用一个方法解决掉这个问题。

二、jdk动态代理

那么这就引入了jdk动态代理。jdk动态代理的使用很简单:如下

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

public class MyHandler implements InvocationHandler {

    private PersonDao personDao;
    public MyHandler(PersonDao personDao){
        this.personDao = personDao;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //前置处理逻辑
        before(args);
        Object invoke = method.invoke(personDao, args);
        //后置处理逻辑
        after();
        return invoke;
    }
    
    public void before( Object[] args){
        System.out.println("烹饪"+args[0]+"...");
        args[0] = "熟肉";
    }
    public void after(){}
}

public class Main {
    public static void main(String[] args) throws Exception {

        PersonDaoImpl personDao = new PersonDaoImpl("苦B勇者", "男");
        PersonDao personProxy = (PersonDao) Proxy.newProxyInstance(PersonDao.class.getClassLoader(), 
new Class[]{PersonDao.class}, new MyHandler(personDao));
        personProxy.eat("生肉");
    }
}

这里的前置和后置逻辑就是对目标对象的增强。

这里我们想到了事务,能不能使用这种方法实现?,再前置逻辑中开启事务,后置逻辑提交事务,中间逻辑是怎样还是怎样的。

从这里勇者仿佛快要领悟新的能力:AOP!

 

《勇者外传小插曲》

手写小jdk动态代理

勇者盲目自信,决定自己动手写一个小小的jdk动态代理。

勇者想怎么可以不使用很多类?勇者知道代理对象存在内存中,如果可以把java文件放在磁盘中,然后用到的时候就取出来一个代理对象那就好了。勇者观察自己造的锅,以后造东西也是遵循差不多的规则,有很多共性,也许可以抽取成一个模板。

于是勇者按照jdk动态代理的逻辑写了计划书:

1、先将一个代理类的文件模板制作好。(得到一个java文件)

2、把代理类文件变成calss文件。

3、用calssLoader加载磁盘上的calss文件。

4、通过魔法反射获得一个代理对象并返回。

勇者开始了苦逼的代码编写:



import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class ProxyUtil {
    //得到代理对象的文件
    public static Object newProxyInstance(Object target) throws Exception {
        //拼接内容
        String content = "";
        Class<?> targetInfo = target.getClass().getInterfaces()[0];
        String targetSimpleName = targetInfo.getSimpleName();
        //这边自己定了
        String packageContent = "package com.proxydemo;";
        String importContent = "import " + targetInfo.getName() + ";";
        String calssContnet = "public class $Proxy implements " + targetSimpleName + "{";
        String fieldContent = "private " + targetSimpleName + " target;";
        String constructorContent = "public $Proxy (" + targetSimpleName + " target){"
                + "this.target = target;"
                + "}";
        Method[] methods = targetInfo.getDeclaredMethods();
        StringBuilder methodsContent = new StringBuilder();
        for (Method m : methods) {
            String returnTypeName = m.getReturnType().getSimpleName();
            String methodName = m.getName();
            Class[] parameterTypes = m.getParameterTypes();

            StringBuilder argsContent = new StringBuilder();
            StringBuilder paramContent = new StringBuilder();
            int varSuffix = 0;
            for (Class parameterType : parameterTypes) {
                String simpleTypeName = parameterType.getSimpleName();
                argsContent.append(simpleTypeName).append(" d").append(varSuffix).append(",");
                paramContent.append(" d").append(varSuffix).append(",");
                varSuffix++;
            }
            if (argsContent.length() > 0) {
                argsContent = new StringBuilder(argsContent.substring(0, argsContent.lastIndexOf(",") - 1));
                paramContent = new StringBuilder(paramContent.substring(0, paramContent.lastIndexOf(",") - 1));
            }

            methodsContent.append("public ").append(returnTypeName).append(" ").append(methodName).append("(").append(argsContent).append("){").append("System.out.println(\"烹饪\"); ").append("target.").append(methodName).append("(").append(paramContent).append(");}");
        }

        content += packageContent + importContent + calssContnet + fieldContent + constructorContent + methodsContent + "}";
        //写入文件、这里自己写路径
        File file = new File("D:\\proxydemo\\$Proxy.java");
        if (!file.exists()) {
            file.createNewFile();
        }
        FileWriter fileWriter = new FileWriter(file);
        fileWriter.write(content);
        fileWriter.flush();
        fileWriter.close();

        //编译成class文件 jdk1.6的工具
        JavaCompiler systemJavaCompiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager standardFileManager = systemJavaCompiler.getStandardFileManager(null, null, null);
        //把文件放进去,可以放多个文件
        Iterable javaFileObjects = standardFileManager.getJavaFileObjects(file);
        JavaCompiler.CompilationTask task = systemJavaCompiler.getTask(null, standardFileManager, null, null, null, javaFileObjects);
        task.call();
        standardFileManager.close();
        //加载文件
        URL[] url = new URL[]{new URL("D:\\\\")};
        URLClassLoader urlClassLoader = new URLClassLoader(url);
        Class<?> aClass = urlClassLoader.loadClass("com.proxydemo.$Proxy");
        //用构造方法获取对象返回
        Constructor<?> constructor = aClass.getConstructor(targetInfo);
        return constructor.newInstance(target);

    }
}

勇者仿写了一些步骤,但是还有很多逻辑都没有实现。所以仅供阅读。

 

你猜勇者最后吃到肉了没?

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值