作为 Java 开发者,你每天都在使用@Override@RequestMapping@WebFilter等注解,但可能只停留在 “会用” 层面,不清楚注解的底层原理、自定义规则,更不知道如何像框架一样通过注解实现 Filter、Servlet 的灵活配置。今天,我们从注解的核心原理出发,结合internalDoFilter()执行逻辑,手把手教你实现注解化的 Filter 链,让你彻底搞懂注解的 “前世今生”。

本文适合 Java 开发、架构师,以及准备面试的同学,全程结合实战案例(包含 Filter 注解化完整代码),拒绝纯理论,做到 “原理 + 源码 + 落地” 三合一。


一、先搞懂:注解的核心本质

1.1 注解是什么?

注解(Annotation)是 JDK 5.0 引入的特性,官方定义是:注解是一种元数据(描述数据的数据),它不直接影响代码的执行逻辑,但可以被编译器、虚拟机或框架读取,用于生成代码、配置文件或运行时动态处理

通俗比喻:注解就像商品的 “标签”—— 商品本身是代码逻辑,标签(注解)不改变商品功能,但能告诉超市(框架)“这个商品要放在生鲜区”“这个商品需要冷藏”,框架根据标签做对应的处理。

1.2 注解的核心价值

  • 替代配置文件:传统的 Filter、Servlet 配置需要写web.xml,注解可以直接标注在类上,简化开发;
  • 代码解耦:将配置信息与业务代码分离(如@WebFilter标注 Filter 的拦截路径,无需修改业务逻辑);
  • 运行时动态处理:框架可通过反射读取注解信息,动态执行逻辑(如 Spring 的@Autowired自动注入)。

1.3 注解与internalDoFilter()的关联

你提供的internalDoFilter()是 Servlet Filter 链的核心执行逻辑,传统方式需要在web.xml中配置<filter><filter-mapping>,而注解(如@WebFilter)的核心作用就是:框架通过扫描注解,自动构建 Filter 链,替代手动配置,最终让internalDoFilter()能按注解定义的顺序执行 Filter


二、注解的核心分类与底层原理

2.1 注解的三大分类(按作用范围)

