小巨人打工第一天:Java设计模式之代理模式,小白看完这篇就懂了

打工第一天

  • 打工人,打工魂,打工都是人上人。
  • 打工才是王道,我们都要做打工人。

打工人小巨人来到一家国企单位,做的是32岁的干饭老系统。系统中现有一个动物接口:

public interface Animal{
     
  void eat();
}

有人类实现了该接口

public class HumanImpl implements Animal{
     
  @Override
  public void eat(){
     
    System.out.println("吃热狗");
  }
}

在一个地方对eat方法进行了调用

 public static void main(String[] args){
     
    Animal a = new HumenImpl();
    a.eat();
 }

  现在公司需求,在吃热狗之前要先去买食材,然后吃完热狗要将装垃圾丢进垃圾袋,然后写一篇对热狗的评价心得。(要求:不能动原来的方法
小巨人左思又想,还是不知道怎么做,于是百度了一下。知道了可以用静态代理来做这件事。

静态代理

public class HumanProxy implements Animal {
     
        private Animal animal;

        public HumanProxy() {
     
            animal = new HumanImpl();
        }

        @Override
        public void eat() {
     
            System.out.println("点外卖");
            animal.eat();
            System.out.println("将垃圾袋丢到垃圾桶");
            System.out.println("写一篇美味的品尝心得");
        }
    }

  小巨人连忙在调用eat的地方测试了一下,将new HumanImpl()换成了new HumanProxy():

 public static void main(String[] args){
     
    Animal a = new HumanProxy();
    a.eat("热狗");
 }
 /**
  *打印结果:
  *         点外卖
  *         吃热狗
  *         将垃圾袋丢到垃圾桶
  *         写一篇美味的品尝心得
  */

  小巨人增加了一个类,实现了跟目标类一样的接口和方法,通过java的多态,小巨人在调用处new了一个代理对象,其他地方没动,实现了该功能。(如果不这样做,直接在eat方法里面去动,结果发现,其他地方调用eat的时候,突然多出了一些列动作,出现了大问题,那小巨人可能已经在吃鱿鱼须了)。

  小巨人很高兴的push了代码。代码检视通过。小巨人下班回家,在路上搓了一顿牛肉饭,美滋滋。

回到家,小巨人总结了一下今天所用的静态代理:

  静态代理,为我们带来了一定的灵活性,是我们在不改变原来的被代理类的方法的情况下,通过在调用处替换被代理类的实例化语句为代理类的实例化语句的方式,实现了改动少量的代码(只改动了调用处的一行代码),就获得额外动作的功能。

来需求了!!

  第二天,开发经理看到小巨人昨天的任务完成的非常好,带来一瓶AD钙来犒劳小巨人。并对小巨人一顿乱夸,小巨人得意的上天了。突然,经理画风急转说到:小巨人,老系统这里还有50类似的方法需要加上买、丢、写三个操作(干饭前后加买、丢、写,喝水前后加买、丢、写等等)相关动作,你这么优秀还熟练,就分给你做了,今天能搞完吗?

小巨人默默的点点头,心里妈卖批!!!!!

经理走后,小巨人心里想着,50个方法,那我岂不是要写50个代理类去分别实现原来的方法???

纳尼,那我岂不是成真的码农了!!!!!

小巨人虽然来自一所普通的二本,但小巨人有着进大厂的梦想,不情愿做一个死写代码的码农,想到其他人肯定也有类似需求,于是又到百度冲浪了。怎么批量处理?小巨人眼神停留在了JDK动态代理这四个字上面!!!!!

  小巨人,奸笑了一下,放下了手中的AD钙,拿起了小键盘,哒哒哒。。

JDK 动态代理

public class DynamicProxy implements InvocationHandler {
     
        private Object target;

        public DynamicProxy(Object target) {
     
            this.target = target;
        }
        /**
        *
        * @param proxy  真实的代理对象【动态生成】
        * @param method  指代的是我们所要调用真实对象的某个方法的Method对象
        * @param args    指代的是调用真实对象某个方法时接受的参数
        * @return        我们此处返回了该Method对象的调用结果
        */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     
            //执行前置增强方法
            System.out.println("点外卖");
            Object result = method.invoke(traget, args);
            //执行后置增强方法
            System.out.println("将垃圾袋丢到垃圾桶");
            System.out.println("写一篇美味的品尝心得");
            return result;
        }
         
        /**
     * 通过Proxy的静态方法,获取代理实例
     * @return  代理对象实例
     */
    public Object getProxyInstance(){
     
        return Proxy.newProxyInstance(Thread.currentThread().getContextClas
        sLoader(), target.getClass().getInterfaces(),  this);  //this即一个invokeHander实现类,实现了invoke()增强方法
    }
    }

  此时,小巨人已经写好了一个动态代理对象,小巨人随手在项目中找到50个需要处理中的一个干饭人类实现买,丢,写的增强。

    public interface People{
     
        void gan();
    }
