【Spring【IOC】】——3、自定义TypeFilter指定@ComponentScan注解的过滤规则

在这里插入图片描述

📫作者简介:zhz小白
公众号:小白的Java进阶之路
专业技能:
1、Java基础,并精通多线程的开发,熟悉JVM原理
2、熟悉Java基础,并精通多线程的开发,熟悉JVM原理,具备⼀定的线上调优经验
3、熟悉MySQL数据库调优,索引原理等,⽇志原理等,并且有出过⼀篇专栏
4、了解计算机⽹络,对TCP协议,滑动窗⼝原理等有⼀定了解
5、熟悉Spring,Spring MVC,Mybatis,阅读过部分Spring源码
6、熟悉SpringCloud Alibaba体系,阅读过Nacos,Sentinel,Seata,Dubbo,Feign,Gateway核⼼源码与设计,⼆次开发能⼒
7、熟悉消息队列(Kafka,RocketMQ)的原理与设计
8、熟悉分库分表ShardingSphere,具有真实⽣产的数据迁移经验
9、熟悉分布式缓存中间件Redis,对其的核⼼数据结构,部署架构,⾼并发问题解决⽅案有⼀定的积累
10、熟悉常⽤设计模式,并运⽤于实践⼯作中
11、了解ElasticSearch,对其核⼼的原理有⼀定的了解
12、了解K8s,Jekins,GitLab
13、了解VUE,GO
14、⽬前有正在利⽤闲暇时间做互游游戏,开发、运维、运营、推销等

本人著作git项目:https://gitee.com/zhouzhz/star-jersey-platform,有兴趣的可以私聊博主一起编写,或者给颗star
领域:对支付(FMS,FUND,PAY),订单(OMS),出行行业等有相关的开发领域
🔥如果此文还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主~

FilterType中常用的规则

在使用@ComponentScan注解实现包扫描时,我们可以使用@Filter指定过滤规则,在@Filter中,通过type来指定过滤的类型。而@Filter注解中的type属性是一个FilterType枚举,其源码为:

public enum FilterType {

	ANNOTATION,

	ASSIGNABLE_TYPE,

	ASPECTJ,

	REGEX,

	CUSTOM

}

FilterType.ANNOTATION:按照注解进行包含或者排除(常用)

使用@ComponentScan注解进行包扫描时,如果要想按照注解只包含标注了@Controller注解的组件。

@ComponentScan(value="com.zhz", includeFilters={
		/*
		 * type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
		 * classes:我们需要Spring在扫描时,只包含@Controller注解标注的类
		 */
		@Filter(type=FilterType.ANNOTATION, classes={Controller.class})
}, useDefaultFilters=false) // value指定要扫描的包

FilterType.ASSIGNABLE_TYPE:按照给定的类型进行包含或者排除(常用)

使用@ComponentScan注解进行包扫描时,如果要想按照给定的类型只包含BookService类(接口)或其子类(实现类或子接口)的组件。

@ComponentScan(value="com.zhz", includeFilters={
		/*
		 * type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
		 */
		// 只要是BookService这种类型的组件都会被加载到容器中,不管是它的子类还是什么它的实现类。记住,只要是BookService这种类型的
		@Filter(type=FilterType.ASSIGNABLE_TYPE, classes={BookService.class})
}, useDefaultFilters=false) // value指定要扫描的包

只要是BookService这种类型的组件,都会被加载到容器中。也就是说,当BookService是一个Java类时,该类及其子类都会被加载到Spring容器中;当BookService是一个接口时,其子接口或实现类都会被加载到Spring容器中。

FilterType.ASPECTJ:按照ASPECTJ表达式进行包含或者排除(很少用)

使用@ComponentScan注解进行包扫描时,按照正则表达式进行过滤。

@ComponentScan(value="com.zhz", includeFilters={
		/*
		 * type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
		 */
		@Filter(type=FilterType.ASPECTJ, classes={AspectJTypeFilter.class})
}, useDefaultFilters=false) // value指定要扫描的包

FilterType.REGEX:按照正则表达式进行包含或者排除(很少用)

使用@ComponentScan注解进行包扫描时,按照正则表达式进行过滤

@ComponentScan(value="com.meimeixia", includeFilters={
		/*
		 * type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
		 */
		@Filter(type=FilterType.REGEX, classes={RegexPatternTypeFilter.class})
}, useDefaultFilters=false) // value指定要扫描的包

FilterType.CUSTOM:按照自定义规则进行包含或者排除

  • 如果实现自定义规则进行过滤时,自定义规则的类必须是org.springframework.core.type.filter.TypeFilter接口的实现类
  • 按照自定义规则进行过滤,首先我们得创建org.springframework.core.type.filter.TypeFilter接口的一个实现类,例如MyTypeFilter
