Caja de herramientas de reflexión MyBatis, pensamiento de diseño de reflexión

La reflexión es un mecanismo muy poderoso y flexible en el mundo de Java. En el lenguaje Java orientado a objetos, solo podemos acceder a las propiedades y métodos de un objeto Java de acuerdo con las especificaciones de palabras clave como público y privado, pero el mecanismo de reflexión nos permite obtener las propiedades o métodos de cualquier objeto Java en tiempo de ejecución.

Algunas personas dicen que la reflexión rompe la encapsulación de clases y destruye nuestro pensamiento orientado a objetos, no lo creo. Creo que es debido al mecanismo de reflexión de Java que resuelve muchos problemas orientados a objetos que no se pueden resolver, es favorecido por muchos frameworks de código abierto de Java. También hay muchas prácticas de reflexión sorprendentes. Por supuesto, esto también incluye la caja de herramientas de reflexión en MyBatis. .

Todo tiene dos caras: cuanto más flexible y potente es la herramienta, mayor es la barrera de uso y también la reflexión. Esta es también la razón por la que la reflexión rara vez se usa al escribir código comercial. Por otro lado, si debe usar la reflexión para resolver problemas comerciales, debe detenerse y pensar si hay un problema con el diseño de nuestro sistema.
Para reducir las barreras para usar la reflexión, MyBatis encapsula una caja de herramientas de reflexión, que contiene MyBatis en sí Para operaciones de reflexión comunes, otros módulos de MyBatis solo necesitan llamar a la API concisa expuesta por la caja de herramientas de reflexión para lograr la función de reflexión deseada.

La implementación de código específico de la caja de herramientas de reflexión se encuentra en el paquete org.apache.ibatis.reflection A continuación, analizaré en profundidad la implementación principal del módulo.

Reflector
Reflector es la base del módulo de reflexión MyBatis. Para usar el módulo de reflexión para operar una Clase, la Clase se encapsula primero en un objeto Reflector y la información de metadatos de la Clase se almacena en caché en el Reflector, lo que puede mejorar la eficiencia de la ejecución de la reflexión.

  1. Proceso de inicialización del núcleo
    Dado que se trata de operaciones de reflexión, Reflector debe gestionar las propiedades y métodos de la clase, esta información se registra en sus campos principales, los detalles son los siguientes.

type (Class <?> type): El tipo de clase encapsulado por el objeto Reflector.

readablePropertyNames, writablePropertyNames (tipo String []): una colección de nombres de propiedades que se pueden leer y escribir.

getMethods, setMethods (tipo Map <String, Invoker>): el conjunto de métodos getter y setter correspondientes a atributos de lectura y escritura, la clave es el nombre del atributo y el valor es un objeto Invoker. Invoker es una encapsulación del objeto Method.

getTypes, setTypes (Map <String, Class <? >> type): el valor de retorno del método getter correspondiente al atributo y el tipo de valor del parámetro del método setter. La clave es el nombre del atributo y el valor es el retorno tipo de valor o tipo de parámetro del método.

defaultConstructor (tipo Constructor <?>): el método de construcción predeterminado.

caseInsensitivePropertyMap (Map <String, String> type): Una colección de todos los nombres de propiedad. Los nombres de propiedad registrados en esta colección están todos en mayúsculas.

Cuando construimos un objeto Reflector, pasamos un objeto Class y, al analizar el objeto Class, se pueden completar los campos centrales anteriores. Todo el proceso central se puede describir de la siguiente manera.

Utilice el campo de tipo para registrar el objeto de clase entrante.

Obtenga todos los métodos de construcción de la clase Class mediante la reflexión y el filtro transversal para obtener el único método de construcción sin parámetros para inicializar el campo defaultConstructor. Esta parte de la lógica se implementa en el método addDefaultConstructor ().

Lea el método getter en la clase Class y complete la colección getMethods y la colección getTypes descritas anteriormente. Esta parte de la lógica se implementa en el método addGetMethods ().

Lea los métodos setter en la clase Class y complete la colección setMethods y la colección setTypes descritas anteriormente. Esta parte de la lógica se implementa en el método addSetMethods ().

