componentscan注解的用法和作用「@component可以注解在什么地方」
spring常用注解作用与常用接口与后置处理器
从spring2.5之后,spring注解驱动开发慢慢取代了Spring的xml配置文件的作用,而且目前流行的SpringBoot开发也是基于spring注解驱动做扩展的,所以想要理解好SpringBoot,就必须掌握一些spring的注解驱动。
以前xml的做法是
现在使用spring注解的做法是

java8之后ComponentScan注册加了@Repeatable(ComponentScans.class),可以直接在配置类上标注多个@Componentscan,在java8之前想配置多个@Componentscan扫描,需要用@ComponentScans
给容器注册组件(bean)的几种方式
1.包扫描+组件标注注解(@Service、@Controller、@Repository、@Componet),这种方式用于导入自己写的类
2.@Bean方式导入,常用入导入第三方包里面的类 默认bean id为导入执行的方法名
3.@Import方式导入(有三种用法),默认的bean id为全类名
4.通过FactoryBean(工厂bean)
-bean创建-初始化-销毁
applicationContextAeare和EmbeddedValueResolverAeare都是通过ApplicationContextAwareProcessor后置处理器来实现的
BeanFactoryPostProcessor是在bean定义信息加载完成后调用
BeanDefinitionRegistryPostProcessor是在bean定义信息将要加载时调用
先执行BeanDefinitionRegistryPostProcessor的实现类再执行BeanFactoryPostProcessor的实现类
创建bean的源码位置
org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization
-org.springframework.beans.factory.BeanFactory#getBean(java.lang.String, java.lang.ClassT)
--org.springframework.beans.factory.support.AbstractBeanFactory#createBean
---org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
执行bean对象的实例化
-org.springframework.beans.factory.support.AbstractBeanFactory#createBean
给bean的属性赋值
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
initializeBean方法主要做了三个操作1.循环执行后置处理器的的前置方法(applyBeanPostProcessorsBeforeInitialization)、2.再执行初始化方法(invokeInitMethods),3.再执行后置处理器的的后置方法(applyBeanPostProcessorsAfterInitialization)
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition)
@CompoentScan 注解
1. String[] value() default {};
@Configuration @ComponentScan(value="com.testSpringAnnotation") public class MainConfig { }
2. String[] basePackages() default {};
3. Class?[] basePackagesClasses() default {};
4. Class? extends BeanNameGenerator nameGenerator() default BeanNameGenerator. class;
5. Class? extends ScopeMetaDataResolver scopeResolver() default AnnotationScopeMetaDataResolver.class;
private String scopeName = "singleton";
这个属性也是可选配置,默认的处理bean作用域的实现类是AnnotationScopeMetaDataResolver.class。源码比较简单,就是取注解上获取指定的scope的value值,如果没有配置,就是用默认的singleton。
6. ScopedProxyMode scopedProxy() default ScopedProxyMode. DEFAULT;
ScopedProxyMode是一个枚举类,可选值有四个:DEFAULT,NO,INTERFACES,TARGET_CLASS。
7. String resourcePattern() default """**/*.class";
8. boolean useDefaultFilters() default true;
9. ComponentScan.Filter[] includeFilters() default {};
第一种:ANNOTATION 按照注解(常用)
@ ComponentScan(value="com.testSpringAnnotation",includeFilters= { @Filter(type=FilterType.ANNOTATION,classes= {Controller.class}) })
第二种:ASSIGNABLE_TYPE 按照给定的类型(常用)
第三种:ASPECTJ 使用ASPECTJ表达式(不常用)
第四种:REGEX,正则表达式。 使用正则表达式
第五种:CUSTOM,自定义类型。 使用自定义规则,需要实现TypeFilter接口,返回true或false来决定是否匹配
10. ComponentScan.Filter[] excludeFilters() default {};
11. boolean lazyInit() default false;
如何将一个组件注册到容器中?Bean注解一招解决-
通过组件扫描和自动注入已经大大简化了我们的开发,然而,Spring仍然不满足于此,经过版本的迭代,现在我们已经可以完全抛弃配置文件使用Spring进行开发了,一起来看看吧。
现在我们不创建Spring的配置文件,那么如何将一个组件注册到容器中呢?其实,我们仍然是需要一个配置文件的,不过这个配置文件能够以一个类的形式存在:
@Configuration用于将一个普通的Java类变为一个Spring的配置类,现在这个类就相当于之前的配置文件了,此时如果想注册一个组件,则使用@Bean注解:
这里需要注意几点,若有方法被@Bean注解标注,则该方法的返回值则为需要注册的组件,而方法名则为组件在容器中的名字,当然了,这些都需要建立在代码是写在配置类的前提下。
如果想要修改组件的名字,可以修改方法名:
若是不想修改方法名,@Bean注解也提供了修改名字的方式:
@Bean中还有initMethod和destroyMethod属性,它们分别用于指定组件的两个生命周期方法:
@ComponentScan注解是用来完成组件扫描的,它需要标注在配置类上:
它的作用等价于如下配置:
我们来聊一聊关于@ComponentScan的一些高级用法,该注解是可以在扫描时指定扫描规则的,比如我们想扫描 com.wwj.spring.demo 这个包,但是包里有一些类、或者一些注解的内容是我们不想要注册的,此时我们就可以指定扫描规则,如下:
在如上的配置中,excludeFilters用来配置需要排除的组件,需要借助@Filter注解,@Filter注解中的type属性用于指定以哪种方式排除组件,Spring一共提供了5种匹配方式:
所以如果想要具体排除某个组件,则使用ASSIGNABLE_TYPE,如果想要排除某个注解标注的所有组件,则使用ANNOTATION:
将excludeFilters切换为includeFilters,功能将变为只扫描匹配的组件,如下:
以上配置的作用是扫描 com.wwj.spring.demo 包下被@Service注解标注的组件,注意一点,由于Spring默认的扫描规则就是扫描所有带@Component注解的组件,所以若是想实现只扫描某个注解,则需要添加配置useDefaultFilters = false来禁用掉Spirng默认的扫描规则。
若是想实现自定义扫描规则,也非常简单,只需实现TypeFilter接口:
然后进行配置即可:
千万别忘了配置useDefaultFilters = false,此时将只能扫描到名字为user的组件。
这些内容在配置文件中也是可以进行配置的,简单举一个例子吧:
@Scope用于指定组件的作用域,关于作用域在上一篇我们已经介绍过了, 所以用法其实非常简单:
不过多介绍,但由此可以引申出一个新的注解: @Lazy ,该注解的作用是指定组件是否懒加载,默认情况下,所有组件会在容器启动的时候被创建,而如果标注@Lazy,则组件会在第一次使用时被创建。我们可以来试验一下,首先编写一个User类:
编写测试代码:
当没有添加@Lazy注解,控制台输出:
当添加了@Lazy注解,控制台没有任何输出,只有调用了 context.getBean_(_"user"_)_; User对象才会被创建。
@Conditional注解的功能是以指定的条件来注册组件,现在我们有两个组件:
一个是夏季水果西瓜,一个是冬季水果猕猴桃,现在有一个需求是当传入参数为夏天时,就注册西瓜,当传入参数是冬天时,就注册猕猴桃,该如何实现呢?
我们可以借助@Conditional注解来实现,首先创建类实现Condition接口:
接下来就可以使用它们进行配置了:
此时在虚拟机参数位置填写 -Dseason=summer ,Watermelon将被注册,当参数被修改为 -Dseason=winter 时,Kiwi将被注册,SpringBoot框架的底层就大量地使用到了这个注解,不过这是题外话了,我将在后续SpringBoot系统的文章中对其再度进行介绍。
我们已经知道,目前将一个组件注册到容器中有多种方式,使用@Bean或者组件扫描都可以,然而在某些情况下,这些方式都不太方便,比如将一个第三方的组件注册到容器中,此时我们可以借助@Import注解:
另一种方式是使用ImportSelector,创建类实现ImportSelector接口:
将需要注册到容器中的组件全类名写到数组中,然后@Import注解只需要填写这个类的信息即可:
还有一种方式是实现ImportBeanDefinitionRegistrar接口,它与第二种方式类似,与之不同的是,这种方式可以自定义组件注册到容器中的名字:
配置如下:
需要注意的是第一种和第二种方式注册的组件,其在容器中的名字是组件的全类名。
FactoryBean也是Spring提供的一种注册组件的方式,不过它比较特殊,看一个例子:
这三个方法非常好理解:
那有同学提出疑问了,这种方式岂不是更加麻烦了,有必要存在吗?当然有了,它牛就牛在你可以随意篡改需要注册的组件,比如:
现在看似注册的是User对象,其实注册的是Cat,不信我们试试:
运行结果:
若是想要获得实现了FactoryBean接口的User对象本身,则需要在名字面前添加 :
运行结果:
(新手勿进,全是代码)@ComponentScan、@ComponentScans详解
这些问题如果都ok,恭喜你,太优秀了,不知道没关系,一起来看看。
到目前为止,介绍了2种注册bean的方式:
通常情况下,项目中大部分类都需要交给spring去管理,按照上面这2种方式,代码量还是挺大的。
为了更方便bean的注册,Spring提供了批量的方式注册bean,方便大量bean批量注册,spring中的@ComponentScan就是干这个事情的。
@ComponentScan用于批量注册bean。
这个注解会让spring去扫描某些包及其子包中所有的类,然后将满足一定条件的类作为bean注册到spring容器容器中。
具体需要扫描哪些包?以及这些包中的类满足什么条件时被注册到容器中,这些都可以通过这个注解中的参数动态配置。
先来看一下这个注解的定义:
@ComponentScan工作的过程:
1. Spring会扫描指定的包,且会递归下面子包,得到一批类的数组
2. 然后这些类会经过上面的各种过滤器,最后剩下的类会被注册到容器中
第一个:需要扫描哪些包?通过 value、backPackages、basePackageClasses 这3个参数来控制
第二:过滤器有哪些?通过 useDefaultFilters、includeFilters、excludeFilters 这3个参数来控制过滤器
这两个问题搞清楚了,就可以确定哪些类会被注册到容器中。
默认情况下,任何参数都不设置的情况下,此时,会将@ComponentScan修饰的类所在的包作为扫描包;默认情况下useDefaultFilters为true,这个为true的时候,spring容器内部会使用默认过滤器,规则是:凡是类上有 @Repository、@Service、@Controller、@Component 这几个注解中的任何一个的,那么这个类就会被作为bean注册到spring容器中,所以默认情况下,只需在类上加上这几个注解中的任何一个,这些类就会自动交给spring容器来管理了。
这几个注解都是spring提供的。
先说一下 @Component 这个注解,看一下其定义:
再来看看 @Repository 源码如下:
其他两个注解 @Service、@Controller 源码和 @Repository 源码类似。
这4个注解本质上是没有任何差别,都可以用在类上面,表示这个类被spring容器扫描的时候,可以作为一个bean组件注册到spring容器中。
spring容器中对这4个注解的解析并没有进行区分,统一采用 @Component 注解的方式进行解析,所以这几个注解之间可以相互替换。
spring提供这4个注解,是为了让系统更清晰,通常情况下,系统是分层结构的,多数系统一般分为controller层、service层、dao层。
@controller通常用来标注controller层组件,@service注解标注service层的组件,@Repository标注dao层的组件,这样可以让整个系统的结构更清晰,当看到这些注解的时候,会和清晰的知道属于哪个层,对于spring来说,将这3个注解替换成@Component注解,对系统没有任何影响,产生的效果是一样的。
下面通过案例来感受@ComponentScan各种用法。
上面几个类中,分别使用了4种注解。
部分输出如下:
指定需要扫毛哪些包,可以通过value或者basePackage来配置,二者选其一,都配置运行会报错,下面我们通过value来配置。
ComponentScanTest中新增个方法
截取了关键几行如下:
可以看出只有controller包和service包中的2个类被注册为bean了。
指定包名的方式扫描存在的一个隐患,若包被重名了,会导致扫描会失效,一般情况下面我们使用basePackageClasses的方式来指定需要扫描的包,这个参数可以指定一些类型,默认会扫描这些类所在的包及其子包中所有的类,这种方式可以有效避免这种问题。
下面来看一下basePackageClasses的方式。
我们可以在需要扫描的包中定义一个标记的接口或者类,他们的唯一的作用是作为basePackageClasses的值,其他没有任何用途。
ComponentScanTest中新增个方法
再来看一下includeFilters这个参数的定义:
是一个 Filter 类型的数组, 多个Filter之间为或者关系,即满足任意一个就可以了 ,看一下 Filter 的代码:
我们自定义一个注解,让标注有这些注解的类自动注册到容器中
下面的代码都在 com.javacode2018.lesson001.demo22.test3 包中。
ComponentScanTest中新增个测试用例
Service1上标注了 @MyBean 注解,被注册到容器了,但是 Service2 上没有标注 @MyBean 啊,怎么也被注册到容器了?
原因:Service2上标注了 @Compontent 注解,而@CompontentScan注解中的 useDefaultFilters 默认是 true ,表示也会启用默认的过滤器,而默认的过滤器会将标注有 @Component、@Repository、@Service、@Controller 这几个注解的类也注册到容器中
如果我们只想将标注有 @MyBean 注解的bean注册到容器,需要将默认过滤器关闭,即:useDefaultFilters=false,我们修改一下ScanBean3的代码如下:
再次运行 test3 输出:
上面的自定义的@MyBean注解,是无法指定bean的名称的,可以对这个注解做一下改造,加个value参数来指定bean的名称,如下:
修改一下Service1的代码:
运行test3用例输出:
此时bean名称就变成了 service1Bean 。
下面的代码都位于 com.javacode2018.lesson001.demo22.test4 包中。
让spring来进行扫描,类型满足IService的都将其注册到容器中。
ComponentScanTest中新增个测试用例
有时候我们需要用到自定义的过滤器,使用自定义过滤器的步骤:
来看一下 TypeFilter 这个接口的定义:
是一个函数式接口,包含一个match方法,方法返回boolean类型,有2个参数,都是接口类型的,下面介绍一下这2个接口。
类元数据读取器,可以读取一个类上的任意信息,如类上面的注解信息、类的磁盘路径信息、类的class对象的各种信息,spring进行了封装,提供了各种方便使用的方法。
看一下这个接口的定义:
类元数据读取器工厂,可以通过这个类获取任意一个类的MetadataReader对象。
源码:
我们来个自定义的Filter,判断被扫描的类如果是 IService 接口类型的,就让其注册到容器中。
来个自定义的TypeFilter类:
ComponentScanTest中新增个测试用例
配置排除的过滤器,满足这些过滤器的类不会被注册到容器中,用法上面和includeFilters用一样,这个我就不演示了,可以自己玩玩
从这个注解的定义上可以看出这个注解可以同时使用多个,如:
还有一种写法,使用@ComponentScans的方式:
@CompontentScan注解是被下面这个类处理的
这个类非常非常关键,主要用户bean的注册,前面我们介绍的@Configuration,@Bean注解也是被这个类处理的。
还有下面这些注解:
以上这些注解都是被ConfigurationClassPostProcessor这个类处理的,内部会递归处理这些注解,完成bean的注册。
以@CompontentScan来说一下过程,第一次扫描之后会得到一批需要注册的类,然后会对这些需要注册的类进行遍历,判断是否有上面任意一个注解,如果有,会将这个类交给ConfigurationClassPostProcessor继续处理,直到递归完成所有bean的注册。
想成为高手,这个类是必看的。
