
一,ComponentScan注解的默认扫描范围
ComponentScan注解的默认扫描范围是启动程序XxxApplication java所在目录及其下的所有子包。
为了方便理解,我们看一下下面这个。
这个项目中的启动类是:SpringbootApplicationjava
该启动类所在的目录是:springboot
那么ComponentScan注解的默认扫描范围是:springboot目录及其下面的所有子包。
二,如何修改ComponentScan注解的扫描范围
ComponentScan注解即可以扫描包,也可以扫描指定的类。我们只需要指定一个包扫描的路径,就可以实现更改包扫描路径的功能了。
1,ComponentScan注解扫描包。
@ComponentScan({"comcompanyuser","comcompanyservice"})
2,ComponentScan注解扫描类。
@ComponentScan(basePackageClasses={XxxServiceclass})
三,ComponentScan注解
ComponentScan注解中定义了12个属性,我们下面详细来看一下。我们的讨论是基于java8的,spring-context的版本是437。
1,String[] value() default {};
指定包扫描路径,value属性的值,就是项目中的一个具体路径。value属性的类型是String数组,也就是支持一次指定多个包扫描路径。这个属性上面添加了一个注解,@AliasFor("basePackages"),这个注解的意思就是说,value这个属性等价于basePackages属性。关于basePackages属性,下面会讲到。
2,String[] basePackages() default {};
指定包扫描路径,basePackages属性的值,就是项目中的一个具体路径。basePackages属性的类型是String数组,也就是支持一次指定多个包扫描路径。basePackages属性上面添加了一个注解,@AliasFor("value"),这个注解的意思就是说,basePackages这个属性等价于value属性。
3,Class<>[] basePackagesClasses() default {};
扫描具体的类。basePackagesClasses属性的类型是Class数组,也就是说支持同时指定多个扫描类。
4,Class< extends BeanNameGenerator> nameGenerator() default BeanNameGenerator class;
配置beanName生成器,默认是BeanNameGenerator。一般情况下,我们都是使用默认的beanName生成器,但是Spring实现了beanName生成器的可配置。
5,Class< extends ScopeMetaDataResolver> scopeResolver() default AnnotationScopeMetaDataResolverclass;
处理检测到的bean的scope范围。什么意思呢?我们都知道spring的bean是有作用域的,默认是singleton,这个默认值就是在ScopeMetaData类中指定的:
private String scopeName = "singleton";
这个属性也是可选配置,默认的处理bean作用域的实现类是AnnotationScopeMetaDataResolverclass。源码比较简单,就是取注解上获取指定的scope的value值,如果没有配置,就是用默认的singleton。
6,ScopedProxyMode scopedProxy() default ScopedProxyMode DEFAULT;
是否为检测到的组件生成代理。
ScopedProxyMode是一个枚举类,可选值有四个:DEFAULT,NO,INTERFACES,TARGET_CLASS。
7,String resourcePattern() default """/class";
控制符合组件检测条件的类文件,默认是包扫描下的 /class。
8,boolean useDefaultFilters() default true;
是否对含有以下注解的类开启检测,默认是开启的。
@Component
@Repository
@Service
@Controller
9,ComponentScanFilter[] includeFilters() default {};
指定某些Filter扫描到的类。听起来有些费劲,说白了就是指定了类型,扫描指定的这些类型。可选类型有5种,定义在枚举类FilterType中:
第一种:ANNOTATION
第二种:ASSIGNABLE_TYPE
第三种:ASPECTJ
第四种:REGEX,正则表达式。
第五种:CUSTOM,自定义类型。
10,ComponentScanFilter[] excludeFilters() default {};
排除过滤器扫描的的类。
11,boolean lazyInit() default false;
扫描到的类是否开启懒加载,默认不开启。
12,
@Retention(RetentionPolicyRUNTIME);
@Target({})
public @interface Filter {
FilterType type() default FilterType ANNOTATION;
@AliasFor("classes")
Class<>[] value() default {};
@AliasFor("value")
Class<>[] classes() default {};
String[] pattern() default {};
}
最近在使用Spring MVC过程中遇到了一些问题,网上搜索不少帖子后虽然找到了答案和解决方法,但这些答案大部分都只是给了结论,并没有说明具体原因,感觉总是有点不太满意。
更重要的是这些所谓的结论大多是抄来抄去,基本源自一家,真实性也有待考证。
那作为程序员怎么能知其所以然呢?
此处请大家内心默读三遍。
用过Spring 的人都知道其核心就是IOC和AOP,因此要想了解Spring机制就得先从这两点入手,本文主要通过对IOC部分的机制进行介绍。
在开始阅读之前,先准备好以下实验材料。
IDEA 是一个优秀的开发工具,如果还在用Eclipse的建议切换到此工具进行。
IDEA有很多的快捷键,在分析过程中建议大家多用Ctrl+Alt+B快捷键,可以快速定位到实现函数。
Spring bean的加载主要分为以下6步:
查看源码第一步是找到程序入口,再以入口为突破口,一步步进行源码跟踪。
Java Web应用中的入口就是webxml。
在webxml找到ContextLoaderListener ,此Listener负责初始化Spring IOC。
contextConfigLocation参数设置了bean定义文件地址。
下面是ContextLoaderListener的官方定义:
翻译过来ContextLoaderListener作用就是负责启动和关闭Spring root WebApplicationContext。
具体WebApplicationContext是什么?开始看源码。
从源码看出此Listener主要有两个函数,一个负责初始化WebApplicationContext,一个负责销毁。
继续看initWebApplicationContext函数。
在上面的代码中主要有两个功能:
进入CreateWebAPPlicationContext函数
进入determineContextClass函数。
进入configureAndReFreshWebApplicaitonContext函数。
WebApplication Context有很多实现类。 但从上面determineContextClass得知此处wac实际上是XmlWebApplicationContext类,因此进入XmlWebApplication类查看其继承的refresh()方法。
沿方法调用栈一层层看下去。
获取beanFactory。
beanFactory初始化。
加载bean。
读取XML配置文件。
XmlBeanDefinitionReader读取XML文件中的bean定义。
继续查看loadBeanDefinitons函数调用栈,进入到XmlBeanDefinitioReader类的loadBeanDefinitions方法。
最终将XML文件解析成Document文档对象。
上一步完成了XML文件的解析工作,接下来将XML中定义的bean注册到webApplicationContext,继续跟踪函数。
用BeanDefinitionDocumentReader对象来注册bean。
解析XML文档。
循环解析XML文档中的每个元素。
下面是默认命名空间的解析逻辑。
不明白Spring的命名空间的可以网上查一下,其实类似于package,用来区分变量来源,防止变量重名。
这里我们就不一一跟踪,以解析bean元素为例继续展开。
解析bean元素,最后把每个bean解析为一个包含bean所有信息的BeanDefinitionHolder对象。
接下来将解析到的bean注册到webApplicationContext中。接下继续跟踪registerBeanDefinition函数。
跟踪registerBeanDefinition函数,此函数将bean信息保存到到webApplicationContext的beanDefinitionMap变量中,该变量为map类型,保存Spring 容器中所有的bean定义。
Spring 实例化bean的时机有两个。
一个是容器启动时候,另一个是真正调用的时候。
相信用过Spring的同学们都知道以上概念,但是为什么呢?
继续从源码角度进行分析,回到之前XmlWebApplication的refresh()方法。
可以看到获得beanFactory后调用了 finishBeanFactoryInitialization()方法,继续跟踪此方法。
预先实例化单例类逻辑。
获取bean。
doGetBean中处理的逻辑很多,为了减少干扰,下面只显示了创建bean的函数调用栈。
创建bean。
判断哪种动态代理方式实例化bean。
不管哪种方式最终都是通过反射的形式完成了bean的实例化。
我们继续回到doGetBean函数,分析获取bean的逻辑。
上面方法中首先调用getSingleton(beanName)方法来获取单例bean,如果获取到则直接返回该bean。方法调用栈如下:
getSingleton方法先从singletonObjects属性中获取bean 对象,如果不为空则返回该对象,否则返回null。
那 singletonObjects保存的是什么?什么时候保存的呢?
回到doGetBean()函数继续分析。如果singletonObjects没有该bean的对象,进入到创建bean的逻辑。处理逻辑如下:
下面是判断容器中有没有注册bean的逻辑,此处beanDefinitionMap相信大家都不陌生,在注册bean的流程里已经说过所有的bean信息都会保存到该变量中。
如果该容器中已经注册过bean,继续往下走。先获取该bean的依赖bean,如果镩子依赖bean,则先递归获取相应的依赖bean。
依赖bean创建完成后,接下来就是创建自身bean实例了。
获取bean实例的处理逻辑有三种,即Singleton、Prototype、其它(request、session、global session),下面一一说明。
如果bean是单例模式,执行此逻辑。
获取单例bean,如果已经有该bean的对象直接返回。如果没有则创建单例bean对象,并添加到容器的singletonObjects Map中,以后直接从singletonObjects直接获取bean。
把新生成的单例bean加入到类型为MAP 的singletonObjects属性中,这也就是前面singletonObjects()方法中获取单例bean时从此Map中获取的原因。
Prototype是每次获取该bean时候都新建一个bean,因此逻辑比较简单,直接创建一个bean后返回。
从相应scope获取对象实例。
判断scope,获取实例函数逻辑。
在相应scope中设置实例函数逻辑。
以上就是Spring bean从无到有的整个逻辑。
从源码角度分析 bean的实例化流程到此基本接近尾声了。
回到开头的问题,ContextLoaderListener中初始化的WebApplicationContext到底是什么呢?
通过源码的分析我们知道WebApplicationContext负责了bean的创建、保存、获取。其实也就是我们平时所说的IOC容器,只不过名字表述不同而已。
本文主要是讲解了XML配置文件中bean的解析、注册、实例化。对于其它命名空间的解析还没有讲到,后续的文章中会一一介绍。
希望通过本文让大家在以后使用Spring的过程中有“一切尽在掌控之中”的感觉,而不仅仅是稀里糊涂的使用。
printf“”里的是原样输出的东西,但是你的y是未知的,是你程序运行到这一步才可以得到的,你没有办法写到“”里,所以你用%d代替,%d不会原样输出,它代表整数,而你的y的数值就是%d的数值,就是当printf里有%d的时候程序知道它的数值是“”,后跟的那个数,也就是这个程序里的y,比如printf(“%d%d%d”,a,b,c);第一个%d输出的是a的值,第二个%d输出的是b的值,第三个就是c的值了,如果是%c就是字符的意思,%f就是浮点型的数
spring-mvc 的启动流程
1、request 请求到达 dispatchServlet-> doService()->doDispatch() 开始处理请求
2、根据doDispatch() 再去调用getHandler() 目的是获取包含 处理器Handler和处理器拦截器 AdapterIntercepers 的处理器拦截链 HandlerExecutionChain
21 getHandler(>
Handler对于Android开发者再熟悉不过了,也是面试题的常客了,所以了解Handler机制的源码就很有必要了,虽然Handler分析的文章已经有很多,但是自己总结一遍,印象才更深刻。
Handler机制,是Android中的一种消息传递机制,在开发中十分常用。由于Android从30开始不允许耗时 *** 作在主线程中执行,必须在子线程中执行完后,将结果发送到主线程中更新UI。所以简单来讲Handler就是子线程和主线程通信的一种技术。
先是常规使用,Handler在主线程中创建,开启子线程处理耗时 *** 作,再通过Handler发送消息到主线程,Handler的handleMessage()方法就会被回调,再更新UI。
以及也很常用的,post()和postDelayed()。
还有一种场景,就是子线程中创建Handler,让子线程成为轮训的线程,接收其他线程的消息,开发中并不多,但是特定场景会很有用,例如有一个一直执行的子线程,一直定时扫描着当前位置信息,到了指定范围,发送一个播放语音的消息的消息到主线程。
接下来就是Handler源码分析了:
一般我们获取Message会调用Handler的obtainMessage()方法,这个方法是获取一个复用的Message对象,内部采用享元模式复用Message对象,在Android中,View绘制,Activity生命周期,都是使用Handler发送Message实现,如果每次都new一个消息对象,肯定是十分消耗内存的,也容易产生GC垃圾回收导致卡顿。
我们平常在主线程使用Handler时,并没有调用过Looperprepare()和Looperloop()这2个方法,为什么创建Handler时不会抛出异常呢?
原因就是创建Handler时,调用LoopermyLooper()获取主线程绑定的Looper不为空,所以没有抛出异常。经过Looper类中查找发现,除了Looperprepare()之外,还有一个prepareMainLooper()的方法。
prepareMainLooper()方法的注释,意思大概就是,创建主线程的Looper对象,该方法由Android框架在主线程自动调用,我们不应该主动调用该方法。
那么什么时候会调用prepareMainLooper()方法呢,AndroidStudio点击方法查找调用链,我们发现在ActivityThread中有调用。ActivityThread是Android程序的主线程,main方法则是启动的方法,我们看到先是调用了LooperprepareMainLooper(),初始化主线程的Looper。再调用了Looperloop()开启主线程轮训。
分类: 电脑/网络 >> 程序设计 >> 其他编程语言
解析:
举个例子说,你在这个网页上点鼠标右键,里面有一项,查看源代码,你点一下,那么你就看见用记事本打开的文件有许多文字了,这个就是这个网页的源代码。一般我们最终看到的用到的都是是程序,比如记事本也是个程序,网页也可以算是个我们看到的结果,浏览器也是个程序,那么这些程序都是别人做出来的,他写的原来的代码经过处理(程序中叫编译,比如刚才看到的那些代码经过浏览器整理就是得到网页了)就是我们最终使用的东西,这些代码也有专门的编写工具(程序),比如网页代码可以用FrontPage来写,你也听过计算机语言,C语言什么的,那么这些语言写的代码经过整理编译最终就是我们使用的程序,比如记事本了,QQ了,等等。源代码是一般不发布的,也就是我们不能看到的,这样就可以保证这些人/公司的知识产权。我这个是最通俗的解释,没有用书上的定义给你解释,但意思是不错的。你要是希望理解这些,可以在网上搜索一下就有很多了。
以上就是关于ComponentScan注解的扫描范围及源码解析全部的内容,包括:ComponentScan注解的扫描范围及源码解析、Spring系列(一)Spring MVC bean 解析、注册、实例化流程源码剖析、C语言源代码分析(越易懂越好,我只是一个初学者)等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)