Lea los campos que no tienen métodos getter / setter en la clase, genere el objeto Invoker correspondiente y complete la colección getMethods, la colección getTypes, la colección setMethods y la colección setTypes. Esta parte de la lógica se implementa en el método addFields ().

Según el keySet de la colección getMethods / setMethods construida en los tres pasos anteriores, inicialice las colecciones readablePropertyNames y writablePropertyNames.

Recorra las colecciones construidas de readablePropertyNames y writablePropertyNames, convierta todos los nombres de propiedad en ellas a mayúsculas y regístrelos en la colección caseInsensitivePropertyMap.

  1. Análisis del método central Después de
    comprender el proceso central de inicialización, continuaremos analizando los métodos involucrados, que también son los métodos centrales de Reflector.

Primero observe el método addGetMethods () y el método addSetMethods (), que se utilizan para analizar el método getter y el método setter () pasados ​​a la clase Class respectivamente. La lógica de los dos es muy similar. Aquí, tomamos el método addGetMethods () como ejemplo para un análisis en profundidad, que incluye principalmente los siguientes tres pasos principales.

El primer paso es obtener información sobre el método. Aquí, se llamará al método getClassMethods () para obtener la firma única de todos los métodos de la clase Class actual (tenga en cuenta que esto incluye métodos heredados de la clase principal y la interfaz) y el objeto Method correspondiente a cada método.

En el proceso de escaneo recursivo de la clase principal y la interfaz principal, la colección Map <String, Method> se utilizará para registrar los métodos atravesados ​​para lograr el efecto de la deduplicación, donde Key es la firma del método correspondiente y Value es el método. objeto correspondiente al método. El formato de la firma del método generado es el siguiente:

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

Por ejemplo, la única firma del método addGetMethods (Class) es:

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

Puede verse que la firma del método generada aquí contiene el valor de retorno y se puede utilizar como el identificador único global del método.

El segundo paso es encontrar el método getter de la matriz Method devuelta en el paso anterior de acuerdo con la especificación de Java y registrarlo en la colección conflicttingGetters. Aquí, en la colección conflicttingGetters (tipo HashMap <String, List> ()), Key es el nombre de la propiedad y Value es el conjunto de métodos getter correspondientes a la propiedad.

¿Por qué una propiedad encuentra múltiples métodos getter? Esto se debe principalmente a la herencia entre clases. En las subclases, podemos anular los métodos de la clase principal. Las anulaciones no solo pueden modificar la implementación específica del método, sino también modificar el valor de retorno del método. El método getter no es una excepción Se producen dos métodos con diferentes firmas en un solo paso.

El tercer paso es resolver los conflictos de firmas de métodos. Aquí, se llamará al método resolveGetterConflicts () para lidiar con el conflicto de este tipo de método getter. La lógica central para lidiar con el conflicto es en realidad comparar el valor de retorno del método getter y dar prioridad al valor de retorno de el método getter de la subclase, por ejemplo:

// 该方法定义在SuperClazz类中

public List getA(); 

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

public ArrayList getA();

Como puede ver, el valor de retorno ArrayList del método SubClazz.getA () es una subclase del valor de retorno List del método getA () en su clase padre SuperClazz, por lo que aquí seleccionamos el método getA () definido en SubClazz como el método getter de la propiedad A.

Después de que el método resolveGetterConflicts () haya procesado los conflictos del método getter anterior, se creará un objeto MethodInvoker correspondiente para cada método getter y luego se almacenará en la colección getMethods. Al mismo tiempo, el mapeo entre el nombre del atributo y el tipo de valor de retorno del método getter correspondiente se mantendrá en la colección getTypes.

En este punto, la lógica central de addGetMethods () se analiza claramente.

Luego regresamos al método de construcción Reflector. Después de completar el procesamiento de los métodos getter / setter en la clase Class a través de los métodos addGetMethods () y addSetMethods (), continuaremos llamando al método addFields () para procesar los campos sin getter / métodos setter.

