Java - Proxy代理

Itachi 2020年01月01日 234次浏览

Proxy - 代理

静态代理

继承方式

目标对象IndexService

/**
 * @author Itachi is.xianglei@gmail.com
 * @Date 2020-01-01 20:58
 */
public class IndexService {

    public void select(){
        System.out.println("select...");
    }

}

代理对象IndexServiceProxy

/**
 * @author Itachi is.xianglei@gmail.com
 * @Date 2020-01-01 20:59
 */
public class IndexServiceProxy extends IndexService {

    @Override
    public void select() {
        System.out.println("开启资源...");
        super.select();
    }

}

控制台输出

开启资源...

select...

针对以上,如果说有一个新需求,需要在开启资源之前加上一个记录时间的功能.那么应该怎么写?

记录时间

/**
 * @author Itachi is.xianglei@gmail.com
 * @Date 2020-01-01 21:21
 */
public class IndexServiceProxyTime extends IndexServiceProxy {

    @Override
    public void select() {
        System.out.println("开始记录时间...");
        super.select();
    }
}
/**
 * @author Itachi is.xianglei@gmail.com
 * @Date 2019-12-31 09:31
 */
public class Test {

    public static void main(String[] args) {

        IndexService service = new IndexServiceProxyTime();
        service.select();

    }

}

控制台输出

开始记录时间...

开启资源...

select...

(存在即合理,在不改变原有代理类的情况下,保证类的单一原则) 那么我们就需要在新建一个记录时间的类去继承IndexServiceProxy

那么现在又有了新的需求,说:需要先开启资源,然后在开始记录时间.那么又改如何实现?

/**
 * @author Itachi is.xianglei@gmail.com
 * @Date 2020-01-01 21:38
 */
public class IndexProxyTime extends IndexService {

    @Override
    public void select() {
        System.out.println("开始记录时间...");
        super.select();
    }
}
/**
 * @author Itachi is.xianglei@gmail.com
 * @Date 2020-01-01 20:59
 */
public class IndexServiceProxy extends IndexProxyTime {

    @Override
    public void select() {
        System.out.println("开启资源...");
        super.select();
    }
}
/**
 * @author Itachi is.xianglei@gmail.com
 * @Date 2019-12-31 09:31
 */
public class Test {

    public static void main(String[] args) {

        IndexService service = new IndexServiceProxy();
        service.select();

    }

}

控制台

开启资源...

开始记录时间...

select...

那么我们就得要在提供一个(IndexProxyTime)只继承IndexService的代理类.然后再修改IndexServiceProxy继承自这个代理类(IndexProxyTime).完成代理.

看一下类图

image.png

这个还只是单单完成了两个逻辑...如果说还要继续改需求呢?让人有点头皮发麻...

类过多,类臃肿,为了实现一个逻辑而新建了一个或多个类..

聚合方式(接口)

新建目标类

/**
 * @author Itachi is.xianglei@gmail.com
 * @Date 2020-01-01 21:59
 */
public interface Service {

    void select();

}
/**
 * @author Itachi is.xianglei@gmail.com
 * @Date 2020-01-01 21:59
 */
public class ServiceImpl implements Service {

    public void select() {
        System.out.println("select...");
    }

}

新建代理类

/**
 * @author Itachi is.xianglei@gmail.com
 * @Date 2020-01-01 22:00
 */
public class ServiceProxyLog implements Service {

    Service target;

    public ServiceProxyLog(Service service) {
        this.target = service;
    }

    public void select() {
        System.out.println("记录日志...");
        target.select();
    }
}

聚合有一个特点就是,你的代理对象和目标对象一定要实现同一个接口,

那么我们的目标对象实现的是Service这个接口,所以我们的代理对象也一样要实现Service

还有就是,我们的代理对象内一定要包含代理对象,那么我们可以提供set方法传,也可以使用构造方法.

测试类

/**
 * @author Itachi is.xianglei@gmail.com
 * @Date 2019-12-31 09:31
 */
public class Test {

    public static void main(String[] args) {

        Service service = new ServiceProxyLog(new ServiceImpl());
        service.select();

    }

}

控制台输出

记录日志...

select...

如果想在记录时间呢?,很简单.

/**
 * @author Itachi is.xianglei@gmail.com
 * @Date 2020-01-01 22:10
 */
public class ServiceProxyTime implements Service {

    Service target;

    public ServiceProxyTime(Service target) {
        this.target = target;
    }

    public void select() {
        System.out.println("开始记录时间...");
        target.select();
    }
}
/**
 * @author Itachi is.xianglei@gmail.com
 * @Date 2019-12-31 09:31
 */
public class Test {

    public static void main(String[] args) {

        Service service = new ServiceProxyTime(new ServiceImpl());
        service.select();

    }

}

控制台输出

开始记录时间...

select...

同样可以完成...如果要先记录时间,在记录日志呢?那就更加简单了..

/**
 * @author Itachi is.xianglei@gmail.com
 * @Date 2019-12-31 09:31
 */
public class Test {

    public static void main(String[] args) {

        Service proxy = new ServiceProxyLog(new ServiceImpl());
        Service service = new ServiceProxyTime(proxy);
        service.select();

    }

}

控制台输出

开始记录时间...

记录日志...

select...