public class GanFanPeople implements People{
     
  @Override
  public void gan(){
     
    System.out.println("端起盆子,干饭,干饭");
  }
}
 public static void main(String[] args){
     
        //1.创建真实对象:a
        Animal a = new HumanImpl();
        //2.创建一个代理类:与代理对象相关联的InvocationHandler,并将stu真实对象 传给Handler
        DynamicProxy aHandler = new DynamicProxy<>(a); //a不能为null
        //3.获取a的代理对象:  使用Proxy的静态方法来【创建】一个a的代理对象,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
        Animal aProxy = (Animal)aHandler.getProxyInstance();
        //4.代理对象吃吃吃,执行的是增强后的方法
        stuProxy.eat();
        //干饭人的处理
        People ganFan = new GanFanPeople();
        StuInvocationHandler<People> ganFanHandler = new StuInvocationHandler<>(ganFan); //ganFan不能为null
        People ganFanPeople = (People) ganFanHandler.getProxyInstance();
        ganFanPeople.gan();
 }
 /**
  *打印结果:
  *         点外卖
  *         吃热狗
  *         将垃圾袋丢到垃圾桶
  *         写一篇美味的品尝心得
  
  *         点外卖
  *         端起盆子,干饭,干饭
  *         将垃圾袋丢到垃圾桶
  *         写一篇美味的品尝心得

  这样,小巨人通过写一个代理类,50个都用这一个代理类,就不用对这50个都写代理类了,省下一大笔功夫。小巨人深感愉悦,一顿舒服的表情。

小巨人打工第一天:Java设计模式之代理模式,小白看完这篇就懂了_第1张图片
  于是小巨人快速的改着另外的地方,突然,小巨人停住了。此时有一个类是无接口的,这个类是用不了JDK动态代理。经过百度一番,发现Jdk的动态代理实现方法是依赖于接口的,首先使用接口来定义好操作的规范。然后通过Proxy类产生的代理对象调用被代理对象的操作,而这个操作又被分发给InvocationHandler接口的 invoke方法具体执行。。

  小巨人翻看了剩余的类,发现有将近十几个类似的类不能使用JDK动态代理,小巨人心想,一定有其他人有我这番困扰,于是打开谷歌浏览器,又百度了一波。

小巨人打工第一天:Java设计模式之代理模式,小白看完这篇就懂了_第2张图片
  互联网的遨游,小巨人发现原来还有一种动态代理方式: cgLib的动态代理

CGLib 动态代理

  现在打工仔、小老板等类现在都没有实现某个接口,现在要在这些方法上下都添加-看房子、首付买房、找女朋友、结婚、生崽、还贷,还贷,还贷。

public class DaGongRen{
     
  @Override
  public void do(){
     
    System.out.println("打工人:来到深圳拼命打工中");
  }
}
public class SmallBoss{
     
  @Override
  public void stayUpLater(){
     
    System.out.println("小老板:拼命熬夜中");
  }
}
public class CglibProxy implements MethodInterceptor {
     

    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class clazz) {
     
        //设置需要创建子类的类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        //通过字节码技术动态创建子类实例
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
     
        System.out.println("看房子");
        //通过代理类调用父类中的方法
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("首付买房");
        System.out.println("找女朋友");
        System.out.println("结婚");
        System.out.println("生崽");
        System.out.println("还贷,还贷,还贷!!");
        return result;
    }
}
public class Main {
     

    public static void main(String[] args) {
     
        CglibProxy proxy = new CglibProxy();
        //通过生成子类的方式创建代理类
        DaGongRen daGongRen = (DaGongRen)proxy.getProxy(DaGongRen.class);
        daGongRen.do();
        SmallBoss smallBoss = (SmallBoss)proxy.getProxy(SmallBoss.class);
        smallBoss.stayUpLater();
    }
}
/**
  *打印结果:
  *         看房子
  *         打工人:来到深圳拼命打工中
  *         首付买房
  *         找女朋友
  *         结婚
  *         生崽         
  *         还贷,还贷,还贷!!
  
  *         看房子
  *         小老板:拼命熬夜中
  *         首付买房
  *         找女朋友
  *         结婚
  *         生崽
  *         还贷,还贷,还贷!!
  */