分类 作用阶段 核心注解示例 底层实现
源码注解 仅编译器处理,不进入字节码 @Override@SuppressWarnings 编译器校验(如@Override检查方法重写)
编译时注解 编译期处理,生成字节码 / 文件 @FunctionalInterface、Lombok 的@Data 注解处理器(Annotation Processor)
运行时注解 运行期通过反射读取 @WebFilter@RequestMapping 反射 API(Class.getAnnotation()

重点:我们实战中用到的@WebFilter、自定义 Filter 注解都属于「运行时注解」,这是框架最常用的类型。

2.2 注解的底层结构(自定义注解的基础)

所有注解都必须继承java.lang.annotation.Annotation接口,一个完整的注解包含以下核心元素:

  1. 元注解:标注在注解上的注解,用于定义注解的 “作用范围、生命周期”;
  2. 注解属性:注解的参数(如@WebFilter(urlPatterns = "/hello")中的urlPatterns);
  3. 注解处理器:读取注解信息并执行逻辑的代码(如 Tomcat 扫描@WebFilter构建 Filter 链)。
2.2.1 核心元注解(必记)
元注解 作用
@Target 定义注解能标注的位置(如ElementType.TYPE标注类,ElementType.METHOD标注方法)
@Retention 定义注解的生命周期(RetentionPolicy.RUNTIME表示运行时保留,可通过反射读取)
@Documented 注解会被 javadoc 工具生成到文档中
@Inherited 注解可被子类继承
2.2.2 注解属性的规则
  • 属性类型只能是:基本类型、String、Class、枚举、注解,或这些类型的数组;
  • 若只有一个属性,建议命名为value,使用时可省略属性名(如@WebFilter("/hello")等价于@WebFilter(urlPatterns = "/hello"));
  • 属性可设置默认值(如@MyFilter(order = 0),默认顺序为 0)。

三、从零实现:注解化的 Filter 链(结合internalDoFilter()

3.1 需求背景

传统 Filter 配置需要写web.xml,缺点是:

  • 配置繁琐,新增 Filter 需修改 XML;
  • 执行顺序需要手动调整<filter-mapping>的位置,易出错;
  • 耦合度高,配置与代码分离,维护成本高。

我们的目标:自定义注解@MyFilter,替代web.xml,实现 Filter 的自动扫描、按注解指定的顺序构建 Filter 链,最终适配internalDoFilter()执行逻辑

3.2 步骤 1:自定义运行时注解@MyFilter

首先定义核心注解,包含 Filter 的 “拦截路径” 和 “执行顺序” 两个核心属性:

import java.lang.annotation.*;

/**
 * 自定义Filter注解,替代web.xml的<filter>配置
 */
// 标注在类上
@Target(ElementType.TYPE)
// 运行时保留,可通过反射读取
@Retention(RetentionPolicy.RUNTIME)
// 生成文档
@Documented
public @interface MyFilter {
    /**
     * 拦截路径(等价于web.xml的<url-pattern>)
     */
    String[] urlPatterns() default "/*";

    /**
     * 执行顺序(等价于web.xml的<filter-mapping>顺序)
     * 数值越小,执行优先级越高
     */
    int order() default 0;

    /**
     * Filter名称(可选)
     */
    String name() default "";
}

3.3 步骤 2:实现自定义 Filter(标注@MyFilter

定义两个 Filter,通过order指定执行顺序,替代传统的web.xml配置:

// 第一个Filter:日志拦截(order=1,优先级高)
@MyFilter(urlPatterns = "/hello", order = 1, name = "LogFilter")
public class LogFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("LogFilter初始化");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 前置逻辑:记录请求日志
        System.out.println("LogFilter前置逻辑:接收到请求,路径=" + ((HttpServletRequest) request).getRequestURI());
        // 调用下一个Filter(传入chain,即internalDoFilter()中的this)
        chain.doFilter(request, response);
        // 后置逻辑:记录响应日志
        System.out.println("LogFilter后置逻辑:响应完成,状态码=" + ((HttpServletResponse) response).getStatus());
    }

    @Override
    public void destroy() {
        System.out.println("LogFilter销毁");
    }
}

// 第二个Filter:权限拦截(order=2,优先级低)
@MyFilter(urlPatterns = "/hello", order = 2, name = "AuthFilter")
public class AuthFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("AuthFilter初始化");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 前置逻辑:权限校验
        String token = ((HttpServletRequest) request).getHeader("token");
        if (token == null || !token.equals("admin")) {
            ((HttpServletResponse) response).sendError(401, "未授权");
            return;
        }
        System.out.println("AuthFilter前置逻辑:权限校验通过");
        // 调用下一个Filter
        chain.doFilter(request, response);
        // 后置逻辑:无
        System.out.println("AuthFilter后置逻辑:权限拦截完成");
    }

    @Override
    public void destroy() {
        System.out.println("AuthFilter销毁");
    }
}

3.4 步骤 3:实现注解扫描器(核心:构建 Filter 链)

框架(如 Tomcat)的核心逻辑是 “扫描注解→收集 Filter→按顺序排序→构建 Filter 链”,我们手动实现这个扫描器,模拟web.xml的配置解析过程:

import javax.servlet.Filter;
import java.io.File;
import java.net.URL;
import java.util.*;

/**
 * 注解扫描器:扫描指定包下的@MyFilter注解,构建Filter链
 */
public class AnnotationFilterScanner {
    // 存储扫描到的Filter(key=拦截路径,value=Filter列表)
    private Map<String, List<FilterInfo>> filterMap = new HashMap<>();

    /**
     * 扫描指定包下的所有类,收集@MyFilter注解的Filter
     * @param packageName 要扫描的包名(如com.example.filter)
     */
    public void scan(String packageName) throws Exception {
        // 1. 将包名转换为文件路径(com.example.filter → com/example/filter)
        String packagePath = packageName.replace(".", "/");
        // 2. 获取类加载器,加载包下的所有资源
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        URL url = classLoader.getResource(packagePath);
        if (url == null) {
            throw new RuntimeException("包路径不存在:" + packagePath);
        }
        // 3. 遍历包下的所有.class文件
        File packageDir = new File(url.getFile());
        File[] files = packageDir.listFiles();
        if (files == null) {
            return;
        }
        for (File file : files) {
            String fileName = file.getName();
            // 只处理.class文件
            if (fileName.endsWith(".class")) {
                // 4. 加载类(com.example.filter.LogFilter)
                String className = packageName + "." + fileName.substring(0, fileName.length() - 6);
                Class<?> clazz = Class.forName(className);
                // 5. 判断类是否标注了@MyFilter注解,且实现了Filter接口
                if (clazz.isAnnotationPresent(MyFilter.class) && Filter.class.isAssignableFrom(clazz)) {
                    MyFilter myFilter = clazz.getAnnotation(MyFilter.class);
                    // 6. 获取注解属性(拦截路径、顺序)
                    String[] urlPatterns = myFilter.urlPatterns();
                    int order = myFilter.order();
                    String name = myFilter.name();
                    // 7. 实例化Filter(反射)
                    Filter filter = (Filter) clazz.newInstance();
                    // 8. 按拦截路径分组存储
                    for (String pattern : urlPatterns) {
                        FilterInfo filterInfo = new FilterInfo(filter, order, name);
                        filterMap.computeIfAbsent(pattern, k -> new ArrayList<>()).add(filterInfo);
                    }
                }
            }
        }
        // 9. 对每个路径下的Filter按order排序(核心:保证执行顺序)
        for (List<FilterInfo> filterList : filterMap.values()) {
            Collections.sort(filterList, Comparator.comparingInt(FilterInfo::getOrder));
        }
    }