只需要改动这一个地方就可以了...如果说要先记录日志,在记录时间呢??那么就只需要调换一下顺序就可以咯...是不是很灵活~~

动态代理

JDK动态代理

/**
 * @author Itachi is.xianglei@gmail.com
 * @Date 2020-01-01 22:29
 */
public interface LoginService {

    void login();

}
/**
 * @author Itachi is.xianglei@gmail.com
 * @Date 2020-01-01 22:30
 */
public class LoginServiceImpl implements LoginService {

    public void login() {
        System.out.println("login...");
    }

}
/**
 * @author Itachi is.xianglei@gmail.com
 * @Date 2019-12-31 09:31
 */
public class Test {

    public static void main(String[] args) {

        final LoginService loginService = new LoginServiceImpl();

        ClassLoader classLoader = loginService.getClass().getClassLoader();

        Class<?>[] interfaces = loginService.getClass().getInterfaces();

        LoginService instance = (LoginService)Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {

            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("记录日志...");
                Object invoke = method.invoke(loginService, args);
                return invoke;
            }
        });

        instance.login();
    }

}

控制台输出

记录日志...

login...

很简单,没啥说的.但是我们要知其然,知其所以然

模拟JDK动态代理底层实现!

注意只是模拟,并不是JDK底层真正就是这样实现的.. JDK并不会拼接后生成java文件并编译成class,JDK是直接生成的字节码文件加载到内存当中的!

ItachiProxy

public class ItachiProxy {

   public static Object getInstance(Object target) throws Exception {
      Class clazz = target.getClass().getInterfaces()[0];
      String infName = clazz.getSimpleName();
      String content = "";
      String line = "\n";//换行
      String tab = "\t";//tab
      String packageContent = "package cn.isgrow;" + line;
      String importContent = "import " + clazz.getName() + ";" + line;
      String clazzFirstLineContent = "public class $Proxy implements " + infName + "{" + line;
      String filedContent = tab + "private " + infName + " target;" + line;

      String constructorContent = tab + "public $Proxy (" + infName + " target){" + line
            + tab + tab + "this.target =target;"
            + line + tab + "}" + line;


      String methodContent = "";
      Method[] methods = clazz.getDeclaredMethods();

      for (Method method : methods) {
         //String
         String returnTypeName = method.getReturnType().getSimpleName();
         //query
         String methodName = method.getName();
         // [String.class===class]
         Class args[] = method.getParameterTypes();
         String argsContent = "";
         String paramsContent = "";
         int flag = 0;
         for (Class arg : args) {
            //String
            String temp = arg.getSimpleName();
            //String p0,
            argsContent += temp + " p" + flag + ",";
            //p0
            paramsContent += "p" + flag + ",";
            flag++;
         }
         if (argsContent.length() > 0) {
            argsContent = argsContent.substring(0, argsContent.lastIndexOf(",") - 1);
            paramsContent = paramsContent.substring(0, paramsContent.lastIndexOf(",") - 1);
         }
         //public String query(String p0){
         //    System.out.println("log");
         //    target.query(p0);
         // }
         methodContent += tab + "public " + returnTypeName + " " + methodName + "(" + argsContent + ") {" + line
               + tab + tab + "System.out.println(\"log\");" + line
               + tab + tab + "target." + methodName + "(" + paramsContent + ");" + line
               + tab + "}" + line;

      }

      content += packageContent + importContent + clazzFirstLineContent + filedContent + constructorContent + methodContent + "}";


      File file = new File("/Users/itachi/Desktop/$Proxy.java");

      try {
         if (!file.exists()) {
            file.createNewFile();
         }

         FileWriter fw = new FileWriter(file);
         fw.write(content);
         fw.flush();
         fw.close();
      } catch (Exception e) {

      }


      JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
      StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
      Iterable units = fileMgr.getJavaFileObjects(file);
      JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
      t.call();
      URL[] urls = new URL[]{new URL("file:/Users/itachi/Desktop/")};
      URLClassLoader urlClassLoader = new URLClassLoader(urls);
      Class cls = urlClassLoader.loadClass("cn.isgrow.$Proxy");
      Constructor constructor = cls.getConstructor(clazz);
      Object o  = constructor.newInstance(target);
      return  o;
   }

}

以上代码,是为了构造出一个你看不到的代理类.这个类往往是通用的模板.需要变动的地方特别少.

在桌面上可以看到已经生成了类文件

image.png

打开看一下生成的$Proxy.java文件内容

package cn.isgrow;

import cn.isgrow.dynamic.LoginService;

public class $Proxy implements LoginService {
    private LoginService target;

    public $Proxy(LoginService target) {
        this.target = target;
    }

    public void login(String p) {
        System.out.println("log");
        target.login(p);
    }
}

使用我们自己编写的动态代理测试

/**
 * @author Itachi is.xianglei@gmail.com
 * @Date 2019-12-31 09:31
 */
public class Test {

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

        LoginService loginService = new LoginServiceImpl();

        LoginService instance = (LoginService)ItachiProxy.getInstance(loginService);
        instance.login("登陆中...");
    }

}

控制台输出

log

登陆中...

一样可以完成代理.

以上代码在代理类的方法内写死了一段代码system.out.println("log") 到后面文章会编写真实的业务情况.

文章所涉及的代码下载地址: https://gitee.com/isidea/example-proxy

cglib动态代理

待续....