Spring系列——@ComponentScan

it2023-02-16  88

Spring系列——@ComponentScan

前言@ComponentScan源码@Filter源码@ComponentScan实战 自定义类型过滤实战

前言

大家都了解过传统的Xml配置方式的Spring,基本上都知道Xml配置中有<Context:compent-scan>标签,它的作用是开启包扫描,把标注了@Controller、@Service、@Repository、@Component注册进Spring容器中,而Spring注解之@ComponentScan的作用就是代替此功能。

@ComponentScan源码

/* * Copyright 2002-2020 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 * * https://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.Repeatable; 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.annotation.AliasFor; 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>Either {@link #basePackageClasses} or {@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 that declares 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 @Configuration}'s Javadoc for usage examples. * * @author Chris Beams * @author Juergen Hoeller * @author Sam Brannen * @since 3.1 * @see Configuration */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Repeatable(ComponentScans.class) public @interface ComponentScan { /** * Alias for {@link #basePackages}. * <p>Allows for more concise annotation declarations if no other attributes * are needed &mdash; for example, {@code @ComponentScan("org.my.pkg")} * instead of {@code @ComponentScan(basePackages = "org.my.pkg")}. */ @AliasFor("basePackages") 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. */ @AliasFor("value") 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) * @see AnnotationBeanNameGenerator * @see FullyQualifiedAnnotationBeanNameGenerator */ 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. * <p>Note that these filters will be applied in addition to the default filters, if specified. * Any type under the specified base packages which matches a given filter will be included, * even if it does not match the default filters (i.e. is not annotated with {@code @Component}). * @see #resourcePattern() * @see #useDefaultFilters() */ Filter[] includeFilters() default {}; /** * Specifies which types are not eligible for component scanning. * @see #resourcePattern */ Filter[] excludeFilters() default {}; /** * Specify whether scanned beans should be registered for lazy initialization. * <p>Default is {@code false}; switch this to {@code true} when desired. * @since 4.1 */ boolean lazyInit() default false; /*** /***@Filter源码忽略 /*** }

我们来看看使用得比较多的userDefaultFilters、lazyInit、excludeFilters、includeFilters属性。

userDefaultFiters:是否使用默认的过滤规则,默认为truelazyInit:是否启用懒加载加载扫描到的BeanexcludeFiters:使用Filter,指定哪些类型不需要扫描includeFiters:使用Filter,指定哪些类型需要扫描

@Filter源码

上面的ComponentScan的excludeFiters、includeFiters都使用了Filter,在这里我们再来了解一下Filter,但是在了解@Filter的使用之前,我们需要先了解FilterType枚举。

FilterType就是我们的过滤类型,如自定义过滤类型、注解过滤类型。Filter的作用就是利用我们的多种过滤类型,指定我们怎么过滤,需要过滤哪些东西。 /** * Enumeration of the type filters that may be used in conjunction with * {@link ComponentScan @ComponentScan}. * * @author Mark Fisher * @author Juergen Hoeller * @author Chris Beams * @since 2.5 * @see ComponentScan * @see ComponentScan#includeFilters() * @see ComponentScan#excludeFilters() * @see org.springframework.core.type.filter.TypeFilter */ public enum FilterType { /** * Filter candidates marked with a given annotation. * @see org.springframework.core.type.filter.AnnotationTypeFilter */ ANNOTATION, /** * Filter candidates assignable to a given type. * @see org.springframework.core.type.filter.AssignableTypeFilter */ ASSIGNABLE_TYPE, /** * Filter candidates matching a given AspectJ type pattern expression. * @see org.springframework.core.type.filter.AspectJTypeFilter */ ASPECTJ, /** * Filter candidates matching a given regex pattern. * @see org.springframework.core.type.filter.RegexPatternTypeFilter */ REGEX, /** Filter candidates using a given custom * {@link org.springframework.core.type.filter.TypeFilter} implementation. */ CUSTOM }

此枚举的作用就是定义过滤类型,但是这里我们只讲解使用频率较高注解和自定义过滤类型,其他的多种过滤类型大家可以去参考官方文档,自行摸索。

