Caixa de ferramentas de reflexão MyBatis, pensamento de design de reflexão

A reflexão é um mecanismo muito poderoso e flexível no mundo Java. Na linguagem Java orientada a objetos, só podemos acessar as propriedades e métodos de um objeto Java de acordo com as especificações de palavras-chave como public e private, mas o mecanismo de reflexão nos permite obter as propriedades ou métodos de qualquer objeto Java em tempo de execução.

Algumas pessoas dizem que a reflexão quebra o encapsulamento de classes e destrói nosso pensamento orientado a objetos, mas acho que não. Acho que é por causa do mecanismo de reflexão do Java que resolve muitos problemas orientados a objetos que não podem ser resolvidos, é o preferido por muitos frameworks de código aberto Java. Também existem muitas práticas de reflexão incríveis. Claro, isso também inclui a caixa de ferramentas de reflexão em MyBatis .

Tudo tem dois lados. Quanto mais flexível e poderosa for a ferramenta, maior será a barreira de uso, e o mesmo ocorre com a reflexão. Essa também é a razão pela qual a reflexão raramente é usada ao escrever código de negócios. Por outro lado, se você deve usar a reflexão para resolver problemas de negócios, você precisa parar e pensar se há um problema com o design do nosso sistema.
Para reduzir as barreiras para usar a reflexão, MyBatis encapsula uma caixa de ferramentas de reflexão, que contém O próprio MyBatis Para operações de reflexão comuns, outros módulos do MyBatis precisam apenas chamar a API concisa exposta pela caixa de ferramentas de reflexão para alcançar a função de reflexão desejada.

A implementação do código específico da caixa de ferramentas de reflexão está localizada no pacote org.apache.ibatis.reflection. Abaixo, vou levá-lo a uma análise aprofundada da implementação principal do módulo.

Reflector
Reflector é a base do módulo de reflexão MyBatis. Para usar o módulo de reflexão para operar uma Classe, a Classe é primeiro encapsulada em um objeto Refletor e as informações de metadados da Classe são armazenadas em cache no Refletor, o que pode melhorar a eficiência da execução da reflexão.

  1. Processo de inicialização do núcleo
    Por envolver operações de reflexão, o Reflector deve gerenciar as propriedades e métodos da classe. Essas informações são registradas em seus campos principais. Os detalhes são os seguintes.

type (Class <?> type): O tipo de classe encapsulado pelo objeto Reflector.

readablePropertyNames, writablePropertyNames (String [] type): uma coleção de nomes de propriedades legíveis e graváveis.

getMethods, setMethods (tipo Map <String, Invoker>): O conjunto de métodos getter e métodos setter correspondentes a atributos legíveis e graváveis, a chave é o nome do atributo e o valor é um objeto Invoker. Invoker é um encapsulamento do objeto Method.

getTypes, setTypes (Map <String, Class <? >> type): o valor de retorno do método getter correspondente ao atributo e o tipo de valor do parâmetro do método setter. A chave é o nome do atributo e o valor é o retorno tipo de valor ou tipo de parâmetro do método.

defaultConstructor (Construtor <?> tipo): O método de construção padrão.

caseInsensitivePropertyMap (tipo Map <String, String>): uma coleção de todos os nomes de propriedades. Os nomes das propriedades registrados nesta coleção são todos maiúsculos.

Quando construímos um objeto Reflector, passamos um objeto Class e, ao analisar o objeto Class, os campos centrais acima podem ser preenchidos.O processo central inteiro pode ser descrito aproximadamente como segue.

Use o campo type para registrar o objeto Class recebido.

Obtenha todos os métodos de construção da classe Class por meio de reflexão e atravesse o filtro para obter o único método de construção sem parâmetro para inicializar o campo defaultConstructor. Esta parte da lógica é implementada no método addDefaultConstructor ().

Leia o método getter na classe Class e preencha a coleção getMethods e a coleção getTypes descritas acima. Esta parte da lógica é implementada no método addGetMethods ().

Leia os métodos setter na classe Class e preencha a coleção setMethods e a coleção setTypes descritas acima. Esta parte da lógica é implementada no método addSetMethods ().

