Cómo se inicializa Spring
Comencemos con un diagrama de clases:
Tomemos prestado el proceso de carga en el proyecto web para familiarizarnos con el proceso de carga de Spring ApplicationContext:
1. En general, en proyectos web, utilizamos la clase ContextLoaderListener org.springframework.web.context para la inicialización del contenedor en muchos casos. El contenedor web (como Tomcat) crea automáticamente una instancia de esta clase y llama al método contextInitialized.
/**
* Inicializa el contexto de la aplicación web raíz.
*/
@Override
public?void?contextInitialized( ServletContextEvent?event) {
initWebApplicationContext(event.getServletContext() );
}
2. El método initWebApplicationContext hereda de la clase principal ContextLoader. La lógica general de este método es: determinar si el contenedor web ha registrado el atributo ROOT_APPLICATION_CONTEXT_STTRIBUTE (WebApplicationContext.class.getName() + ".ROOT") Si es así, se lanzará una excepción para garantizar que solo haya una raíz Spring. en un contenedor web. Al crear un contenedor, es necesario determinar qué clase debe crearse para crear una instancia del contenedor raíz Spring del contenedor web actual. Si configuramos el nombre "contextClass" del parámetro de contexto. la clase que configuramos, que debe implementar la interfaz ConfigurableWebApplicationContext o heredada de una subclase que implementa esta interfaz (por ejemplo, XmlWebApplicationContext, GroovyWebApplicationContext y AnnotationConfigWebApplicationContext), generalmente no la configuraremos, Spring usará el nombre de clase registrado en el mismo directorio que ContextLoader. ContextLoader.properties como contenedor raíz. Spring utilizará el nombre de clase registrado en el mismo directorio que ContextLoader.properties como el tipo de contenedor raíz (el valor predeterminado es org.springframework.web.context.support.XmlWebApplicationContext; configure el contenedor; como propiedades de contenedor web. El siguiente código eliminará las secciones de registro y excepción.
público?WebApplicationContext initWebApplicationContext(ServletContext?servletContext) {
if(servletContext.getAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) !=?null) {
throw?new ?IllegalStateException(
"No se puede inicializar el contexto porque ya existe un contexto de aplicación raíz presente - "?+
"¡Compruebe si hay varias definiciones de ContextLoader* en su web.xml! " );
}
if(this.context?==?null) {
this .context?= createWebApplicationContext(servletContext);
}
if?(this.context?instanceof?ConfigurableWebApplicationContext ) {
ConfigurableWebApplicationContext?cwac?= (ConfigurableWebApplicationContext)?this.context;
if?isActive()) {
if?(cwac.getParent() ==?null) {
ApplicationContext?parent?= loadParentContext(servletContext);
cwac.setParent(parent) )
}
configureAndRefreshWebApplicationContext(cwac,?servletContext);
f?servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,this.context);
ClassLoader?ccl?= Thread.currentThread().getContextClassLoader ( );
if?(ccl?== ContextLoader.class.getClassLoader()) {
currentContext?=?this.context;
}< / p>
else?if(ccl! =?null) {
currentContextPerThread.put(ccl,?this.context);
}
return?this.context;
}
3.El método configureAndRefreshWebApplicationContext es responsable de inicializar el contenedor. La lógica de este método incluye principalmente los siguientes puntos: Establecer contextId (obtenido del parámetro). contextId, si no, el valor predeterminado es WebApplicationCon
texto) nombre de clase + ":" + ruta de contexto del servlet); establezca la ubicación de configuración (obtenida del parámetro contextConfigLocation, si no está configurado, el valor predeterminado es /WEB-INF/applicationContext.xml, que se puede ver en XmlWebApplicationContext); este Contexto; Llame al método de actualización () de este Contexto.
protected?void?configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContextwac, ServletContext?sc) {
Si? ( ObjectUtils.identityToString(wac).equals(wac.getId())){
String?idParam?=?sc.getInitParameter(CONTEXT_ID_PARAM);
if( idParam! =?null) {
wac.setId(idParam);
}
¿otra cosa?{
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_ PREFIX?+
ObjectUtils.getDisplayString(sc.ContextPath())
Wac.setId(idParam;
}
else?getContextPath()));
}
}
wac.setServletContext(sc);
String?configLocationParam?=?sc.getInitParameter(CONFIG_LOCATION_PARAM);
if?(configLocationParam! =?null ) {
wac.setConfigLocation(configLocationParam);
}
ConfigurableEnvironment?env?=?wac.getEnvironment();
if?(env?instanceof?ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)?env).initPropertySources(sc,?null);
}
customizeContext( sc,?wac);
wac.refresh();
}
4. El método de actualización en la clase de implementación de la interfaz ConfigurableApplicationContext AbstractApplicationContext define una plantilla. La carga de recursos, el análisis de archivos de configuración, el registro de definiciones de beans y la inicialización de componentes se realizan en esta plantilla. Cada paso del trabajo está definido en el método de respuesta, de forma concisa y clara. Los siguientes métodos omiten el manejo de excepciones.
sincronizado?(this.startupShutdownMonitor) {.
prepareRefresh(); //preparar
ConfigurableListableBeanFactory?beanFactory?= getFreshBeanFactory() //obtener un bean factory
prepareBeanFactory(beanFactory); //preparar la fábrica
postProcessBeanFactory(beanFactory); //ejecutar al procesar la fábrica
invokeBeanFactoryPostProcessors( beanFactory); // Ejecutado al procesar la fábrica
registerBeanPostProcessors(beanFactory); //Registrar Bean para generar interceptor
initMessageSource() //Inicializar fuente del mensaje
initApplicationEventMulticaster; ();//inicializa la emisora de eventos de la aplicación
onRefresh();//inicializa otros beans específicos de la subclase de este contenedor
registerListeners();//Verifica y registra oyentes< / p>
FinishBeanFactoryInitialization(beanFactory);//Inicializa un Bean singleton no diferido
finishRefresh();//Publica el mensaje de actualización final
}
5. Entre estos métodos, el más importante es contener. Los métodos más importantes son el método contieneFreshBeanFactory y el método FinishBeanFactoryInitialization. Uno se usa para obtener la fábrica de Bean y el otro se usa para crear una instancia del Bean e inyectarlo. Aquí primero analizamos el método contieneFreshBeanFactory. En la cadena de herencia, AbstractApplicationContext implementa este método y define el método de actualizaciónBeanFactory para que lo implementen los descendientes.
Si existe un BeanFactory, destrúyelo y crea otro.
protected;final;void;refreshBeanFactory();throws;BeansException {
if(hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
probar{
DefaultListableBeanFactory?beanFactory?setSerializationId(getId());
personalizarBeanFactory (beanFactory);
loadBeanDefinitions(beanFactory);
¿sincronizado?( this.beanFactoryMonitor) {
this.beanFactory?=?beanFactory;
}
}
}
6. Las subclases de XmlWebApplicationContext implementarán el método loadBeanDefinitions. La lógica de este método es: crear un nuevo XmlBeanDefinitionReader; configurar el ResourceLoader de este lector (XmlWebApplicationContext establece su propio ResourceLoader porque implementa indirectamente la interfaz ResourceLoader y usa este lector para cargar definiciones de Bean).
protegido?void?loadBeanDefinitions(DefaultListableBeanFactory?beanFactory)?throwsBeansException, IOException {
XmlBeanDefinitionReader?beanDefinitionReader?beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver (¿nuevo? ResourceEntityResolver(this));
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions( beanDefinitionReader);
}
7. El método loadeBeanDefinitions utiliza un lector para leer las definiciones de beans de cada configuración. La lógica general de este método es analizar algunos recursos (que representan recursos específicos, como archivos) de cada ubicación.
El método loadBeanDefinitions(String,Set
public?int? loadBeanDefinitions(String?location, Set
¿ResourceLoader? ResourceLoader?= getResourceLoader();
if?(resourceLoader?==?null) {
throw?new?BeanDefinitionStoreException(
" No se puede recuperar desde la ubicación ["? +?ubicación?+?"] : no hay ResourceLoader disponible");
}
if?(resourceLoader?instanceof?ResourcePatternResolver) {
intentar?{
Recurso[]? recursos?= ((ResourcePatternResolver)resourceLoader).getResources(ubicación);
int?loadCount?= loadBeanDefinitions(recursos) );
if(actualResources! =?null) {
for(¿Recurso? recurso?:?resources) {
actualResources.add (recurso) ;
}
}
if(logger. isDebugEnabled()) {
logger.debug("Cargado " ?+? loadCount?+?" Definición de frijol cargada desde el patrón de ubicación ["? +ubicación?+?"]");
}
catch?(IOException?ex) {
throw?new?BeanDefinitionStoreException(
"¿No se pudo resolver el patrón de recursos de definición de bean ["? + ?ubicación?+?"]",?ex);
catch?ex);
}
}
else ? {
Recurso?recurso?=?resourceLoader.getResource(ubicación);
int?loadCount?=loadBeanDefinitions(recurso);
si?(actualResources ! =?null) {
actualResources.add(recurso);
}
if?(logger.isDebugEnabled()) {
logger.debug("Loaded "?+?loadCount?+?" Agregar desde la ubicación ["? +?ubicación?+?"]
Definición de Bean cargado");
}
return?loadCount;
}
8. La implementación real de la definición de Bean de carga es XmlBeanDefinitionReader Obtiene el flujo de entrada de cada recurso y lo encapsula como un InputSource; llama a doLoadBeanDefinitions para obtener el documento de InputSource y utiliza el método RegisterBeanDefinitions para registrar BeanDefinitions de acuerdo con el documento. BeanDefinitionParserDelegate. para analizar el elemento Documento
protected.void.parseBeanDefinitions(Element.root, BeanDefinitionParserDelegate.delegate) {
if(delegate.isDefaultNamespace(root)) {
NodeList?nl?= root.getChildNodes();
for(int?i?= 0;?i?
Nodo?ele?= (Elemento)?nodo;
if?(delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele,?delegate);
}
else?parseCustomElement(ele);
}
}
}
. p> }
}
¿otra cosa?{
delegate.parseCustomElement(raíz);
}
}
}
9.BeanDefinitionParserDelegate utilizará diferentes NamespaceHandlers para analizar según el espacio de nombres del nodo. Cuando XmlBeanDefinitionReader crea DefaultBeanDefinitionDocumentReader, pasará un XmlReaderContext, que contiene un NamespaceHandlerResolver. HandlerResolver guarda un mapa que se utiliza para resolver controladores para un espacio de nombres específico. Archivo de controladores para guardar el tipo de controlador. Los controladores admitidos por Spring se muestran en la siguiente figura:
9.BeanDefinitionDelegate utiliza el NamespaceHandler obtenido para analizar el nodo del elemento para obtener BeanDefinition. El proceso de análisis de diferentes NamespaceHandlers es diferente. Además, un NamespaceHandler también contiene varios analizadores para analizar diferentes tipos de elementos. Tomando ContextNamspaceHandler como ejemplo, los analizadores que admite son los siguientes: