设计模式之代理模式

文章目录

    • 1、代理模式基本介绍
    • 2、Jdk中的动态代理
      • 2.1、场景推导
      • 2.2、Jdk动态代理
    • 3、静态代理
    • 4、代理模式的关键点
    • 5、代理模式和适配器模式的比较
    • 6、代理模式UML图

1、代理模式基本介绍

代理模式的定义:

  • 为其他对象提供一种代理以控制对这个对象的访问
  • 在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

简单来说

  • 代理模式就是代理对象具备真实对象的功能,并代替真实对象完成相应操作,并能够在操作执行的前后,对操作进行增强处理

  • 为真实对象提供代理,然后供其他对象通过代理访问真实对象

2、Jdk中的动态代理

2.1、场景推导

实现一个简单的加减乘除运算功能

interface ICalc{
    int add(int a,int b);
    int sub(int a,int b);
    int mul(int a,int b);
    int div(int a,int b);
}
class  CalcImpl implements ICalc{
    @Override
    public int add(int a, int b) {
        return a+b;
    }

    @Override
    public int sub(int a, int b) {
        return a-b;
    }

    @Override
    public int mul(int a, int b) {
        return a*b;
    }

    @Override
    public int div(int a, int b) {
        return a/b;
    }
}
class AppTest{
    public static void main(String[] args) {
        CalcImpl c = new CalcImpl();
        System.out.println(c.add(4,2));
        System.out.println(c.sub(4,2));
        System.out.println(c.mul(4,2));
        System.out.println(c.div(4,2));
    }
}

现在变化来了,客户要求为每个方法添加日志,记录方法开始和结束的时机

package com.hh.demo.designpattern;

interface ICalc{
    int add(int a,int b);
    int sub(int a,int b);
    int mul(int a,int b);
    int div(int a,int b);
} 
class  CalcImpl implements ICalc{
    @Override
    public int add(int a, int b) {
        System.out.println("add方法开始!" +"a="+a+"b="+b);
        int r = a+b;
        System.out.println("add方法结束!" +"r="+r);
        return r;
    }

    @Override
    public int sub(int a, int b) {
        System.out.println("sub方法开始!" +"a="+a+"b="+b);
        int r = a-b;
        System.out.println("sub方法结束!" +"r="+r);
        return r;
    }

    @Override
    public int mul(int a, int b) {
        System.out.println("mul方法开始!" +"a="+a+"b="+b);
        int r = a*b;
        System.out.println("mul方法结束!" +"r="+r);
        return r;
    }

    @Override
    public int div(int a, int b) {
        System.out.println("div方法开始!" +"a="+a+"b="+b);
        int r = a/b;
        System.out.println("div方法结束!" +"r="+r);
        return r;
    }
}
class AppTest{
    
    public static void main(String[] args) {
        CalcImpl c = new CalcImpl();
        System.out.println(c.add(4,2));
        System.out.println(c.sub(4,2));
        System.out.println(c.mul(4,2));
        System.out.println(c.div(4,2));
    }
}

梭哈搞定,打完收工!

我们发现,这样完成业务根本不是一个好办法

  1. 代码在重复,核心业务(加减乘除)和非核心业务(打印日志)在不断的重复。
  2. 如果Icalc和CalcImpl不是我们自己创建的,是被发现的,那我们手里是没有源代码的,不能直接修改源代码(开闭原则)
  3. 需求如果再次变化,需要加入开方,求余的过程 ;又或者客户要求 上午需要日志,下午不需要日志!!!

我们尝试使用动态代理来完成上述功能

2.2、Jdk动态代理

Jdk动态代理:在程序的执行过程中,使用jdk的反射机制,创建代理对象,并动态的指定代理的目标类

在这里插入图片描述

先来看看业务逻辑是怎么实现的

package com.hh.demo.designpattern;

interface ICalc {
    int add(int a, int b);

    int sub(int a, int b);

    int mul(int a, int b);

    int div(int a, int b);
}
class CalcImpl implements ICalc {
    @Override
    public int add(int a, int b) {
        int r = a + b;
        return r;
    }

    @Override
    public int sub(int a, int b) {
        int r = a - b;
        return r;
    }

    @Override
    public int mul(int a, int b) {
        int r = a * b;
        return r;
    }

