spring注解扫描组件注册
最近对单点系统进行微服务拆分,被各个springboot的组件注册搞得云里雾里的。(有的是通过springboot的自动配置进IOC容器的,有的是自己添加构造方法添加进IOC容器。)决定抽时间将spring注解扫描组件注册重新复习一下,好久没写博客了,也该用笔记记录一下自己的学习过程,再不清晰的时候回来看一下加深印象。
一、@Configuration和@Bean给容器注册组件
现在我们有如下一个bean,想要将其注入到IOC容器中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | package com.kun.bean; import org.springframework.beans.factory.annotation.Value; public class Person {<br> private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this .name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this .age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age "]" ; } } |
传统通过配置文件方式定义bean的方式如下:
1 2 3 4 | < bean id="person" class="com.kun.bean.Person"> < property name="age" value="${}"></ property > < property name="name" value="zhangsan"></ property > </ bean > |
现在我们通过一个config类来替代原来的配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package com.kun.config; import com.kun.bean.Person; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScans; import org.springframework.context.annotation.Configuration; @Configuration public class MainConfig { @Bean ({ "person" }) public Person person01() { return new Person( "lisi" , Integer.valueOf( 20 )); } } |
这样,我们就将Person的实例注入到了IOC容器中,测试如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | package com.kun; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.kun.bean.Person; import com.kun.config.MainConfig; public class MainTest { @SuppressWarnings ( "resource" ) public static void main(String[] args) { // ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); // Person bean = (Person) applicationContext.getBean("person"); // System.out.println(bean); ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig. class ); Person bean = applicationContext.getBean(Person. class ); System.out.println(bean); String[] namesForType = applicationContext.getBeanNamesForType(Person. class ); for (String name : namesForType) { System.out.println(name); } } } |
最终我们能够从IOC容器中获取到注入的Person对象。
注意:通过@Configuration和@Bean注册的对象,在IOC容器中的key默认为构造方法的方法名,如果想要改变,则给@Bean注解增加value属性,IOC容器中的key可改为value属性的值。
二、@ComponentScan自动扫描组件
传统通过配置文件方式定义bean的方式如下:
1 2 | <!-- 包扫描、只要标注了@Controller、@Service、@Repository,@Component --> < context:component-scan base-package="com.kun" default-filters="false"></ context:component-scan > |
现在我们通过一个config类来替代原来的配置文件中的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | package com.kun.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.ComponentScans; import com.kun.bean.Person; import org.springframework.core.annotation.Order; //配置类==配置文件 @Configuration //告诉Spring这是一个配置类 @Order ( 2 ) @ComponentScans ( value = { @ComponentScan (value= "com.kun" ,includeFilters = { /* @Filter(type=FilterType.ANNOTATION,classes={Controller.class}), @Filter(type=FilterType.ASSIGNABLE_TYPE,classes={BookService.class}),*/ @Filter (type=FilterType.CUSTOM,classes={MyTypeFilter. class }) },useDefaultFilters = false ) } ) //@ComponentScan value:指定要扫描的包 //excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件 //includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件 //FilterType.ANNOTATION:按照注解 //FilterType.ASSIGNABLE_TYPE:按照给定的类型; //FilterType.ASPECTJ:使用ASPECTJ表达式 //FilterType.REGEX:使用正则指定 //FilterType.CUSTOM:使用自定义规则 public class MainConfig { //给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id @Bean ( "person" ) public Person person01(){ return new Person( "lisi" , 20 ); } } |
@ComponentScan注解是一个数组,其中数组中的元素如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | /* * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.context.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.core.type.filter.TypeFilter; /** * Configures component scanning directives for use with @{@link Configuration} classes. * Provides support parallel with Spring XML's {@code <context:component-scan>} element. * * <p>One of {@link #basePackageClasses()}, {@link #basePackages()} or its alias * {@link #value()} may be specified to define specific packages to scan. If specific * packages are not defined scanning will occur from the package of the * class with this annotation. * * <p>Note that the {@code <context:component-scan>} element has an * {@code annotation-config} attribute, however this annotation does not. This is because * in almost all cases when using {@code @ComponentScan}, default annotation config * processing (e.g. processing {@code @Autowired} and friends) is assumed. Furthermore, * when using {@link AnnotationConfigApplicationContext}, annotation config processors are * always registered, meaning that any attempt to disable them at the * {@code @ComponentScan} level would be ignored. * * <p>See @{@link Configuration}'s javadoc for usage examples. * * @author Chris Beams * @since 3.1 * @see Configuration */ @Retention (RetentionPolicy.RUNTIME) @Target (ElementType.TYPE) @Documented public @interface ComponentScan { /** * Alias for the {@link #basePackages()} attribute. * Allows for more concise annotation declarations e.g.: * {@code @ComponentScan("org.my.pkg")} instead of * {@code @ComponentScan(basePackages="org.my.pkg")}. */ String[] value() default {}; /** * Base packages to scan for annotated components. * <p>{@link #value()} is an alias for (and mutually exclusive with) this attribute. * <p>Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names. */ String[] basePackages() default {}; /** * Type-safe alternative to {@link #basePackages()} for specifying the packages * to scan for annotated components. The package of each class specified will be scanned. * <p>Consider creating a special no-op marker class or interface in each package * that serves no purpose other than being referenced by this attribute. */ Class<?>[] basePackageClasses() default {}; /** * The {@link BeanNameGenerator} class to be used for naming detected components * within the Spring container. * <p>The default value of the {@link BeanNameGenerator} interface itself indicates * that the scanner used to process this {@code @ComponentScan} annotation should * use its inherited bean name generator, e.g. the default * {@link AnnotationBeanNameGenerator} or any custom instance supplied to the * application context at bootstrap time. * @see AnnotationConfigApplicationContext#setBeanNameGenerator(BeanNameGenerator) */ Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator. class ; /** * The {@link ScopeMetadataResolver} to be used for resolving the scope of detected components. */ Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver. class ; /** * Indicates whether proxies should be generated for detected components, which may be * necessary when using scopes in a proxy-style fashion. * <p>The default is defer to the default behavior of the component scanner used to * execute the actual scan. * <p>Note that setting this attribute overrides any value set for {@link #scopeResolver()}. * @see ClassPathBeanDefinitionScanner#setScopedProxyMode(ScopedProxyMode) */ ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT; /** * Controls the class files eligible for component detection. * <p>Consider use of {@link #includeFilters()} and {@link #excludeFilters()} * for a more flexible approach. */ String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN; /** * Indicates whether automatic detection of classes annotated with {@code @Component} * {@code @Repository}, {@code @Service}, or {@code @Controller} should be enabled. */ boolean useDefaultFilters() default true ; /** * Specifies which types are eligible for component scanning. * <p>Further narrows the set of candidate components from everything in * {@link #basePackages()} to everything in the base packages that matches * the given filter or filters. * @see #resourcePattern() */ Filter[] includeFilters() default {}; /** * Specifies which types are not eligible for component scanning. * @see #resourcePattern() */ Filter[] excludeFilters() default {}; /** * Declares the type filter to be used as an {@linkplain ComponentScan#includeFilters() * include filter} or {@linkplain ComponentScan#excludeFilters() exclude filter}. */ @Retention (RetentionPolicy.RUNTIME) @Target ({}) @interface Filter { /** * The type of filter to use. Default is {@link FilterType#ANNOTATION}. */ FilterType type() default FilterType.ANNOTATION; /** * The class or classes to use as the filter. In the case of * {@link FilterType#ANNOTATION}, the class will be the annotation itself. * In the case of {@link FilterType#ASSIGNABLE_TYPE}, the class will be the * type that detected components should be assignable to. And in the case * of {@link FilterType#CUSTOM}, the class will be an implementation of * {@link TypeFilter}. * <p>When multiple classes are specified, OR logic is applied, e.g. "include * types annotated with {@code @Foo} OR {@code @Bar}". * <p>Specifying zero classes is permitted but will have no effect on component * scanning. */ Class<?>[] value(); } } |
常用的属性字段有:
value:指定要扫描的包
basePackages:同value
excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件
includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件
Filter常用的枚举如下:
1 2 3 4 5 | FilterType.ANNOTATION:按照注解 FilterType.ASSIGNABLE_TYPE:按照给定的类型; FilterType.ASPECTJ:使用ASPECTJ表达式 FilterType.REGEX:使用正则指定 FilterType.CUSTOM:使用自定义规则<br><br>着重看了一下自定义的过滤规则,需要实现TypeFilter接口,复写完match方法,根据方法的返回值来判断是否扫描进IOC容器。 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | package com.kun.config; import java.io.IOException; import org.springframework.core.io.Resource; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.ClassMetadata; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.TypeFilter; public class MyTypeFilter implements TypeFilter { /** * metadataReader:读取到的当前正在扫描的类的信息 * metadataReaderFactory:可以获取到其他任何类信息的 */ @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { // TODO Auto-generated method stub //获取当前类注解的信息 AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); //获取当前正在扫描的类的类信息 ClassMetadata classMetadata = metadataReader.getClassMetadata(); //获取当前类资源(类的路径) Resource resource = metadataReader.getResource(); String className = classMetadata.getClassName(); System.out.println( "--->" +className); if (className.contains( "er" )){ return true ; } return false ; } } |
三、@Scope设置组件的作用域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | package com.kun.config; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Lazy; import com.kun.bean.Color; import com.kun.bean.ColorFactoryBean; import com.kun.bean.Person; import com.kun.bean.Red; import com.kun.condition.LinuxCondition; import com.kun.condition.MyImportBeanDefinitionRegistrar; import com.kun.condition.MyImportSelector; import com.kun.condition.WindowsCondition; //类中组件统一设置。满足当前条件,这个类中配置的所有bean注册才能生效; @Conditional ({WindowsCondition. class }) @Configuration @Import ({Color. class ,Red. class ,MyImportSelector. class ,MyImportBeanDefinitionRegistrar. class }) //@Import导入组件,id默认是组件的全类名 public class MainConfig2 { //默认是单实例的 /** * ConfigurableBeanFactory#SCOPE_PROTOTYPE * @see ConfigurableBeanFactory#SCOPE_SINGLETON * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST request * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION sesssion * @return\ * @Scope:调整作用域 * prototype:多实例的:ioc容器启动并不会去调用方法创建对象放在容器中。 * 每次获取的时候才会调用方法创建对象; * singleton:单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中。 * 以后每次获取就是直接从容器(map.get())中拿, * request:同一次请求创建一个实例 * session:同一个session创建一个实例 * * 懒加载: * 单实例bean:默认在容器启动的时候创建对象; * 懒加载:容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化; * */ // @Scope("prototype") @Bean ( "person" ) public Person person(){ System.out.println( "给容器中添加Person...." ); return new Person( "张三" , 25 ); } } |
关于@Scope注解没啥可说的,多一句嘴,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。
那么对于有状态的bean呢?Spring对一些(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态的bean采用ThreadLocal进行处理,让它们也成为线程安全的状态,因此有状态的Bean就可以在多线程中共享了。默认Contrller也是单例模式,如果出现线程安全问题需要考虑controller中是否有全局的成员变量被多线程共享,或者将其改成多例模式。
四、@Lazy设置单例bean的懒加载
默认的多例bean在IOC容器启动时并不创建,只有在第一次使用(获取)Bean创建对象的时候新生成一个,如果想要单例bean也实现懒加载的策略,此时则需要@Lazy注解。
1 2 3 4 5 6 | @Lazy @Bean ( "person" ) public Person person(){ System.out.println( "给容器中添加Person...." ); return new Person( "张三" , 25 ); } |
五、@Conditional按照条件注册bean
@Conditional注解对类中组件统一设置。满足当前条件,这个类中配置的所有bean注册才能生效。该注解的value为自定义的条件类,该类实现
org.springframework.context.annotation.condition接口,复写其中的matches方法,根据方法的返回值来判断条件是否生效,以决定配置类是否生效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package com.kun.config; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import com.kun.condition.WindowsCondition; //类中组件统一设置。满足当前条件,这个类中配置的所有bean注册才能生效; @Conditional ({WindowsCondition. class }) @Configuration public class MainConfig2 { } |
自定义的WindowsCondition类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package com.kun.condition; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotatedTypeMetadata; //判断是否windows系统 public class WindowsCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); String property = environment.getProperty( "os.name" ); if (property.contains( "Windows" )){ return true ; } return false ; } } |
六、@Import快速的给IOC容器注册一个组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package com.kun.config; import org.springframework.context.annotation.Configuration; import com.kun.bean.Color; import com.kun.bean.ColorFactoryBean; import com.kun.bean.Person; import com.kun.bean.Red; import com.kun.condition.LinuxCondition; import com.kun.condition.MyImportBeanDefinitionRegistrar; import com.kun.condition.MyImportSelector; import com.kun.condition.WindowsCondition; @Configuration @Import ({Color. class ,Red. class ,MyImportSelector. class ,MyImportBeanDefinitionRegistrar. class }) //@Import导入组件,id默认是组件的全类名 public class MainConfig2 { } |
另外,我们可以实现ImportSelector或者ImportBeanDefinitionRegistrar接口复写相应的方法注册指定的bean:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package com.kun.condition; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; //自定义逻辑返回需要导入的组件 public class MyImportSelector implements ImportSelector { //返回值,就是到导入到容器中的组件全类名 //AnnotationMetadata:当前标注@Import注解的类的所有注解信息 @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { // TODO Auto-generated method stub //importingClassMetadata //方法不要返回null值 return new String[]{ "com.kun.bean.Blue" , "com.kun.bean.Yellow" }; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | package com.kun.condition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata; import com.kun.bean.RainBow; public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { /** * AnnotationMetadata:当前类的注解信息 * BeanDefinitionRegistry:BeanDefinition注册类; * 把所有需要添加到容器中的bean;调用 * BeanDefinitionRegistry.registerBeanDefinition手工注册进来 */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { boolean definition = registry.containsBeanDefinition( "com.kun.bean.Red" ); boolean definition2 = registry.containsBeanDefinition( "com.kun.bean.Blue" ); if (definition && definition2){ //指定Bean定义信息;(Bean的类型,Bean。。。) RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow. class ); //注册一个Bean,指定bean名 registry.registerBeanDefinition( "rainBow" , beanDefinition); } } } |
这两种方法任选其一即可,如果读过spring源码,那么一定对RootBeanDefinition类很熟悉,Spring通过BeanDefinition将配置文件中的配置信息转换为容器的内部表示,并将这些BeanDefiniton注册到BeanDefinitonRegistry中。Spring容器的BeanDefinitionRegistry就像是Spring配置信息的内存数据库,主要是以map的形式保存,后续操作直接从BeanDefinitionRegistry中读取配置信息。一般情况下,BeanDefinition只在容器启动时加载并解析,除非容器刷新或重启,这些信息不会发生变化,当然如果用户有特殊的需求,也可以通过编程的方式在运行期调整BeanDefinition的定义。因此,第二种方法更好理解一些。
最后别忘了通过@Import注解将我们自定义的选择器添加到配置类中,@Import({MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})。
七、通过BeanFactory注册组件
实现org.springframework.beans.factory.FactoryBean接口,自定义一个BeanFactory。getObject()中返回的对象会被注册到IOC容器中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | package com.kun.bean; import org.springframework.beans.factory.FactoryBean; //创建一个Spring定义的FactoryBean public class ColorFactoryBean implements FactoryBean<Color> { //返回一个Color对象,这个对象会添加到容器中 @Override public Color getObject() throws Exception { // TODO Auto-generated method stub System.out.println( "ColorFactoryBean...getObject..." ); return new Color(); } @Override public Class<?> getObjectType() { // TODO Auto-generated method stub return Color. class ; } //是单例? //true:这个bean是单实例,在容器中保存一份 //false:多实例,每次获取都会创建一个新的bean; @Override public boolean isSingleton() { // TODO Auto-generated method stub return false ; } } |
再通过@Bean注解将自定义的FactoryBean注册到IOC容器中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package com.kun.config; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.kun.bean.Color; import com.kun.bean.ColorFactoryBean; import com.kun.bean.Person; import com.kun.bean.Red; @Configuration public class MainConfig2 { @Bean public ColorFactoryBean colorFactoryBean(){ return new ColorFactoryBean(); } } |
注意:默认获取到的是工厂bean调用getObject创建的对象,要获取工厂Bean本身,我们需要给id前面加一个& &colorFactoryBean
八、小结
给容器中注册组件
1)、包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)[自己写的类]
2)、@Bean[导入的第三方包里面的组件]
3)、@Import[快速给容器中导入一个组件]
1)、@Import(要导入到容器中的组件);容器中就会自动注册这个组件,id默认是全类名
2)、ImportSelector:返回需要导入的组件的全类名数组;
3)、ImportBeanDefinitionRegistrar:手动注册bean到容器中
4)、使用Spring提供的 FactoryBean(工厂Bean)
1)、默认获取到的是工厂bean调用getObject创建的对象
2)、要获取工厂Bean本身,我们需要给id前面加一个& &colorFactoryBean
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步