Leia os campos que não têm métodos getter / setter na classe, gere o objeto Invoker correspondente e preencha a coleção getMethods, coleção getTypes, coleção setMethods e coleção setTypes. Esta parte da lógica é implementada no método addFields ().

De acordo com o keySet da coleção getMethods / setMethods construída nas três etapas anteriores, inicialize as coleções readablePropertyNames e writablePropertyNames.

Percorra as coleções construídas readablePropertyNames e writablePropertyNames, converta todos os nomes de propriedades nelas para maiúsculas e registre-as na coleção caseInsensitivePropertyMap.

  1. Análise do método central Depois de
    compreender o processo central de inicialização, continuaremos a analisar os métodos envolvidos, que também são os métodos centrais do Reflector.

Primeiro, olhe para o método addGetMethods () e o método addSetMethods (), que são usados ​​para analisar os métodos getter e setter () passados ​​para a classe Class, respectivamente. A lógica dos dois é muito semelhante. Aqui, tomamos o método addGetMethods () como um exemplo para uma análise aprofundada, que inclui principalmente as três etapas principais a seguir.

A primeira etapa é obter informações do método. Aqui, o método getClassMethods () será chamado para obter a assinatura exclusiva de todos os métodos da classe Class atual (observe que isso inclui métodos herdados da classe pai e da interface) e o objeto Method correspondente a cada método.

No processo de varredura recursiva da classe pai e da interface pai, a coleção Map <String, Method> será usada para registrar os métodos percorridos para obter o efeito de desduplicação, onde Key é a assinatura do método correspondente e Value é o Método objeto correspondente ao método. O formato da assinatura do método gerado é o seguinte:

返回值类型#方法名称:参数类型列表

Por exemplo, a única assinatura do método addGetMethods (Class) é:

java.lang.String#addGetMethods:java.lang.Class

Pode-se ver que a assinatura do método gerada aqui contém o valor de retorno e pode ser usada como o identificador globalmente exclusivo do método.

A segunda etapa é encontrar o método getter do array Method retornado na etapa anterior de acordo com a especificação Java e registrá-lo na coleção conflitoGetters. Aqui, na coleção confltingGetters (tipo HashMap <String, List> ()), Key é o nome da propriedade e Value é o conjunto de métodos getter correspondentes à propriedade.

Por que uma propriedade encontra vários métodos getter? Isso é causado principalmente pela herança entre as classes. Nas subclasses, podemos substituir os métodos da classe pai. As substituições podem não apenas modificar a implementação específica do método, mas também modificar o valor de retorno do método. O método getter não é exceção Dois métodos com assinaturas diferentes são produzidos em uma etapa.

A terceira etapa é resolver conflitos de assinatura de método. Aqui, o método resolveGetterConflicts () será chamado para lidar com o conflito desse tipo de método getter. A lógica central de lidar com o conflito é, na verdade, comparar o valor de retorno do método getter e dar prioridade ao valor de retorno de o método getter da subclasse, por exemplo:

// 该方法定义在SuperClazz类中

public List getA(); 

// 该方法定义在SubClazz类中,SubClazz继承了SuperClazz类

public ArrayList getA();

Como você pode ver, o valor de retorno ArrayList do método SubClazz.getA () é uma subclasse da Lista de valores de retorno do método getA () em sua classe pai SuperClazz, então aqui selecionamos o método getA () definido em SubClazz como o método getter da propriedade A.

Depois que o método resolveGetterConflicts () processou os conflitos do método getter acima, um objeto MethodInvoker correspondente será criado para cada método getter e, em seguida, armazenado na coleção getMethods. Ao mesmo tempo, o mapeamento entre o nome do atributo e o tipo de valor de retorno do método getter correspondente será mantido na coleção getTypes.

Neste ponto, a lógica central de addGetMethods () é claramente analisada.

Em seguida, retornamos ao método de construção do Refletor. Depois de concluir o processamento dos métodos getter / setter na classe Class por meio dos métodos addGetMethods () e addSetMethods (), continuaremos a chamar o método addFields () para processar os campos sem getter métodos / setter.

