由于篇幅很长,所以将思路和实现分开了,本篇幅是自定义扫描的实现。
自定义扫描的思路及原理在我的另一篇文章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
}
}
我们访问可以匹配到的路径,可以发现拦截器生效了,没有匹配到的路径访问则没有生效。
嗯,标特否!