Aquí tomamos como ejemplo el procesamiento de campos sin métodos getter. El método addFields () generará los objetos GetFieldInvoker correspondientes para estos campos y los registrará en la colección getMethods. Al mismo tiempo, también registrarán el nombre del atributo y el tipo de atributo en la colección getTypes. La misma lógica se aplica a los campos que no tienen un método de establecimiento.

  1. En el
    proceso de inicialización de Invoker del objeto Reflector, los métodos getter / setter de todas las propiedades se encapsularán en objetos MethodInvoker, y los campos sin getter / setter también generarán los objetos Get / SetFieldInvoker correspondientes. Echemos un vistazo a la definición de esta interfaz de Invoker:
public interface Invoker {
    
    

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

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

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

}

La relación de herencia de la interfaz de Invoker se muestra en la siguiente figura:
Inserte la descripción de la imagen aquí
Diagrama de relación de herencia de la interfaz de Invoker

Entre ellos, MethodInvoker implementa el método Method encapsulado subyacente (por ejemplo, método getter / setter) a través de la reflexión para completar el efecto de lectura y escritura de la propiedad, y Get / SetFieldInvoker lee y escribe el campo Field encapsulado subyacente a través de la reflexión, logrando así la lectura de la propiedad y efecto de escritura
4. ReflectorFactory
A través del análisis anterior, sabemos que habrá una serie de operaciones de reflexión en el proceso de inicialización del Reflector. Para mejorar la velocidad de inicialización del Reflector, MyBatis proporciona la interfaz de fábrica ReflectorFactory para almacenar en caché los objetos Reflector. El método principal es obtener Reflector, el método findForClass () del objeto.

DefaultReflectorFactory es la implementación predeterminada de la interfaz ReflectorFactory. De manera predeterminada, mantiene una colección ConcurrentHashMap <Class <?>, Reflector> (campo reflectorMap) en la memoria para almacenar en caché todos los objetos Reflector que crea.

En la implementación de su método findForClass (), el caché reflectorMap se consulta primero de acuerdo con la clase de clase entrante. Si se encuentra el objeto Reflector correspondiente, regresa directamente; de ​​lo contrario, el objeto Reflector correspondiente se crea y se registra en el caché reflectorMap, esperando el próximo uso.

La fábrica de objetos predeterminada
ObjectFactory es una fábrica de reflejos en MyBatis, que proporciona dos sobrecargas del método create (). Podemos crear objetos del tipo especificado a través de los dos métodos create ().

DefaultObjectFactory es la implementación predeterminada de la interfaz ObjectFactory. La capa inferior de su método create () es crear un objeto llamando al método instantiateClass (). El método instantiateClass () seleccionará el constructor apropiado para instanciar el objeto basado en la lista de parámetros pasada a través de la reflexión.

Además de utilizar la implementación predeterminada de DefaultObjectFactory, también podemos configurar una clase de implementación de extensión de interfaz de ObjectFactory personalizada en el archivo de configuración mybatis-config.xml (la clase de prueba proporcionada por MyBatis incluye una implementación de ObjectFactory personalizada, que puede ser referida al código fuente ) para completar extensiones de funciones personalizadas.

Herramienta de análisis de atributos
En el análisis de ejemplo anterior de la capa de persistencia del sistema de pedidos, 20 minutos para que pueda comenzar rápidamente con el ejemplo del sistema de pedidos presentado por MyBatis , estamos en el mapeo ResultMap de orderMap, si desea configurar el uno- relación a muchos entre Order y OrderItem, puede usar etiquetas para hacer la Configuración; si el número de OrderItems es claro, puede usar directamente el método de índice de subíndice de matriz (es decir, ordersItems [0]) para completar la colección orderItems.

La navegación "." Y el análisis de los subíndices de la matriz aquí también se realizan en la caja de herramientas de reflexión. A continuación, presentaremos las tres clases de herramientas relacionadas con el análisis de propiedades en el paquete reflect.property En las siguientes clases de herramientas, como MetaClass y MetaObject, también se requieren capacidades de resolución de propiedades.

La clase de herramienta PropertyTokenizer es responsable de analizar la expresión compuesta por "." Y "[]". PropertyTokenizer hereda la interfaz Iterator y puede procesar de forma iterativa expresiones anidadas de varios niveles.

