Análisis simple del código fuente de Spring

prefacio

Durante la entrevista hace algún tiempo, me preguntaron sobre el código fuente de Spring, la pregunta en realidad no era demasiado profunda, pero como ha pasado mucho tiempo desde la última vez que vi el código fuente de Spring, casi lo olvido. así que básicamente no respondí.

Con el fin de consolidar y al mismo tiempo comprender algunas cuestiones que antes no estaban claras, tengo este artículo. El texto completo es largo, pero en general no es difícil de entender. Se trata de analizar algunos problemas comunes desde una perspectiva macro:
proceso de creación de Spring , ciclo de vida del bean , proceso de creación de bean , proceso de creación de beanDefinition , proceso de inicio de Spring Boot y algunos pequeños punto _

Referencia de contenido:

  1. Análisis detallado del código fuente de Spring: https://www.bilibili.com/video/BV1T54y1n7PB/?p=77
  2. Análisis de perfil COI y AOP: https://www.bilibili.com/video/BV1584y1r7n6

El proceso de creación de Spring.

Lo primero que hay que tener claro es que cada aplicación Spring que creamos se divide principalmente en dos tipos de interfaces, BeanFactoryy ApplicationContextla segunda hereda la primera y tiene más funciones que la primera, como AOP.

En versiones anteriores de Spring, el uso de XmlBeanFactory era una forma de crear un contenedor de Spring.
XmlBeanFactory implementa la interfaz BeanFactory, que puede analizar la definición de Bean desde el archivo de configuración XML
e inicializar el objeto Bean. En esta versión, Spring no integra directamente funciones AOP, pero puede
integrar marcos AOP de terceros, como AspectJ o JBoss AOP, a través del módulo Spring AOP.

Sin embargo, con la mejora continua y la actualización de la versión Spring, XmlBeanFactory se abandona y
se reemplaza por ApplicationContext. ApplicationContext hereda la interfaz BeanFactory y
proporciona un soporte más funcional para los usuarios, incluido el soporte nativo para AOP. Por lo tanto, si desea usar
Spring AOP, se recomienda usar ApplicationContext como su contenedor de Spring en lugar del obsoleto
XmlBeanFactory.

Para obtener más información, consulte la Guía de Java escrita anteriormente.

La clase de implementación de estas dos interfaces es equivalente a una fábrica, y aquí se usa el patrón de fábrica . Es solo que esta fábrica tiene muchas funciones complejas, lo que también muestra que uno de los beneficios del modelo de fábrica es que si administra de manera centralizada la creación de objetos, puede agregar algunas funciones adicionales de manera uniforme.

Entre ellos, Spring administra objetos que requieren singletons ConcurrentHashMappara garantizar la seguridad de los subprocesos. Al mismo tiempo, al crear un objeto singleton, sychronizedtambién se utilizan bloqueos. Para obtener más información, consulte org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingletonel método.

ciclo de vida del frijol

El ciclo de vida del bean se divide principalmente en tres etapas : inicialización, uso y destrucción . Y puede ser controlado por los siguientes métodos:

  1. Instanciación : cuando se inicia el contenedor Spring, instanciará todos los beans configurados en el archivo de configuración o la anotación a través del mecanismo de reflexión. Esta es la etapa inicial del ciclo de vida del frijol.

  2. Asignación de atributos : después de la creación de instancias, Spring asignará valores a los atributos de Bean de acuerdo con los valores de atributo especificados en el archivo de configuración o anotación. Esta es la segunda fase del ciclo de vida del frijol.

  3. Método de inicialización personalizado : cuando se completa la asignación de atributos, Spring llamará al método de inicialización personalizado, que se puede realizar implementando la interfaz InitializingBean o especificando el método init-method en el archivo de configuración o la anotación. Esta es la tercera fase del ciclo de vida del frijol.

  4. Uso : Después de ejecutar el método de inicialización, la aplicación puede utilizar el Bean. En esta etapa, el Bean puede recibir varias solicitudes y llamadas para atender la aplicación.

  5. Método de destrucción personalizado : cuando finaliza la aplicación, el contenedor Spring llama al método de destrucción personalizado del bean. Esto se puede lograr implementando la interfaz de AvailableBean o especificando el método de destrucción en un archivo de configuración o una anotación. Esta es la fase final del ciclo de vida del frijol.