Aqui, tomamos o processamento de campos sem métodos getter como exemplo. O método addFields () gerará objetos GetFieldInvoker correspondentes para esses campos e os registrará na coleção getMethods. Ao mesmo tempo, eles também registrarão o nome do atributo e o tipo de atributo na coleção getTypes. A mesma lógica se aplica a campos que não possuem um método setter.

  1. No
    processo de inicialização do Invoker do objeto Reflector, os métodos getter / setter de todas as propriedades serão encapsulados em objetos MethodInvoker e os campos sem getter / setter também gerarão os objetos Get / SetFieldInvoker correspondentes. Vamos dar uma olhada na definição desta interface do Invoker:
public interface Invoker {
    
    

   // 调用底层封装的Method方法或是读写指定的字段

   Object invoke(Object target, Object[] args);

   Class<?> getType(); // 返回属性的类型

}

O relacionamento de herança da interface do Invoker é mostrado na figura a seguir:
Insira a descrição da imagem aqui
Diagrama de relacionamento de herança da interface do Invoker

Entre eles, o MethodInvoker implementa o método encapsulado subjacente do método (por exemplo, método getter / setter) por meio de reflexão para completar o efeito de leitura e escrita da propriedade, e Get / SetFieldInvoker lê e grava o campo Field encapsulado subjacente por meio de reflexão, alcançando assim a leitura da propriedade e efeito de escrita
4. ReflectorFactory
Através da análise acima, sabemos que haverá uma série de operações de reflexão no processo de inicialização do Reflector. Para melhorar a velocidade de inicialização do Reflector, MyBatis fornece a interface de fábrica do ReflectorFactory para armazenar objetos Reflector. O método principal é obter o Reflector.O método findForClass () do objeto.

DefaultReflectorFactory é a implementação padrão da interface ReflectorFactory. Por padrão, ele mantém uma coleção ConcurrentHashMap <Class <?>, Reflector> (campo reflectorMap) na memória para armazenar em cache todos os objetos Reflector que cria.

Na implementação de seu método findForClass (), o cache reflectorMap é consultado primeiro de acordo com a classe de classe recebida. Se o objeto Reflector correspondente for encontrado, ele retorna diretamente; caso contrário, o objeto Reflector correspondente é criado e registrado no cache reflectorMap, esperando a próxima vez que use.

A fábrica de objetos padrão
ObjectFactory é uma fábrica de reflexos em MyBatis, que fornece duas sobrecargas do método create (). Podemos criar objetos do tipo especificado por meio dos dois métodos create ().

DefaultObjectFactory é a implementação padrão da interface ObjectFactory.A camada inferior de seu método create () é criar um objeto chamando o método instantiateClass (). O método instantiateClass () selecionará o construtor apropriado para instanciar o objeto com base na lista de parâmetros passada por meio de reflexão.

Além de usar a implementação padrão de DefaultObjectFactory, também podemos configurar uma classe de implementação de extensão de interface ObjectFactory customizada no arquivo de configuração mybatis-config.xml (a classe de teste fornecida por MyBatis inclui uma implementação ObjectFactory customizada, que pode ser referida ao código-fonte ) para completar extensões de funções personalizadas.

Ferramenta de análise de atributos
Na análise de exemplo anterior da camada de persistência do sistema de pedidos, 20 minutos para levá-lo rapidamente para começar com o exemplo de sistema de pedidos introduzido por MyBatis , estamos no mapeamento ResultMap do orderMap, se você quiser configurar o one- relacionamento para muitos entre Order e OrderItem, você pode usar tags para fazer a configuração; se o número de OrderItems for claro, você pode usar diretamente o método de índice de subscrito de matriz (ou seja, ordersItems [0]) para preencher a coleção orderItems.

A navegação "." E a análise dos subscritos da matriz aqui também são feitas na caixa de ferramentas de reflexão. A seguir, apresentaremos as três classes de ferramentas relacionadas à análise de propriedades no pacote reflection.property. Nas seguintes classes de ferramentas, como MetaClass e MetaObject, os recursos de resolução de propriedades também são necessários.

A classe da ferramenta PropertyTokenizer é responsável por analisar a expressão composta por "." E "[]". PropertyTokenizer herda a interface Iterator e pode processar iterativamente expressões de vários níveis aninhadas.

