自定义注解扫描方法

import com.huang.mapper.TestAnnotation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;

import java.lang.annotation.Annotation;
import java.util.*;

/**
 * @author a1
 */

public class SectionRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {

    Logger LOG = LoggerFactory.getLogger(SectionRegister.class);

    private static Map<String, TestAnnotation> SECTION_MAP = new HashMap<String, TestAnnotation>();
    public  void setSectionMap(Map<String, TestAnnotation> sectionMap) {
        SECTION_MAP = sectionMap;
    }
    public Map<String, TestAnnotation> getSectionMap(){
        return SECTION_MAP;
    }
    private ResourceLoader resourceLoader;

    private ClassLoader classLoader;

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

    }
    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;

    }
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        logPackageScan(importingClassMetadata);
        registerSections(importingClassMetadata, registry);

    }

    private void logPackageScan(AnnotationMetadata metadata) {
        Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(SectionScan.class.getName(), true);
        if (defaultAttrs != null && defaultAttrs.size() > 0) {
            LOG.info("section package scan: " + buildPackages((String[]) defaultAttrs.get("basePackages")));
        }
    }

    private String buildPackages(String[] basePackages) {
        if (basePackages == null || basePackages.length == 0) {
            return "null";
        }
        StringBuilder stringBuilder = new StringBuilder();
        for (String s : basePackages) {
            stringBuilder.append(s).append(",");
        }
        stringBuilder.substring(0, stringBuilder.length() - 2);
        return stringBuilder.toString();
    }

    public void registerSections(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.setResourceLoader(this.resourceLoader);
        Set<String> basePackages;
        Map<String, Object> attrs = metadata.getAnnotationAttributes(SectionScan.class.getName());
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(TestAnnotation.class);
        scanner.addIncludeFilter(annotationTypeFilter);
        basePackages = getBasePackages(metadata);

        Map<String, TestAnnotation> sectionMap = new HashMap<String, TestAnnotation>();
        for (String basePackage : basePackages) {
            Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
            ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
            try {
                // 这里特别注意一下类路径必须这样写
                // 获取指定包下的所有类
                basePackage = basePackage.replace(".", "/");
                Resource[] resources = resourcePatternResolver.getResources( ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +basePackage+"**/*.class");
                MetadataReaderFactory metadata1 = new SimpleMetadataReaderFactory();
                for (Resource resource : resources) {
                    MetadataReader metadataReader = metadata1.getMetadataReader(resource);
                    ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                    sbd.setResource(resource);
                    sbd.setSource(resource);
                    candidates.add(sbd);
                }
                for (BeanDefinition beanDefinition : candidates) {
                    String classname = beanDefinition.getBeanClassName();
                    // 扫描Section注解
                    TestAnnotation s = Class.forName(classname).getAnnotation(TestAnnotation.class);
                    if (s != null) {
                        sectionMap.put(classname, s);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        //使用容器存储扫描出来的对象(类全限定名:section对象)
        setSectionMap(sectionMap);
        if(sectionMap!=null&&sectionMap.size()>0){
            RootBeanDefinition beanDefinition = new RootBeanDefinition(SectionRegister.class);
            beanDefinition.setSynthetic(true);
            registry.registerBeanDefinition("myAnnotation", beanDefinition);
        }
    }

    protected ClassPathScanningCandidateComponentProvider getScanner() {

        return new ClassPathScanningCandidateComponentProvider(false, this.environment) {

            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                if (beanDefinition.getMetadata().isIndependent()) {

                    if (beanDefinition.getMetadata().isInterface()
                            && beanDefinition.getMetadata().getInterfaceNames().length == 1
                            && Annotation.class.getName().equals(beanDefinition.getMetadata().getInterfaceNames()[0])) {
                        try {
                            Class<?> target = ClassUtils.forName(beanDefinition.getMetadata().getClassName(),
                                    SectionRegister.this.classLoader);
                            return !target.isAnnotation();
                        } catch (Exception ex) {
                            this.logger.error(
                                    "Could not load target class: " + beanDefinition.getMetadata().getClassName(), ex);

                        }
                    }
                    return true;
                }
                return false;

            }
        };
    }
    protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> attributes = importingClassMetadata
                .getAnnotationAttributes(SectionScan.class.getCanonicalName());
        Set<String> basePackages = new HashSet<String>();
        for (String pkg : (String[]) attributes.get("basePackages")) {
            if (pkg != null && !"".equals(pkg)) {
                basePackages.add(pkg);
            }
        }
        if (basePackages.isEmpty()) {
            basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
        }
        return basePackages;
    }


}

注解类:

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import({SectionRegister.class})
public @interface SectionScan {
    String[] basePackages() default {};
}

切面类:

import com.huang.mapper.ss.SectionRegister;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

//声明这是一个组件
@Component
//声明这是一个切面Bean
@Aspect
@Slf4j
public class ServiceAspect {
    @Autowired
    Environment environment;
    @Resource(name= "myAnnotation" )
    SectionRegister myAnnotation;

    @Pointcut("@within(com.huang.mapper.TestAnnotation)")
    public void aspect() {
    }

    @Around(value = "aspect()")
    public Object around(JoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            log.info("around :" + joinPoint);
            long end = System.currentTimeMillis();
            String[] s = joinPoint.toString().split(" ");
            log.info("property :" + s[0]+" sss2:"+s[1]);
            System.out.println(s[1].substring(0,s[1].lastIndexOf(".")));
            TestAnnotation testAnnotation = myAnnotation.getSectionMap().get(s[1].substring(0,s[1].lastIndexOf(".")));
            System.out.println(testAnnotation.datasource());
            String property = environment.getProperty(testAnnotation.datasource());
            log.info("property :" + property);
            log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms!");
        } catch (Throwable e) {
            long end = System.currentTimeMillis();
            log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " + e.getMessage());
            throw e;
        }
        return null;
    }

    //配置后置返回通知,使用在方法aspect()上注册的切入点
    @AfterReturning("aspect()")
    public void afterReturn(JoinPoint joinPoint) {
        log.info("afterReturn " + joinPoint);
    }

    //配置抛出异常后通知,使用在方法aspect()上注册的切入点
    @AfterThrowing(pointcut = "aspect()", throwing = "ex")
    public void afterThrow(JoinPoint joinPoint, Exception ex) {
        log.info("afterThrow " + joinPoint + "\t" + ex.getMessage());
    }

}

