《猪弟拱Java》连载番外篇:Java代理(上)

大家好,我是磊叔的猪弟,猪在我心中从来不是蠢的代名词,而是懒的代名词;大病一场之后发现自己更懒了,磊叔实在看不下去了,拖着我去打了一波篮球,感觉浑身又充满了力量,锻炼还是很重要的,趁着这股傻劲,赶紧拱了拱Java中的动态代理。

代理是什么?

代理这个词大家并不陌生,我们通过一个案例来描述一下代理的概念。

案例: 譬如说邓紫棋有个经纪人叫小明,经纪人就是一个代理对象,而邓紫棋本身就是被代理的对象,邓紫棋可以有多个经纪人,当然经纪人也可以代理多个明星,那么经纪人的功能就是 帮助邓紫棋打理一些繁琐的事情,比如邓紫棋手上同时有做很多的事情要做:

  • ①想买一套房子;
  • ②又要谈多个演出;
  • ③想要去旅游,又没选好地方。

那么此时经纪人就发挥功效了,一个经纪人可以帮着选房子、一个经纪人去谈演出、一个经纪人去选旅游地点,然后把结果都拿给邓紫棋做选择,这样邓紫棋要做的事就是付房款、去表演、去旅行,因为这些事情是经纪人完成不了的。所以这样子就可以把多件繁琐的事情给拆了出来,把不必要邓紫棋亲自去做的繁琐的事情交给经纪人,自己只处理问题的核心。下面是两张使用代理和不使用代理的两张直观图。

结合上面的案例,可以对代理有个直观的了解,关于有代理是好还是坏,大家自行判断,只能说心疼宝强 。

Java中的代理

代理模式

代理(Proxy)是一种设计模式,提供了对目标对象另一种访问方式;即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。常用代理来做方法增强,将核心代码和关注点代码(重复代码)分离,最好的例子就是Spring的AOP。

Java代理的种类

Java中的代理有三大类:

  • 静态代理
  • JDK动态代理
  • glib子类代理

代理能干嘛

代理能干嘛,能干的事情老多了,我们还是用一个场景来讲述代理的功能。

场景一:(请忽略场景中的其他与主题无关的漏洞)

部门经理S让程序员 小A、刘B、阿C 去开发某一块功能,小A负责写各种场景短信的统一调用方法,刘B和阿C 负责的功能会调用到发短信方法,唰唰唰,小A的短信功能写好了,然后唰唰唰,刘B和阿C的功能也都做完了。测试的时候,刘B的各种模块功能都很正常,但就是短信发不出去,阿C的短信能正常发出去,此时测试就找上了刘B,刘B觉得自己的代码很NB不可能出问题,然后叫测试去找小A,小A看了半天没看出什么猫腻,然后叫测试找刘B,这下两人都觉得自己的代码没毛病,怎么办?

测试无奈,只好叫部门经理出面,部门经理看了看,给小A提了一个需求:在调用短信方法之前打印入参日志,在发送结束之后打印调用结果,这样出了什么问题,看一下日志就一目了然了。但是小A就有点崩溃了,小A为每一个场景都写了一个类,足足几十个场景对应了几十个类(此处的类设计是有问题的,不是关注点,请忽略),要一个一个去手动加日志,没办法,小A就去写了,唰唰唰,复制粘贴完成了。下面是小A的代码原来的代码

/**
 * 功能描述: 短信接口
 * 
 * @author 小A 
 * @since v1.0.0 
 */
public interface ISmsService {
   /**
     * 发送场景短信
     *
     * @param content
     * @param phoneNumber
     * @return
     */
    boolean sendMsg(String content, String phoneNumber);
}
复制代码
/**
 * 功能描述:场景短信实现
 *
 * @author 小A
 * @since v1.0.0
 */
public class SmsServiceForScan1 implements ISmsService {
    /**
     * 短信支持组件
     */
    private SmsSendSupport smsSendSupport = new SmsSendSupport();
    /**
     * 发送场景一短信
     *
     * @param content
     * @param phoneNumber
     * @return
     */
    @Override
    public boolean sendMsg(String content, String phoneNumber) {
        return smsSendSupport.send(content, phoneNumber, 1);
    }
}
复制代码
/**
 *
 功能描述:
 *
 * @author 小A
 * @since v1.0.0
 */