PropertyCopier é uma classe de ferramenta para cópia de propriedade. Ele fornece uma função semelhante a BeanUtils.copyProperties () no Spring. Ele realiza a cópia do valor da propriedade entre dois objetos do mesmo tipo. Seu método principal é o método copyBeanProperties ().

A função fornecida pela classe do utilitário PropertyNamer é converter nomes de métodos em nomes de propriedades e detectar se um nome de método é getter ou setter.

MetaClass
MetaClass fornece a função de obter as informações de descrição do atributo na classe. A camada inferior depende do Refletor apresentado acima. No método de construção da MetaClass, a classe recebida será encapsulada em um objeto Refletor e registrada no campo refletor. pesquisa subsequente de atributo de MetaClass O objeto Reflector será usado.

O método findProperty () em MetaClass é o método principal para pesquisa de propriedade. Ele lida principalmente com a pesquisa de propriedade da navegação ".". Este método usa o PropertyTokenizer apresentado acima para analisar a expressão de nome de entrada, que pode passar "." "Navigate vários níveis, por exemplo, order.deliveryAddress.customer.name.

MetaClass irá processar esta expressão camada por camada. Primeiro, encontre a propriedade deliveryAddress por meio do Refletor correspondente ao tipo de Pedido. Depois que a pesquisa for bem-sucedida, crie o objeto MetaClass correspondente (e o objeto Refletor subjacente) de acordo com o tipo da propriedade deliveryAddress (ou seja, tipo de endereço) e, em seguida, continue a pesquisar O atributo do cliente é processado de forma recursiva até que o atributo de nome em Cliente seja finalmente encontrado. Esta parte da lógica de pesquisa recursiva está localizada no método MetaClass.buildProperty () .

No processo de pesquisa de propriedades na MetaClass acima, os métodos hasGetter () e hasSetter () também são chamados para determinar se a propriedade especificada na expressão de propriedade tem um método getter / setter correspondente. Esses dois métodos também analisam primeiro a expressão de nome de entrada por meio de PropertyTokenizer e, em seguida, realizam uma consulta recursiva. Na consulta recursiva, ele contará com o método Reflector.hasGetter () para encontrar a coleção getMethods ou a coleção setMethods apresentada acima e encontrar o getter / setter correspondente ao método property.

A implementação de outros métodos em MetaClass também depende principalmente de PropertyTokenizer para analisar expressões e, em seguida, pesquisar recursivamente.O processo de pesquisa dependerá dos métodos relacionados do Reflector.

ObjectWrapper
MetaClass encapsula informações meta da classe e ObjectWrapper encapsula informações meta do objeto. Em ObjectWrapper, as informações de propriedade de um objeto são abstraídas e métodos relacionados para consultar as informações de propriedade do objeto e métodos relacionados para atualizar o valor da propriedade são fornecidos.

A classe de implementação do Insira a descrição da imagem aqui
ObjectWrapper é mostrada na figura abaixo: BaseWrapper é uma implementação abstrata da interface ObjectWrapper, na qual existe apenas um campo do tipo MetaObject. BaseWrapper implementa resolveCollection (), getCollectionValue () e setCollectionValue () três métodos de processamento para objetos de coleção para subclasses. Entre eles, o método resolveCollection () retorna a propriedade especificada como um objeto de coleção, e a camada inferior depende do método MetaObject.getValue () para atingir (mais sobre isso posteriormente). Os métodos getCollectionValue () e setCollectionValue () analisarão as informações do subscrito da expressão de atributo e, em seguida, obterão / definirão o elemento correspondente na coleção. A análise da expressão de atributo aqui ainda depende da classe de ferramenta PropertyTokenizer apresentada anteriormente.

BeanWrapper herda a classe abstrata BaseWrapper Além de encapsular um objeto JavaBean, a camada inferior também encapsula o objeto MetaClass correspondente ao tipo JavaBean e o objeto MetaObject herdado de BaseWrapper.

