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 .