proceso de creación de frijol

En primer lugar, a través de la creación del contenedor Spring de punto de interrupción, podemos saber que org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitializationel bean se crea en este método.

Todo el camino hasta el punto de interrupción, puede llegar al org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletonsmétodo Podemos ver que este método se basa en beanNamela búsqueda beanDefinitionpara determinar si el bean existe.

Aquí hay que recorrer continuamente beanDefinitionNamesla colección para crear beans. Tenga en cuenta que la colección es Listun tipo, lo que indica que se conserva el orden, es decir, la creación de beans aquí es estrictamente de acuerdo con el orden.

Además, si el bean es abstracto, no es singleton o tiene carga diferida, no se inicializará. Una cosa que vale la pena señalar aquí es que solo estos beans se inicializan juntos cuando se crea beanFactory, y otros tipos de beans no son así.

Finalmente llegamos a org.springframework.beans.factory.support.AbstractBeanFactory#doGetBeanlos métodos. Este método controla la creación y adquisición del bean, es decir, este método se utiliza para obtener el bean, si no se puede obtener, llamará a diferentes situaciones para su procesamiento, llamará a la inicialización y luego devolverá el bean org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean. Vale la pena señalar que antes de la llamada createBean(), primero se preparará la dependencia de construcción del bean, y también se generará el error de la dependencia circular en este momento.

Lo mencionado anteriormente createBean()es el método central para crear beans, que es todo el proceso antes y después de la creación de beans. Si crea un bean, llegará a org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBeaneste método, como se muestra a continuación:

doCreateBean()El proceso de creación de beans se puede dividir en tres partes: construcción de objetos createBeanInstance(), relleno de propiedades populateBean(), inicialización de instanciasinitializeBean() y registro y destrucciónregisterDisposableBeanIfNecessary() . Vea abajo:

La etapa de construcción de objetos es simplemente crear un objeto .

@AutowireLa etapa de llenado de atributos es inyectar los atributos marcados con anotaciones , aquí se utiliza el cache de tercer nivel, recuerden lo que mencioné antes, doGetBean()aquí se llamará primero getSingleton(), y esta búsqueda se buscará en el paso de cache de tercer nivel paso a paso, como sigue la imagen:

Si allowEarlyReferencees true, entonces se ejecutará singletonFactory que acaba de agregar y se completará la operación AOP. De manera similar, si ocurre una dependencia circular cuando se crea el objeto y se completan las propiedades, no importa en este momento, porque se buscará en el caché de tercer nivel.

La etapa de instancia de inicialización es principalmente para llamar a varias funciones de enlace que hemos escrito , como la interfaz implementada y la interfaz BeanNameWareimplementada . initializingBeanAOP también se ejecuta en este momento. No hay necesidad de preocuparse por hacer AOP nuevamente después de hacer AOP en el caché de tercer nivel. Spring colocará los beans que han hecho AOP en la colección para juzgar que los beans han sido AOPed.

La fase de registro y destrucción consiste en registrar los beans que implementan la interfaz de destrucción .

Para obtener detalles sobre el caché de tercer nivel, consulte este artículo: Dependencias circulares y soluciones en Spring .

El fragmento de código anterior doGetBean()también explica por qué si es una dependencia circular del constructor, no se puede resolver (porque al menos el objeto debe inicializarse antes de que se agregue al caché, preste atención a la ubicación de ejecución), si lo es, no se puede resolver addSingletonFactory( prototypeporque addSingletonFactoryla condición previa es que este bean es un patrón singleton).

El proceso de creación de beanDefinition

Para las aplicaciones Spring, según ApplicationContextel tipo de creación ( ClassPathXmlApplication, FileSystemXmlApplication, XmlWebApplicationContext ), se utilizan diferentes estrategias para leer el archivo xml para completar la creación de beanDefinition.

Antes que nada, debemos aclarar una idea, como se mencionó anteriormente, el núcleo de Spring IOC es beanFactory. En el proceso de nuestra creación ApplicationContext, se llamará org.springframework.context.support.AbstractApplicationContext#refreshy este método completa la creación de beanFactory.