package com.zhz.filter;

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 zhouhengzhe
 * @description: 自定义规则
 * @date 2022/11/4 22:49
 * @since v1
 */
public class MyTypeFilter implements TypeFilter {

    /**
     * 参数:
     * metadataReader:读取到的当前正在扫描的类的信息
     * metadataReaderFactory:可以获取到其他任何类的信息的(工厂)
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {

        // 这儿我们先让其返回false
        return false;
    }
}

  • 当我们实现TypeFilter接口时,需要实现该接口中的match()方法,match()方法的返回值为boolean类型。
  • 当返回true时,表示符合规则,会包含在Spring容器中;当返回false时,表示不符合规则,那就是一个都不匹配,自然就都不会被包含在Spring容器中。
  • 在match()方法中存在两个参数,分别为MetadataReader类型的参数和MetadataReaderFactory类型的参数
    • metadataReader:读取到的当前正在扫描的类的信息
    • metadataReaderFactory:可以获取到其他任何类的信息的工厂

使用@ComponentScan注解进行如下配置

@ComponentScan(value="com.meimeixia", includeFilters={
		/*
		 * type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
		 */
		// 指定新的过滤规则,这个过滤规则是我们自个自定义的,过滤规则就是由我们这个自定义的MyTypeFilter类返回true或者false来代表匹配还是没匹配
		@Filter(type=FilterType.CUSTOM, classes={MyTypeFilter.class})
}, useDefaultFilters=false) // value指定要扫描的包

如果FilterType枚举中的类型无法满足我们的需求时,我们也可以通过实现org.springframework.core.type.filter.TypeFilter接口来自定义过滤规则,此时,将@Filter中的type属性设置为FilterType.CUSTOM,classes属性设置为自定义规则的类所对应的Class对象。

实现自定义过滤规则

从上面可以知道,我们在项目的com.meimeixia.config包下新建了一个类,即MyTypeFilter,它实现了org.springframework.core.type.filter.TypeFilter接口。此时,我们先在MyTypeFilter类中打印出当前正在扫描的类名:

package com.zhz.filter;

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 zhouhengzhe
 * @description: 自定义规则
 * @date 2022/11/4 22:49
 * @since v1
 */
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);

        // 这儿我们先让其返回false
        return false;
    }
}

我们在MainConfig类中配置自定义过滤规则:

package com.zhz.config;

import com.zhz.bean.Person;
import com.zhz.filter.MyTypeFilter;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;

/**
 * @author zhouhengzhe
 * @description: todo
 * @date 2022/11/4 10:27
 * @since v1
 */

@ComponentScan(value = {"com.zhz"}, includeFilters = {
        /*
         * type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
         * classes:除了@Controller标注的组件之外,IOC容器中剩下的组件我都要,即相当于是我要排除@Controller和@Service这俩注解标注的组件。
         */
        @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})}, useDefaultFilters = false)
@Configuration
public class MainConfig {

    /**
     * @Bean注解是给IOC容器中注册一个bean,类型自然就是返回值的类型,id默认是用方法名作为id
     */
    @Bean(name = "person")
    public Person person1() {
        return new Person("zhz", 20);
    }
}

测试类如下:

package com.zhz.test;

import com.zhz.config.MainConfig;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author zhouhengzhe
 * @description: todo
 * @date 2022/11/4 10:58
 * @since v1
 */
public class IOCTest {

    @SuppressWarnings("resource")
    @Test
    public void test() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        // 我们现在就来看一下IOC容器中有哪些bean,即容器中所有bean定义的名字
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
    }
}

演示效果如下:
在这里插入图片描述

可以看到,已经输出了当前正在扫描的类的名称,同时,除了Spring内置的bean的名称之外,只输出了mainConfig和person,而没有输出使用@Repository、@Service、@Controller这些注解标注的组件的名称。这是因为当前MainConfig类上标注的@ComponentScan注解是使用的自定义规则,而在自定义规则的实现类(即MyTypeFilter类)中,直接返回了false,那么就是一个都不匹配了,自然所有的bean就都没被包含进去容器中了。

我们可以在MyTypeFilter类中简单的实现一个规则,例如,当前扫描的类名称中包含有"er"字符串的,就返回true,否则就返回false。此时,MyTypeFilter类中match()方法的实现代码:

package com.zhz.filter;

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 zhouhengzhe
 * @description: 自定义规则
 * @date 2022/11/4 22:49
 * @since v1
 */
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);
        if (className.contains("er")){
            return true;
        }
        // 这儿我们先让其返回false
        return false;
    }
}

在com.zhz包下的所有类都会通过MyTypeFilter类中的match()方法来验证类名中是否包含有"er"字符串,若包含则返回true,否则返回false。
在这里插入图片描述

