1. 程式人生 > 實用技巧 >Spring原始碼01---容器重新整理前配置

Spring原始碼01---容器重新整理前配置

一、容器重新整理前原始碼分析

做過 SSM 框架整合的都會知道,如果我們需要啟動專案,需要在 web.xml 中進行如下配置

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>Archetype Created Web Application</display-name>
    <!--監聽器載入指定位置的spring配置檔案(預設使用名稱為applicationContext.xml)-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath*:spring-config/applicationContext-*.xml
        </param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

該 web.xml 配置了 Spring 框架的最基本結構,裡面包含 ContextLoaderListener 這個監聽器和 contextConfigLocation , ContextLoaderListener 就是 Spring 框架的入口,而 contextConfigLocation 就是 Spring 配置檔案存在的路徑.

我們檢視 Spring 的方法呼叫棧,發現了AbstractApplicationContext 這個類的 refresh 方法,想必大家對這個方法都不會陌生吧,這個方法就是整個 Spring IOC 的核心方法,同時我們也可以看到 ContextLoaderListener 類的 contextInitialized 方法才是最開始呼叫的方法,這樣我們也驗證了 ContextLoaderListener 才是 Spring 框架的入口.

找到 ContextLoaderListener 類中的 contextInitialized方法,在進入該方法之前,我們可以發現 ContextLoaderListener 繼承了 ContextLoader 這個類

所以 ContextLoaderListener 類在呼叫 本類的 contextInitialized 方法之前會先初始化父類中的一些屬性,這裡有一個比較重要的屬性defaultStrategies ,我們來看一下

public class ContextLoader {
	private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";

	private static final Properties defaultStrategies;

	static {
		// 1、從屬性檔案載入預設策略實現,目前這是嚴格的內部操作,並不意味著應用程式開發人員可以定製.
		try {
			// 2、根據 DEFAULT_STRATEGIES_PATH 和 ContextLoader.class 構建 ClassPathResource 物件
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
			// 3、載入 org.springframework.web.context.ContextLoader.properties 配置檔案中的鍵值對,
			// 並賦值給 ContextLoader Properties 型別的屬性 defaultStrategies
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
		}
	}
}

這裡 DEFAULT_STRATEGIES_PATH 是一個常量,它的值為 ContextLoader.properties , ClassPathResource 的 path 屬性可以是絕對路徑也可以是相對路徑,在這邊為相對路徑(相對於載入資源的類 ContextLoader),指向的絕對路徑為 org.springframework.web.context.ContextLoader.properties

我們可以在 ContextLoader 的同級目錄找到 ContextLoader.properties 這個配置檔案

然後就是載入org.springframework.web.context.ContextLoader.properties 中的鍵值對,並賦值給 ContextLoader 類中的 Properties 型別的屬性 defaultStrategies

我們開啟 ContextLoader.properties ,發現裡面配置了一個鍵值對,也就是在ContextLoaderListener 類呼叫 contextInitialized 方法之前會預設載入 XmlWebApplicationContext 這個 IOC 容器.

初始化工作完成之後,接著我們就正式進入到ContextLoaderListener 類的 contextInitialized 方法

@Override
public void contextInitialized(ServletContextEvent event) {
	initWebApplicationContext(event.getServletContext());
}

由於ContextLoaderListener 沒有initWebApplicationContext() 方法的具體實現,所以這裡呼叫的是它的父類ContextLoader 中的initWebApplicationContext() 方法

下面我們來到 ContextLoader 類中的initWebApplicationContext 方法,該方法的具體定義如下

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
	//  1、校驗 WebApplicationContext 是否已經初始化過了,如果已經初始化,則丟擲異常(英文註釋已經寫的很清楚了)
	if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
		throw new IllegalStateException(
				"Cannot initialize context because there is already a root application context present - " +
				"check whether you have multiple ContextLoader* definitions in your web.xml!");
	}

	Log logger = LogFactory.getLog(ContextLoader.class);
	servletContext.log("Initializing Spring root WebApplicationContext");
	if (logger.isInfoEnabled()) {
		logger.info("Root WebApplicationContext: initialization started");
	}
	long startTime = System.currentTimeMillis();

	try {
		// Store context in local instance variable, to guarantee that
		// it is available on ServletContext shutdown.
		if (this.context == null) {
			// 2、建立一個 WebApplicationContext 並儲存到 ContextLoader 類中 WebApplicationContext 型別的 context 屬性----(詳細見程式碼塊一)
			this.context = createWebApplicationContext(servletContext);
		}
		if (this.context instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
			if (!cwac.isActive()) {
				// The context has not yet been refreshed -> provide services such as
				// setting the parent context, setting the application context id, etc
				if (cwac.getParent() == null) {
					// The context instance was injected without an explicit parent ->
					// determine parent for root web application context, if any.
					ApplicationContext parent = loadParentContext(servletContext);
					cwac.setParent(parent);
				}
				// 3、配置和重新整理 Web 應用上下文----(詳細見程式碼塊三)
				configureAndRefreshWebApplicationContext(cwac, servletContext);
			}
		}
		// 4、設定 WebApplicationContext 屬性
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

		ClassLoader ccl = Thread.currentThread().getContextClassLoader();
		if (ccl == ContextLoader.class.getClassLoader()) {
			currentContext = this.context;
		}
		else if (ccl != null) {
			currentContextPerThread.put(ccl, this.context);
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
					WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
		}
		if (logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - startTime;
			logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
		}

		return this.context;
	}
	catch (RuntimeException ex) {
		logger.error("Context initialization failed", ex);
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
		throw ex;
	}
	catch (Error err) {
		logger.error("Context initialization failed", err);
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
		throw err;
	}
}