    @Override
    public int div(int a, int b) {
        int r = a / b;
        return r;
    }
}
//调用处理器
class MyHandler implements InvocationHandler {
    //关联
    private ICalc calculator;
    public MyHandler(ICalc calculator) {
        this.calculator = calculator;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getName()+"开始,参数:"+ Arrays.toString(args));
        //利用反射机制,调用方法
        //把method所代表的方法,当作calculator对象的调用,参数是args
        //Object是为了通用
        Object res = method.invoke(calculator, args);
        System.out.println(method.getName()+"结束,结果是:"+ res);
        return res;//这个返回值会返回到代理对象的方法调用处
    }
}
public class AppTest {
    public static void main(String[] args) {
        ICalc calculator = new CalcImpl();
        //当前类的字节码获得当前类的类加载器
        ClassLoader classLoader = AppTest.class.getClassLoader();
        //创建代理对象,需要传入三个参数
        ICalc proxy = (ICalc) Proxy.newProxyInstance(classLoader, new Class[]{ICalc.class}, new MyHandler(calculator));
        //总之,对代理对象的方法的调用,都统统会进入调用处理器中
        proxy.add(3, 2);
        proxy.sub(3, 2);
        proxy.mul(3, 2);
        proxy.div(3, 2);
    }
    /**
     * add开始,参数:[3, 2]
     * add结束,结果是:5
     * sub开始,参数:[3, 2]
     * sub结束,结果是:1
     * mul开始,参数:[3, 2]
     * mul结束,结果是:6
     * div开始,参数:[3, 2]
     * div结束,结果是:1
     *
     * Process finished with exit code 0
     */
}

我们先来看看动态代理api

Proxy.newProxyInstance();

里面传三个参数,分别是:

image-20221005161215388

  • 第一个参数:

    • 实例化一个对象,必然会调用类的构造器。在调用构造器之前,Jvm会加载该类的字节码,而Jvm就是使用类加载器来加载类的字节码,这一步是Jvm自动完成的。

    • 简单来说:只要实例化对象,一定要加载类的字节码,加载字节码就一定要类的加载器。

    • 使用动态代理的api实例化对象是一种不常用的创建对象的方式,但这也是一种实例化,需要我们手动把类的加载器传入

    • 使用构造器实例化对象时Jvm会自动找到类加载器。

  • 第二个参数:

    • 第一个参数传入的类加载器,加载的是哪个类的字节码?加载的字节码就是在运行期动态生成的字节码,这个动态生成的字节码是不需要源代码的。

    • 字节码确实可以自动生成,那么动态代理api成成的字节码的内容,是根据什么生成的呢?恰恰是根据第二个参数生成的。动态生成代理,会生成一个实现了目标接口的类的字节码,在上面的栗子中就是生成了一个ICalc接口的类的字节码!

  • 第三个参数:调用处理器 InvocationHandler

    • 我们已经知道,动态代理会加载自己动态生成的字节码,且这个字节码是根据某个接口生成的,在上面的例子中就是根据ICalc接口生成的实现了ICalc接口的类的字节码
    • 实现一个接口,就要实现其中的抽象方法,那麽动态代理生成的字节码,实现了ICalc接口,必然就要实现其中的add、sub等方法
    • 这些方法被实现的方法体是什么内容呢?这恰恰是由第三个参数决定的,MyHandler类的 invoke方法,就是方法体的内容!!
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    第一个参数:动态代理的对象

    第二个参数:调用的接口方法

    第三个参数:调用的接口方法的参数

}

但是目前这个写法还是有缺点的,太复杂了,对于新手不是很友好,我们来封装一下

package com.hh.demo.designpattern;

interface ICalc {
    int add(int a, int b);

    int sub(int a, int b);

    int mul(int a, int b);

    int div(int a, int b);
}
class CalcImpl implements ICalc {
    @Override
    public int add(int a, int b) {
        int r = a + b;
        return r;
    }

    @Override
    public int sub(int a, int b) {
        int r = a - b;
        return r;
    }

    @Override
    public int mul(int a, int b) {
        int r = a * b;
        return r;
    }