    /**
     * 根据请求路径获取Filter链(适配internalDoFilter()执行逻辑)
     * @param urlPattern 请求路径(如/hello)
     * @return 按顺序排序的Filter列表
     */
    public List<Filter> getFilterChain(String urlPattern) {
        List<FilterInfo> filterInfoList = filterMap.get(urlPattern);
        if (filterInfoList == null) {
            return Collections.emptyList();
        }
        // 转换为Filter列表,供internalDoFilter()执行
        List<Filter> filterList = new ArrayList<>();
        for (FilterInfo info : filterInfoList) {
            filterList.add(info.getFilter());
        }
        return filterList;
    }

    // 内部类:存储Filter信息(Filter实例+执行顺序+名称)
    private static class FilterInfo {
        private Filter filter;
        private int order;
        private String name;

        public FilterInfo(Filter filter, int order, String name) {
            this.filter = filter;
            this.order = order;
            this.order = order;
            this.name = name;
        }

        public Filter getFilter() {
            return filter;
        }

        public int getOrder() {
            return order;
        }

        public String getName() {
            return name;
        }
    }
}

3.5 步骤 4:整合internalDoFilter()执行逻辑

将注解扫描器构建的 Filter 链,接入核心执行逻辑,模拟 Tomcat 的 Filter 链执行过程:

import javax.servlet.Servlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * 模拟Filter链执行器,核心逻辑与internalDoFilter()一致
 */
public class FilterChainExecutor {
    // Filter链
    private List<Filter> filters;
    // 当前执行的Filter索引
    private int pos = 0;
    // 目标Servlet
    private Servlet servlet;

    public FilterChainExecutor(List<Filter> filters, Servlet servlet) {
        this.filters = filters;
        this.servlet = servlet;
    }

    /**
     * 核心执行逻辑(与internalDoFilter()完全一致)
     */
    public void internalDoFilter(ServletRequest request, ServletResponse response) throws Exception {
        if (pos < filters.size()) {
            Filter filter = filters.get(pos);
            pos++;
            // 执行当前Filter的前置逻辑,传入this(自身)用于调用下一个Filter
            filter.doFilter(request, response, (req, res) -> {
                // 回调:执行下一个Filter(即再次调用internalDoFilter)
                internalDoFilter(req, res);
            });
        } else {
            // 所有Filter执行完毕,调用Servlet的service方法
            servlet.service(request, response);
        }
    }

    // 测试用Servlet
    public static class HelloServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
            resp.getWriter().write("Hello, Annotation Filter!");
            System.out.println("HelloServlet执行:返回响应");
        }
    }

    // 测试主方法
    public static void main(String[] args) throws Exception {
        // 1. 扫描注解,构建Filter链
        AnnotationFilterScanner scanner = new AnnotationFilterScanner();
        scanner.scan("com.example.filter"); // 替换为你的Filter包名
        // 2. 获取/hello路径的Filter链
        List<Filter> filterList = scanner.getFilterChain("/hello");
        // 3. 初始化Filter(模拟Tomcat的Filter初始化)
        for (Filter filter : filterList) {
            filter.init(null);
        }
        // 4. 创建目标Servlet
        Servlet servlet = new HelloServlet();
        // 5. 构建Filter链执行器
        FilterChainExecutor executor = new FilterChainExecutor(filterList, servlet);
        // 6. 模拟请求(传入token=admin,权限校验通过)
        HttpServletRequest request = new MockHttpServletRequest("/hello", "admin");
        HttpServletResponse response = new MockHttpServletResponse();
        // 7. 执行Filter链
        executor.internalDoFilter(request, response);
    }

    // 模拟HttpServletRequest(简化版)
    private static class MockHttpServletRequest implements HttpServletRequest {
        private String requestURI;
        private String token;

        public MockHttpServletRequest(String requestURI, String token) {
            this.requestURI = requestURI;
            this.token = token;
        }

        @Override
        public String getRequestURI() {
            return requestURI;
        }

        @Override
        public String getHeader(String name) {
            if ("token".equals(name)) {
                return token;
            }
            return null;
        }

        // 省略其他未实现的方法
        @Override
        public String getMethod() { return "GET"; }
        // 其他方法按需实现...
    }

    // 模拟HttpServletResponse(简化版)
    private static class MockHttpServletResponse implements HttpServletResponse {
        @Override
        public void sendError(int sc, String msg) throws IOException {
            System.out.println("响应错误:" + sc + ",原因:" + msg);
        }

        // 省略其他未实现的方法
        @Override
        public java.io.PrintWriter getWriter() throws IOException {
            return new java.io.PrintWriter(System.out);
        }
        @Override
        public int getStatus() { return 200; }
        // 其他方法按需实现...
    }
}