PropertyCopier es una clase de herramienta para copiar propiedades, proporciona una función similar a BeanUtils.copyProperties () en Spring, realiza la copia del valor de la propiedad entre dos objetos del mismo tipo, su método principal es el método copyBeanProperties ().

La función proporcionada por la clase de utilidad PropertyNamer es convertir nombres de métodos en nombres de propiedades y detectar si un nombre de método es un método getter o setter.

MetaClass
MetaClass proporciona la función de obtener la información de descripción de atributo en la clase. La capa inferior se basa en el Reflector introducido anteriormente. En el método de construcción de MetaClass, la Clase entrante se encapsulará en un objeto Reflector y se registrará en el campo reflector. búsqueda de atributo posterior de MetaClass Se utilizará el objeto Reflector.

El método findProperty () en MetaClass es el método principal para la búsqueda de propiedades. Se ocupa principalmente de la búsqueda de propiedades de la navegación ".". Este método utiliza el PropertyTokenizer presentado anteriormente para analizar la expresión de nombre entrante, que puede pasar "." "Navegar varios niveles, por ejemplo, order.deliveryAddress.customer.name.

MetaClass procesará esta expresión capa por capa. Primero, busque la propiedad deliveryAddress a través del Reflector correspondiente al tipo de pedido. Después de que la búsqueda sea exitosa, cree el objeto MetaClass correspondiente (y el objeto Reflector subyacente) de acuerdo con el tipo de propiedad deliveryAddress (es decir, tipo de dirección), y luego continuar con la búsqueda. El atributo de cliente se procesa de forma recursiva hasta que finalmente se encuentra el atributo de nombre en Cliente. Esta parte de la lógica de búsqueda recursiva se encuentra en el método MetaClass.buildProperty () .

En el proceso de buscar propiedades en la MetaClass anterior, también se llaman a los métodos hasGetter () y hasSetter () para determinar si la propiedad especificada en la expresión de propiedad tiene un método getter / setter correspondiente. Estos dos métodos también primero analizan la expresión de nombre entrante a través de PropertyTokenizer y luego realizan una consulta recursiva. En la consulta recursiva, se basará en el método Reflector.hasGetter () para encontrar la colección getMethods o la colección setMethods presentada anteriormente, y buscar el getter / setter correspondiente al método de propiedad.

La implementación de otros métodos en MetaClass también se basa principalmente en PropertyTokenizer para analizar expresiones y luego buscar de forma recursiva.El proceso de búsqueda se basará en los métodos relacionados de Reflector.

ObjectWrapper
MetaClass encapsula la metainformación de la clase y ObjectWrapper encapsula la metainformación del objeto. En ObjectWrapper, se abstrae la información de propiedad de un objeto y se proporcionan métodos relacionados para consultar la información de propiedad del objeto y métodos relacionados para actualizar el valor de la propiedad.

La clase de implementación de Inserte la descripción de la imagen aquí
ObjectWrapper se muestra en la siguiente figura: BaseWrapper es una implementación abstracta de la interfaz ObjectWrapper, en la que solo hay un campo de tipo MetaObject. BaseWrapper implementa resolveCollection (), getCollectionValue () y setCollectionValue () tres métodos de procesamiento para objetos de colección para subclases. Entre ellos, el método resolveCollection () devuelve la propiedad especificada como un objeto de colección, y la capa inferior se basa en el método MetaObject.getValue () para lograrlo (más sobre esto más adelante). El método getCollectionValue () y el método setCollectionValue () analizarán la información del subíndice de la expresión de atributo y luego obtendrán / establecerán el elemento correspondiente en la colección. El análisis de la expresión de atributo aquí todavía se basa en la clase de herramienta PropertyTokenizer presentada anteriormente.

BeanWrapper hereda la clase abstracta BaseWrapper Además de encapsular un objeto JavaBean, la capa inferior también encapsula el objeto MetaClass correspondiente al tipo JavaBean y el objeto MetaObject heredado de BaseWrapper.

