Spring第四篇-@ComponentScan、 @ComponentScans、@Component、@Repository、@Service、@Controller

到目前为止,介绍了2种注册bean的方式:

  1. xml中bean元素的方式
  2. @Bean注解标注方法的方式

如果有很多类都需要注册bean呢? 手动分别为每个类添加@confiuration和@Bean?
这也太麻烦了。

@ComponentScan

@ComponentScan用于批量注册bean
这个注解的源码:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
	@AliasFor("basePackages")
	String[] value() default {};
	@AliasFor("value")
	String[] basePackages() default {};
	Class<?>[] basePackageClasses() default {};
	Class<? extends BeanNameGenerator> nameGenerator() default
	BeanNameGenerator.class;
	Class<? extends ScopeMetadataResolver> scopeResolver() default
	AnnotationScopeMetadataResolver.class;
	ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
	String resourcePattern() default "**/*.class";
	boolean useDefaultFilters() default true;
	Filter[] includeFilters() default {};
	Filter[] excludeFilters() default {};
	boolean lazyInit() default false;
}

ElementType.TYPE可用在任何类型上面,不过我们通常用在类上面

参数:
value、basePackages:指的是要扫描的包 如 com.zzb.pojo
nameGenerator:自定义bean名称生成器
resourcePattern:需要扫描包中的那些资源,默认是:**/*.class,即会扫描指定包中所有的class文件
useDefaultFilters:对扫描的类是否启用默认过滤器,默认为true
includeFilters:过滤器:过滤被扫描的类,判断哪些放行进行注册
excludeFilters:过滤器,和includeFilters作用相反,对扫描的类进行排除,被排除的类不被注册到容器中
lazyInit:是否延迟初始化被注册的bean
其他参数会在后面提及。

@ComponentScan工作的过程:

  1. Spring会扫描指定的包,且会递归下面子包,得到一批类的数组
  2. 然后这些类会经过上面的各种过滤器,最后剩下的类会被注册到容器中

总结:

  1. value、backPackages、basePackageClasses 这3个参数来控制需要扫描哪些包,来获取类数组
  2. useDefaultFilters、includeFilters、excludeFilters 这3个参数来控制过滤器

默认情况下,任何参数都不设置的情况下,此时,会将@ComponentScan修饰的类所在的包作为扫描包;默认情况下useDefaultFilters为true,这个为true的时候,spring容器内部会使用默认过滤器。

规则是:凡是类上有 @Repository、@Service、@Controller、@Component 这几个注解中的任何一个的,那么这个类就会被作为bean注册到spring容器中,所以默认情况下,只需在类上加上这几个注解中的任何一个,这些类就会自动交给spring容器来管理了。

ComponentScan就像一个扫描器,扫描其所在的包中的以下注解。

另外@Repeatable(ComponentScans.class)修饰,可以看出此**@ComponentScan可以重复使用**,ComponentScans是其容器。关于@Repeatable在上一篇中已讲,如何重复使用也在上一篇中讲了。

@Component、@Repository、@Service、@Controller

@Component

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
	String value() default "";
}

通常情这个注解用在类上面,标注这个类为一个组件,默认情况下,被扫描的时候会被作
为bean注册到容器中。
value参数:被注册为bean的时候,用来指定bean的名称,如果不指定,默认为类名首字母小
写。如:类UserService对应的beanname为userService

@Repository

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(annotation = Component.class)
	String value() default "";
}

Repository上面有@Component注解。
value参数上面有 @AliasFor(annotation = Component.class) ,设置value参数的时候,也相当于给 @Component 注解中的value设置值。

@Service、@Controller 源码和 @Repository 源码类似。都是继承了Component.class

所以说这几个注解本质上是一样的,本质完全相同,只是名字不同

spring提供这4个注解,是为了让系统进行分层,controller层、service层、dao层。

@controller通常用来标注controller层组件,@service注解标注service层的组件,@Repository标注dao层的组件,这样可以让整个系统的结构更清晰,当看到这些注解的时候,会和清晰的知道属于哪个层,对于spring来说,将这3个注解替换成@Component注解,对系统没有任何影响,产生的效果是一样的。

举个例子:
当com.zzb.test包中有个类被@ComponentScan修饰时,这个类作为了扫描类。扫描类所在包及其以下的包,如com.zzb.test.dao包、com.zzb.test.service包、com.zzb.test.controller包,其中被@Component、@Repository、@Service、@Controller修饰的类都会被扫描到,并注册Bean.
这里就光贴个测试类了,其他类就不贴了

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ComponentScanTest {
	@Test
	public void test1() {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean1.class);
		for (String beanName : context.getBeanDefinitionNames()) {
			System.out.println(beanName + "->" + context.getBean(beanName));
		}
	}
}

@ComponentScan的使用

@ComponentScan除了可以无参使用,直接使用默认外,还有其他使用方法。
无参使用就是方法一。
方法2:(直接指定包)

@ComponentScan({
	"com.zzb.test.controller",
	"com.zzb.test.service"
})

@ComponentScan的basePackageClasses参数

指定扫描范围

案例:
在com.zzb.test.beans包中定义一个接口ScanClass

package com.zzb.test.beans;
public interface ScanClass {
}

同样的包下,再来两个Bean类

package com.zzb.test.beans;
@Component
public class Bean1{
}
package com.zzb.test.beans;
@Component
public class Bean2{
}

在com.zzb.test包下定义一个扫描类
并指定ScanClass 接口为basePackageClasses 的值
@ComponentScan(basePackageClasses = ScanClass.class)

package com.zzb.test;
@ComponentScan(basePackageClasses = ScanClass.class)
public class ScanBean {
}

测试类:

package com.zzb.test;
import com.zzb.test.annotation.ScanBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean.class);
        String[] names = context.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name + "->" + context.getBean(name));
        }

    }
}

输出:(部分)

scanBean->com.zzb.test.beans.ScanBean@53f65459
bean1->com.zzb.test.beans.Bean1@3b088d51
bean2->com.zzb.test.beans.Bean2@1786dec2

现在的目录结构是这样的:
Spring第四篇-@ComponentScan、 @ComponentScans、@Component、@Repository、@Service、@Controller
在此结构上做修改:
如果将ScanBean放入abc包中,依然可以检测到并注册Bean1和Bean2
如果将ScanBean放入abc包中,并且@ComponentScan设置无参,不可检测到Bean
如果将ScanClass放入abc包中,不可检测到Bean
如果将ScanClass放入test包中,依然可以检测到并注册Bean1和Bean2

总结:

  1. @ComponentScan无参数时,其所在同级目录和下级目录的包可扫描到。其他目录无法扫描到。
  2. @ComponentScan的basePackageClasses参数指定的接口或者类,其所在同级目录或下级目录中的包可以扫描到。
  3. 配置basePackageClasses后,默认扫描失效,@ComponentScan其所在同级目录或下级目录中的包不再进行扫描,除非basePackageClasses指定了相关类。

@ComponentScan的includeFilters参数

Filter[] includeFilters() default {};

是一个 Filter 类型的数组,多个Filter之间为或者关系,即满足任意一个就可以了。
Filter:也是一个注解

@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
FilterType type() default FilterType.ANNOTATION;
	@AliasFor("classes")
	Class<?>[] value() default {};
	@AliasFor("value")
	Class<?>[] classes() default {};
	String[] pattern() default {};
}

参数:
type:枚举类型,5种

public enum FilterType {
    ANNOTATION,//通过注解的方式来筛选候选者
    ASSIGNABLE_TYPE,//通过指定的类型来筛选候选者
    ASPECTJ,//ASPECTJ表达式方式
    REGEX,//正则表达式方式
    CUSTOM;//用户自定义过滤器来筛选候选者

    private FilterType() {
    }
}

value、classes:

  1. 当type=FilterType.ANNOTATION时,通过classes参数可以指定一些注解,用来判断被扫描的类上是否有classes参数指定的注解。
  2. 当type=FilterType.ASSIGNABLE_TYPE时,通过classes参数可以指定一些类型,用来判断被扫描的类是否是classes参数指定的类型。
  3. 当type=FilterType.CUSTOM时,表示这个过滤器是用户自定义的,classes参数就是用来指定用户自定义的过滤器,自定义的过滤器需要实现org.springframework.core.type.filter.TypeFilter接口

pattern:2种情况如下
4. 当type=FilterType.ASPECTJ时,通过pattern来指定需要匹配的ASPECTJ表达式的值
5. 当type=FilterType.REGEX时,通过pattern来自正则表达式的值

扫描包含指定注解的类

案例:自定义一个注解,让标注有这些注解的类自动注册到容器中。

自定义注解 type=FilterType.ANNOTATION

package com.zzb.test;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyBean {
	@AliasFor(annotation = Component.class)
	String value() default "";//这个参数使我们可以配置Bean的注册名称
	//@AliasFor注解在上一篇文章中已讲。
}

Bean类 一个自定义注解修饰,一个@Component修饰

package com.zzb.test;

@MyBean
public class Service1 {
}

扫描类

package com.zzb.test;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;

@ComponentScan(includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes =
MyBean.class)
})
public class ScanBean {
}
package com.zzb.test;

import org.springframework.stereotype.Component;

@Component
public class Service2 {
}

测试类

package com.zzb.test;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean.class);
        String[] names = context.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name + "->" + context.getBean(name));
        }
    }
}

输出:

scanBean->com.zzb.test.ScanBean@17579e0f
service1->com.zzb.test.Service1@4d41cee
service2->com.zzb.test.Service2@3712b94

两个Bean都注册到容器中了,原因:
@CompontentScan注解中的 useDefaultFilters默认是 true ,表示会启用默认的过滤器。
同时includeFilters 参数又添加了一个过滤器。
这两个过滤器以 或 的方式起作用,满足一个就会被注册。

扫描包含指定类型的类

指定类型,接口、类都行。用接口的话Bean类就实现,用类就继承。这里不进行演示了,有兴趣的可以自己写写看。
在这只贴个扫描类上的注解了。以接口为例。

@ComponentScan(
	useDefaultFilters = false, //不启用默认过滤器
	includeFilters = {
		@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = 指定接口实现类.class) //@1
	}
)

自定义Filter

自定义的过滤器,使用自定义过滤器的步骤:

  1. 设置@Filter中type的类型为:FilterType.CUSTOM
  2. 自定义过滤器类,需要实现接口:org.springframework.core.type.filter.TypeFilter
  3. 设置@Filter中的classses为自定义的过滤器类型

TypeFilter

@FunctionalInterface
public interface TypeFilter {
	boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException;
}

@FunctionalInterface
规定规定此接口为函数式接口
函数式接口:一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口
ps:jdk8之后接口中可以实现默认方法和静态方法了。
match方法,2个参数都是接口类型,返回值boolean类型。

MetadataReader 接口
类元数据读取器,可以读取一个类上的任意信息,如类上面的注解信息、类的磁盘路径信息、类的class对象的各种信息,spring进行了封装,提供了各种方便使用的方法
MetadataReaderFactory 接口
类元数据读取器工厂,可以通过这个类获取任意一个类的MetadataReader对象。

自定义Filter案例:
自定义的Filter,判断被扫描的类如果是 User 接口类型的,就让其注册到容器中
接口

package com.zzb.test;
public interface User {
}

Bean

package com.zzb.test;
public class User1 implements User{
}

package com.zzb.test;
public class User2 implements User{
}

自定义TypeFilter实现类MyFilter 即:自定义过滤器

package com.zzb.test;

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;

public class MyFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        Class getClass = null;
        try {
            //当前被扫描的类
            getClass = Class.forName(metadataReader.getClassMetadata().getClassName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        //判断getClass是否是规定类型
        boolean result = User.class.isAssignableFrom(getClass);
        return result;
    }
}

扫描类

package com.zzb.test;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;

@ComponentScan(
    basePackages = {"com.zzb.test"},
    useDefaultFilters = false, //不启用默认过滤器
    includeFilters = {
            @ComponentScan.Filter(type = FilterType.CUSTOM, classes =
                    MyFilter.class)
    })
public class ScanBean {

}

测试类还是用前面写的,没变化。
输出:

user1->com.zzb.test.User1@1786dec2
user2->com.zzb.test.User2@74650e52

@ComponentScan的excludeFilters参数

配置排除的过滤器,满足这些过滤器的类不会被注册到容器中,用法上面和includeFilters用一样。这里不进行演示了,有兴趣的可以自己写写看。

上一篇:spring___AOP方式一 使用spring api


下一篇:C# 依赖注入与控制反转