3.6 执行结果(验证注解化 Filter 链)

运行main方法,输出如下(符合注解指定的顺序):

LogFilter初始化
AuthFilter初始化
LogFilter前置逻辑:接收到请求,路径=/hello
AuthFilter前置逻辑:权限校验通过
HelloServlet执行:返回响应
AuthFilter后置逻辑:权限拦截完成
LogFilter后置逻辑:响应完成,状态码=200

四、注解的高级应用:框架级注解设计

4.1 注解与反射的结合(核心)

框架(如 Spring、Tomcat)处理注解的核心流程:

  1. 扫描:通过类加载器扫描指定包下的类,判断是否标注目标注解;
  2. 解析:通过反射(Class.getAnnotation())读取注解属性;
  3. 处理:根据注解属性执行逻辑(如 Filter 的拦截路径匹配、顺序排序);
  4. 执行:将解析后的组件(如 Filter)加入执行链,调用核心方法(如internalDoFilter())。

4.2 注解的性能优化

  • 缓存注解信息:反射读取注解耗时,框架会将注解信息缓存到内存(如 Tomcat 的Context组件缓存@WebFilter信息);
  • 避免运行时频繁扫描:仅在应用启动时扫描一次注解,而非每次请求都扫描;
  • 注解处理器(APT):编译期生成代码,替代运行时反射(如 Lombok 的@Data编译期生成 getter/setter)。

4.3 对比:注解 vs 传统 XML 配置

维度 注解 XML 配置
开发效率 高(直接标注在类上,无需切换文件) 低(需手动编写 XML,易出错)
维护成本 低(配置与代码同文件) 高(配置与代码分离,易遗漏)
灵活性 低(注解写死在代码中,修改需重新编译) 高(修改 XML 无需重新编译)
适用场景 固定配置(如 Filter 拦截路径) 动态配置(如多环境切换)

五、面试高频考点:注解核心问题

5.1 @WebFilter的底层实现?

Tomcat 启动时,ContextConfig组件会扫描类路径下的@WebFilter注解:

  1. 读取urlPatternsdispatcherTypes等属性;
  2. 将注解信息转换为FilterDefFilterMap(等价于web.xml<filter><filter-mapping>);
  3. order(或注解扫描顺序)构建 Filter 链,最终传入internalDoFilter()执行。

5.2 注解为什么能被反射读取?

因为注解的@Retention设置为RetentionPolicy.RUNTIME,编译器会将注解信息写入字节码,运行时 JVM 会加载这些信息,反射 API 可通过Class.getAnnotation()读取。

5.3 如何保证 Filter 注解的执行顺序?

  • 自定义注解添加order属性,扫描后按order排序;
  • Tomcat 的@WebFilterorder属性,按类名的字母顺序执行;
  • Spring 的@Order@WebFilter结合FilterRegistrationBean可指定顺序。

六、总结:核心知识点回顾

  1. 注解的本质:元数据,不直接执行逻辑,但可被框架读取,用于动态配置(如 Filter 链构建);
  2. 运行时注解核心@Retention(RetentionPolicy.RUNTIME) + 反射读取,是@WebFilter等框架注解的基础;
  3. 注解与 Filter 链的结合:框架通过扫描注解构建 Filter 列表,按顺序传入internalDoFilter(),实现注解化的拦截逻辑。

掌握注解的底层原理,不仅能应对面试,更能在项目中实现 “注解化配置”(如自定义权限注解、日志注解),理解 Spring、Tomcat 等框架的设计思路。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