public class SmsServiceForScan2 implements ISmsService {
    /**
     * 短信支持组件
     */
    private SmsSendSupport smsSendSupport = new SmsSendSupport();
    /**
     * 发送场景一短信
     *
     * @param content
     * @param phoneNumber
     * @return
     */
    @Override
    public boolean sendMsg(String content, String phoneNumber) {
        return smsSendSupport.send(content, phoneNumber, 1);
    }
}
复制代码

下面小A对每一个类都手动打上了日志:

/**
 * 功能描述:
 *
 * @author 小A
 * @since v1.0.0
 */
public class SmsServiceForScan1 implements ISmsService {
    /**
     * 日志
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(SmsServiceForScan1.class);
    /**
     * 短信支持组件
     */
    private SmsSendSupport smsSendSupport = new SmsSendSupport();
    /**
     * 发送场景一短信
     *
     * @param content
     * @param phoneNumber
     * @return
     */
    @Override
    public boolean sendMsg(String content, String phoneNumber) {
        LOGGER.info("sendMsgForScan1入参为:{},{}",content,phoneNumber);
        boolean result = smsSendSupport.send(content, phoneNumber, 1);
        LOGGER.info("返回结果为:{}",result);
        return result;
    }
}
复制代码

其他几十个类与上面类似,此处省略......

唰唰唰,经过一番测试,在日志中发现刘B的代码中发送过来的电话号码都是null,这下锅就甩给了刘B,至于是什么原因导致的电话号码为null不是我们关注的重点。

好的,小A成功甩锅,回头CodeReview的时候,部门经理S发现了小A的这段加日志的代码,部门经理很和蔼,叫了小A过去看了一下那段代码。

S:小伙子,工作完成的不错,但是你看这段代码有什么毛病吗?

小A:(看了一下)没毛病啊。

S:功能确实是实现了,但是你可以发现这些代码中打日志的代码都是可以提取出来的不是吗?

小A:(想了一下)好像是的哎!可是该怎么做呢。

S:你知道Java中的代理吗?

小A:(恍然大悟)谢谢经理,我知道该怎么做了。

小A回去就找了一下代理的资料,唰唰唰,就完成了,比之前可快多了呢,代码如下:

静态代理实现日志打印:

/**
 * 功能描述:短信代理类
 *
 * @author 小A
 * @since v1.0.0
 */
public class SmsServiceProxy implements ISmsService {

    private static final Logger LOGGER = LoggerFactory.getLogger(SmsServiceProxy.class);

    private ISmsService smsService;

    /**
     * 构造器
     */
    public SmsServiceProxy(ISmsService smsService) {
        this.smsService = smsService;
    }

    /**
     * 代理发送短信方法
     */
    @Override
    public boolean sendMsg(String content, String phoneNumber) {
        //统一打印入参
        LOGGER.info("sendMsgForScan1入参为:{},{}", content, phoneNumber);
        //执行核心代码
        boolean result = smsService.sendMsg(content, phoneNumber);
        //统一打印返回结果
        LOGGER.info("返回结果为:{}", result);
        //返回结果
        return result;
    }
}
复制代码

经过代理类的改造,之前每个类中重复打印日志的代码都可以删除了,代码量一下子减少了很多,结构感更加强了。

部门经理看了也很欣慰,叫了小A过来,问道。

S:你为什么不选择动态代理和cglib子类代理呢?

小A:我也有想过,不过静态代理相对于其他两种代理是有一个优点的——轻量,而且静态代理在这里已经能够够用了

S:嗯,不错不错。

下面先说下三类代理的使用条件和代理范围

① 静态代理可以代理某一类对象,这一类对象必须实现同一接口,所以它的使用条件就是被代理类要实现接口。上面的各种场景短信都实现了ISmsService接口,所以代理类可以代理所有场景短信实现类,并调用真正的短信发送方法去发送正确的场景短信。

① 动态代理的使用条件也是被代理类要实现接口,但是动态代理能够代理所有实现了接口的类,强大必然也会有缺点:动态代理依赖Java反射机制,反射是一个比较影响性能的功能,所以动态代理性能上会比静态代理低。

③cglib子类代理,首先需要依赖第三方库,然后它是基于字节码来生成子类代理的,没有特定的使用条件,所以也不需要实现接口,它可以代理所有的类,所以论性能是比不上静态代理的。

本来想把所有的一次性写完的,不料被朋友拖出去吃了顿火锅,回来就晚了,无奈,把动态代理和cglib子类代理放在下一篇吧

为了为大家提供更加方便的阅读,小伙伴们可以关注我们的公众微信号哦...

你可能感兴趣的:(《猪弟拱Java》连载番外篇:Java代理(上))