En este método, obtainFreshBeanFactory()primero se crea una beanFactory. Se obtainFreshBeanFactory()llamará al método org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitionspara completar la creación de beanDefinition. Aquí crearemos uno XmlBeanDefinitionReaderpara leer la ubicación del archivo xml pasado cuando iniciamos el programa, analizaremos la etiqueta y la encapsularemos en un BeanDefinitionobjeto de tipo, y la almacenaremos en beanFactory beanDefinitionMap(también ConcurrentHashMapun tipo). BeanDefinitionEs equivalente a la información de los beans, y los beans se crean reflejando esta información.

El procesamiento mencionado aquí se basa en ClassPathXmlApplication. Diferentes ApplicationContextclases de implementación tienen formas ligeramente diferentes de obtener beanDefinition.

Después de que se crea beanFactory, refresh()se volverá a llamar finishBeanFactoryInitialization()para completar la creación de un bean singleton sin carga diferida.Al igual que dijimos anteriormente, algunos beans se crean durante el proceso de creación de beanFactory.

El proceso de inicio de Spring Boot

El inicio de Spring Boot se divide en cuatro etapas: construcción de servicios, preparación del entorno, creación de contenedores y llenado de contenedores .

Para obtener más información, consulte el segundo enlace anterior.

Cualquier proyecto de Spring Boot debe ejecutarse SpringApplication.run()(tenga en cuenta que aquí está SpringApplicationla estática de la clase run()) para comenzar. Este método primero creará un SpringApplicationobjeto de tipo. Durante la creación de este objeto, se completarán muchos preparativos necesarios para configurar ApplicationContext (como obtener la información de configuración actual del sistema, juzgar el tipo de aplicación web de acuerdo con el archivo de clase en el classpath, imprimir la imagen del banner, etc.).

Luego llame SpringApplicational objeto de tipo que acaba de crear run():

Este método completará la creación del contenedor (ApplicationContext). Entre ellos createApplicationContext(), se inicializará un ApplicationContext y se combinará la información recién obtenida, al mismo tiempo se prepareContext()inicializará el beanDefinitionMap y luego refreshContext()se llamará a las operaciones pertinentes de beanFactory.

Vale la pena señalar que refreshContext()las operaciones relevantes de beanFactory se realizan aquí, al igual que en Spring, y también llegarán a org.springframework.context.support.AbstractApplicationContext#refresh:

También se llama para obtainFreshBeanFactory()crear un beanFactory, pero este método no hace nada en este momento, porque Spring Boot se usa ServletWebServerApplicationContextcomo contenedor, se ha creado el beanFactory y la llamada obtainFreshBeanFactory()en realidad no hará nada en este momento.

Además, en este método se onRefresh()completa el inicio del servidor Tomcat.

Para obtener refresh()una introducción detallada a cada método llamado en el método, consulte el video: https://www.bilibili.com/video/BV1hv4y1z7PQ.

Además, el escaneo de beanDefinition en Spring Boot es diferente del método mencionado anteriormente . Spring Boot ya tiene algunas definiciones básicas de beanDefinitions al principio, como la clase de inicio de nuestro propio programa, y ​​luego ejecuta invokeBeanFactoryPostProcessors()varios postprocesadores de beanFactory llamando.Lo más importante es ConfigurationClassPostProcessorque escanea todas las clases de configuración y usa ConfigurationClassParserTake to scan ellos _

Implementa BeanFactoryPostProcessorla interfaz postProcessBeanFactory(). Puede ver que se llamará a este método processConfigBeanDefinitions()para procesarlo. Aquí se llamará para ConfigurationClassUtils.checkConfigurationClassCandidate()juzgar si es una clase de configuración o no. Puede rastrear el código fuente de este método para saber que la clase de configuración mencionada aquí no necesariamente tiene que existir @Configuration. @Component, @ComponentScan, @Import, @ImportResourcetodos pueden ser procesados.