我们可以发现在com.zhz下只要包含er的Bean都扫进去了,当然有一个比较特殊的类,她就是MainConfig,他是一定会被扫进去的,因为他觉定Person类能不能扫进去。所以该包下的每一个类都会进到这个自定义规则里面进行匹配,若匹配成功,则就会被包含在容器中。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,根据你的需求,我可以给出一个简单的实现方案,具体步骤如下: 1. 定义三个注解:@Component、@Autowired和@Configuration。 @Component注解用于标记需要被IoC容器管理的Bean组件,@Autowired注解用于标记需要被注入的对象,@Configuration注解用于标记配置类。 ``` @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Component { } ``` ``` @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { } ``` ``` @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Configuration { } ``` 2. 定义一个注解扫描器@ComponentScan,用于指定需要扫描的包路径。 ``` @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface ComponentScan { String[] value() default {}; } ``` 3. 定义IoC容器类AnnotationConfigApplicationContext,实现以下功能: - 扫描指定包下的所有类,并将所有被@Component注解的类实例化并加入IoC容器中; - 根据@Autowired注解自动注入依赖关系; - 支持@Configuration注解,即使用@Configuration注解标记的类应该被解析为配置类,并按照其中的@Bean方法实例化并加入IoC容器中; - 支持@ComponentScan注解,即指定需要扫描的包路径。 ``` public class AnnotationConfigApplicationContext { private Map<String, Object> beans = new HashMap<>(); public AnnotationConfigApplicationContext(Class<?> configClass) throws Exception { // 扫描包路径 ComponentScan componentScan = configClass.getDeclaredAnnotation(ComponentScan.class); String[] basePackages = componentScan.value(); List<Class<?>> classes = new ArrayList<>(); for (String basePackage : basePackages) { classes.addAll(getClasses(basePackage)); } // 实例化被@Component标注的类 for (Class<?> clazz : classes) { if (clazz.isAnnotationPresent(Component.class)) { Object bean = clazz.getDeclaredConstructor().newInstance(); beans.put(clazz.getName(), bean); } } // 自动注入被@Autowired标注的属性 for (Object bean : beans.values()) { for (Field field : bean.getClass().getDeclaredFields()) { if (field.isAnnotationPresent(Autowired.class)) { Object dependency = beans.get(field.getType().getName()); field.setAccessible(true); field.set(bean, dependency); } } } // 实例化@Configuration标注的类中的@Bean方法 for (Class<?> clazz : classes) { if (clazz.isAnnotationPresent(Configuration.class)) { Object config = clazz.getDeclaredConstructor().newInstance(); for (Method method : clazz.getDeclaredMethods()) { if (method.isAnnotationPresent(Bean.class)) { Object bean = method.invoke(config); beans.put(bean.getClass().getName(), bean); } } } } } public Object getBean(Class<?> clazz) { return beans.get(clazz.getName()); } private List<Class<?>> getClasses(String packageName) throws Exception { List<Class<?>> classes = new ArrayList<>(); String path = packageName.replace(".", "/"); URL url = Thread.currentThread().getContextClassLoader().getResource(path); File directory = new File(url.toURI()); if (directory.exists()) { File[] files = directory.listFiles(); for (File file : files) { String fileName = file.getName(); if (fileName.endsWith(".class")) { String className = packageName + "." + fileName.substring(0, fileName.length() - 6); Class<?> clazz = Class.forName(className); classes.add(clazz); } } } return classes; } } ``` 4. 自定义两个业务类Group和User,并在类上使用@Component注解进行标记。 ``` @Component public class Group { private String name; public Group() { this.name = "defaultGroup"; } public Group(String name) { this.name = name; } public String getName() { return name; } } ``` ``` @Component public class User { private String name; @Autowired private Group group; public User() { this.name = "defaultUser"; } public User(String name) { this.name = name; } public String getName() { return name; } public Group getGroup() { return group; } } ``` 5. 创建一个测试类Test,通过AnnotationConfigApplicationContext类实例化IoC容器,并从容器中获取Group和User实例进行测试。 ``` public class Test { public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); Group group = (Group) context.getBean(Group.class); System.out.println(group.getName()); User user = (User) context.getBean(User.class); System.out.println(user.getName()); System.out.println(user.getGroup().getName()); } } ``` 6. 创建一个配置类AppConfig,使用@Configuration和@Bean注解来实例化Bean组件。 ``` @Configuration @ComponentScan("com.example") public class AppConfig { @Bean public Group group() { return new Group("testGroup"); } @Bean public User user() { return new User("testUser"); } } ``` 通过以上步骤,可以实现一个简单的IoC容器以及依赖注入功能。注:以上代码仅供参考,可能存在错误和不足之处,需要根据实际情况进行调整和完善。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhz小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值