ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2021-04-30 20:57:46  阅读:186  来源: 互联网

标签:Service Repository Spring class public test ComponentScan com zzb


到目前为止,介绍了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

现在的目录结构是这样的:
在这里插入图片描述
在此结构上做修改:
如果将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用一样。这里不进行演示了,有兴趣的可以自己写写看。

标签:Service,Repository,Spring,class,public,test,ComponentScan,com,zzb
来源: https://blog.csdn.net/whatname123/article/details/116302166

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有