ConfigurationClassParserEl método se llamará más tarde parse()para procesar la clase de configuración escaneada previamente. Este método eventualmente llegará doProcessConfigurationClass()a procesar recursivamente todas las clases de configuración (la clase de configuración aquí no solo se refiere a @Configurationalgunos , sino a cualquier @Componentclase que esté marcada directa o indirectamente).

Para el breve párrafo anterior sobre el proceso de configuración automática de Spring Boot , consulte el video para obtener más detalles: https://www.bilibili.com/video/BV1NY411P7VX

Hasta ahora, se han completado los cuatro pasos principales del inicio de Spring Boot.

algunos pequeños puntos

¿Cómo encuentra Spring el bean correspondiente según el tipo?

Hay una colección de mapas en beanFactory de Spring allBeanNamesByType, la clave es Classel tipo, el tipo de dependencia de almacenamiento y el valor es String[]el almacenamiento de todos los nombres de beans singleton y non-singleton (id).

¿Cómo se almacena FactoryBean en Spring?

Si configura el método de fábrica estático o no estático de un bean (método de fábrica) a través de un archivo xml, entonces el principio no es difícil de entender.La discusión principal aquí es cómo maneja Spring cuando se usan interfaces FactoryBean.

En primer lugar, para beanDefinition, la clase que implementa FactoryBeane inyecta en el contenedor es la misma que otras clases inyectadas en el contenedor, solo se generará un beanDefinition, y el tipo de este beanDefinition es el tipo de la clase, no el getObject()tipo del objeto ( ) a generar .

Entonces, es lógico que Spring solo pueda obtener FactoryBeanel tipo de bean, ¿cómo obtiene el objeto que genera?

Después del empuje anterior, sabemos que el bean finalmente se obtiene a través de doGetBean()la adquisición. Aquí, después de obtener el bean, todavía se llamará getObjectForBeanInstance(), como se muestra en la siguiente figura:

Este método no se expandirá, es aproximadamente para juzgar si obtener el bean o la fábrica del bean esta vez (tenga en cuenta que el nombre y beanName se pasan aquí). Si desea obtener el bean de fábrica, simplemente devuélvalo directamente, porque el bean de fábrica (factoryBean) se almacena en beanDefinitionMap al principio. Si desea obtener el objeto generado por la fábrica, llame al método de fábrica para crear el objeto de acuerdo con la situación, porque si se determina que el objeto es un patrón singleton, Spring lo almacenará en caché beanFactory,factoryBeanObjectCache de modo que el objeto creado una vez se obtiene directamente aquí Eso es todo.

Tenga en cuenta que los objetos producidos por el bean de fábrica no se almacenan en singletonObjects.

Por qué @AutoConfigurationpuede retrasar el registro

Sabemos que las clases de configuración siempre preceden a las clases de configuración automática y he aquí por qué. El principio específico es un poco complicado y se explicará en varios pasos aquí.

Registrar clases de autoconfiguración

Primero, permítanme presentarles cómo registrar la clase de configuración automática. A diferencia de las clases de configuración ordinarias, que se pueden anotar, el registro de las clases de configuración automáticas será diferente.

Antes de Spring Boot 2.7.0,@AutoConfiguration debemos crear una carpeta en la ruta del proyecto META-INF/spring.factoriesy configurarla de la siguiente manera en el archivo :

No importa si olvida el formato aquí, solo mire un contenedor con función de configuración automática, tendrá este archivo, solo imite el formato.

Después de Spring Boot 2.7.0, @AutoConfigurationa veces , debemos crear una carpeta en la ruta del proyecto META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsy luego configurarla en el archivo de la siguiente manera:

Aquí solo necesita escribir el nombre de clase completo de la clase de configuración.

Luego recuerde agregar @Configuration/ a la clase @AutoConfiguration.

Además, después de 3.0, solo se puede usar el segundo método para declarar la clase de configuración automática.

La clase de configuración y la clase de configuración automática no se refieren a si hay una @AutoConfiguration(después de todo, esta anotación solo está disponible después de Spring Boot 2.7.0, pero el concepto de clase de configuración automática existe desde hace mucho tiempo), sino a distinguirse del método de registro.

