Compreensão pessoal do agente dinâmico Java

Modo proxy estático

O acima é um diagrama de classe de implementação de proxy estático simples

Use o modo proxy para controlar o acesso do programa ao objeto RealSubject . Se um acesso anormal for encontrado, você pode limitar diretamente o fluxo ou retorno, ou pode executar o pré-processamento e o pós-processamento relacionados antes e depois que o processamento de negócios é realizado para ajude o chamador superior a proteger a camada inferior Os detalhes. Por exemplo, na estrutura RPC, o proxy pode concluir uma série de operações, como serialização, operações de E / S de rede, balanceamento de carga, recuperação de falhas e descoberta de serviço, enquanto o chamador superior percebe apenas uma chamada local.

O modo proxy também pode ser usado para implementar a função de carregamento lento . Sabemos que consultar o banco de dados é uma operação demorada e, às vezes, os dados consultados não são realmente usados ​​pelo programa. A função de carregamento lento pode efetivamente evitar esse desperdício. Quando o sistema acessa o banco de dados, ele pode primeiro obter um objeto proxy. No momento, nenhuma operação de consulta de banco de dados é realizada. Naturalmente, não há dados reais no objeto proxy; quando o sistema realmente precisa usar os dados Quando, chame o objeto proxy para completar a consulta ao banco de dados e retornar os dados. O princípio de carregamento lento em estruturas ORM comuns (por exemplo, MyBatis, Hibernate) é praticamente o mesmo.

Além disso, o objeto proxy pode coordenar o relacionamento entre o objeto RealSubject real e o chamador e obter o efeito de desacoplamento até certo ponto.

Proxy dinâmico JDK

A implementação do modo proxy descrito acima também é chamado de "modo proxy estático", pois é necessário criar uma classe Proxy para cada classe RealSubject durante a fase de compilação. Quando há muitas classes que precisam ser proxy, um grande número de eles irão aparecer. Classe de proxy.

Nesse cenário, podemos usar o proxy dinâmico JDK para resolver esse problema. O núcleo do proxy dinâmico JDK é a interface InvocationHandler . Aqui está uma implementação de demonstração de InvocationHandler, o código é o seguinte:

public class DemoInvokerHandler implements InvocationHandler {
    private Object target; // 真正的业务对象,也就是RealSubject对象
    public DemoInvokerHandler(Object target) { // 构造方法
        this.target = target;
    }
    public Object invoke(Object proxy, Method method, Object[] args)
             throws Throwable {
        // ...在执行业务方法之前的预处理...
        Object result = method.invoke(target, args);
        // ...在执行业务方法之后的后置处理...
        return result;
    }
    public Object getProxy() {
        // 创建代理对象
        return Proxy.newProxyInstance(Thread.currentThread()
            .getContextClassLoader(),
                target.getClass().getInterfaces(), this);
    }
}

Em seguida, podemos criar um método main () para simular o chamador superior, criar e usar um proxy dinâmico:

public class Main {
    public static void main(String[] args) {
        Subject subject = new RealSubject();
        DemoInvokerHandler invokerHandler = new DemoInvokerHandler(subject);
        // 获取代理对象
        Subject proxy = (Subject) invokerHandler.getProxy();
        // 调用代理对象的方法,它会调用DemoInvokerHandler.invoke()方法
        proxy.operation();
    }
}

Para classes de negócios que requerem a mesma lógica de proxy, você só precisa fornecer uma classe de implementação de interface InvocationHandler. Durante a execução do Java, o JDK irá gerar dinamicamente a classe de proxy correspondente para cada classe RealSubject e carregá-la na JVM, em seguida, criar o objeto de instância de proxy correspondente e retorná-lo ao chamador superior.

O ponto de entrada da implementação do proxy dinâmico JDK é o método estático Proxy.newProxyInstance (). Seus três parâmetros são o carregador de classes para carregar a classe de proxy gerada dinamicamente, a interface implementada pela classe de negócios e o objeto InvocationHandler apresentado acima. A implementação específica do método Proxy.newProxyInstance () é a seguinte:

public static Object newProxyInstance(ClassLoader loader,
     Class[] interfaces, InvocationHandler h) 
         throws IllegalArgumentException {
    final Class<?>[] intfs = interfaces.clone();
    // ...省略权限检查等代码
    Class<?> cl = getProxyClass0(loader, intfs);  // 获取代理类
    // ...省略try/catch代码块和相关异常处理
    // 获取代理类的构造方法
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    final InvocationHandler ih = h;
    return cons.newInstance(new Object[]{h});  // 创建代理对象
}