En la implementación del método get () y el método set (), BeanWrapper obtendrá / establecerá el valor de atributo correspondiente de acuerdo con la expresión de atributo pasada. Tome el método get () como ejemplo. Primero, determinará si la expresión contiene un subíndice de matriz. Si contiene un subíndice, obtendrá el elemento correspondiente de la colección a través de los métodos resolveCollection () y getCollectionValue (); si no contiene un subíndice, pasará MetaClass encuentra el GetFieldInvoker correspondiente cuyo nombre de atributo está en la colección Reflector.getMethods, y luego llama al método Invoker.invoke () para leer el valor del atributo.

La implementación de otros métodos en BeanWrapper es en su mayoría similar al método get () y al método set (), confiando en MetaClass y MetaObject para completar la lectura y escritura de información de atributos en objetos relacionados. No los presentaré uno por uno aquí. Si está interesado, puede consultar el código fuente .

CollectionWrapper es una implementación de la interfaz ObjectWrapper para colecciones Collection, que encapsula los objetos de la colección Collection. Solo están disponibles los métodos isCollection (), add (), addAll () y los métodos heredados de BaseWrapper. Otros métodos arrojarán UnsupportedOperationException.

MapWrapper es una implementación para el tipo Map. Esta implementación es relativamente simple, por lo que dejaré que usted la analice. Durante el análisis, puede consultar el MetaObject que se presentará a continuación.
MetaObject a
través de la introducción de ObjectWrapper, aprendimos que ObjectWrapper implementa funciones básicas como leer y escribir valores de propiedad de objeto, detectar getter / setter, etc. Al analizar clases de implementación como BeanWrapper, podemos ver que la capa subyacente depende de MetaObject. Se mantiene un campo originalObject en MetaObject para apuntar al objeto JavaBean encapsulado, y también se mantiene el objeto ObjectWrapper (campo objectWrapper) correspondiente al objeto JavaBean.

Los métodos de nivel de clase en MetaObject y ObjectWrapper, por ejemplo, el método hasGetter (), el método hasSetter (), el método findProperty (), etc., se implementan llamando directamente a los métodos correspondientes de MetaClass u ObjectWrapper. Otros métodos a nivel de objeto se implementan junto con ObjectWrapper, como los métodos MetaObject.getValue () / setValue ().

Tome el método getValue () como ejemplo. El método primero analiza la expresión de propiedad especificada de acuerdo con PropertyTokenizer. Si la expresión es una consulta de propiedad de varios niveles que contiene la navegación ".", Entonces se obtiene la subexpresión y la propiedad correspondiente se crea el objeto El objeto MetaObject asociado continúa llamando de forma recursiva al método getValue () hasta que finaliza el procesamiento recursivo, y la salida recursiva llamará al método ObjectWrapper.get () para obtener el valor final de la propiedad.

En MetaObject, la lógica central del método setValue () es básicamente similar al método getValue (), que también es una búsqueda recursiva. Sin embargo, hay una diferencia a la que debe prestar atención: si el valor final de la propiedad que debe establecerse no está vacío, se llamará al método ObjectWrapper.instantiatePropertyValue () durante la búsqueda recursiva del método setter () para inicializar any encontrado durante el proceso recursivo Un objeto vacío, pero si encuentra un elemento de colección vacío, este método no puede inicializarlo. El método ObjectWrapper.instantiatePropertyValue () en realidad se basa en el método create () de la interfaz ObjectFactory (la implementación predeterminada es DefaultObjectFactory) para crear el tipo de objeto correspondiente.

Después de comprender el método de usar MetaObject y BeanWrapper juntos y la lógica de encontrar recursivamente el valor de atributo especificado por la expresión de atributo, la implementación de los métodos restantes de MetaObject es más fácil de analizar, por lo que no entraré en detalles aquí.

Resuma
la caja de herramientas de reflexión en MyBatis. Primero, la implementación central de la clase Reflector más central y de nivel inferior en la caja de herramientas de reflexión; luego, las diversas clases de herramientas proporcionadas por la caja de herramientas de reflexión sobre la base de Reflector, incluida la clase de fábrica ObjectFactory, la clase de empaquetado ObjectWrapper y la MetaClass que registra metadatos, MetaObject, etc.

Supongo que te gusta

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