La clase de configuración automática tiene muchas más funciones, como arreglar el orden, lo que nos hace más conveniente usar@ConditionalOn... este tipo de anotación , porque este tipo de anotación se juzga de inmediato, es decir, si cumple las condiciones inmediatamente al cargar, por lo que es muy sensible al orden de carga.

Por ejemplo, tanto beanA como beanB están debajo de la ruta, beanA solo se inyecta cuando beanB se inyecta en el contenedor, pero si beanA se inyecta primero debido a una disposición de secuencia incorrecta y beanB no se ha inyectado en este momento, la inyección de beanA fallará. .

Confíe en @AutoConfigureOrder, podemos cambiar el orden de inicialización de la clase de configuración, tenga en cuenta que es diferente @Order, solo se pueden modificar el orden AOP y el orden de ensamblaje automático durante la inyección de bean (para obtener más detalles, consulte el blog: El uso incorrecto del Orden de carga de frijoles para disipar los rumores , que no se presentarán aquí).

Además @AutoConfigureBefore, etc., también puede ajustar el orden de carga de la clase de configuración automática, y estas anotaciones están unificadas por @AutoConfigurationuna anotación (todos se convierten en uno de sus atributos), y también vale la pena señalar que @AutoConfigurationel proxyBeanMethodsvalor predeterminado es falsemejorar el inicio. velocidad de Spring Boot.

Cómo escanear clases de configuración automática

Registramos la clase de configuración automática anterior, entonces, ¿cómo la escanea Spring Boot?

De hecho, muchos blogs han mencionado este punto, que es la anotación en nuestra clase de inicio de Spring Boot @SpringBootApplication, que se incluye en esta anotación @EnableAutoConfiguration, y esta anotación también se incluye @Import(AutoConfigurationImportSelector.class).

Entre ellos, la clase se presenta aquí AutoConfigurationImportSelector, esta es una ImportSelector, escaneará el beanDefinition , para que selectImports()pueda averiguar qué está pasando mirando la clase directamente, no la expandiré aquí, y finalmente la usaré ClassLoaderpara getResources()escanear los dos archivos (porque antes de 3.0, spring.factoriestodavía es compatible y se pueden usar juntos).

Entonces, cuando se analiza recursivamente la clase de inicio del proyecto Spring Boot (mencionado anteriormente ConfigurationClassParser), cuando se analiza la clase de inicio @Import, se encontrará AutoConfigurationImportSelector, introduciendo así la clase de configuración automática que registramos.

Cómo asegurarse de que el escaneo de la clase de configuración automática debe ser posterior a la clase de configuración

Del análisis anterior, sabemos cómo se escanea la clase de configuración automática, pero solo en base a lo anterior, no hay garantía de que la clase de configuración automática se escaneará después de la clase de configuración, pero ahora, según el conocimiento anterior, esto también puede hacerse Fue resuelto.

Lo mencionado anteriormente AutoConfigurationImportSelectorno es solo uno ImportSelector, sino que, lo que es más importante, implementa DeferredImportSelectorla interfaz, que se hereda ImportSelector, y este es el punto central.

¿Aún recuerdas que sirve ConfigurationClassParserpara parse()escanear recursivamente beanDefinition?, en este método, al final del escaneo, se llamará DeferredImportSelectora la clase de implementación obtenida en el escaneo anterior, y se postergará la ejecución:

De hecho, solo mire DeferredImportSelectorlos comentarios, que también se indican claramente arriba. Esto ImportSelectorse ejecutará después de escanear todas las clases de configuración. El código fuente anterior es solo una prueba directa de su garantía de este comportamiento.

Pero no sé si todavía tiene una pregunta, es decir, si declaramos una clase de configuración en la misma ruta donde iniciamos la clase y luego spring.factoriesregistramos la clase en , entonces el comportamiento de esta clase se considera una clase de configuración , o es una clase de configuración automática?

La respuesta a esta pregunta es en realidad clases de configuración automática. Mirando el código fuente, encontraremos que en realidad juzgará primero si es una clase de configuración automática ConfigurationClassParsery , si lo es, no se cargará ahora.parse()org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter#getAutoConfigurations

Supongo que te gusta

Origin blog.csdn.net/weixin_55658418/article/details/131216667
Recomendado
Clasificación