Por meio da implementação do método newProxyInstance (), pode-se ver que o proxy dinâmico JDK completa a geração e o carregamento da classe de proxy no método getProxyClass0 (). A implementação específica do método getProxyClass0 () é a seguinte:

private static Class getProxyClass0 (ClassLoader loader, 
        Class... interfaces) {
    // 边界检查,限制接口数量(略)
    // 如果指定的类加载器中已经创建了实现指定接口的代理类,则查找缓存;
    // 否则通过ProxyClassFactory创建实现指定接口的代理类
    return proxyClassCache.get(loader, interfaces);
}

proxyClassCache é um campo estático definido na classe Proxy. É usado principalmente para armazenar em cache as classes de proxy que foram criadas. A definição é a seguinte:

private static final WeakCache[], Class> proxyClassCache
     = new WeakCache<>(new KeyFactory(), 
           new ProxyClassFactory());

O método WeakCache.get () tentará primeiro encontrar a classe de proxy no cache. Se não puder ser encontrada, criará um objeto Factory e chamará seu método get () para obter a classe de proxy. Factory é uma classe interna em WeakCache. O método Factory.get () chamará o método ProxyClassFactory.apply () para criar e carregar a classe proxy.

O método ProxyClassFactory.apply () primeiro detecta o conjunto de interfaces que a classe proxy precisa implementar, depois determina o nome da classe proxy, cria a classe proxy e grava no arquivo, finalmente carrega a classe proxy e retorna o objeto Class correspondente para uso subsequente Instancie o objeto de classe proxy. A implementação específica deste método é a seguinte:

public Class apply(ClassLoader loader, Class[] interfaces) {
    // ... 对interfaces集合进行一系列检测(略)
    // ... 选择定义代理类的包名(略)
    // 代理类的名称是通过包名、代理类名称前缀以及编号这三项组成的
    long num = nextUniqueNumber.getAndIncrement();
    String proxyName = proxyPkg + proxyClassNamePrefix + num;
    // 生成代理类,并写入文件
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
    
    // 加载代理类,并返回Class对象
    return defineClass0(loader, proxyName, proxyClassFile, 0, 
      proxyClassFile.length);
}

O método ProxyGenerator.generateProxyClass () irá gerar o bytecode da classe proxy de acordo com o nome especificado e conjunto de interface, e decidir se salvá-lo no disco de acordo com as condições. O código específico deste método é o seguinte:

public static byte[] generateProxyClass(final String name,
       Class[] interfaces) {
    ProxyGenerator gen = new ProxyGenerator(name, interfaces);
    // 动态生成代理类的字节码,具体生成过程不再详细介绍,感兴趣的读者可以继续分析
    final byte[] classFile = gen.generateClassFile();
    // 如果saveGeneratedFiles值为true,会将生成的代理类的字节码保存到文件中
    if (saveGeneratedFiles) { 
        java.security.AccessController.doPrivileged(
            new java.security.PrivilegedAction() {
                public Void run() {
                    // 省略try/catch代码块
                    FileOutputStream file = new FileOutputStream(
                        dotToSlash(name) + ".class");
                    file.write(classFile);
                    file.close();
                    return null;
                }
            }
        );
    }
    return classFile; // 返回上面生成的代理类的字节码
}

Finalmente, para ver claramente a definição real da classe proxy gerada dinamicamente pelo JDK, precisamos descompilar o bytecode da classe proxy gerada acima. O exemplo acima é a classe proxy gerada pelo RealSubject, o código obtido após a descompilação é o seguinte:

public final class $Proxy37 
      extends Proxy implements Subject {  // 实现了Subject接口
    // 这里省略了从Object类继承下来的相关方法和属性
    private static Method m3;
    static {
        // 省略了try/catch代码块
        // 记录了operation()方法对应的Method对象
        m3 = Class.forName("com.xxx.Subject")
          .getMethod("operation", new Class[0]);
    }
    // 构造方法的参数就是我们在示例中使用的DemoInvokerHandler对象
    public $Proxy11(InvocationHandler var1) throws {
        super(var1); 
    }
    public final void operation() throws {
        // 省略了try/catch代码块
        // 调用DemoInvokerHandler对象的invoke()方法
        // 最终调用RealSubject对象的对应方法
        super.h.invoke(this, m3, (Object[]) null);
    }
}

Até agora, o uso básico e os princípios básicos do proxy dinâmico JDK foram apresentados. Para resumir brevemente, o princípio de implementação do proxy dinâmico JDK é criar classes de proxy dinamicamente e carregá-las por meio do carregador de classes especificado e passar o objeto InvocationHandler como o parâmetro de construção ao criar o objeto de proxy. Quando o objeto proxy é chamado, o método InvocationHandler.invoke () é chamado para executar a lógica do proxy e, finalmente, chamar o método correspondente do objeto de negócios real.

CGLib

O proxy dinâmico JDK é nativamente suportado por Java e não requer dependências externas. No entanto, conforme analisado acima, ele só pode fazer proxy com base em interfaces. Para classes que não herdam nenhuma interface, o proxy dinâmico JDK é inútil .

Se você deseja fazer proxy de uma classe que não implementa nenhuma interface, pode considerar o uso de CGLib.

CGLib (Code Generation Library) é uma biblioteca de geração de bytecode baseada em ASM , que nos permite modificar e gerar bytecode dinamicamente em tempo de execução. CGLib usa tecnologia de bytecode para implementar funções de proxy dinâmicas.O princípio subjacente é gerar uma subclasse para a classe de destino por meio da tecnologia de bytecode e usar a interceptação de método na subclasse para interceptar todas as chamadas de método da classe pai para alcançar a função de proxy.

Como o CGLib implementa proxy dinâmico gerando subclasses, ele não pode fazer proxy de métodos modificados pela palavra-chave final (porque os métodos finais não podem ser substituídos). Desta forma, CGLib e proxy dinâmico JDK podem se complementar: quando a classe de destino implementa a interface, use o proxy dinâmico JDK para criar o objeto de proxy; quando a classe de destino não implementa a interface, use CGLib para implementar a função de o proxy dinâmico . Em uma variedade de estruturas de software livre, como Spring e MyBatis, você pode ver o uso combinado de proxy dinâmico JDK e CGLib.

A implementação do CGLib consiste em dois membros importantes.

  • Enhancer : Especifique o objeto de destino a ser proxy e o objeto que realmente lida com a lógica do proxy. Finalmente, o objeto proxy é obtido chamando o método create (). Todas as chamadas de método não finais deste objeto serão encaminhadas para MethodInterceptor para processamento .

  • MethodInterceptor : As chamadas de método do objeto proxy dinâmico serão encaminhadas para o método de interceptação para aprimoramento.

O uso desses dois componentes é semelhante ao Proxy e InvocationHandler no proxy dinâmico JDK.

por exemplo:

public class CglibProxy implements MethodInterceptor {
    // 初始化Enhancer对象
    private Enhancer enhancer = new Enhancer(); 
    
    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz); // 指定生成的代理类的父类
        enhancer.setCallback(this); // 设置Callback对象
        return enhancer.create(); // 通过ASM字节码技术动态创建子类实例
    }
    
    // 实现MethodInterceptor接口的intercept()方法
    public Object intercept(Object obj, Method method, Object[] args,
                   MethodProxy proxy) throws Throwable {
        System.out.println("前置处理");
        Object result = proxy.invokeSuper(obj, args); // 调用父类中的方法
        System.out.println("后置处理");
        return result;
    }
}  

// 这里是测试的类
public class CGLibTest { // 目标类
    public String method(String str) {   // 目标方法
        System.out.println(str);
        return "CGLibTest.method():" + str;
    }
    
    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        // 生成CBLibTest的代理对象
        CGLibTest proxyImp = (CGLibTest) 
            proxy.getProxy(CGLibTest.class);
        // 调用代理对象的method()方法
        String result = proxyImp.method("test");
        System.out.println(result);
    }
}

Javassist

Javassist é uma biblioteca de classes de código aberto que gera bytecode Java . Sua principal vantagem é que é simples e rápido. Você pode usar diretamente a API Java fornecida por Javassist para modificar dinamicamente a estrutura da classe ou gerar dinamicamente a classe.

O uso de Javassist é relativamente simples, vamos primeiro ver como usar a API Java fornecida por Javassist para criar classes dinamicamente. O código de amostra é o seguinte:

public class JavassistMain {
    public static void main(String[] args) throws Exception {
        ClassPool cp = ClassPool.getDefault(); // 创建ClassPool
        // 要生成的类名称为com.test.JavassistDemo
        CtClass clazz = cp.makeClass("com.test.JavassistDemo");
    
        StringBuffer body = null;
        // 创建字段,指定了字段类型、字段名称、字段所属的类
        CtField field = new CtField(cp.get("java.lang.String"), 
            "prop", clazz);
        // 指定该字段使用private修饰
        field.setModifiers(Modifier.PRIVATE);
    
        // 设置prop字段的getter/setter方法
        clazz.addMethod(CtNewMethod.setter("getProp", field));
        clazz.addMethod(CtNewMethod.getter("setProp", field));
        // 设置prop字段的初始化值,并将prop字段添加到clazz中
        clazz.addField(field, CtField.Initializer.constant("MyName"));
    
        // 创建构造方法,指定了构造方法的参数类型和构造方法所属的类
        CtConstructor ctConstructor = new CtConstructor(
            new CtClass[]{}, clazz);
        // 设置方法体
        body = new StringBuffer();
        body.append("{\n prop=\"MyName\";\n}");
        ctConstructor.setBody(body.toString());
        clazz.addConstructor(ctConstructor); // 将构造方法添加到clazz中
    
        // 创建execute()方法,指定了方法返回值、方法名称、方法参数列表以及
        // 方法所属的类
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "execute",
                new CtClass[]{}, clazz);
        // 指定该方法使用public修饰
        ctMethod.setModifiers(Modifier.PUBLIC);
        // 设置方法体
        body = new StringBuffer();
        body.append("{\n System.out.println(\"execute():\" " +
                "+ this.prop);");
        body.append("\n}");
        ctMethod.setBody(body.toString());
        clazz.addMethod(ctMethod); // 将execute()方法添加到clazz中
        // 将上面定义的JavassistDemo类保存到指定的目录
        clazz.writeFile("/Users/xxx/"); 
        // 加载clazz类,并创建对象
        Class<?> c = clazz.toClass();
        Object o = c.newInstance();
        // 调用execute()方法
        Method method = o.getClass().getMethod("execute", 
          new Class[]{});
        method.invoke(o, new Object[]{});
    }
}

Depois de executar o código acima, você pode encontrar o arquivo JavassistDemo.class gerado no diretório especificado, descompilá-lo e obter o código JavassistDemo da seguinte maneira:

public class JavassistDemo {
    private String prop = "MyName";
    public JavassistDemo() {
        prop = "MyName";
    }
    public void setProp(String paramString) {
        this.prop = paramString;
    }
    public String getProp() {
        return this.prop;
    }
    public void execute() {
        System.out.println("execute():" + this.prop);
    }
}

Javassist também pode implementar funções de proxy dinâmicas e os princípios subjacentes também são implementados criando subclasses da classe de destino. Aqui, usamos Javassist para criar um objeto proxy para o JavassitDemo gerado acima. A implementação específica é a seguinte:

public class JavassitMain2 {
    public static void main(String[] args) throws Exception {
        ProxyFactory factory = new ProxyFactory();
        // 指定父类,ProxyFactory会动态生成继承该父类的子类
        factory.setSuperclass(JavassistDemo.class);
        // 设置过滤器,判断哪些方法调用需要被拦截
        factory.setFilter(new MethodFilter() {
            public boolean isHandled(Method m) {
                if (m.getName().equals("execute")) {
                    return true;
                }
                return false;
            }
        });
        // 设置拦截处理
        factory.setHandler(new MethodHandler() {
            @Override
            public Object invoke(Object self, Method thisMethod, 
                Method proceed, Object[] args) throws Throwable {
                System.out.println("前置处理");
                Object result = proceed.invoke(self, args);
                System.out.println("执行结果:" + result);
                System.out.println("后置处理");
                return result;
            }
        });
        // 创建JavassistDemo的代理类,并创建代理对象
        Class<?> c = factory.createClass();
        JavassistDemo JavassistDemo = (JavassistDemo) c.newInstance();
        JavassistDemo.execute(); // 执行execute()方法,会被拦截
        System.out.println(JavassistDemo.getProp());
    }
}

O conhecimento básico de Javassist é apresentado aqui. Javassist pode usar diretamente as strings da linguagem Java para gerar classes, o que é relativamente fácil de usar. Javassist também tem melhor desempenho, que é o método de geração de proxy padrão do Dubbo .

 

Acho que você gosta

Origin blog.csdn.net/qq_32323239/article/details/109428092
Recomendado
Clasificación