程式碼塊一、createWebApplicationContext(servletContext)

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
	// 1、確定要建立應用上下文的 contextClass (這裡是 org.springframework.web.context.support.XmlWebApplicationContext)
	// ----(詳細見程式碼塊二)
	Class<?> contextClass = determineContextClass(sc);
	// 2、判斷當前的 contextClass 是不是 ConfigurableWebApplicationContext 的子類或者是子介面,如果不是直接丟擲異常
	if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
		throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
				"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
	}
	// 3、使用 BeanUtils 工具類生成 contextClass 的例項物件,並且將其向上轉型為 ConfigurableWebApplicationContext
	return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

程式碼塊二、determineContextClass(ServletContext servletContext)

protected Class<?> determineContextClass(ServletContext servletContext) {
	// 1、獲取 web.xml 中配置的初始化引數 contextClass
	String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
	// 2、如果 web.xml 中配置了引數 contextClass 
	if (contextClassName != null) {
		try {
			// 3、利用反射構建出 contextClassName 的例項
			return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
		}
		catch (ClassNotFoundException ex) {
			throw new ApplicationContextException(
					"Failed to load custom context class [" + contextClassName + "]", ex);
		}
	}
	else {
		// 4、如果 web.xml 中沒有配置 contextClass 引數,那麼就從我們開始初始化的 defaultStrategies 中獲取預設的 
		// WebApplicationContext 即: org.springframework.web.context.support.XmlWebApplicationContext
		contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
		try {
			// 5、利用反射構建出 contextClassName 的例項
			return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
		}
		catch (ClassNotFoundException ex) {
			throw new ApplicationContextException(
					"Failed to load default context class [" + contextClassName + "]", ex);
		}
	}
}

建立 WebApplicationContext 這裡小總結一下,如果 web.xml 配置了 contextClass 引數,那麼使用該引數配置的值利用反射構建出 contextClass 的 Class 例項,如果 web.xml 中沒有配置 contextClass 引數,那麼就從預設的 defaultStrategies 中取出 預設的 WebApplication (org.springframework.web.context.support.XmlWebApplicationContext.class),然後通過 BeanUtils 例項化,得到XmlWebApplicationContext 的 Class 例項,最後將該物件賦值給ContextLoader 類的 context (WebApplication型別) 屬性

程式碼塊三、configureAndRefreshWebApplicationContext(cwac, servletContext)

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
	// 1、如果應用上下文的 id 是原始的預設值,那麼基於可用的資訊,生成一個更有用的 id
	if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
		// 2、獲取 servletContext 中配置的 contextId (也就是web.xml 中配置的初始化引數 contextId)
		String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
		if (idParam != null) {
			// 3、如果 web.xml 中配置了 contextId ,將其設定為 wac 的屬性
			wac.setId(idParam);
		}
		else {
			// 4、如果 web.xml 中沒有配置 contextId 引數,生成預設的 id ,這裡的 id 是: org.springframework.web.context.WebApplicationContext:
			wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
					ObjectUtils.getDisplayString(sc.getContextPath()));
		}
	}
	// 5、將 ServletContext 物件設定為 wac 的屬性
	wac.setServletContext(sc);
	// 6、獲取 web.xml 中配置的 contextConfigLocation 引數的值,由於我們這裡配置了該引數,並且引數的值就是 Spring 配置檔案的存放路徑
	String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
	// 7、如果 web.xml 中配置了 contextConfigLocation 引數
	if (configLocationParam != null) {
		// 8、將其賦值給 wac 的 String[] 型別 configLocations 屬性,同時會建立 Environment 物件----(詳細見程式碼塊四)
		wac.setConfigLocation(configLocationParam);
	}
	// 9、獲取環境資訊
	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		// 10、初始化屬性源----(詳細見程式碼塊十一)
		((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
	}
	// 11、自定義上下文----(詳細見程式碼塊十二)
	customizeContext(sc, wac);
	// 12、最終呼叫 AbstractApplicationContext 的 refresh 方法, 這是 IOC 的核心方法
	wac.refresh();
}

