今天就跟大家聊聊有关如何在Spring中实现初始化和解析XML,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。
1、Spring的入口
在我们的项目中,web.xml必不可少,其中就定义了Spring的监听器。
<listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener>
我们来看ContextLoaderListener类,可以看到它实现了ServletContextListener接口,
contextInitialized就是Spring初始化的入口方法。
Spring还有一个入口,叫做org.springframework.web.servlet.DispatcherServlet,它们之间是父子容器的关系,最终都会调用到同一个方法org.springframework.context.support.AbstractApplicationContext.refresh()。
2、初始化
Spring的初始化第一步就是要加载配置文件,然后解析里面的配置项。
ok,我们来到XmlWebApplicationContext类的loadBeanDefinitions方法。
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException { String[] configLocations = getConfigLocations(); if (configLocations != null) { for (String configLocation : configLocations) { reader.loadBeanDefinitions(configLocation); } } }
可以看到,configLocations是一个数组,它获取的就是配置文件。在笔者的项目中,只有一个配置文件,名字是applicationContext.xml。下一步就是通过loadBeanDefinitions这个方法解析这个配置文件。
3、解析XML配置
首先把一个配置文件封装成一个Resource对象,然后获取Resource对象的输入流,转换成InputSource对象,最后解析成Document对象。下面代码只保留了主要部分。
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException { ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader instanceof ResourcePatternResolver) { // Resource pattern matching available. try { //这里的location就是配置文件-applicationContext.xml,转成Resource对象 Resource[] resources=resourceLoader).getResources(location); //获取resources对象的输入流 再转成JDK的InputSource对象,最后解析成Document InputStream inputStream = resources.getInputStream(); InputSource inputSource = new InputSource(inputStream); Document doc = doLoadDocument(inputSource, resource); } catch (IOException ex) { throw new BeanDefinitionStoreException( "Could not resolve bean definition resource pattern [" + location + "]", ex); } } }
applicationContext.xml配置文件解析成Document对象,它的Root节点信息如下:
[ [#text:], [context:component-scan: null], [#text:], [bean: null], [#text:], [bean: null], [#text:], [bean: null], [#text:], [bean: null], [#text:], [#comment: 指定了表现层资源的前缀和后缀 viewClass:JstlView表示JSP模板页面需要使用JSTL标签库 prefix 和suffix:查找视图页面的前缀和后缀,比如传进来的逻辑视图名为hello,则该该 jsp视图页面应该存放在“WEB-INF/jsp/hello.jsp”], [#text:], [bean: null], [#text: ] ]
4、加载Bean信息
上一步我们看到Spring已经把applicationContext.xml这个配置文件解析成了Document对象,接下来就是关键的一步。先看源码
//这里拿到的是Document对象的根节点,根节点信息参考上图 protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; //这里有两个分支。 //一个是处理默认的节点(import、alias、bean、beans) //一个是处理自定义的节点(context:component-scan) if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
4.1 component-scan的解析
首先定位到自定义解析方法delegate.parseCustomElement(ele);
最终调用了org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse(Element element, ParserContext parserContext),不过它是怎么调用到这个类的呢?说起来就比较有意思了。
我们先来看Spring里面的一个配置文件,/META-INF/spring.handlers
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
这里面配置了不同标签的处理类,比如context标签处理类就是ContextNamespaceHandler,然后通过反射实例化这个处理类,调用它的init()方法。init()方法里面它又注册了一堆处理类,其中就有我们很感兴趣的component-scan。
public NamespaceHandler resolve(String namespaceUri) { //handlerMappings里有个方法loadAllProperties(),获取Spring所有的配置项 Map<String, Object> handlerMappings = getHandlerMappings(); Object handlerOrClassName = handlerMappings.get(namespaceUri); if (handlerOrClassName == null) { return null; } else if (handlerOrClassName instanceof NamespaceHandler) { return (NamespaceHandler) handlerOrClassName; } else { String className = (String) handlerOrClassName; try { //以context:component-scan举例 //这里拿到的className就是org.springframework.context.config.ContextNamespaceHandler //通过反射,实例化这个ContextNamespaceHandler,然后调用init方法 Class<?> handlerClass = ClassUtils.forName(className, this.classLoader); NamespaceHandler namespaceHandler = BeanUtils.instantiateClass(handlerClass); namespaceHandler.init(); handlerMappings.put(namespaceUri, namespaceHandler); return namespaceHandler; } } } public void init() { registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser()); registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser()); //...未完 }
最终Spring就可以通过component-scan这个标签,拿到ComponentScanBeanDefinitionParser类,调用它的parse()方法。
public BeanDefinition parse(Element element, ParserContext parserContext) { //获取包扫描路径,对应配置文件中的base-package="com.viewscenes.netsupervisor" String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE); basePackage = parserContext.getReaderContext().getEnvironment(). resolvePlaceholders(basePackage); //这里可能有多个包路径,分割成数组 String[] basePackages = StringUtils.tokenizeToStringArray(basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); /** * configureScanner 配置扫描器。 * scanner.doScan 扫描执行 * registerComponents 这里重点是对registerComponents的支持 * * @return */ ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element); Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages); registerComponents(parserContext.getReaderContext(), beanDefinitions, element); return null; }
4.1.1 configureScanner 配置扫描器
这里面重点就是注册了默认的过滤器。use-default-filters,默认值是true,如果配置文件配置了此属性的值为false,有些注解就加不进来,到下一步扫描的时候就注册不了Bean。
protected void registerDefaultFilters() { //这个就是配置的use-default-filters,如果配置了false。那么下面的 // Component、ManagedBean、Named注解都不会被扫描到 if (useDefaultFilters) { this.includeFilters.add(new AnnotationTypeFilter(Component.class)); ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader(); try { this.includeFilters.add(new AnnotationTypeFilter( ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false)); logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning"); } catch (ClassNotFoundException ex) { // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip. } //...未完 } }
4.1.2 doScan扫描
doScan分为三个步骤。
findCandidateComponents 扫描包路径下的所有class文件,过滤有Component注解的类,转换成BeanDefinition对象,加入一个LinkedHashSet中。
循环上一步返回的LinkedHashSet,设置基本属性,比如setLazyInit、setScope。
注册BeanDefinition对象,向Map容器中缓存beanName和BeanDefinition,向List中加入beanName。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>(); for (String basePackage : basePackages) { //findCandidateComponents方法扫描class文件,判断Component注解,转成BeanDefinition对象返回。 //值得注意的是,Component不止是@Component,还有 //@Controller、@Service、@Repository,因为在这三个注解上面还有个@Component。 //这就相当于它们都是Component的子注解。 Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this.scopeMetadataResolver. resolveScopeMetadata(candidate); //设置属性,没有配置的都是默认值 candidate.setScope(scopeMetadata.getScopeName()); candidate.setxxx(scopeMetadata.getxxxName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); //registerBeanDefinition方法 注册BeanDefinition,等同于下面两句 //this.beanDefinitionMap.put(beanName, beanDefinition); //this.beanDefinitionNames.add(beanName); registerBeanDefinition(definitionHolder, this.registry); } } return beanDefinitions; }
最后整个方法返回的就是beanDefinition对象的Set集合,以两个Controller为例。
[ Generic bean: class [com.viewscenes.netsupervisor.controller.IndexController]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\apache-tomcat-7.0.78\webapps\springmvc_dubbo_producer\WEB-INF\classes\com\viewscenes\netsupervisor\controller\IndexController.class], Generic bean: class [com.viewscenes.netsupervisor.controller.UserController]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\apache-tomcat-7.0.78\webapps\springmvc_dubbo_producer\WEB-INF\classes\com\viewscenes\netsupervisor\controller\UserController.class] ]
4.1.3 对annotation-config的支持
我们知道,在Spring配置文件有个配置是context:annotation-config 但如果配置了context:component-scan 就不必再配置config,这是因为在解析component-scan的时候已经默认添加了annotation-config的支持,除非你手动设置了annotation-config="false",不过这可不太妙,因为在IOC的时候就没办法支持@Autowired等注解了。
protected void registerComponents(XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) { boolean annotationConfig = true; if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) { annotationConfig = Boolean.valueOf(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE)); } if (annotationConfig) { //判断annotation-config属性的值 Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<BeanDefinitionHolder>(4); if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class); beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); } if (!registry.containsBeanDefinition(REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(RequiredAnnotationBeanPostProcessor.class); beanDefs.add(registerPostProcessor(registry, def, REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); } if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class); beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); } ......未完 } }
4.2 bean标签的解析
bean标签的解析,就是默认的处理方法。
获取bean标签的id,并且把beanName赋值为id,设置别名。新建AbstractBeanDefinition对象,通过反射设置beanClass,解析property属性名称和值。
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) { //获取bean_id String id = ele.getAttribute(ID_ATTRIBUTE); String beanName = id; AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); String[] aliasesArray = StringUtils.toStringArray(aliases); //最后返回已经包含了beanName、class对象和一系列方法的BeanDefinition对象 return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean) { String className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); try { //根据className反射设置setBeanClass和setBeanClassName AbstractBeanDefinition bd = createBeanDefinition(className, parent); //设置默认方法 setScope、setLazyInit、setAutowireMode... parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); //设置property属性 <bean><property name="id" value="1001"></property></bean> parsePropertyElements(ele, bd); return bd; } return null; }
看完上述内容,你们对如何在Spring中实现初始化和解析XML有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注亿速云行业资讯频道,感谢大家的支持。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。