通过完成老大布置的需求,小巨人得知了动态代理的几种实现方式:

  1. JDK动态代理:java.lang.reflect包中的 Proxy类和 InvocationHandler接口提
    供了生成动态代理类的能力。
  2. Cglib动态代理:Cglib (Code Generation Library )是一个第三方代码生成类
    库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。

又到了小巨人总结时刻:

动态代理和反射的关系:反射是动态代理的一种实现方式。

Cglib 与JDK动态代理最大的区别就是:

  • 使用JDK动态代理的对象必须实现一个或多个接口
  • 使用 cglib 代理的对象则无需实现接口,达到代理类无侵入。

Java 实现动态代理的大致步骤(文字烦,参数不知道的的看以上代码,更清晰):

  • JDK动态代理
  1. 一个接口和对应的实现类;
  2. 新建代理类实现InvocationHandler类,重写invoke方法,method调用.invoke(Object obj, Object... args)方法,其增强代码写在该方法上下即可;
  3. 通过Proxy.newProxyInstance()获取对应的代理对象,然后代理对象执行方法即可。
  • CGLib动态代理
  1. 一个业务实现类;
  2. 新建代理类,实现MethodInterceptor类,重写intercept方法,参数中的methodProxy对象调用invokeSuper方法,其增强代码写在该方法上下即可;
  3. 创建Enhancer对象,设置子类和代理类信息,然后调用create方法获得代理对象,然后代理对象执行方法即可。

随后,小巨人在阿里云开发者社区看到Hollis大神对Spring AOP中动态代理的解析:

  Spring AOP 中的动态代理主要有两种方式,JDK 动态代理和 CGLIB 动态代理。

  • JDK 动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。
    JDK 动态代理的核心是 InvocationHandler 接口和 Proxy 类。

如果目标类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。

  • CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态
    的生成某个类的子类,注意,CGLIB 是通过继承的方式做的动态代理,因此如果某个类被
    标记为 final,那么它是无法使用 CGLIB 做动态代理的。

所以AOP主要可以用于:日志记录,性能统计,安全控制,事务处理,异常处理等场景下。

开发经理对小巨人此次需要完成特别满意,奖励了两个大鸡腿,在公司大会上奖励了小巨人一个塞得鼓鼓的大红包!

小巨人打工第一天:Java设计模式之代理模式,小白看完这篇就懂了_第3张图片

这期需求做完,一阵子没再来需求,小巨人于是在摸鱼,摸螃蟹,摸龙虾。

你可能感兴趣的:(Java,java,设计模式,spring,Java设计模式,Spring,AOP)