這裡說一下 wac.setConfigLocation(configLocationParam) 這個方法的呼叫,我們知道 wac 的本質是 XmlWebApplication 型別的,強轉為了 ConfigurableWebApplicationContext 型別,根據多型的執行機制,編譯型別是ConfigurableWebApplicationContext ,但是實質是XmlWebApplication 型別,在XmlWebApplication 中沒有定義 setConfigLocation() 方法,那麼我們就去XmlWebApplication 父類中去尋找有沒有setConfigLocation() 方法,很遺憾還是沒有,那麼就繼續往上找,結果在父類的父類 AbstractRefreshableConfigApplicationContext 中找到了setConfigLocation() 方法,XmlWebApplication 物件執行時呼叫的是父類的父類 AbstractRefreshableConfigApplicationContext 中的setConfigLocation() 方法,所以AbstractRefreshableConfigApplicationContext 中的setConfigLocation() 方法可以看成是實現了ConfigurableWebApplicationContext 介面中的setConfigLocation() 方法.

程式碼塊四、wac.setConfigLocation(configLocationParam)

// AbstractRefreshableConfigApplicationContext
public void setConfigLocations(String... locations) {
	if (locations != null) {
		Assert.noNullElements(locations, "Config locations must not be null");
		// 1、建立一個 String[] 陣列,長度為配置 Spring 配置檔案的個數
		this.configLocations = new String[locations.length];
		// 2、遍歷陣列
		for (int i = 0; i < locations.length; i++) {
			// 3、解析給定路徑,必要時用相應的環境屬性值替換佔位符----(詳細見程式碼塊五)
			this.configLocations[i] = resolvePath(locations[i]).trim();
		}
	}
	else {
		this.configLocations = null;
	}
}

程式碼塊五 resolvePath()

protected String resolvePath(String path) {
	// 1、getEnvironment():獲取環境資訊----(詳細見程式碼塊六)
	// 2、resolveRequiredPlaceholders(path):解析給定路徑,必要時用相應的環境屬性值替換佔位符
	return getEnvironment().resolveRequiredPlaceholders(path);
}

程式碼塊六、getEnvironment()

public ConfigurableEnvironment getEnvironment() {
	if (this.environment == null) {
		// 建立環境----(詳細見程式碼塊七)
		this.environment = createEnvironment();
	}
	return this.environment;
}

程式碼塊七、createEnvironment()

protected ConfigurableEnvironment createEnvironment() {
	// 返回一個標準的環境----(詳細見程式碼塊八)
	return new StandardServletEnvironment();
}

程式碼塊八、new StandardEnvironment()

StandardServletEnvironment 類繼承體系如下

建立 StandardServletEnvironment 物件時,必須先初始化父類,通過繼承結構圖我們可以知道執行new StandardServletEnvironment() 必須先呼叫父類即 AbstractEnvironment 的構造方法

AbstractEnvironment 的構造方法如下

public AbstractEnvironment() {
	1、自定義屬性源----(詳細見程式碼塊九)
	customizePropertySources(this.propertySources);
	if (logger.isDebugEnabled()) {
		logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources);
	}
}

程式碼塊九、customizePropertySources

protected void customizePropertySources(MutablePropertySources propertySources) {
}

AbstractEnvironment中的 customizePropertySources方法為空方法,通過protected 關鍵字進行修飾,用於給子類進行重寫.因此,此時我們會走到子類 StandardServletEnvironment中的重寫方法.

protected void customizePropertySources(MutablePropertySources propertySources) {
	// 1、新增 servletConfigInitParams 屬性源(作為佔位符,它對應的值最後會被 servletConfig 的值替換掉)
	propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
	// 2、新增 servletContextInitParams 屬性源(作為站位符,它對應的值最後會被 servletContext 的值替換掉)
	propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
	if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
		// 3、新增 jndiProperties 屬性源
		propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
	}
	// 呼叫 StandardServletEnvironment 的父類(StandardEnvironment)的 customizePropertySources 方法----(詳細見程式碼塊十)
	super.customizePropertySources(propertySources);
}

程式碼塊十、StandardEnvironment 類中的 customizePropertySources 方法

