spring自定义扫描实现

由于篇幅很长,所以将思路和实现分开了,本篇幅是自定义扫描的实现。

自定义扫描的思路及原理在我的另一篇文章https://blog.csdn.net/weixin_43795033/article/details/109516474

业务需求:

自定义一个注解@MyMapping使当spring启动的时候自动扫描到环境中所有加了@MyMapping注解的Controller的方法

的url路径,并注册一个拦截器或者过滤器去拦截/忽略这些动态匹配到的路由。

首先定义注解@MyMapping

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyMapping {

    String value() default "";

    String comment() default "";
}

接着定义一个扫描注解@MyMapperScan

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({MyUrlImportBeanDefinitionRegistrar.class})
public @interface MyMapperScan {

    @AliasFor("basePackage")
    String value() default "";

    @AliasFor("value")
    String basePackage() default "";
}

然后在启动类或者一个有@Configuration注解的类上面加上@MyMapperScan

//路径自己写
@MyMapperScan("xxx.**.xxx")
@SpringBootApplication
public class AppConfig {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(AppConfig.class, args);

//        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//        context.register(AppConfig.class);
//        context.refresh();
    }
}

然后定义拦截器MyInterceptor

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("拦截器执行!");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("拦截器结束!");
    }
}

然后定义MyUrlClassPathBeanDefinitionScanner自定义扫描器

public class MyUrlClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {

    private Class<? extends Annotation> annotationClass;
    private Set<String> URLS = new HashSet<>();

    static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
    @Nullable
    private ResourcePatternResolver resourcePatternResolver;
    private String resourcePattern = DEFAULT_RESOURCE_PATTERN;

    public MyUrlClassPathBeanDefinitionScanner(boolean useDefaultFilters, Class<? extends Annotation> annotationClass) {
        super(useDefaultFilters);
        this.annotationClass = annotationClass;
        registerFilters();
    }

    public MyUrlClassPathBeanDefinitionScanner(boolean useDefaultFilters) {
        super(useDefaultFilters);
        registerFilters();
    }

    private void registerFilters() {
        //放行指定annotationClass的类型
        if (this.annotationClass != null) {
            this.addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
        } else {
            //放行所有的类型
            this.addIncludeFilter(new TypeFilter() {
                @Override
                public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                    return true;
                }
            });
        }
//        this.addExcludeFilter(new TypeFilter() {
//            @Override
//            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//                return false;
//            }
//        });
    }

    private ResourcePatternResolver getResourcePatternResolver() {
        if (this.resourcePatternResolver == null) {
            this.resourcePatternResolver = new PathMatchingResourcePatternResolver();
        }
        return this.resourcePatternResolver;
    }

    public void doScan(String... basePackages) {
        for (String basePackage : basePackages) {
            scanCandidateComponents(basePackage);
        }
    }

    private void scanCandidateComponents(String basePackage) {
        try {
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    resolveBasePackage(basePackage) + '/' + this.resourcePattern;
            Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
            for (Resource resource : resources) {
                if (resource.isReadable()) {
                    try {
                        MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                        if (isCandidateComponent(metadataReader)) {
                            createUrls(metadataReader.getAnnotationMetadata().getClassName());
                        }
                    } catch (Throwable ex) {
                        throw new BeanDefinitionStoreException(
                                "Failed to read candidate component class: " + resource, ex);
                    }
                }
            }
        } catch (IOException ex) {
            throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
        }
    }

    private void createUrls(String beanClassName) {
        try {
            Class aClass = Class.forName(beanClassName);
            RequestMapping requestMapping = (RequestMapping) aClass.getAnnotation(RequestMapping.class);
            GetMapping getMapping;
            PostMapping postMapping;
            String[] path_parents = requestMapping.value();
            String[] paths;
            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                MyUrl myUrl = method.getAnnotation(MyMapping.class);
                if (myUrl != null) {
                    requestMapping = method.getAnnotation(RequestMapping.class);
                    if (requestMapping == null) {
                        getMapping = method.getAnnotation(GetMapping.class);
                        if (getMapping == null) {
                            postMapping = method.getAnnotation(PostMapping.class);
                            if (postMapping == null) {
                                paths = null;
                            } else {
                                paths = postMapping.value();
                            }
                        } else {
                            paths = getMapping.value();
                        }
                    } else {
                        paths = requestMapping.value();
                    }
                    if (paths == null) {
                        continue;
                    } else {
                        for (String path_parent : path_parents) {
                            for (String path : paths) {
                                URLS.add("/" + path_parent + "/" + path);
                            }
                        }
                    }
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public Set<String> getURLS() {
        return URLS;
    }
}

然后定义一个配置类去作为注册我们拦截器的包装类

public class MyInterceptorConfig implements WebMvcConfigurer {
    private final HandlerInterceptor interceptor;
    private final List<String> urls;

    public MyInterceptorConfig(HandlerInterceptor interceptor, List<String> urls) {
        this.interceptor = interceptor;
        this.urls = urls;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor).addPathPatterns(urls);
    }
}

接着定义一个执行扫描的切入点MyUrlImportBeanDefinitionRegistrar

public class MyUrlImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    private ResourceLoader resourceLoader;
    private Environment environment;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    /*此方法会在spring自定义扫描执行之后执行,这个时候beanDefinitionMap已经有扫描到的beanDefinition对象了*/
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MyMapperScan.class.getName()));
        MyUrlClassPathBeanDefinitionScanner scanner = new MyUrlClassPathBeanDefinitionScanner(false, Controller.class);
        if (this.resourceLoader != null) {
            scanner.setResourceLoader(resourceLoader);
        }
        if (this.environment != null) {
            scanner.setEnvironment(environment);
        }
        //扫描到所有的满足要求的bean的路径
        scanner.doScan(attributes.getStringArray("basePackage"));
        //随便起一个bean的名字(不能与其他beanName重复)
        String beanName = "myInterceptorConfig";
        //构造beanDefinition
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        //beanDefinition的beanClass
        beanDefinition.setBeanClass(MyInterceptorConfig.class);
        //给beanDefintion的beanClass构造方法传入参数
        ConstructorArgumentValues constructorArgumentValues = beanDefinition.getConstructorArgumentValues();
        constructorArgumentValues.addIndexedArgumentValue(0, new MyInterceptor());
        constructorArgumentValues.addIndexedArgumentValue(1, new ArrayList<String>(scanner.getURLS()));
        //注册这个beanDefinition,spring会在之后的流程中去调用getBean方法创建这个bean
        registry.registerBeanDefinition(beanName, beanDefinition);
    }
}

 

至此我们的动态注册自定义路径拦截器的需求就完成了

我们测试一下:

@RestController
@RequestMapping("myTest")
public class TestController {
    @MyMapping
    @GetMapping("test")
    public void test() {
    //会被匹配到
    }
    @MyMapping
    @GetMapping("test1")
    public void test1() {
    //会被匹配到
    }
    @MyMapping
    public void test2() {
    //不会被匹配到,因为没有@GetMapping、@PostMapping、@RequestMapping
    }
    @GetMapping("test3")
    public void test3() {
    //不会被匹配到,因为没有@MyMapping
    }
}

我们访问可以匹配到的路径,可以发现拦截器生效了,没有匹配到的路径访问则没有生效。

嗯,标特否!

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值