Table of Contents generated with DocToc

  • The proxy pattern
  • Static agent
    • Inherit the agent
    • Polymerization agent
  • A dynamic proxy
    • JDK dynamic proxy
    • Imitation dynamic agent

The proxy pattern

In the Proxy Pattern, one class represents the functionality of another class. This type of design pattern is structural. In the proxy mode, we create objects with existing objects to provide functional interfaces to the outside world.

Intent: Provide a proxy for other objects to control access to this object. When to use: You want some control over access to a class.

Several implementations of implementation agents:

  • Static proxies, such as through inheritance or aggregation
  • Dynamic proxy, such as JDK dynamic proxy or Cglib dynamic proxy

Terms:

  • Proxy object: object with added control logic (enhanced object)
  • Target object: The object to which control logic is to be added (the object to be enhanced)

Static agent

Inherit the agent

Disadvantages: When there are multiple proxy services, the number of proxy classes increases, and if there are proxy links, there are more and more complex classes

public class UserDao {
    public void save(String user) {
        System.out.println("save user info:"+ user); }}public class UserDaoLogProxy extends UserDao {
    @Override
    public void save(String user) {
        System.out.println("com.sen.sp.DogProxy: dog proxy logic");
        super.save(user); }}Copy the code

Polymerization agent

The target object implements the same interface as the proxy object, and the proxy object contains the target object. Disadvantages: Proxy classes also increase with services, but there is no need to create a new inherited link class when forming proxy links compared with inherited proxies

public interface UserDao {
    void save(String user);
}
public class UserDaoImpl implements UserDao {
    @Override
    public void save(String user) {
        System.out.println("save user info:"+ user); }}public class UserDaoLogProxy implements UserDao {
    UserDao target;
    public UserDaoLogProxy(UserDao target) {
        this.target = target;
    }
    @Override
    public void save(String user) {
        System.out.println(this.getClass().getSimpleName()+": logs logic"); target.save(user); }}public class UserDaoVerifyProxy implements UserDao {
    UserDao target;
    public UserDaoVerifyProxy(UserDao target) {
        this.target = target;
    }
    @Override
    public void save(String user) {
        System.out.println(this.getClass().getSimpleName()+": verify logic"); target.save(user); }}public static void main(String[] args) {
    UserDao target = new UserDaoImpl();
    // The target object exists as an attribute of the proxy object, which is more flexible than the order in which the proxy is inherited. There is no problem of link inheritance producing more classes
    //UserDao proxy = new UserDaoVerifyProxy(new UserDaoLogProxy(target));
    UserDao proxy = new UserDaoLogProxy(new UserDaoVerifyProxy(target));
    proxy.save("lishi");
}
Copy the code

A dynamic proxy

JDK dynamic proxy

JDK dynamic proxies only allow dynamic Proxy interfaces, because dynamically generated Proxy classes inherit from Proxy classes, based on the Java principle of single inheritance, so only interfaces can be Proxy

public class JdkProxyDemo {
    public static void main(String[] args) {
        Object proxy = Proxy.newProxyInstance(App.class.getClassLoader(), newClass<? >[]{ReadInterface.class, WriteInterface.class},new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // If an Object method is called, it is executed by a proxy, such as toString,hashCodeClass<? > declaringClass = method.getDeclaringClass();if (Object.class.equals(declaringClass)) {
                    return method.invoke(this, args);
                }
                return "this is method result,method:" + method.getName() + ",args:{" + args[0] + "}"; }}); ReadInterface readProxy = (ReadInterface) proxy; System.out.println(readProxy.read("d:/")); //this is method result,method:read,args:{d:/}

        WriteInterface writeProxy = (WriteInterface) proxy;
        System.out.println(writeProxy.write("e:/")); //this is method result,method:write,args:{e:/}
    }

    public interface ReadInterface {
        String read(String path);
    }
    public interface WriteInterface {
        String write(String path); }}Copy the code

It is design guesswork to only dynamically proxy interfaces, because there are some problems with dynamically proxy a class. In proxy mode, the proxy class only does some additional interception processing; the actual processing is forwarded to the original class. If you allow dynamic proxiing of a class, the proxy class inherits the target class, and the proxy object inherits all the fields of the target class, which are actually unused and a waste of memory space. Since business proxy objects only intercept and forward methods, object field access is handled on the original object. Even more deadly is that if the proxy class has a final method, the dynamically generated class can’t override that method, can’t proxy, and access fields from the proxy object, which is obviously not the desired result. The Spring AOP framework is such a pattern

Imitation dynamic agent

The JDK can only Proxy interfaces but not classes because dynamically generated Proxy classes inherit from Proxy classes. You can implement Proxy classes if you inherit from Proxy target classes instead

Dynamic proxy implementation steps:

  1. Generate Java source files
  2. Dynamically compile Java files into class bytecode files
  3. The class object is obtained by loading the class bytecode file into the JVM through the class loader