/** * 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. * <p>Default is {@link FilterType#ANNOTATION}. * @see #classes * @see #pattern */ FilterType type() default FilterType.ANNOTATION; /** * Alias for {@link #classes}. * @see #classes */ @AliasFor("classes") Class<?>[] value() default {}; /** * The class or classes to use as the filter. * <p>The following table explains how the classes will be interpreted * based on the configured value of the {@link #type} attribute. * <table border="1"> * <tr><th>{@code FilterType}</th><th>Class Interpreted As</th></tr> * <tr><td>{@link FilterType#ANNOTATION ANNOTATION}</td> * <td>the annotation itself</td></tr> * <tr><td>{@link FilterType#ASSIGNABLE_TYPE ASSIGNABLE_TYPE}</td> * <td>the type that detected components should be assignable to</td></tr> * <tr><td>{@link FilterType#CUSTOM CUSTOM}</td> * <td>an implementation of {@link TypeFilter}</td></tr> * </table> * <p>When multiple classes are specified, <em>OR</em> logic is applied * &mdash; for example, "include types annotated with {@code @Foo} OR {@code @Bar}". * <p>Custom {@link TypeFilter TypeFilters} may optionally implement any of the * following {@link org.springframework.beans.factory.Aware Aware} interfaces, and * their respective methods will be called prior to {@link TypeFilter#match match}: * <ul> * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li> * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware} * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware} * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware} * </ul> * <p>Specifying zero classes is permitted but will have no effect on component * scanning. * @since 4.2 * @see #value * @see #type */ @AliasFor("value") Class<?>[] classes() default {}; /** * The pattern (or patterns) to use for the filter, as an alternative * to specifying a Class {@link #value}. * <p>If {@link #type} is set to {@link FilterType#ASPECTJ ASPECTJ}, * this is an AspectJ type pattern expression. If {@link #type} is * set to {@link FilterType#REGEX REGEX}, this is a regex pattern * for the fully-qualified class names to match. * @see #type * @see #classes */ String[] pattern() default {}; }

Filter中的type指定过滤类型,classes是指定的类为value属性的别名。

@ComponentScan实战

@实体类创建 package org.example.componentscan; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @author shiKui */ @Data @AllArgsConstructor @NoArgsConstructor public class User { private String username; } Controller创建 package org.example.componentscan; import org.springframework.stereotype.Controller; @Controller public class UserController { } UserDao创建 package org.example.componentscan; import org.springframework.stereotype.Repository; @Repository public class UserDao { } UserService创建 package org.example.componentscan; import org.springframework.stereotype.Service; @Service public class UserService { } 配置类创建 @ComponentScan(value = "org.example.componentscan",useDefaultFilters = false,lazyInit = true, excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class}), includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})) @Configuration public class ComponentScanConfig { @Bean public User user() { return new User(); } }

指定扫描org.example.componentscan 不使用自定义的过滤器 开启懒加载加载Bean 使用excludeFilters,基于注解的方式排除Conntroller 使用includeFilters,基于注解的方式添加Controller,Service

测试 @Test public void componentScanAnnotationTest(){ AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ComponentScanConfig.class); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); for (String beanName : beanDefinitionNames) { System.out.println("beanName:" + beanName + "===" + "bean对象:" + applicationContext.getBean(beanName)); } } 测试结果 我们可以看到Controller被排除了,@Service注册了,因为我们没有使用默认的过滤方式,所以Dao也没有注册进来。

自定义类型过滤实战

自定义过滤类型需要实现TypeFilter接口,我们来看看TypeFilter接口的源码

/* * Copyright 2002-2016 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 * * https://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.core.type.filter; import java.io.IOException; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; /** * Base interface for type filters using a * {@link org.springframework.core.type.classreading.MetadataReader}. * * @author Costin Leau * @author Juergen Hoeller * @author Mark Fisher * @since 2.5 */ @FunctionalInterface public interface TypeFilter { /** * Determine whether this filter matches for the class described by * the given metadata. * @param metadataReader the metadata reader for the target class * @param metadataReaderFactory a factory for obtaining metadata readers * for other classes (such as superclasses and interfaces) * @return whether this filter matches * @throws IOException in case of I/O failure when reading metadata */ boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException; }

这个接口是个函数式接口,match方法如果返回true就注册,如果false反之。

实现TypeFilter接口 package org.example.componentscan; 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 shiKui */ public class MyFilterType implements TypeFilter { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { return true; } }

所有匹配到的都返回true,也就是都能注册进Spring

重新定义一个配置类 package org.example.componentscan; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; @Configuration @ComponentScan(value = "org.example.componentscan",includeFilters = @ComponentScan.Filter(type = FilterType.CUSTOM,classes = MyFilterType.class),useDefaultFilters = false) public class ComponentScanCustomConfig { }

扫描org.example.componentscan,不使用默认的过滤,使用includeFilters 属性通过我们自定义的过滤器,注册组件。

测试方法 @Test public void componentScanCustomTest(){ AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ComponentScanCustomConfig.class); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); for (String beanName : beanDefinitionNames) { System.out.println("beanName:" + beanName + "===" + "bean对象:" + applicationContext.getBean(beanName)); } } 测试结果 结果是意料之中。 至此相信大家对@ComponentScan也有一定的了解了,如果大家想更深入的了解,可以跟着Spring的官方文档、源码去一步步深入了解。 至此,如果我哪里不对,或者大家有什么想法可以在评论中提出。
最新回复(0)