    @Override
    public int div(int a, int b) {
        int r = a / b;
        return r;
    }
}
class MyHandler implements InvocationHandler {
    //关联
    private Object target;
    public MyHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getName()+"开始,参数:"+ Arrays.toString(args));
        //利用反射机制,调用方法
        //把method所代表的方法,当作calculator对象的调用,参数是args
        //Object是为了通用
        Object res = method.invoke(target, args);
        System.out.println(method.getName()+"结束,结果是:"+ res);

        return res;//这个返回值会返回到代理对象的方法调用处
    }
}
//----------------------------------------------------------------------------------------------------
class MyProxy{
    //封装:对外隐藏复杂的实现细节,暴露出简单的使用方法
    public Object getProxy(Object target){
        //当前类的字节码获得当前类的类加载器
        ClassLoader classLoader = MyProxy.class.getClassLoader();
        //获取target所属的类,所实现的接口
        Class<?>[] interfaces = target.getClass().getInterfaces();
        //创建代理对象,需要传入三个参数
        Object proxy =  Proxy.newProxyInstance(classLoader,interfaces, new MyHandler(target));

        return proxy;
    }
}
public class AppTest {
    public static void main(String[] args) {
        ICalc calculator = new CalcImpl();
        ICalc proxy = (ICalc) new MyProxy().getProxy(calculator);
        proxy.add(3, 2);
        proxy.sub(3, 2);
        proxy.mul(3, 2);
        proxy.div(3, 2);
    }
    /**
     * add开始,参数:[3, 2]
     * add结束,结果是:5
     * sub开始,参数:[3, 2]
     * sub结束,结果是:1
     * mul开始,参数:[3, 2]
     * mul结束,结果是:6
     * div开始,参数:[3, 2]
     * div结束,结果是:1
     *
     * Process finished with exit code 0
     */
}

MyProxy类对外隐藏复杂的实现细节,暴露出简单的使用方法。似乎有点代理的意思了

目前看起来似乎挺好的,但是仍然有问题:

目前我们创建的代理对象,只能在真实对象的真实方法调用前后加上日志,无法扩展其他功能,比如,用户不想加日志功能,而是想加缓存功能,或者权限控制…

再次封装代码,我们定义一个接口,用来描述代理类对应方法执行前后需要拓展执行的方法。

称这个接口为Interceptor拦截器,也可以理解为切面

package com.hh.demo.designpattern;

interface ICalc {
    int add(int a, int b);

    int sub(int a, int b);

    int mul(int a, int b);

    int div(int a, int b);
}
class CalcImpl implements ICalc {
    @Override
    public int add(int a, int b) {
        int r = a + b;
        return r;
    }

    @Override
    public int sub(int a, int b) {
        int r = a - b;
        return r;
    }

    @Override
    public int mul(int a, int b) {
        int r = a * b;
        return r;
    }

    @Override
    public int div(int a, int b) {
        int r = a / b;
        return r;
    }
}
class MyHandler implements InvocationHandler {
    //关联
    private Object target;
    private Interceptor interceptor;

    public MyHandler(Object target,Interceptor interceptor) {
        this.target = target;
        this.interceptor = interceptor;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //方法执行前的钩子函数
        interceptor.before(target, method, args);

        Object res = method.invoke(target, args);

        //方法执行后的钩子函数
        interceptor.after(target, method, args, res);
        // 返回到代理对象的方法调用处
        return res;
    }
}
class MyProxy{
    public Object getProxy(Object target, Interceptor interceptor){
        //当前类的字节码获得当前类的类加载器
        ClassLoader classLoader = MyProxy.class.getClassLoader();
        //获取target所属的类,所实现的接口
        Class<?>[] interfaces = target.getClass().getInterfaces();
        //创建代理对象,需要传入三个参数
        Object proxy =  Proxy.newProxyInstance(classLoader,interfaces, new MyHandler(target,interceptor));

        return proxy;
    }
}
interface Interceptor {
    //前置通知
    void before(Object target, Method method, Object[] args);
	//后置通知
    void after(Object target, Method method, Object[] args, Object returnVal);
}
//----------------------------------------------------------------------------------------------------
//用户制作拦截器的实现类
class LogInterceptor implements Interceptor{

    @Override
    public void before(Object target, Method method, Object[] args) {
        System.out.println(String.format("方法名为:%s,参数为:%s", method.getName(), Arrays.toString(args)));
    }