protected void customizePropertySources(MutablePropertySources propertySources) {
	// 新增 systemProperties 屬性源
	propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
	// 新增 systemEnvironment 屬性源
	propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

至此,程式碼塊六中的 createEnvironment()方法執行結束,此時的 environment 值如下

程式碼塊十一、initPropertySources(sc, null)

public void initPropertySources(ServletContext servletContext, ServletConfig servletConfig) {
	WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
}

public static void initServletPropertySources(
		MutablePropertySources propertySources, ServletContext servletContext, ServletConfig servletConfig) {

	Assert.notNull(propertySources, "'propertySources' must not be null");
	if (servletContext != null && propertySources.contains(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME) &&
			propertySources.get(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME) instanceof StubPropertySource) {
		// 1、將 propertySources 中 servletContextInitParams 對應的值替換為 servletContext
		propertySources.replace(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME,
				new ServletContextPropertySource(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME, servletContext));
	}
	if (servletConfig != null && propertySources.contains(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME) &&
			propertySources.get(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME) instanceof StubPropertySource) {
		// 2、將 propertySources 中 servletConfigInitParams 對應的值替換為 servletConfig
		propertySources.replace(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME,
				new ServletConfigPropertySource(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME, servletConfig));
	}
}

程式碼塊十二:customizeContext(sc, wac) 自定義上下文

protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
	// 1、確定上下文的初始化類 ( 詳細見程式碼塊十三)
	List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
			determineContextInitializerClasses(sc);
	// 2、遍歷 initializerClasses
	for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
		Class<?> initializerContextClass =
				GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
		if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) {
			throw new ApplicationContextException(String.format(
					"Could not apply context initializer [%s] since its generic parameter [%s] " +
					"is not assignable from the type of application context used by this " +
					"context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(),
					wac.getClass().getName()));
		}
		// 3、利用反射例項化初始化類的類例項,得到初始化類物件,並賦值給 ContextLoader 的 contextInitializers 屬性
		this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
	}

	AnnotationAwareOrderComparator.sort(this.contextInitializers);
	// 4、迴圈遍歷實現了 ApplicationContextInitializer 介面的初始化類,並挨個呼叫其 initialize() 方法
	for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
		// 5、回撥初始化類的 initialize() 方法
		initializer.initialize(wac);
	}
} 

程式碼塊十三、determineContextInitializerClasses

protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
		determineContextInitializerClasses(ServletContext servletContext) {
	// 1、建立 List 集合用來儲存 ApplicationContextInitializer 的實現類的 Class 例項
	List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
			new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>();
	// 2、如果 web.xml 中配置了 globalInitializerClasses 引數,獲取該引數對應的值
	String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM);
	if (globalClassNames != null) {
		for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
			classes.add(loadInitializerClass(className));
		}
	}
	// 3、如果 web.xml 中配置了 contextInitializerClasses 引數,獲取該引數對應的值
	String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
	// 4、如果 contextInitializerClasses 引數對應的配置值不為空        
	if (localClassNames != null) {
		// 5、將 contextInitializerClasses 引數對應的配置值使用 , ; \t\n 分隔符分割成 String 型別的陣列,然後遍歷該陣列
		for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) {
			// 6、loadInitializerClass(className):返回初始化類的 Class 例項----(詳細見程式碼塊十四)
			// 7、新增到 List 集合中
			classes.add(loadInitializerClass(className));
		}
	}

	return classes;
}

程式碼塊十四、loadInitializerClass(className)

private Class<ApplicationContextInitializer<ConfigurableApplicationContext>> loadInitializerClass(String className) {
	try {
		// 1、通過類載入器利用反射獲取初始化類的 Class 例項
		Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
		// 2、如果初始化類沒有實現 ApplicationContextInitializer 介面,丟擲異常
		if (!ApplicationContextInitializer.class.isAssignableFrom(clazz)) {
			throw new ApplicationContextException(
					"Initializer class does not implement ApplicationContextInitializer interface: " + clazz);
		}
		// 3、將初始化類的類例項強轉為 ApplicationContextInitializer<ConfigurableApplicationContext> 並返回
		return (Class<ApplicationContextInitializer<ConfigurableApplicationContext>>) clazz;
	}
	catch (ClassNotFoundException ex) {
		throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex);
	}
}

  

二、customizeContext方法擴充套件

從程式碼塊十二到程式碼塊十四,可以很明顯的看出 customizeContext 方法是 Spring 提供給開發者的一個擴充套件點,我們可以通過此方法對 ConfigurableApplicationContext進行一些自定義操作,具體使用如下.

1、建立一個ApplicationContextInitializer 介面的實現類

例如下面的 SpringApplicationContextInitializer ,並在 initialize 方法中進行自己的邏輯操作,例如:新增監聽器、新增 BeanFactoryPostProcessor 等

public class SpringApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("spring initializer...");
    }
}

2、在 web.xml 中,定義 contextInitializerClasses 或 globalInitializerClasses 引數,引數值為 SpringApplicationContextInitializer 的全路徑.

參考:https://joonwhee.blog.csdn.net/article/details/86555733