Jdk动态代理和基于反射实现任务调度

代理模式

代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
简单示意图:

代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。Java 动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念。

JDK动态代理实现过程

  • 核心接口InvocationHandler
    InvocationHandler是java.lang.reflect下的一接口,是由代理类调用处理程序必须实现的接口,每个代理类都有一个关联的调用处理程序。通常在代理模式中,需要重点留意这个接口,这里我先提一嘴,后边回根据字节码生成来详细看到具体的代理类
  • 自定义接口
public interface Subject {
    /**
     * 你好
     *
     * @param name
     * @return
     */
     String SayHello(String name);

    /**
     * 再见
     *
     * @return
     */
     String SayGoodBye();
}
  • 实现类
/**
 * @Description 实际委托类(被代理类)
 * @Author Fangchenjiang
 * @Date 2021/5/18 12:11
 */
public class RealSubject implements Subject {
    @Override
    public String SayHello(String name) {
        return  "hello,"+name;
    }

    @Override
    public String SayGoodBye() {
        return "goodbye,xf";
    }
}

  • 核心代理调度器实现
    此接口作为代理类的调度器处理程序,需要依赖一个委托类(真实对象类),
/**
 * @Description 代理调度实现类
 * @Author Fangchenjiang
 * @Date 2021/5/18 12:12
 */
public class InvocationHandlerImpl implements InvocationHandler {

    private Object subject;  //依赖的真实对象

    public InvocationHandlerImpl(Object subject)
    {
        this.subject = subject;
    }

    /**
     * 该方法负责集中处理动态代理类上的所有方法调用。
     * 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
     *
     * @param proxy  代理类实例
     * @param method 被调用的方法对象
     * @param args   调用参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        //在代理真实对象前我们可以添加一些自己的操作
        System.out.println("在调用之前,我要干点啥呢?");

        System.out.println("Method:" + method);

        //当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        Object returnValue = method.invoke(subject, args);

        //在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("在调用之后,我要干点啥呢?");

        return returnValue;
    }
}
  • 代理的使用
**
 * @Description 主测试类
 * @Author Fangchenjiang
 * @Date 2021/5/18 12:14
 */
public class Main {
    public static void main(String[] args) {
        //代理的真实对象
        Subject realSubject = new RealSubject();

        /**
         * InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
         * 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用.
         * 即:要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法
         */
        InvocationHandler handler = new InvocationHandlerImpl(realSubject);

        ClassLoader loader = realSubject.getClass().getClassLoader();
        Class[] interfaces = realSubject.getClass().getInterfaces();
        /**
         * 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
         */
        Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);


        System.out.println("动态代理对象的类型:"+subject.getClass().getName());

        String hello = subject.SayHello("jiankunking");
        System.out.println(hello);

//        String goodbye = subject.SayGoodBye();
//        System.out.println(goodbye);
        System.out.println(subject.toString());
        // 将生成的字节码保存到本地,
//        createProxyClassFile();
    }


    private static void createProxyClassFile(){
        String name = "ProxySubject";
        byte[] data = ProxyGenerator.generateProxyClass(name,new Class[]{Subject.class});
        FileOutputStream out =null;
        try {
            out = new FileOutputStream(name+".class");
            System.out.println((new File("hello")).getAbsolutePath());
            out.write(data);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null!=out) try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

字节码查看代理类

通过上面的createProxyClassFile方法,我们通过反编译查看真正的代理类ProxySubject

import com.example.demo.proxy.Subject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class ProxySubject extends Proxy implements Subject {
  private static Method m1;
  
  private static Method m4;
  
  private static Method m3;
  
  private static Method m2;
  
  private static Method m0;
  
  public ProxySubject(InvocationHandler paramInvocationHandler) {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject) {
    try {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final String SayGoodBye() {
    try {
      return (String)this.h.invoke(this, m4, null);
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final String SayHello(String paramString) {
    try {
      return (String)this.h.invoke(this, m3, new Object[] { paramString });
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final String toString() {
    try {
      return (String)this.h.invoke(this, m2, null);
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final int hashCode() {
    try {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  static {
    try {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m4 = Class.forName("com.example.demo.proxy.Subject").getMethod("SayGoodBye", new Class[0]);
      m3 = Class.forName("com.example.demo.proxy.Subject").getMethod("SayHello", new Class[] { Class.forName("java.lang.String") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    } catch (NoSuchMethodException noSuchMethodException) {
      throw new NoSuchMethodError(noSuchMethodException.getMessage());
    } catch (ClassNotFoundException classNotFoundException) {
      throw new NoClassDefFoundError(classNotFoundException.getMessage());
    } 
  }
}

我们可以看到生成ProxySubject就是最终生成的代理类,客户端通过此类并传入真实对象实现类的引用去完成整个控制访问的过程,该代理类不仅继承代理模式核心基类Proxy并实现了Subject接口。

反射实现任务调度

实际开发场景回遇到任务调度,常见的比如定时器业务,但是定时器也回存在一定的缺陷,无法实现任意时刻工作,某些动态配置需要重启应用程序才能生效,定时器开关实时切换等。所以针对特殊场景,可能需要手动开发某个任务调度接口,只需将className和methodName作为请求参数给到客户端,后端接受请求参数通过反射实现业务的调度即可。

  • SpringMVC实现
	@Autowired
    private WebApplicationContext webApplicationContext;
	@RequestMapping(value = "/callTask", method = RequestMethod.GET)
    @ApiOperation(value = "定时任务调度")
    public String callMethodByAnnotation(@RequestParam @ApiParam(required = true, value = "className") String className,
                                        @RequestParam @ApiParam(required = true, value = "methodName") String methodName) {
        try {
            Object obj = webApplicationContext.getBean(Class.forName(className));
            if (obj == null) {
                return "定时任务不存在";
            }
            Class<?> cls = obj.getClass();
            Method method = cls.getDeclaredMethod(methodName);
            method.invoke(obj);

            return "调用定时任务成功";
        } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException | BeansException | ClassNotFoundException e) {
            e.printStackTrace();
            return "调用定时任务失败";
        }
    }

通过反射获取Spring容器Bean实例,解析该实例对象拿到具体的业务工作方法,调用Method类的invoke即可。注意:该业务调度类事先保证托付给到Spring容器。这样,我们就只需将接口地址给到客户端,就能通过http方式实现简单的手动任务调度,本质还是基于反射的好处。

总结

JDK的动态代理和反射的运用场景在开发中比较常见和实用,需要牢记并且掌握其中的原理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值