    @Override
    public void after(Object target, Method method, Object[] args, Object returnVal) {
        System.out.println(String.format("返回结果为:%s", returnVal.toString()));
    }
}
public class AppTest {
    public static void main(String[] args) {
        ICalc calculator = new CalcImpl();
        ICalc proxy = (ICalc) new MyProxy().getProxy(calculator,new LogInterceptor());
        proxy.add(3, 2);
        proxy.sub(3, 2);
        proxy.mul(3, 2);
        proxy.div(3, 2);
    }
    /**
     * 方法名为:add,参数为:[3, 2]
     * 返回结果为:5
     * 方法名为:sub,参数为:[3, 2]
     * 返回结果为:1
     * 方法名为:mul,参数为:[3, 2]
     * 返回结果为:6
     * 方法名为:div,参数为:[3, 2]
     * 返回结果为:1
     *
     * Process finished with exit code 0
     */
}

这样就简单了很多,应对不同的需求我们就去定制不同的代理类和拦截器,实现不同的需求

可是现在变化又来了,客户有了新需求:

针对ICalc接口的日志功能,add方法使用中文日志,sub方法使用英文日志,mul方法和div方法不要日志。

这时只能用判断来解决了,针对不同方法有不同的日志。我们来实现一下

//用户制作拦截器的实现类
class LogInterceptor implements Interceptor{

    @Override
    public void before(Object target, Method method, Object[] args) {
        if("add".equals(method.getName())){
            System.out.println(String.format("方法名为:%s,参数为:%s", method.getName(), Arrays.toString(args)));
        }else if("sub".equals(method.getName())){
            System.out.println(String.format("methodName is:%s,parameter is:%s", method.getName(), Arrays.toString(args)));
        }else{
            System.out.println(method.getName()+Arrays.toString(args));
        }

    }

    @Override
    public void after(Object target, Method method, Object[] args, Object returnVal) {
        if("add".equals(method.getName())){
            System.out.println(String.format("返回结果为:%s", returnVal.toString()));
        }else if("sub".equals(method.getName())){
            System.out.println(String.format("result is:%s", returnVal.toString()));
        }else{
            System.out.println(returnVal.toString());
        }
    }
}

运行结果:

方法名为:add,参数为:[3, 2]
返回结果为:5
methodName is:sub,parameter is:[3, 2]
result is:1
mul[3, 2]
6
div[3, 2]
1

Process finished with exit code 0

可以看到,虽然做虽然满足了客户的需求,但是有很多的 if else ,感觉好像怪怪的!!!

仔细想想,这设计违反了什么设计原则呢?单一职责设计原则

那就拆分呗,设计原则不就是讲究一个字吗? 我们针对于四个方法,做四个拦截器,这里写两个作为演示

//用户制作拦截器的实现类
class  addInterceptor implements Interceptor{

    @Override
    public void before(Object target, Method method, Object[] args) {
        if("add".equals(method.getName())){
            System.out.println(String.format("方法名为:%s,参数为:%s", method.getName(), Arrays.toString(args)));
        }
    }

    @Override
    public void after(Object target, Method method, Object[] args, Object returnVal) {
        if("add".equals(method.getName())) {
            System.out.println(String.format("返回结果为:%s", returnVal.toString()));
        }
    }
}
class  subInterceptor implements Interceptor{

    @Override
    public void before(Object target, Method method, Object[] args) {
        if("sub".equals(method.getName())){
            System.out.println(String.format("methodName is:%s,parameter is:%s", method.getName(), Arrays.toString(args)));
        }
    }

    @Override
    public void after(Object target, Method method, Object[] args, Object returnVal) {
        if("sub".equals(method.getName())){
            System.out.println(String.format("result is:%s", returnVal.toString()));
        }
    }
}

客户端:

public class AppTest {
    public static void main(String[] args) {
        //calculator是目标对象
        ICalc calculator = new CalcImpl();
        //根据目标对象calculator,动态生成一个代理对象
        ICalc proxy = (ICalc) new MyProxy().getProxy(calculator,new addInterceptor());
        proxy.add(3, 2);
        proxy.sub(3, 6);
    }
    /**
     * 方法名为:add,参数为:[3, 2]
     * 返回结果为:5
     *
     * Process finished with exit code 0
     */
}

但是问题又来了

现在,getProxy方法只能传addInterceptor 或者subInterceptor,不能同时用啊

有同学可能想到用可变参数解决这个问题,没错,是可以的;

但是换一种思路,既然能根据目标对象动态代理生成一个代理对象,那我是不是可以将这个代理对象再当成一个新的目标对象动态代理一下?等等,感觉cpu要烧了!!!!