Na implementação dos métodos get () e set (), o BeanWrapper obterá / definirá o valor do atributo correspondente de acordo com a expressão de atributo passada. Tome o método get () como exemplo. Primeiro, ele determinará se a expressão contém um subscrito de matriz. Se contiver um subscrito, obterá o elemento correspondente da coleção por meio dos métodos resolveCollection () e getCollectionValue (); se ele não contém um subscrito, ele passará a MetaClass, localizará o GetFieldInvoker correspondente cujo nome de atributo está na coleção Reflector.getMethods e, em seguida, chamará o método Invoker.invoke () para ler o valor do atributo.

A implementação de outros métodos no BeanWrapper são principalmente semelhantes aos métodos get () e set (), contando com MetaClass e MetaObject para completar a leitura e gravação de informações de atributos em objetos relacionados. Não os apresentarei um por um aqui. Se você estiver interessado, pode consultar o código-fonte . Aprenda.

CollectionWrapper é uma implementação da interface ObjectWrapper para coleções Collection, que encapsula objetos de coleção Collection. Apenas os métodos isCollection (), add (), addAll () e métodos herdados de BaseWrapper estão disponíveis. Outros métodos irão lançar UnsupportedOperationException.

MapWrapper é uma implementação para o tipo Map. Esta implementação é relativamente simples, então deixarei para você analisá-la. Durante a análise, você pode consultar o MetaObjeto que será apresentado a seguir.
MetaObject
através da introdução de ObjectWrapper, aprendemos que ObjectWrapper implementa funções básicas, como ler e escrever valores de propriedade de objeto, detectar getter / setter, etc. Ao analisar classes de implementação como BeanWrapper, podemos ver que a camada subjacente depende do MetaObject. Um campo originalObject é mantido no MetaObject para apontar para o objeto JavaBean encapsulado, e o objeto ObjectWrapper (campo objectWrapper) correspondente ao objeto JavaBean também é mantido.

Os métodos de nível de classe em MetaObject e ObjectWrapper, por exemplo, método hasGetter (), método hasSetter (), método findProperty (), etc., são todos implementados chamando diretamente os métodos correspondentes de MetaClass ou ObjectWrapper. Outros métodos de nível de objeto são implementados em conjunto com ObjectWrapper, como os métodos MetaObject.getValue () / setValue ().

Tome o método getValue () como exemplo. O método primeiro analisa a expressão de propriedade especificada de acordo com PropertyTokenizer. Se a expressão for uma consulta de propriedade de vários níveis contendo "." Navegação, então a subexpressão é obtida e a propriedade correspondente o objeto é criado O objeto MetaObject associado continua a chamar recursivamente o método getValue () até que o processamento recursivo termine e a saída recursiva chamará o método ObjectWrapper.get () para obter o valor final da propriedade.

No MetaObject, a lógica central do método setValue () é basicamente semelhante ao método getValue (), que também é uma pesquisa recursiva. No entanto, há uma diferença à qual você precisa prestar atenção: se o valor final da propriedade que precisa ser definido não estiver vazio, o método ObjectWrapper.instantiatePropertyValue () será chamado durante a pesquisa recursiva para o método setter () inicializar any found durante o processo recursivo Um objeto vazio, mas se encontrar um elemento de coleção vazio, não pode ser inicializado por este método. O método ObjectWrapper.instantiatePropertyValue () realmente depende do método create () da interface ObjectFactory (a implementação padrão é DefaultObjectFactory) para criar o tipo de objeto correspondente.

Depois de entender o método de usar MetaObject e BeanWrapper juntos e a lógica de encontrar recursivamente o valor do atributo especificado pela expressão de atributo, a implementação dos métodos restantes do MetaObject é mais fácil de analisar, portanto, não entrarei em detalhes aqui.

Resuma
a caixa de ferramentas de reflexão em MyBatis. Primeiro, a implementação central da classe Reflector mais central e de nível inferior na caixa de ferramentas de reflexão; em seguida, as várias classes de ferramentas fornecidas pela caixa de ferramentas de reflexão com base no Reflector, incluindo a classe de fábrica ObjectFactory, a classe de empacotamento ObjectWrapper e a MetaClass que registra metadados, MetaObject, etc.

Acho que você gosta

Origin blog.csdn.net/Rinvay_Cui/article/details/113833635
Recomendado
Clasificación