1. Definição do modo proxy
Forneça um objeto com um objeto proxy e o objeto proxy controla o acesso ao objeto original, ou seja, o cliente não manipula diretamente o objeto original, mas indiretamente manipula o objeto original através do objeto proxy.
Um exemplo de um padrão de proxy famoso é a contagem de referência: quando várias cópias de um objeto complexo são necessárias, o padrão de proxy pode ser combinado com o modo meta para reduzir a quantidade de memória. Uma abordagem típica é criar um objeto complexo e um proxy múltiplo, cada proxy referente ao objeto original. As operações que agem no agente serão encaminhadas para o objeto original. Uma vez que todos os agentes não existem, os objetos complexos são removidos.
É simples entender o modelo de proxy, mas na verdade há um modelo de proxy na vida:
Podemos comprar passagens de trem na estação de trem, mas também podemos comprá -los no escritório de vendas de ingressos para trem. O escritório de vendas de ingressos do trem aqui é o agente para compras de ingressos na estação de trem. Ou seja, emitimos uma solicitação de compra de ingressos no consultório de vendas. O consultório de vendas enviará a solicitação para a estação de trem, e a estação de trem enviará a resposta bem -sucedida à compra para o canal de vendas, e o canal de vendas dirá novamente.
No entanto, os ingressos só podem ser adquiridos no consultório de vendas, mas não reembolsos, enquanto os ingressos podem ser adquiridos na estação de trem, para que as operações suportadas pelo agente possam ser diferentes das do objeto comissionado.
Deixe -me dar outro exemplo que você encontrará ao escrever um programa:
Se houver um projeto existente (você não possui código -fonte, só poderá chamá -lo) que pode chamar o Int Compute (String exp1) para implementar o cálculo da expressão do sufixo. Se você deseja usar este projeto para implementar o cálculo da expressão do Infix, pode gravar uma classe proxy e definir uma computação (String exp2). Este parâmetro EXP2 é uma expressão de infix. Portanto, você precisa converter a expressão do infixo em uma expressão de sufixo (pré -processamento) antes de chamar o computação () do projeto existente e, em seguida, chama o compute () do projeto existente. Obviamente, você também pode receber o valor de retorno e fazer outras operações, como salvar o arquivo (pós -processo). Este processo usa o modo proxy.
Ao usar um computador, você também encontrará aplicativos de modo proxy:
Proxy remoto: não podemos acessar o Facebook por causa da GFW na China. Podemos acessá -lo navegando na parede (configurando um proxy). O processo de acesso é:
(1) O usuário envia a solicitação HTTP para o proxy
(2) O proxy envia uma solicitação HTTP para o servidor da web
(3) O servidor da web envia a resposta HTTP ao proxy
(4) O proxy envia a resposta HTTP de volta ao usuário
2. Proxy estático
A chamada proxy estática significa que uma classe de proxy é gerada durante o estágio de compilação para concluir uma série de operações no objeto Proxy. A seguir, é apresentado o diagrama de classe de estrutura do padrão de proxy:
1. Participantes do modelo de proxy
Existem quatro papéis no modo proxy:
Interface de tópico: isto é, a interface comportamental implementada pela classe Proxy.
Objeto de destino: isto é, o objeto sendo proxyed.
Objeto de proxy: o cliente proxy usado para encapsular a classe de tópico real é o seguinte é a estrutura do diagrama de classes do padrão de proxy:
2. Idéias para implementar o modelo de agente
Tanto o objeto proxy quanto o objeto de destino implementam a mesma interface comportamental.
A classe proxy e a classe de destino implementam a lógica da interface separadamente.
Instanciar um objeto de destino no construtor da classe proxy.
Chamando a interface comportamental do objeto de destino na classe Proxy.
Se o cliente quiser chamar a interface comportamental do objeto de destino, ele só poderá operar através da classe proxy.
3. Exemplos de proxy estático
A seguir, é apresentado um exemplo de carregamento preguiçoso para ilustrar o proxy estático. Quando iniciamos um sistema de serviço, pode levar muito tempo para carregar uma determinada classe. Para obter melhor desempenho, ao iniciar o sistema, geralmente não inicializamos essa classe complexa, mas inicializamos sua classe de proxy. Isso separará os métodos de consumo de recursos usando proxy para separação, o que pode acelerar a velocidade de inicialização do sistema e reduzir o tempo de espera do usuário.
Defina uma interface de tópico
Public Interface Assunto {public void SayHello (); public void Saysgoodbye ();} Defina uma classe de destino e implemente a interface do tópico
public class RealSubject implementa sujeito {public void SayHello () {System.out.println ("Hello World"); } public void dizgoodbye () {System.out.println ("Goodbye World"); }} Defina uma classe de proxy para proxy o objeto de destino.
classe pública staticproxy implementa sujeito {private realsubject realsubject = null; public staticProxy () {} public void SayHello () {// é carregado naquele momento, carregamento preguiçoso if (realsubject == null) {realsubject = new realsubject (); } realsubject.sayhello (); } // O método Saygoodbye é o mesmo ...} Definir um cliente
public class cliente {public static void main (string [] args) {staticproxy sp = new staticproxy (); sp.Sayhello (); sp.Saygoodbye (); }}O acima é um exemplo de teste simples de um proxy estático. Pode não parecer prático. No entanto, esse não é o caso. Usando um proxy, também podemos transformar os métodos de objeto de destino. Por exemplo, uma série de conexões é criada no pool de conexão do banco de dados. Para garantir que as conexões sejam abertas com pouca frequência, essas conexões quase nunca são fechadas. No entanto, sempre temos o hábito de fechar a conexão aberta. Dessa forma, podemos usar o modo proxy para reproxar o método de fechamento na interface de conexão e alterá -lo para reciclá -lo no pool de conexão do banco de dados, em vez de realmente executar o método da conexão#Close. Existem muitos outros exemplos, e você precisa experimentá -los você mesmo.
3. Agente dinâmico
O proxy dinâmico refere -se a gerar classes de proxy dinamicamente em tempo de execução. Ou seja, o bytecode da classe proxy será gerado e carregado em tempo de execução para o carregador de classe do proxy atual. Comparados com as classes de processamento estático, as classes dinâmicas têm muitos benefícios.
Não há necessidade de escrever uma classe de encapsulamento completamente idêntica para o tópico real. Se houver muitos métodos na interface do tópico, também é problemático escrever um método de proxy para cada interface. Se a interface mudar, as classes reais de tema e proxy devem ser modificadas, o que não é propício à manutenção do sistema;
O uso de alguns métodos dinâmicos de geração de proxy pode até formular a lógica de execução da classe proxy em tempo de execução, melhorando bastante a flexibilidade do sistema.
Existem muitas maneiras de gerar proxy dinâmico: o JDK vem com proxy dinâmico, cglib, javassista etc. Esses métodos têm suas próprias vantagens e desvantagens. Este artigo explora principalmente o uso de proxy dinâmico e análise de código -fonte no JDK.
Aqui está um exemplo para explicar o uso de proxy dinâmico no JDK:
classe pública DynamicProxy implementa InvocationHandler {private realSubject = null; Public Object Invoke (proxy do objeto, método do método, objeto [] args) {if (realsubject == null) {realsubject = new realsubject (); } métod.inVoke (RealSubject, args); retornar realsubject; }}Exemplo de código do cliente
classe pública client {public static void main (strings [] args) {sujeito sujeito = (sujeito) proxy.newInstance (classLoader.getSystemloader (), realsubject.class.getInterfaces (), new DynamicProxy ()); Sujeito.sayhello (); Sujeito.saygoodbye (); }}Como pode ser visto no código acima, precisamos usar proxy dinâmico no JDK. Use o método estático proxy.NewInstance (ClassLoader, Interfaces [], InvokeHandler) para criar uma classe de proxy dinâmica. O método NewInstance possui três parâmetros, que representam o carregador de classe, uma lista de interfaces que você deseja que a classe proxy seja implementada e uma instância que implementa a interface InvokeHandler. O proxy dinâmico entregou o processo de execução de cada método para o método de invocar o processamento.
O proxy dinâmico do JDK exige que o proxy seja uma interface, mas uma classe simples não pode. As classes de proxy geradas pela JDK Dynamic Proxy herdarão a classe Proxy, e a classe Proxy implementará toda a lista de interface que você passou. Portanto, o tipo pode ser fundido para o tipo de interface. Abaixo está o diagrama da estrutura do proxy.
Pode -se observar que o proxy é todos métodos estáticos; portanto, se a classe proxy não implementar nenhuma interface, é o tipo de proxy e não possui métodos de instância.
Obviamente, se você ingressar, precisará proxy uma classe que não implemente uma determinada interface, e os métodos dessa classe são os mesmos que os definidos por outras interfaces e podem ser facilmente implementados usando a reflexão.
Public classe DynamicProxy Iplementos InvokeHandler {// A classe que você deseja proxy Private TargetClass TargetClass = null; // inicialize esta classe public dynamicproxy (TargetClass TargetClass) {this.targetclass = TargetClass; } Public Object Invoke (proxy do objeto, método do método, objeto [] args) {// use a reflexão para obter a classe que você deseja proxy Method myMethod = TargetClass.getClass (). myMethod.setAccessible (true); retornar myMethod.invoke (TargetClass, args); }}4. Análise de código -fonte dinâmico de proxy dinâmico JDK (JDK7)
Depois de analisar o exemplo acima, sabemos apenas como usar o proxy dinâmico. No entanto, ainda está nebuloso sobre como a classe de proxy é criada, que chamou o método Invoke, etc. A seguinte análise
1. Como os objetos proxy são criados?
Primeiro, veja o código -fonte do método proxy.NewInstance:
Public Static Object NewProxyInstance (ClassLoader carregador, classe <?> [] interfaces, InvocationHandler H) lança ilegalArgumentException {} // Obter informações da interface final <?> [] intfs = interfaces.clone (); Final SecurityManager SM = System.getSecurityManager (); if (sm! = null) {checkProxyAccess (refletion.getCalerClass (), carregador, intfs); } // gerar classe de proxy <?> Cl = getProxyclass0 (carregador, intfs); // ... ok, vejamos o primeiro tempo primeiro}A partir do código -fonte, pode -se observar que a geração de classes de proxy depende do método getProxyclass0. Em seguida, vamos dar uma olhada no código -fonte getProxyclass0:
Classe estática privada <?> getProxyclass0 (ClassLoader carregador, classe <?> ... interfaces) {// O número de listas de interface não pode exceder 0xffff se (interfaces.lengths> 65535) {lança a nova ilegalargumentException ("limite de interface excedido"); } // Observe aqui, a seguinte explicação é fornecida em detalhes para retornar proxyclasscache.get (carregador, interfaces); } A explicação do proxyclasscache.get é: se a classe proxy que implementa a lista de interface já existe, pegue -a diretamente do cache. Se não existir, alguém será gerado através do ProxyclassFactory.
Antes de olhar para o código -fonte do proxyclasscache.get, vamos entender brevemente o proxyclasscache:
private estático final fracascache <classe de classe, classe <?> [], classe <? >> proxyclasscache = new frwakcache <> (new keyFactory (), new proxyclassFactory ());
O proxyclasscache é um cache do tipo fracoCACHE. Seu construtor possui dois parâmetros. Um deles é o proxyclassFactory usado para gerar a classe de proxy. A seguir, o código -fonte do proxyclasscache.get:
classe final fracascache <k, p, v> {... public v get (k key, p parâmetro) {}}Aqui K representa a chave, P representa parâmetros, v representa valor
public v get (K -Key, parâmetro p) {// Java7 Método de julgamento NullObject, se o parâmetro estiver vazio, uma exceção com a mensagem especificada será lançada. Se não estiver vazio, retorne. Objects.RequirenOnNull (parâmetro); // Limpe a estrutura de dados do fracashashmap que mantém referências fracas, que geralmente são usadas para cache o ExpungestaleEntries (); // Obtenha CacheKey do objeto da fila CacheKey = cacheKey.valueof (chave, refqueue); // Encha o fornecedor com carga preguiçosa. Concorrente é um mapa segura para roscas ConcurrentMap <Object, Fornecedor <V>> valoresMap = map.get (cachekey); if (valoresmap == null) {concurrentmap <objeto, fornecedor <V >> OldValuesMap = map.putifabsent (cacheKey, valoresMap = new ConcurrentHashMap <> ()); if (OldValuesMap! = NULL) {valoresMap = OldValuesMap; }} // Crie subcky e recupere o possível fornecedor <V> armazenado por isso // subckey do objeto valoresmap subkey = objects.requiteNononnull (subkeyFactory.Apply (key, parâmetro)); Fornecedor <V> fornecedor = valoresMap.get (Subkey); Fábrica de fábrica = nulo; while (true) {if (fornecedor! = null) {// obtenha valor do fornecedor. Esse valor pode ser uma realização de fábrica ou cache. // As três frases seguintes são o código principal, que retorna a classe que impleta o InvokeHandler e contém as informações necessárias. V valor = fornecedor.get (); if (value! = null) {return value; }} // senão nenhum fornecedor no cache // ou um fornecedor que retornou nulo (pode ser um cachevalue limpo // ou uma fábrica que não foi bem -sucedida na instalação do cachevalue) // o processo a seguir é o processo de preenchimento do suprimento; A função do loop while é obter continuamente a classe que os implementam o InvokeHandler. Esta classe pode ser obtida no cache ou gerada a partir de proxyfactoryclass.
A fábrica é uma classe interna que implementa a interface do fornecedor <V>. Essa classe substitui o método GET e um método de instância do tipo proxyfactoryclass é chamado no método get. Este método é a maneira real de criar uma classe proxy. Vamos ver o código -fonte do método ProxyFactoryClass#Aplicar:
Classe public <?> Aplicar (ClassLoader carregador, classe <?> [] interfaces) {map <classe <?>, boolean> interfaceSet = new IdentityHashmap <> (interfaces.lengthing); para (classe <?> intf: interfaces) { /* verifique se o carregador de classe resolve o nome dessa interface com o mesmo objeto de classe.* / classe <?> interfaceClass = null; tente {// Carregar informações sobre cada interfaceclass = class.ForName (intf.getName (), false, carregador); } Catch (classNotFoundException e) {} // Se a classe carregada com sua própria classe não for igual à classe em que você passou, faça uma exceção se (interfaceClass! = intf) {jogue nova ilegalArgumentException (intf + "não é visível do carregador de classe"); } // Se a entrada não for um tipo de interface if (! } // Verifique se a interface é repetida if (interfaceSet.put (interfaceclass, boolean.true)! = Null) {lança new ilegalArgumentException ("interface repetida:" + interfaceClass.getName ()); }} String proxypkg = null; // Pacote para definir a classe proxy in /* Registre o pacote de uma interface não pública de procuração para que a classe proxy seja definida no mesmo pacote. * Verifique se todas as interfaces não públicas de procuração estão no mesmo pacote. */// Este parágrafo depende se existem interfaces que não são públicas na interface que você passou. Se sim, todas essas interfaces devem ser definidas em um pacote. Caso contrário, jogue exceções para (classe <?> Intf: interfaces) {int flags = intf.getModifiers (); if (! modifier.ispublic (sinalizadores)) {string name = intf.getName (); int n = name.LastIndexOf ('.'); String pkg = ((n == -1)? "": Name.substring (0, n + 1)); if (proxypkg == null) {proxypkg = pkg; } else se (! }}}} if (proxypkg == null) {// Se não houver interfaces de proxy não public, use com.sun.proxy package proxypkg = refletutil.proxy_package + "."; } / * * Escolha um nome para a classe proxy gerar. */ long num = nextUnikeNumber.getAndIncrement (); // Gere o nome da classe da classe de proxy aleatória, $ proxy + num string proxyname = proxypkg + proxyclassNameprefix + num; /** Gere o arquivo de classe da classe Proxy, retorne o fluxo de bytes*/ byte [] proxyclassfile = proxygenerator.gereateProxyclass (proxyname, interfaces); tente {return definitivamenteClass0 (carregador, proxyname, proxyclassFile, 0, proxyclassfile.length); } Catch (ClassFormaterror e) {// Lançamento final nova ilegalArgumentException (e.toString ()); }}}O#Aplicação do ProxyFactoryClass#mencionado acima mencionado é um método para realmente gerar classes de proxy, que são realmente imprecisas. Depois de ler o código -fonte aqui, descobriremos que o ProxyGenerator#GenerateProxyclass é o método para gerar verdadeiramente classes de proxy. Gere o arquivo de classe correspondente de acordo com a classe Java Bytecode Composition (consulte meu outro artigo Java Bytecode Learning Notes). O código -fonte específico do ProxyGenerator#GenerateProxyclass é o seguinte:
byte privado [] generateclassFile () { / * * Etapa 1: montar objetos proxymethod para todos os métodos para * gerar código de despacho de procuração para. */ // Método AddProxymethod é adicionar todos os métodos a uma lista e corresponder à classe correspondente // Aqui estão três métodos correspondentes ao objeto, tostragem e igual a addproxyMethod (hashcodemethod, object.class); addProxyMethod (EqualsMethod, object.class); addProxyMethod (ToStringMethod, object.class); // Compare a interface na lista de interface com os métodos na interface para (int i = 0; i <interfaces.length; i ++) {métodos [] métodos = interfaces [i] .getMethods (); for (int j = 0; j <métodos. }} / * * Para cada conjunto de métodos proxy com a mesma assinatura, * verifique se os tipos de retorno dos métodos são compatíveis. */ for (List <Proxymethod> SignMethods: proxymethods.values ()) {checkReturntypes (sigmethods); } / * * Etapa 2: As estruturas de montagem FieldInfo e MethodInfo para todos os campos e métodos da classe que estamos gerando. */// Adicione um método construtor ao método, que é apenas um construtor, que é um construtor com a interface InvocationHandler.//This é o método real para adicionar um método ao arquivo de classe, ou seja, a classe proxy. No entanto, ainda não foi processado. Está apenas adicionado primeiro e aguarde o loop. A descrição do nome do construtor no arquivo de classe é <Inir> tente {methods.add (generateconstructor ()); for (list <roxymethod> signsthods: proxymethods.values ()) {for (proxymethod pm: signsthods) {// adicione um atributo de tipo de método a cada método de proxy. O número 10 é o identificador do arquivo de classe, o que significa que esses atributos são campos. // Adicione cada método de proxy ao método da classe proxy. }} // Adicione um bloco de inicialização estática e inicialize cada atributo. Aqui, o bloco de código estático também é chamado de construtor de classe. Na verdade, é um método com o nome <Clinit>, então adicione -o ao método da lista de métodos. } catch (ioexception e) {tiro o novo internalError ("Exceção de E/S inesperada"); } // O número de métodos e atributos não pode exceder 65535, incluindo o número anterior de interfaces. // Isso ocorre porque, no arquivo de classe, esses números são representados em hexadecimal de 4 bits; portanto, o valor máximo é 2 ao poder do 16º -1 if (métodos.size ()> 65535) {lançar novo ilegalargumentException ("limite do método excedido"); } if (fields.size ()> 65535) {lança nova ilegalArgumentException ("limite de campo excedido"); } // A próxima etapa é escrever um arquivo de classe, incluindo números mágicos, nomes de classe, pools constantes e outras séries de bytecodes. Não vou entrar em detalhes. Se você precisar, você pode consultar o conhecimento relevante da JVM Virtual Machine ByteCode. cp.getclass (Dottoslash (ClassName)); cp.getclass (superclassName); for (int i = 0; i <interfaces.length; i ++) {cp.getclass (Dottoslash (interfaces [i] .getName ())); } cp.SetReadonly (); ByteArrayOutputStream bout = new ByteArrayOutputStream (); DataOutputStream Dout = new DataOutputStream (BUT); tente {// u4 mágica; Dout.WriteInt (0xcafeBabe); // u2 minor_version; Dout.WriteShort (ClassFile_minor_version); // u2 major_version; Dout.WriteShort (classfile_major_version); cp.Write (Dout); // (gravar pool constante) // u2 access_flags; Dout.WriteShort (ACC_PUBLIC | ACC_FINAL | ACC_SUPER); // u2 this_class; Dout.WriteShort (cp.getclass (Dottoslash (ClassName))); // u2 super_class; Dout.WriteShort (cp.getclass (superclassName)); // u2 interfaces_count; Dout.WriteShort (interfaces.length); // interfaces u2 [interfaces_count]; for (int i = 0; i <interfaces.length; i ++) {Dout.WriteShort (cp.getclass (Dottoslash (interfaces [i] .getName ()))); } // U2 campos_count; Dout.WriteShort (fields.size ()); // campos field_info [campos_count]; para (FieldInfo f: campos) {f.Write (Dout); } // u2 métodos_count; Dout.WriteShort (Methods.size ()); // Method_info Métodos [Methods_Count]; for (MethodInfo M: Métodos) {M.Write (Dout); } // U2 Atributes_count; Dout.WriteShort (0); // (sem atributos de classes para classes de proxy)} Catch (ioException e) {lança new InternalError ("Exceção de E/S inesperada"); } return bout.tobytearray (); }Após camadas de chamadas, uma classe de proxy é finalmente gerada.
2. Quem chamou Invoke?
Simulamos o JDK para gerar uma classe de proxy por si só, com o nome da classe TestProxygen:
classe pública TestGeneratorProxy {public static void main (string [] args) lança ioexception {byte [] classfile = proxygenerator.generateproxyclass ("testproxygen", sujeito.class.getInterfaces ()); Arquivo de arquivo = novo arquivo ("/usuários/yadoao/desktop/testproxygen.class"); FileOutputStream fos = new FileOutputStream (FILE); Fos.Write (ClassFile); fos.flush (); fos.close (); }}Decompilar o arquivo de classe com JD-Gui, e o resultado é o seguinte:
importar com.su.dynamicproxy.isubject; importar java.lang.reflect.invocationHandler; importar java.lang.reflect.method; importar java.lang.reflect.proxy; importproxsTroxspxyg.lang.refLect.undClaredRowLeoxException; final da classe final; Método estático privado M1; Método estático privado m0; Método estático privado M4; Método estático privado m2; public testProxygen (InvocationHandler paraminvocationHandler) lança {super (paraminvocationHandler); } public Final Void SayHello () lança {tente {this.h.invoke (this, m3, null); retornar; } catch (error | RUNTimeException localError) {Throw localError; } catch (localwlowable) {lança uma nova não declaração de abastecimento (local de trabalho); }} public final boolean Equals (objeto paramobject) lança {try {return ((boolean) this.h.invoke (this, m1, novo objeto [] {paramObject})). booleanValue (); } catch (error | RUNTimeException localError) {Throw localError; } catch (localwlowable) {lança uma nova não declaração de abastecimento (local de trabalho); }} public final int hashCode () lança {try {return ((integer) this.h.invoke (this, m0, null)). intvalue (); } catch (error | RUNTimeException localError) {Throw localError; } catch (localwlowable) {lança uma nova não declaração de abastecimento (local de trabalho); }} public Final Void SayGoodBye () lança {tente {this.h.invoke (this, m4, nulo); retornar; } catch (error | RUNTimeException localError) {Throw localError; } catch (localwlowable) {lança uma nova não declaração de abastecimento (local de trabalho); }} public final string tostring () lança {try {return (string) this.h.invoke (this, m2, null); } catch (error | RUNTimeException localError) {Throw localError; } catch (localwlowable) {lança uma nova não declaração de abastecimento (local de trabalho); }} static {try {m3 = class.ForName ("com.su.dynamicproxy.isubject"). getMethod ("sayhello", nova classe [0]); m1 = classe.ForName ("java.lang.Object"). m0 = classe.ForName ("java.lang.object"). getMethod ("hashcode", nova classe [0]); m4 = classe.ForName ("com.su.dynamicproxy.isubject"). getMethod ("saygoodbye", nova classe [0]); m2 = classe.ForName ("java.lang.object"). getMethod ("tostring", nova classe [0]); retornar; } Catch (NosuchMethodException LocalnosuchMethodException) {tire o novo NosuchMethoderror (LocalnosuchMethodException.getMessage ()); } Catch (ClassNotFoundException LocalClassNotFoundException) {THLHET NOVA NOCLASSDEFFONDERROR (LOCALCLASSNOTFOUNDEXCECCECTION.GETMESSAGE ()); }}} Primeiro, notei que o construtor da classe de proxy gerado é passado em uma classe que implementa a interface InvokeHandler como um parâmetro e chamou o construtor do proxy da classe pai, que inicializou a variável de membro protegida Invokehander H em proxy.
Notei vários blocos de inicialização estática novamente. O bloco de inicialização estática aqui é inicializar a lista de interface de proxy e os métodos HashCode, ToString e Equals.
Finalmente, existe o processo de chamada desses métodos, todos os quais são retornos de chamada para o método Invoke.
Isso termina com a análise desse padrão de proxy.