public class AppTest {
    public static void main(String[] args) {
        //calculator是目标对象
        ICalc calculator = new CalcImpl();
        //根据目标对象calculator,动态生成一个代理对象
        ICalc proxy = (ICalc) new MyProxy().getProxy(calculator,new addInterceptor());
        //我们把proxy这个代理对象,再当成一个新的目标对象
        ICalc proxy2 = (ICalc) new MyProxy().getProxy(proxy,new subInterceptor());
        //我们发现add方法和sub方法都能拦截
        proxy2.add(3, 2);
        proxy2.sub(3, 6);
    }
    /**
     * 方法名为:add,参数为:[3, 2]
     * 返回结果为:5
     * methodName is:sub,parameter is:[3, 6]
     * result is:-3
     *
     * Process finished with exit code 0
     */
}

简单理解就是套娃,贴一张图帮助理解

在这里插入图片描述

现在代码总没有问题了吧?是吗?那我说目前代码还有问题呢?

问题是:添加拦截器的顺序是逆向的,对用户不友好

解决方法:

public class AppTest {
    public static void main(String[] args) {
        //calculator是目标对象
        ICalc calculator = new CalcImpl();

        List<Interceptor> interceptors = new ArrayList<>();
        interceptors.add(new addInterceptor());
        interceptors.add(new subInterceptor());

        for (int i =interceptors.size() - 1; i >=0; i--) {
           Interceptor interceptor = interceptors.get(i);
           calculator = (ICalc) MyProxy.getProxy(calculator, interceptor);
        }
        calculator.add(1,2);
        calculator.sub(3,2);

    }
    /**
     *     方法名为:add,参数为:[1, 2]
     *     返回结果为:3
     *     methodName is:sub,parameter is:[3, 2]
     *     result is:1
     *
     *     Process finished with exit code 0
     */
}

但是现在客户端代码很复杂了,对用户不友好呀

我们封装一下倒叙添加拦截器的逻辑

class MyProxy{
    public static Object getProxy(Object target, Interceptor interceptor){
        //当前类的字节码获得当前类的类加载器
        ClassLoader classLoader = MyProxy.class.getClassLoader();
        //获取target所属的类,所实现的接口
        Class<?>[] interfaces = target.getClass().getInterfaces();
        //创建代理对象,需要传入三个参数
        Object proxy =  Proxy.newProxyInstance(classLoader,interfaces, new MyHandler(target,interceptor));

        return proxy;
    }
    //封装倒叙添加拦截器
    public static Object getProxy2(Object target, List<Interceptor> interceptors){

        for (int i =interceptors.size() - 1; i >=0; i--) {
            Interceptor interceptor = interceptors.get(i);
            target = (ICalc) MyProxy.getProxy(target, interceptor);
        }
        return  target;
    }
}

客户端代码:

public class AppTest {
    public static void main(String[] args) {
        //calculator是目标对象
        ICalc calculator = new CalcImpl();

        List<Interceptor> interceptors = new ArrayList<>();
        interceptors.add(new addInterceptor());
        interceptors.add(new subInterceptor());

        ICalc proxy2 = (ICalc) MyProxy.getProxy2(new CalcImpl(), interceptors);

        proxy2.add(1,2);
        proxy2.sub(3,2);

    }
    /**
     *     方法名为:add,参数为:[1, 2]
     *     返回结果为:3
     *     methodName is:sub,parameter is:[3, 2]
     *     result is:1
     *
     *     Process finished with exit code 0
     */
}

现在还有问题,以后,用户要添加拦截器,删除拦截器,必然要修改应用程序代码,要修改 List,这不合理鸭

用户应该是改配置而不是改代码,所以这个interceptors根本不用传

//封装倒叙添加拦截器
public static Object getProxy2(Object target) throws Exception{
    //拦截器集合不是用户传进来的,是读取配置文件得到的,配置文件放在同一个包下
    Properties prop = new Properties();
    InputStream in = MyProxy.class.getResourceAsStream("myconfig.properties");
    prop.load(in);
    String str = prop.getProperty("interceptors");
    String[] split = str.split(",");

    List<Interceptor> interceptors = new ArrayList<>();
    for (String hh : split) {
        interceptors.add((Interceptor)Class.forName(hh).newInstance());
    }
    for (int i =interceptors.size() - 1; i >=0; i--) {
        Interceptor interceptor = interceptors.get(i);
        target = (ICalc) MyProxy.getProxy(target, interceptor);
    }
    return  target;
}

