@ComponentScan 注解概述 1.扫描时排除注解标注的类 例如,我们现在排除@Controller、@Service和@Repository注解,我们可以在PersonConfig类上通过@ComponentScan注解的excludeFilters()实现。例如,我们在PersonConfig类上添加了如下的注解。
1 2 3 @ComponentScan(value = "io.mykit.spring", excludeFilters = { @Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class, Repository.class}) })
2.扫描时只包含注解标注的类 例如,我们需要Spring在扫描时,只包含@Controller注解标注的类,可以在PersonConfig类上添加@ComponentScan注解,设置只包含@Controller注解标注的类,并禁用默认的过滤规则,如下所示。
1 2 3 4 @ComponentScan(value = "io.mykit.spring", includeFilters = { @Filter(type = FilterType.ANNOTATION, classes = {Controller.class}) }, useDefaultFilters = false)
3.重复注解 不知道小伙伴们有没有注意到ComponentScan注解类上有一个如下所示的注解。
1 @Repeatable(ComponentScans.class)
4.FilterType中常用的规则 (1)ANNOTATION ANNOTATION:按照注解进行过滤
例如,使用@ComponentScan注解进行包扫描时,按照注解只包含标注了@Controller注解的组件,如下所示。
1 2 3 @ComponentScan(value = "io.mykit.spring", includeFilters = { @Filter(type = FilterType.ANNOTATION, classes = {Controller.class}) }, useDefaultFilters = false)
(2)ASSIGNABLE_TYPE ASSIGNABLE_TYPE:按照给定的类型进行过滤
例如,使用@ComponentScan注解进行包扫描时,按照给定的类型只包含PersonService类(接口)或其子类(实现类或子接口)的组件,如下所示。
1 2 3 @ComponentScan(value = "io.mykit.spring", includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {PersonService.class}) }, useDefaultFilters = false)
此时,只要是PersonService类型的组件,都会被加载到容器中。也就是说:当PersonService是一个Java类时,Person类及其子类都会被加载到Spring容器中;当
PersonService是一个接口时,其子接口或实现类都会被加载到Spring容器中。
(3)ASPECTJ ASPECTJ:按照ASPECTJ表达式进行过滤**
例如,使用@ComponentScan注解进行包扫描时,按照ASPECTJ表达式进行过滤,如下所示。
1 2 3 @ComponentScan(value = "io.mykit.spring", includeFilters = { @Filter(type = FilterType.ASPECTJ, classes = {AspectJTypeFilter.class}) }, useDefaultFilters = false)
(4)REGEX REGEX:按照正则表达式进行过滤
例如,使用@ComponentScan注解进行包扫描时,按照正则表达式进行过滤,如下所示。
1 2 3 @ComponentScan(value = "io.mykit.spring", includeFilters = { @Filter(type = FilterType.REGEX, classes = {RegexPatternTypeFilter.class}) }, useDefaultFilters = false)
(5)CUSTOM CUSTOM:按照自定义规则进行过滤
如果实现自定义规则进行过滤时,自定义规则的类必须是org.springframework.core.type.filter.TypeFilter接口的实现类。
例如,按照自定义规则进行过滤,首先,我们需要创建一个org.springframework.core.type.filter.TypeFilter接口的实现类MyTypeFilter,如下所示。
1 2 3 4 5 6 public class MyTypeFilter implements TypeFilter { @Override public boolean match (MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { return false ; } }
当我们实现TypeFilter接口时,需要实现TypeFilter接口中的match()方法,match()方法的返回值为boolean类型。当返回true时,表示符合规则,会包含在Spring容器中;当返回false时,表示不符合规则,不会包含在Spring容器中。另外,在match()方法中存在两个参数,分别为MetadataReader类型的参数和MetadataReaderFactory类型的参数,含义分别如下所示。
metadataReader:读取到的当前正在扫描的类的信息。
metadataReaderFactory:可以获取到其他任务类的信息。
接下来,使用@ComponentScan注解进行如下配置。
1 2 3 4 @ComponentScan(value = "io.mykit.spring", includeFilters = { @Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class}) }, useDefaultFilters = false)
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 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; import java.io.IOException; /** * @author binghe * @version 1.0.0 * @description 自定义过滤规则 */ public class MyTypeFilter implements TypeFilter { /** * metadataReader:读取到的当前正在扫描的类的信息 * metadataReaderFactory:可以获取到其他任务类的信息 */ @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { //获取当前类注解的信息 AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); //获取当前正在扫描的类的信息 ClassMetadata classMetadata = metadataReader.getClassMetadata(); //获取当前类的资源信息,例如:类的路径等信息 Resource resource = metadataReader.getResource(); //获取当前正在扫描的类名 String className = classMetadata.getClassName(); //打印当前正在扫描的类名 System.out.println("-----> " + className); return false; } }
@Scope注解概述 @Scope注解中的取值如下所示
singleton:表示组件在Spring容器中是单实例的,这个是Spring的默认值 ,Spring在启动的时候会将组件进行实例化并加载到Spring容器中 ,之后,每次从Spring容器中获取组件时,直接将实例对象返回,而不必再次创建实例对象。从Spring容器中获取对象,小伙伴们可以理解为从Map对象中获取对象。
prototype:表示组件在Spring容器中是多实例的,Spring在启动的时候并不会对组件进行实例化 操作,而是每次从Spring容器中获取组件对象时,都会创建一个新的实例对象并返回。
request:每次请求都会创建一个新的实例对象,request作用域用在spring容器的web环境中。
session:在同一个session范围内,创建一个新的实例对象,也是用在web环境中。
application:全局web应用级别的作用于,也是在web环境中使用的,一个web应用程序对应一个bean实例,通常情况下和singleton效果类似的,不过也有不一样的地方,singleton是每个spring容器中只有一个bean实例,一般我们的程序只有一个spring容器,但是,一个应用程序中可以创建多个spring容器,不同的容器中可以存在同名的bean,但是sope=aplication的时候,不管应用中有多少个spring容器,这个应用中同名的bean只有一个。
其中,request和session作用域是需要Web环境支持的,这两个值基本上使用不到,如果我们使用Web容器来运行Spring应用时,如果需要将组件的实例对象的作用域设置为request和session,我们通常会使用request.setAttribute(“key”,object)和session.setAttribute(“key”, object)的形式来将对象实例设置到request和session中,通常不会使用@Scope注解来进行设置。
Spring在启动时,默认会将单实例bean进行实例化,并加载到Spring容器中。也就是说,单实例bean默认在Spring容器启动的时候创建对象,并将对象加载到Spring容器中。如果我们需要对某个bean进行延迟加载,我们该如何处理呢?此时,就需要使用到@Lazy注解了。
也就是说 @Bean 注入的时候spring 默认的 是单例模式 singleton ,并且在Spring启动的时候会将组件进行实例化并加载到Spring容器中。
加上@Lazy 注解,容器初始化并不会实例化,当用到这个对象的时候才会 加入容器中。以后每次获取bean对象时,直接返回创建好的对象。
总结:懒加载,也称延时加载。仅对单例bean生效 。单例bean是在Spring容器启动的时候加载的,添加@Lazy注解后就会延迟加载,在Spring容器启动的时候并不会加载,而是在第一次使用此bean的时候才会加载,但当你多次获取bean的时候不会重复加载,只是在第一次获取的时候会加载,这不是延迟加载的特性,而是单例Bean的特性。
那么可以按照特定的条件才注入bena 么?可以的,请看下面
@Conditional注解概述 Spring支持按照条件向IOC容器中注册bean,满足条件的bean就会被注册到IOC容器中,不满足条件的bean就不会被注册到IOC容器中
@Conditional注解可以按照一定的条件进行判断,满足条件向容器中注册bean,不满足条件就不向容器中注册bean。
实例 比如,如果当前操作系统是Windows操作系统,则向Spring容器中注册binghe001;如果当前操作系统是Linux操作系统,则向Spring容器中注册binghe002。
1 2 3 4 5 6 7 8 @Data @NoArgsConstructor @AllArgsConstructor public class Person implements Serializable { private static final long serialVersionUID = 7387479910468805194L ; private String name; private Integer age; }
1 2 3 4 5 6 7 8 9 10 11 12 13 @Configuration public class SpringbootBean { @Conditional({WindowsCondition.class}) @Bean("binghe001") public Person person01 () { return new Person ("binghe001" , 18 ); } @Conditional({LinuxCondition.class}) @Bean("binghe002") public Person person02 () { return new Person ("binghe002" , 20 ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void testAnnotationConfig () { ApplicationContext context = new AnnotationConfigApplicationContext (SpringbootBean.class); Environment environment = context.getEnvironment(); String osName = environment.getProperty("os.name" ); System.out.println(osName); String[] names = context.getBeanNamesForType(Person.class); Arrays.stream(names).forEach(System.out::println); Map<String, Person> beans = context.getBeansOfType(Person.class); System.out.println(beans); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class LinuxCondition implements Condition { @Override public boolean matches (ConditionContext context, AnnotatedTypeMetadata metadata) { ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); ClassLoader classLoader = context.getClassLoader(); Environment environment = context.getEnvironment(); BeanDefinitionRegistry registry = context.getRegistry(); String property = environment.getProperty("os.name" ); return property.contains("linux" ); } }
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 public class WindowsCondition implements Condition { @Override public boolean matches (ConditionContext context, AnnotatedTypeMetadata metadata) { ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); ClassLoader classLoader = context.getClassLoader(); Environment environment = context.getEnvironment(); BeanDefinitionRegistry registry = context.getRegistry(); String property = environment.getProperty("os.name" ); return property.contains("Windows" ); } }
修改LinuxCondition 直接返回true 的时候。可见单元测试会包含binghe002 bean
说明改配置可以成功
@Conditional注解也可以标注在类上,标注在类上含义为:满足当前条件,这个类中配置的所有bean注册才能生效
@Conditional的扩展注解 @ConditionalOnBean:当容器中有指定Bean的条件下进行实例化。 @ConditionalOnMissingBean:当容器里没有指定Bean的条件下进行实例化。
@ConditionalOnClass:当classpath类路径下有指定类的条件下进行实例化。 @ConditionalOnMissingClass:当类路径下没有指定类的条件下进行实例化。
@ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。 @ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。
@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。
@ConditionalOnExpression:当SpEL表达式为true的时候,才会实例化一个Bean。 @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。 @ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。 @ConditionalOnResource:当类路径下有指定的资源时触发实例化。 @ConditionalOnJndi:在JNDI存在的条件下触发实例化。
@Import注解概述 我们自己写的类,可以通过包扫描+标注注解(@Controller、@Servcie、@Repository、@Component)的形式将其注册到IOC容器中,如果不是我们自己写的类,比如,我们在项目中引入了一些第三方的类库,此时,我们需要将这些第三方类库中的类注册到Spring容器中,该怎么办呢?此时,我们就可以使用@Bean和@Import注解将这些类快速的导入Spring容器中。
注册bean的方式 向Spring容器中注册bean通常有以下几种方式
包扫描+标注注解(@Controller、@Servcie、@Repository、@Component),通常用于自己写的类。
@Bean注解,通常用于导入第三方包中的组件。
@Import注解,快速向Spring容器中导入组件。
@Import只允许放到类上面,不能放到方法上
下面使用@Import注解给容器中快速导入一个组件
在扫面类加上,即可把自己项目的类或者第三方的类放入到spring 容器。 默认容器 id 是类名的全路径
@Import注解的使用方式 1.直接填class数组方式 下面的bean id 分别是com.aa.A 和 com.aa.B
@bean 注入容器的是方法名为容器id 这里区分下
1 @Import({com.aa.A.class, com.aa.B.class})
2.ImportSelector方式【重点】 直接填class数组方式 :假设好多需要导入的类,那么在类上写好几十个显然不优雅的, 所以ImportSelector方式可以不就一下
ImportSelector接口 例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import org.springframework.context.annotation.ImportSelector;import org.springframework.core.type.AnnotationMetadata;public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String []{ Person.class.getName() }; } }
加上配置
1 @Import(value = {MyImportSelector.class})
输出可以看到person 已经注入了容器
ImportSelector接口是至spring中导入外部配置的核心接口,其主要作用是收集需要导入的配置类,selectImports()方法的返回值就是我们向Spring容器中导入的类的全类名。如果该接口的实现类同时实现EnvironmentAware, BeanFactoryAware ,BeanClassLoaderAware或者ResourceLoaderAware,那么在调用其selectImports方法之前先调用上述接口中对应的方法,如果需要在所有的@Configuration处理完在导入时可以实现DeferredImportSelector接口。
编写自己得enablexxx
1 2 3 4 5 6 7 8 9 import org.springframework.context.annotation.Import;import java.lang.annotation.*;@Retention(RetentionPolicy.RUNTIME) @Documented @Target(ElementType.TYPE) @Import(MyImportSelector.class) public @interface EnableSpringStudy {}
然后打jar, 其他项目只要加上@EnableSpringStudy 注解 就自动注入了你写好得bean 了,是不是好像写了一个框架一样。手动滑稽。
之后还有种方式自动注入。参考
在resources 文件夹下新建 META-INF\spring.factories
1 org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.shuzhuo.core.EncryptedAutoConfigure
1 2 3 4 5 6 7 8 9 10 11 12 13 import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;@Configuration @EnableSpringStudy public class EncryptedAutoConfigure {}
是不是很nb 得样子,手动滑稽
假设需要动态注入bean 呢?
3.ImportBeanDefinitionRegistrar方式 Spring官方在动态注册bean时,大部分套路其实是使用ImportBeanDefinitionRegistrar接口
使用方法
ImportBeanDefinitionRegistrar需要配合@Configuration和@Import注解,@Configuration定义Java格式的Spring配置文件,@Import注解导入实现了ImportBeanDefinitionRegistrar接口的类。
ImportBeanDefinitionRegistrar 实例
1 2 public class Company { }
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 import org.springframework.beans.factory.config.BeanDefinition;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;public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions (AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { boolean personBean = registry.containsBeanDefinition("person" ); boolean department = true ; if (personBean && department){ BeanDefinition beanDefinition = new RootBeanDefinition (Company.class); registry.registerBeanDefinition("Company" , beanDefinition); } } }
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 import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Conditional;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;@Configuration @Import(value = {MyImportBeanDefinitionRegistrar.class}) public class SpringbootBean { @Conditional({WindowsCondition.class}) @Bean public Person person () { return new Person ("binghe001" , 18 ); } }
1 2 3 4 5 6 7 8 9 10 @Test public void testAnnotationConfig () { ApplicationContext context = new AnnotationConfigApplicationContext (SpringbootBean.class); Environment environment = context.getEnvironment(); String osName = environment.getProperty("os.name" ); String[] beanDefinitionNames = context.getBeanDefinitionNames(); Arrays.stream(beanDefinitionNames).forEach(System.out::println); }
这里没有连贯性,先学习吧
那么如何使用FactoryBean向Spring容器中注册bean?
FactoryBean概述 T getObject():返回由FactoryBean创建的bean实例,如果isSingleton()返回true,则该实例会放到Spring容器中单实例缓存池中。
boolean isSingleton():返回由FactoryBean创建的bean实例的作用域是singleton还是prototype。
Class getObjectType():返回FactoryBean创建的bean类型。
FactoryBean 实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import org.springframework.beans.factory.FactoryBean;public class PersonFactoryBean implements FactoryBean <Person> { @Override public Person getObject () throws Exception { return new Person (); } @Override public Class<?> getObjectType() { return Person.class; } @Override public boolean isSingleton () { return true ; } }
1 2 3 4 @Bean public PersonFactoryBean personFactoryBean () { return new PersonFactoryBean (); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void testAnnotationConfig () { ApplicationContext context = new AnnotationConfigApplicationContext (SpringbootBean.class); String[] beanDefinitionNames = context.getBeanDefinitionNames(); Arrays.stream(beanDefinitionNames).forEach(System.out::println); Object personFactoryBean1 = context.getBean("personFactoryBean" ); Object personFactoryBean2 = context.getBean("personFactoryBean" ); System.out.println("personFactoryBean1类型:" + personFactoryBean1.getClass()); System.out.println("personFactoryBean2类型:" + personFactoryBean2.getClass()); System.out.println(personFactoryBean1 == personFactoryBean2); Object personFactoryBean3 = context.getBean("&personFactoryBean" ); System.out.println("personFactoryBean3类型:" + personFactoryBean3.getClass()); }
虽然我在代码中使用@Bean注解注入的PersonFactoryBean对象,但是,实际上从Spring容器中获取到的bean对象却是调用PersonFactoryBean类中的getObject()获取到的Person对象。
想获取PersonFactoryBean实例,该怎么办呢?
只需要在获取bean对象时,在id前面加上&符号即可