启动类


@SpringBootApplication()
@MapperScan(basePackages = "com.huang.mapper")
@SectionScan(basePackages={"com.huang.mapper"})

public class MybatisApplication {
    public static void main(String[] args) {
        SpringApplication.run(MybatisApplication.class, args);
    }

    @Bean
    public Map<String,String> getMyProperties(){
        Map<String, String> objectObjectConcurrentHashMap = new ConcurrentHashMap<>();
        return objectObjectConcurrentHashMap;
    }
}

参考原地址:https://blog.youkuaiyun.com/congcongxianshen/article/details/100585037

<think>嗯,用户问的是springdoc能否扫描自定义注解。这个问题看起来挺具体的,用户应该是个开发者,正在用springdoc做API文档生成,可能遇到了自定义注解无法被识别的情况。 从引用内容来看,用户提供了两段参考资料:一段讲springdoc的自动配置和自定义OpenAPI对象,另一段讲依赖导入。但用户真正关心的是注解扫描机制——这属于比较深入的使用场景。 springdoc底层依赖的是swagger-core的注解处理机制。需要分三层考虑:1) 原生Swagger注解(如@Operation/@Parameter)肯定支持;2) Spring原生注解(如@RequestMapping)通过springdoc适配器转换;3) 用户自定义注解需要额外处理。 用户可能以为springdoc会像Spring那样自动扫描所有注解,但实际上它只识别特定的注解集。不过解决方案是存在的:可以通过实现OpenApiCustomiser接口来手动添加注解对应的文档信息,或者用@Operation注解包装自定义注解。 考虑到用户的技术水平(能问出这个问题应该是有经验的开发者),可以在回答中适当引入扩展机制的具体代码示例。另外要提醒版本兼容性问题,因为springdoc在1.6.x和2.x版本对自定义注解的支持程度略有不同。 用户语气比较急切,可能正在调试代码,所以回答要直接给出可操作的解决方案。最后生成的拓展问题也特意选了"如何扩展"这个技术点,应该能覆盖用户后续可能遇到的疑问。 </think>Springdoc 默认**不会主动扫描或识别自定义注解**,但它提供了**扩展机制**允许你将自定义注解的效果集成到生成的 OpenAPI 文档中。以下是关键点和方法: --- ### 1. **核心机制:注解处理** Springdoc 主要依赖 **Swagger Core 库**处理注解。它原生支持 Swagger/OpenAPI 规范定义的注解(如 `@Operation`, `@Parameter`, `@Schema`),并自动扫描 **Spring Web 注解**(如 `@RestController`, `@GetMapping`, `@PathVariable`)。 **⚠️ 自定义注解需手动映射**:若你定义了如 `@CustomAuth`、`@CustomDescription` 等注解,Springdoc 不会自动理解其含义。 --- ### 2. **集成自定义注解方法** #### ✅ 方法一:包装自定义注解 在控制器方法上**叠加 `@Operation` 或 `@Parameter`** 等 Swagger 注解,显式描述行为: ```java @CustomAuth // 你的自定义注解 @Operation(summary = "需认证的接口", description = "@CustomAuth 要求用户登录") @GetMapping("/secure") public String secureEndpoint() { ... } ``` #### ✅ 方法二:实现 `OpenApiCustomiser` 通过实现 `OpenApiCustomiser` 接口,**动态修改生成的 OpenAPI 模型**: ```java @Bean public OpenApiCustomiser integrateCustomAnnotations() { return openApi -> { openApi.getPaths().forEach((path, pathItem) -> { // 检查路径是否使用了 @CustomAuth 注解 if (isCustomAuthPresent(path)) { pathItem.readOperations().forEach(op -> { op.addExtension("x-custom-auth", true); // 添加自定义扩展字段 op.setDescription("该接口需要自定义认证"); // 修改描述 }); } }); }; } ``` > **优势**:集中管理逻辑,避免污染业务代码[^1]。 #### ✅ 方法三:实现 `OperationCustomizer`/`ParameterCustomizer` 更细粒度地定制操作或参数的 OpenAPI 描述: ```java @Component public class CustomAnnotationOperationCustomizer implements OperationCustomizer { @Override public Operation customize(Operation operation, HandlerMethod handlerMethod) { // 检查方法上的自定义注解 if (handlerMethod.getMethod().isAnnotationPresent(CustomRateLimit.class)) { operation.addExtension("x-rate-limit", "100req/min"); } return operation; } } ``` --- ### 3. **扩展注解支持(高级)** 若需深度集成(如让 Springdoc 直接解析 `@CustomAnnotation`): - **实现 `AnnotatedElementPlugin`**:扩展 Swagger Core,将自定义注解映射到 OpenAPI 模型属性。 - **使用 `ModelConverters`**:注册自定义转换器处理注解(复杂度较高,需熟悉 Swagger Core)。 --- ### 总结 | **方式** | **适用场景** | **是否需要修改业务代码** | |--------------------|----------------------------------|--------------------------| | 包装 Swagger 注解 | 简单、少量注解 | 是 | | `OpenApiCustomiser`| 集中化管理,批量处理 | 否 | | `OperationCustomizer`| 方法/参数级别的细粒度控制 | 否 | | Swagger Core 扩展 | 深度集成,作为通用解决方案 | 否(但开发复杂) | > **建议**:优先使用 `OpenApiCustomiser` 或 `*Customizer` 接口,避免业务代码与文档工具强耦合[^1][^2]。 --- ### 相关问题 1. **如何为使用自定义注解的接口自动添加 OpenAPI 安全配置?** > 可在 `OpenApiCustomiser` 中动态添加 `SecurityRequirement` 对象。 2. **Springdoc 能否扫描第三方库中的控制器?** > 需确保第三方库组件被 Spring 扫描到(如使用 `@ComponentScan`)。 3. **如何隐藏带有特定自定义注解的接口?** > 在 `OpenApiCustomiser` 中移除对应 `PathItem` 或使用 `@Hidden` 注解包装。 4. **自定义注解的参数如何动态显示在文档中?** > 通过 `OperationCustomizer` 读取注解参数值并设置到 `Operation` 对象中。 [^1]: 参考 springdoc-openapi 自定义配置与扩展机制文档。 [^2]: 详见 Swagger Core 注解处理模型与 Springdoc 集成文档。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值