客户端:

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

    ICalc proxy2 = (ICalc) MyProxy.getProxy2(new CalcImpl());

    proxy2.add(1,2);
    proxy2.sub(3,2);

}

客户端代码变得非常简洁

3、静态代理

业务场景:现在需要做一个图书解析器,解析一本书里面有多少个句子,多少个副词

package com.hh.demo.designpattern;
//图书解析器
class BookParser{
    //接收一本书的内容,字符串的值,是很大的
    private String content = "天下大事,分久必合,合久必分...!!";

    public Integer numberOfSentence(){
        //每次解析,都有很高的执行代价
        return content.split("[.!?]").length;
    }
    public Integer numberOfVerb() throws InterruptedException {
        //假设执行了很多逻辑;
        Thread.sleep(1000);
        return 80;
    }
    public Integer numberOfAdverb() throws InterruptedException {
        //假设执行了很多逻辑;
        Thread.sleep(1000);
        return 220;
    }
}

public class AppTest {
    public static void main(String[] args) throws InterruptedException {
        BookParser bp = new BookParser();
        Integer a = bp.numberOfAdverb();
        System.out.println("有"+ a + "个副词");
        Integer a2 = bp.numberOfAdverb();
        System.out.println("有"+ a2 + "个副词");
        Integer a3 = bp.numberOfAdverb();
        System.out.println("有"+ a3 + "个副词");
    }
    /**
     * //每隔一秒出现一个结果
     * 有220个副词
     * 有220个副词
     * 有220个副词
     *
     * Process finished with exit code 0
     */

}

现在有个问题,每解析一次就要花费1s, 这是极其不合理的;

我们可以做一个代理,每次调方法时进入代理,代理判断一下这个数字有没有统计过,如果统计过了,直接返回这个值,就不用去调用真实对象,如果没有统计过,就去调真实对象,然后返回值,并将这个值存到缓存中;

//图书解析器
class BookParser{
    //接收一本书的内容,字符串的值,是很大的
    private String content = "天下大事,分久必合,合久必分...!!";

    public Integer numberOfSentence(){
        //每次解析,都有很高的执行代价
        return content.split("[.!?]").length;
    }
    public Integer numberOfVerb() throws InterruptedException {
        //假设执行了很多逻辑;
        Thread.sleep(1000);
        return 80;
    }
    public Integer numberOfAdverb() throws InterruptedException {
        //假设执行了很多逻辑;
        Thread.sleep(1000);
        return 220;
    }
}
class BookParserProxy extends BookParser{
    //因为没有定义接口,所以为了与真实对象有相同的方法,继承一下BookParser
    private Integer numberOfSentence;
    private Integer numberOfVerb;
    private Integer numberOfAdverb;

    @Override
    public Integer numberOfSentence() {
        if(numberOfSentence == null){
            numberOfSentence = super.numberOfSentence();
        }
        return numberOfSentence;
    }

    @Override
    public Integer numberOfVerb() throws InterruptedException {
        if(numberOfVerb == null){
            numberOfVerb = super.numberOfVerb();
        }
        return numberOfVerb;
    }

    @Override
    public Integer numberOfAdverb() throws InterruptedException {
        if(numberOfAdverb == null){
            numberOfAdverb = super.numberOfAdverb();
        }
        return numberOfAdverb;
    }
}
public class AppTest {
    public static void main(String[] args) throws InterruptedException {
        BookParser bp = new BookParserProxy();
        Integer a = bp.numberOfAdverb();
        System.out.println("有"+ a + "个副词");
        Integer a2 = bp.numberOfAdverb();
        System.out.println("有"+ a2 + "个副词");
    }
    /**
     * //等待一秒后两个结果同时出现,说明什么,说明第二次没有调用真实方法
     * 有220个副词
     * 有220个副词
     *
     * Process finished with exit code 0
     */

}

这个就叫做静态代理,自己手写的代码,写死的代理类

而动态代理是运行时动态的生成字节码

4、代理模式的关键点

  1. 代理对象,一定与目标对象有相同的接口。这样才能做代理
  2. 代理对象中,一定有目标对象
  3. 代理对象,具有对目标对象的访问权限

其中上面的栗子中充分体现了前两点;第三点也可以体现,只需要改一下前置通知的返回值为boolean,然后做个判断就好啦