public class ProxyUtil  {
    // The proxy class enhances the callback
    public interface InvocationHandler {
        Object invoke(Object proxy, Method method, Object[] args);
    }
    // Generate proxy class Java source files
    private static String generateProxyJava(Class
        target) {
        StringBuilder sb = new StringBuilder();
        sb.append("package " + target.getPackage().getName() + "; \n");
        sb.append("import com.sen.dynamicproxy.jdkproxy.ProxyUtil.InvocationHandler; \n");
        sb.append("import java.lang.reflect.Method; \n");
        sb.append("import " + target.getCanonicalName() + "; \n");

        sb.append("public class $" + target.getSimpleName() + "Proxy " + (target.isInterface() ? "implements " : "extends ") + target.getSimpleName() + " {\n");
        sb.append(" private InvocationHandler handler; \n");
        sb.append(" public $" + target.getSimpleName() + "Proxy(InvocationHandler handler){\n");
        sb.append(" this.handler=handler; \n");
        sb.append(" }\n");

        for(Method method : target.getDeclaredMethods()) { Class<? >[] types = method.getParameterTypes(); String args ="";
            String params = "";
            String argsType = "";
            for (int i = 0; i < types.length; i++) {
                argsType += "," + types[i].getCanonicalName() + ".class";
                args += "," + types[i].getCanonicalName() + " p" + i;
                params += ",p" + i;
            }
            if (args.length() > 0) {
                args = args.substring(1);
                params = params.substring(1);
                argsType = argsType.substring(1);
            }
            String returnType = "void".equals(method.getReturnType().getName()) ? "" : "return (" + method.getReturnType().getName() + ")";

            sb.append(" @Override\n");
            sb.append(" public " + method.getReturnType().getName() + "" + method.getName() + "(" + args + ") {\n");
            sb.append(" Method method = null; \n");
            sb.append(" try {\n");
            sb.append(" method = " + target.getSimpleName() + ".class.getDeclaredMethod(\"" + method.getName() + "\",new Class
       [] {" + argsType + "}); \n");
            sb.append(" } catch (Exception e) {\n");
            sb.append(" throw new RuntimeException(e); \n");
            sb.append(" }\n");
            sb.append("" + returnType + "handler.invoke(this,method,new Object[]{" + params + "}); \n");
            sb.append(" }\n");
        }
        sb.append("}");
        return sb.toString();
    }

    
    public static Object newProxyInstance(ClassLoader loader, Class
        target, InvocationHandler handler) throws Exception{
        String distDir = ProxyUtil.class.getClassLoader().getResource("").getPath()+"/";
        File packageDir = new File(distDir + target.getPackage().getName().replaceAll("\ \."."/"));
        if(! packageDir.exists()) { packageDir.mkdirs(); } Path path = Paths.get(packageDir.getPath() +"/ $" + target.getSimpleName() + "Proxy.java");
        Files.write(path, generateProxyJava(target).getBytes(), StandardOpenOption.CREATE,StandardOpenOption.TRUNCATE_EXISTING);

        // Compile Java source files
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        int flag = compiler.run(null.null.null, path.toString());
        if(flag! =0) {throw new RuntimeException("Failed to compile");
        }
        // If the proxy class is generated externally, URLClassLoader is required to load it, such as D:/com/sen/$testproxy.java
        // loader = new URLClassLoader(new URL[]{new URL("file://D://")});

        // Load the class and get the proxy object by reflectionClass<? > aClass = loader.loadClass(target.getPackage().getName() +"$" + target.getSimpleName() + "Proxy"); Constructor<? > declaredConstructor = aClass.getDeclaredConstructor(InvocationHandler.class);returndeclaredConstructor.newInstance(handler); }}Copy the code

Example:

public class ProxyDemo {
    public interface Animal {
        String eat(String food);
    }

    public static class Hunter {
        public boolean fire(a){
            System.out.println("Hunter fire");
            return false; }}public static void main(String[] args) throws Exception {
        // Proxy interface exampleAnimal animalProxy = (Animal) ProxyUtil.newProxyInstance(ProxyUtil.class.getClassLoader(), Animal.class, (proxy, method, args1) -> { Class<? > declaringClass = method.getDeclaringClass();if (Object.class.equals(declaringClass)) {
                try {
                    return method.invoke(this, args1);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            System.out.println("proxy interface logs --------------------");
            return method.getName() + "," + args1[0];
        });
        System.out.println("animalProxy result:"+animalProxy.eat("cookie"));
        
        
        // A proxy object example that requires the object to be passed into InvocationHandler, as in the following example, or through the InvocationHandler constructor
        Hunter hunterProxy = (Hunter) ProxyUtil.newProxyInstance(ProxyUtil.class.getClassLoader(), Hunter.class, new ProxyUtil.InvocationHandler() {
            Hunter hunter = new Hunter();
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) {
                try {
                    System.out.println("proxy class logs --------------------");
                    return method.invoke(hunter, args);
                } catch (Exception e) {
                    throw newRuntimeException(e); }}}); System.out.println("hunterProxy result:"+hunterProxy.fire()); }}Copy the code

In the above example, for readability, the proxy class Java source file is generated without determining whether the method is rewritable, and the code is optimized. JDK dynamic proxy implementation does not generate Java source files, directly generates the class bytecode of the proxy class, and then calls the native method to convert the class bytecode into the class
object. Specific see method Java. Lang. Reflect. Proxy. ProxyClassFactory. Apply

public Class<? > apply(ClassLoader loader, Class<? >[] interfaces) { // .... Other code omitted byte [] proxyClassFile = ProxyGenerator. GenerateProxyClass (proxyName, interfaces, accessFlags); Try {// Call the local method to convert the bytecode proxyClassFile to the Class<? > object return defineClass0 (loader, proxyName, proxyClassFile, 0, proxyClassFile length); } catch (ClassFormatError e) { // .... }} private static native Class<? > defineClass0(ClassLoader loader, String name, byte[] b, int off, int len);Copy the code