`
高级java工程师
  • 浏览: 408965 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

springboot 如何扫描程序中带有指定注解的类和方法

阅读更多
使用springboot的人基本都知道swagger,那么swagger是如何生成swagger-ui.html页面的呢?相信大家都能猜到,就是扫描程序中带有指定注解的类(带有@RestController和 @Controller)和方法(@RequestMapping等),然后又根据方法上的@ApiOperation和@ApiImplicitParams去生成页面上要显示的一些元素。
实际项目中我们也会有类似需求,例如权限校验,我们需要先扫描程序中有哪些接口(就是找有哪些类有@RestController注解),然后在根据自定义的一些注解,扫描是否这些controller的方法需要配置权限校验。那么如何来实现?


第一步,

根据basePackage,开始遍历所有的类,然后测试该类是否添加了@RestController注解。
需要注意的是,当我们的类是在jar文件中时,不需要递归,但是在普通的文件中(目录存放文件)需要递归循环。

private List<Class<?>> getClassesWithAnnotationFromPackage(String packageName, Class<? extends Annotation> annotation) {
        List<Class<?>> classList = new ArrayList<Class<?>>();
        String packageDirName = packageName.replace('.', '/');
        Enumeration<URL> dirs = null;

        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
        }
        catch (IOException e) {
            log.error("Failed to get resource", e);
            return null;
        }

        while (dirs.hasMoreElements()) {
            URL url = dirs.nextElement();//file:/D:/E/workspaceGitub/springboot/JSONDemo/target/classes/com/yq/controller
            String protocol = url.getProtocol();//file

            //https://docs.oracle.com/javase/7/docs/api/java/net/URL.html
            //http, https, ftp, file, and jar
            //本文只需要处理file和jar
            if ("file".equals(protocol) ) {
                String filePath = null;
                try {
                    filePath = URLDecoder.decode(url.getFile(), "UTF-8");///D:/E/workspaceGitub/springboot/JSONDemo/target/classes/com/yq/controller
                }
                catch (UnsupportedEncodingException e) {
                    log.error("Failed to decode class file", e);
                }

                filePath = filePath.substring(1);
                getClassesWithAnnotationFromFilePath(packageName, filePath, classList, annotation);
            } else if ("jar".equals(protocol)) {
                JarFile jar = null;
                try {
                    jar = ((JarURLConnection) url.openConnection()).getJarFile();
                    //扫描jar包文件 并添加到集合中
                }
                catch (Exception e) {
                    log.error("Failed to decode class jar", e);
                }

                List<Class<?>> alClassList = new ArrayList<Class<?>>();
                findClassesByJar(packageName, jar, alClassList);
                getClassesWithAnnotationFromAllClasses(alClassList, annotation, classList);
            }
            else {
                log.warn("can't process the protocol={}", protocol);
            }
        }

        return classList;
    }



private static void findClassesByJar(String pkgName, JarFile jar, List<Class<?>> classes) {
        String pkgDir = pkgName.replace(".", "/");
        Enumeration<JarEntry> entry = jar.entries();

        while (entry.hasMoreElements()) {
            // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文
            JarEntry jarEntry = entry.nextElement();
            String name = jarEntry.getName();
            // 如果是以/开头的
            if (name.charAt(0) == '/') {
                // 获取后面的字符串
                name = name.substring(1);
            }

            if (jarEntry.isDirectory() || !name.startsWith(pkgDir) || !name.endsWith(".class")) {
                continue;
            }
            //如果是一个.class文件 而且不是目录
            // 去掉后面的".class" 获取真正的类名
            String className = name.substring(0, name.length() - 6);
            Class<?> tempClass = loadClass(className.replace("/", "."));
            // 添加到集合中去
            if (tempClass != null) {
                classes.add(tempClass);
            }
        }
    }

    /**
     * 加载类
     * @param fullClsName 类全名
     * @return
     */
    private static Class<?> loadClass(String fullClsName ) {
        try {
            return Thread.currentThread().getContextClassLoader().loadClass(fullClsName );
        } catch (ClassNotFoundException e) {
            log.error("PkgClsPath loadClass", e);
        }
        return null;
    }


    //filePath is like this 'D:/E/workspaceGitub/springboot/JSONDemo/target/classes/com/yq/controller'
    private void getClassesWithAnnotationFromFilePath(String packageName, String filePath, List<Class<?>> classList,
                                           Class<? extends Annotation> annotation) {
        Path dir = Paths.get(filePath);//D:\E\workspaceGitub\springboot\JSONDemo\target\classes\com\yq\controller

        try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
            for(Path path : stream) {
                String fileName = String.valueOf(path.getFileName()); // for current dir , it is 'helloworld'
                //如果path是目录的话, 此处需要递归,
                boolean isDir = Files.isDirectory(path);
                if(isDir) {
                    getClassesWithAnnotationFromFilePath(packageName + "." + fileName , path.toString(), classList, annotation);
                }
                else  {
                    String className = fileName.substring(0, fileName.length() - 6);

                    Class<?> classes = null;
                    String fullClassPath = packageName + "." + className;
                    try {
                        log.info("fullClassPath={}", fullClassPath);
                        classes = Thread.currentThread().getContextClassLoader().loadClass(fullClassPath);
                    }
                    catch (ClassNotFoundException e) {
                        log.error("Failed to find class={}", fullClassPath, e);
                    }

                    if (null != classes && null != classes.getAnnotation(annotation)) {
                        classList.add(classes);
                    }
                }
            }
        }
        catch (IOException e) {
            log.error("Failed to read class file", e);
        }
    }

    private void getClassesWithAnnotationFromAllClasses(List<Class<?>> inClassList,
                                                      Class<? extends Annotation> annotation, List<Class<?>> outClassList) {
        for(Class<?> myClasss : inClassList) {
            if (null != myClasss && null != myClasss.getAnnotation(annotation)) {
                outClassList.add(myClasss);
            }
        }
    }



第二步

根据第一步提供的controller,遍历该类的所有方法,检查该方法是否添加MyChecker注解(MyChecker为自定义的注解)
 private void geMethodWithAnnotationFromFilePath(String fullClassPath, Map<String, String> checkIdMethodMap,
                                                             Class<? extends Annotation> methodAnnotation) {
        Class<?> classes = null;
        try {
            log.info("fullClassPath={}", fullClassPath);
            classes = Thread.currentThread().getContextClassLoader().loadClass(fullClassPath);

            Method[] methods = classes.getDeclaredMethods();

            for (Method method : methods) {
                MyChecker myAnnotation = method.getAnnotation(MyChecker.class);
                if (null != myAnnotation) {
                    checkIdMethodMap.put (myAnnotation.id(), method.getName() );
                }
            }
        }
        catch (ClassNotFoundException e) {
            log.error("Failed to find class={}", fullClassPath, e);
        }



效果截图

http://dl2.iteye.com/upload/attachment/0132/0836/2c30583b-ee70-368b-b4b7-3cb38e5b1dba.png
  • 大小: 105.9 KB
分享到:
评论

相关推荐

    springboot初学者注解详解 springboot注解.docx

    - **作用**:通过 `@ComponentScan` 注解指定要扫描的包,Spring 会自动查找其中带有 `@Component`、`@Controller`、`@Service` 或 `@Repository` 注解的类,并将其注册为 Bean。 ##### 10. `@Configuration` - **...

    springboot注解.docx

    `@Configuration`表明当前类是配置类,`@EnableAutoConfiguration`启动自动配置功能,而`@ComponentScan`则扫描并注册所有带有@Component、@Service、@Repository、@Controller等注解的类。 2. **@ResponseBody**:...

    springboot helloworld程序(带简单测试用例)

    在SpringBoot HelloWorld程序中,我们通常会创建一个简单的Controller类,该类中有一个返回"Hello, World!"的RESTful API。下面是一个简单的Controller示例: ```java import org.springframework.web.bind....

    springBoot基础项目(带有swagger页面)

    在 Spring Boot 中,此类通常包含 `@SpringBootApplication` 注解,该注解会启用 Spring 的自动配置功能、组件扫描和 Spring Web 模块。启动类是应用程序的入口点,当运行该类时,Spring Boot 会初始化并启动整个...

    基于注解、SpringBoot的Dubbo提供者消费者DEMO

    - 配置启动类:创建一个带有`@SpringBootApplication`和`@EnableDubbo`注解的启动类,启用Dubbo功能。 4. **运行与测试**:完成上述步骤后,你可以运行项目,服务提供者和消费者都会启动。由于本DEMO是可直接运行...

    java(spring boot)自定义注解

    在这个例子中,`@MyMonitor`有两个属性:`metricName`和`importance`,它们都带有默认值。 2. 使用注解:在需要监控的方法上使用自定义注解,指定相关指标。 ```java @Service public class MyService { @...

    springboot简单源代码

    通过 `@ComponentScan`,SpringBoot 会发现并注册带有 `@Component`、`@Service`、`@Repository` 和 `@Controller` 注解的类。 内嵌 Web 服务器如 Tomcat 或 Jetty,使得我们无需额外的步骤就可以启动一个 HTTP ...

    自定义注解得使用,模拟spring通过注解方式创建bean实例

    这样,我们就创建了一个简单的注解处理器,它可以扫描指定包下所有带有`MyComponent`注解的类,并将它们注册为Spring的bean,bean的名称由注解的`value`属性决定。 最后,为了让Spring知道并执行我们的`...

    创建简单springboot框架

    接下来,在启动类中加入`@ServletComponentScan`注解,以便让Spring Boot在启动时扫描并注册带有`@WebServlet`注解的类。 ```java @SpringBootApplication @ServletComponentScan public class App { public ...

    springboot

    2. 控制器(Controller):这些是 SpringMVC 中处理 HTTP 请求的类,通常带有 `@RestController` 或 `@Controller` 注解,用于接收前端请求并返回响应。 3. 服务(Service):业务逻辑层,处理数据操作和复杂的业务...

    springboot启动周期流程

    - **加载主配置**:找到主配置类,通常是带有@SpringBootApplication注解的类,将其作为配置元数据加载到ApplicationContext。 - **调用ApplicationPreparedEvent**:发布此事件,允许在ApplicationContext准备好...

    springboot项目Demo

    通常,这将由一个带有`@RestController`注解的控制器类中的方法处理。例如,我们可以有一个名为`HelloController`的类,其中包含一个`hello()`方法,这个方法可能使用`@GetMapping("/hello")`注解来指定HTTP GET请求...

    springboot-api.zip

    3. **主应用类**:应用的入口通常是带有`@SpringBootApplication`注解的类,它整合了`@Configuration`(配置类)、`@EnableAutoConfiguration`(启用自动配置)和`@ComponentScan`(扫描组件)功能。 4. **Web支持*...

    springboot的demo

    2. **主应用类**:项目中会有个带有`@SpringBootApplication`注解的主类,如`Application.java`,这是SpringBoot应用的入口点。该注解包含了`@SpringBootConfiguration`、`@EnableAutoConfiguration`和`@...

    springboot框架的项目实例

    在IDEA中导入`springboothao`项目后,你可以启动主类,通常带有`@SpringBootApplication`注解的类,这将启动SpringBoot应用。应用会自动扫描指定包及其子包下的Bean,进行依赖注入和自动配置。 在SpringBoot项目中...

    大作业基于java的SpringBoot脚手架项目源码.zip

    它会扫描指定的包及其子包,寻找带有@Component、@Service、@Repository和@Controller等注解的类,并将它们纳入Spring容器管理。 2. **配置文件(YAML/Properties)**:SpringBoot支持application.yml或application...

    springboot参考指南中文word文档

    - **定位main应用类**: 主类通常带有`@SpringBootApplication`注解。 **3.3 配置类** - **导入其他配置类**: 使用`@Import`注解导入其他配置类。 - **导入XML配置**: 尽管Spring Boot鼓励使用Java配置,但仍然支持...

    springboot例子

    **主应用类(Main Application)**:通常会有一个带有`@SpringBootApplication`注解的主类,这个注解集成了`@Configuration`(配置类)、`@EnableAutoConfiguration`(启用自动配置)和`@ComponentScan`(组件扫描)...

    SpringBoot笔记.md

    - `@ComponentScan`:用于扫描指定包及其子包下带有@Component注解的类,自动注册为Bean。 #### 二、Spring基础回顾 ##### 2.1 IOC(Inversion of Control)基本概念 控制反转是一种设计原则,其核心思想是将...

Global site tag (gtag.js) - Google Analytics