类似于现在要做一个权限验证的业务逻辑,再前置通知里面判断,返回true,才能调目标对象。

5、代理模式和适配器模式的比较

  1. 代理模式中,代理对象和它所包裹的目标对象,必须实现相同的接口;适配器模式种 ,适配器和它所包裹的对象不用实现相同的接口
  2. 代理模式中,代理对象可以控制它所包裹的目标对象的方法是否执行;适配器模式中,适配器总是调用目标对象的方法,无法控制

6、代理模式UML图

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/22260.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

I2C通信协议原理和MPU6050

一、串口通讯 只能在两个设备之间进行 若要三台设备两两通信&#xff0c;则每个设备得需要两组窗口&#xff0c;为3组相互独立的窗口通讯 为解决这个问题&#xff1a;设计了总线通讯&#xff0c;有多种&#xff0c;I2C为其中一种 二、I2C通信 &#xff08;1&#…

VMware虚拟机三种网络模式详解之NAT(地址转换模式)

VMware虚拟机三种网络模式详解 NAT&#xff08;地址转换模式&#xff09; 二、NAT&#xff08;地址转换模式&#xff09; 刚刚我们说到&#xff0c;如果你的网络ip资源紧缺&#xff0c;但是你又希望你的虚拟机能够联网&#xff0c;这时候NAT模式是最好的选择。NAT模式借助虚拟…

进攻即是最好的防御!19个练习黑客技术的在线网站

前言 进攻即是最好的防御&#xff0c;这句话同样适用于信息安全的世界。这里罗列了19个合法的来练习黑客技术的网站&#xff0c;不管你是一名开发人员、安全工程师、代码审计师、渗透测试人员&#xff0c;通过不断的练习才能让你成为一个优秀安全研究人员。以下网站希望能给各…

Hadoop基础学习---5、MapReduce概述和WordCount实操(本地运行和集群运行)、Hadoop序列化

1、MapReduce概述 1.1 MapReduce定义 MapReduce是一个分布式运算程序的编程框架&#xff0c;是用户开发“基于Hadoop的数据分析应用”的核心框架。 MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序&#xff0c;并发运行在一个Had…

【计算机系统基础bomb lab】CSAPP实验:Bomb Lab

【计算机系统基础bomb lab】CSAPP实验&#xff1a;Bomb Lab CSAPP 实验&#xff1a;Bomb Lab实验内容简述实验环境实验过程&#xff1a;phase 1phase 1 调试过程 实验过程&#xff1a;phase 2phase 2 调试过程 实验过程&#xff1a;phase 3phase 3 调试过程 实验过程&#xff1…

Java字符串知多少:String、StringBuffer、StringBuilder

一、String 1、简介 String 是 Java 中使用得最频繁的一个类了&#xff0c;不管是作为开发者的业务使用&#xff0c;还是一些系统级别的字符使用&#xff0c; String 都发挥着重要的作用。String 是不可变的、final的&#xff0c;不能被继承&#xff0c;且 Java 在运行时也保…

C++的cout详解

2023年5月20日&#xff0c;周六早上&#xff1a; 我发现我找不到非常详细的cout类的成员函数&#xff0c;只好自己写了。 不定期更新。 cout的继承关系 cout类继承自ostream类&#xff0c;ostream类继承自ios类&#xff0c;ios类继承自ios_base类 cout类拥有的所有成员函数 …

pg事务:事务的处理

事务的处理 事务块 从事务形态划分可分为隐式事务和显示事务。隐式事务是一个独立的SQL语句&#xff0c;执行完成后默认提交。显示事务需要显示声明一个事务&#xff0c;多个sql语句组合到一起称为一个事务块。 事务块通过begin&#xff0c;begin transaction&#xff0c;st…

QT学习记录(三)绘图

按照下面两个教程学习 QT学习教程&#xff08;全面&#xff09;_Strive--顾的博客-CSDN博客_qt学习 天山老妖S的博客_QT开发(3)_51CTO博客 1、绘图 VC项目右键增加QT GUI Class&#xff0c;在QT Designer中编辑DlgDraw.ui 在DlgDraw中重载函数 void DlgDraw::paintEvent(Q…

C++之堆排

堆排的原理和结构&#xff1a; 堆排序是一种常见的排序算法&#xff0c;基于堆这种数据结构实现。堆是一种特殊的树形数据结构&#xff0c;它满足以下两个条件&#xff1a; 堆是一棵完全二叉树。 堆的任意节点的值&#xff0c;都必须大于等于&#xff08;或小于等于&#xff0…

基于ROS2的costmap中Obstacle Layer中对障碍物信息的增加与删除机制的方案调研。

文章目录 1.背景2.目标3. 障碍物信息添加方式发送数据的数据结构与接收数据的数据结构 4. 障碍物清理机制4.1 可调参数4.2 优化光追算法4.3 障碍物跟踪 1.背景 基于costmap地图&#xff0c;使用navigation导航时&#xff0c;会出现由于激光雷达/图像测距的局限性&#xff0c; …

由浅入深Netty粘包与半包解决方案

目录 1 粘包现象2 半包现象3 现象分析4 解决方案4.1 方法1&#xff1a;短链接4.2&#xff1a;方法2&#xff1a;固定长度4.3 方法3&#xff1a;固定分隔符4.4 方法4&#xff1a;预设长度 1 粘包现象 服务端代码 public class HelloWorldServer {static final Logger log Logg…

【libcurl 】win32 构建 Release版本 修改cmakelist 链接openssl1.1.*

以下库均已MD的构建以vs2019 V142构建MD构建 直接换用了一个openssl库,libcurl连接报错 $(ProjectDir)..\..\..\3rdparty\openssl\xdw_openssl1_1_1\lib\win32\libcrypto.lib

【Unity】 UI自适应案例

UI自适应案例 案例一:背包自动布局1. 创建背包面板2. 背包子项自动布局3. C#代码:动态添加子项到背包中案例二:文字自适应高度1. 创建文字面板2. 组件基本设置3. C#代码:动态更新文字并自适应高度案例一:背包自动布局 需求:动态添加背包组件,设定每行特定个数并自动匹配…

C++学习之路-变量和基本内置类型

变量和基本内置类型 一、基本内置类型1.1 算数类型1.2 带符号类型和无符号类型1.3 类型转换含有无符号类型的表达式 1.4 字面值常量整形和浮点型字面值字符和字符串字面值转义序列指定字面值的类型 二、变量2.1 变量的定义初始化列表初始化默认初始化 2.2 变量声明和定义的关系…

彻底理解粘性定位 - position: sticky(IT枫斗者)

彻底理解粘性定位 - position: sticky 介绍 粘性定位可以被认为是相对定位(position: relative)和固定定位(position: fixed)的混合。元素在跨越特定阈值前为相对定位&#xff0c;之后为固定定位。例如: .sticky-header { position: sticky; top: 10px; }在 视口滚动到元素…

python处理字符串、文本实例及注释

1、多个界定符切割字符串 代码 line = asdf fjdk; afed, fjek,asdf, foo import re re.split(r[;,\s]\s*, line) 结果 在上面的例子中,分隔符可以是逗号,分号或者是空格,并且后面紧跟着任意个的空格。只要这个模式被找到,那么匹配的分隔符两边的实体都会被当成是结果中…

【数据结构与算法】- 期末考试

课程链接: 清华大学驭风计划 代码仓库&#xff1a;Victor94-king/MachineLearning: MachineLearning basic introduction (github.com) 驭风计划是由清华大学老师教授的&#xff0c;其分为四门课&#xff0c;包括: 机器学习(张敏教授) &#xff0c; 深度学习(胡晓林教授), 计算…

可算是熬出头了,测试6年,费时8个月,入职阿里,涨薪14K

前言 你的努力&#xff0c;终将成就无可替代的自己。 本科毕业后就一直从事测试的工作&#xff0c;和多数人一样&#xff0c;最开始从事点点点的工作&#xff0c;看着自己的同学一步一步往上走&#xff0c;自己还是在原地踏步&#xff0c;说实话这不是自己想要的状态。 一年半…

在 Android 手机上恢复出厂设置后恢复照片的 4 种简单方法(新方法)

“嗨&#xff0c;谁能帮我恢复我的照片&#xff0c;因为我不小心恢复了出厂设置&#xff0c;而且我没有做备份&#xff1f;几个月来我一直试图通过使用恢复软件来恢复我的照片&#xff0c;root 了一个深扫描&#xff0c;但没用……” 恢复出厂设置可以清除电子设备的所有信息并…