Java 注解深度解析
作为 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接口,一个完整的注解包含以下核心元素:
- 元注解:标注在注解上的注解,用于定义注解的 “作用范围、生命周期”;
- 注解属性:注解的参数(如
@WebFilter(urlPatterns = "/hello")中的urlPatterns); - 注解处理器:读取注解信息并执行逻辑的代码(如 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)处理注解的核心流程:
- 扫描:通过类加载器扫描指定包下的类,判断是否标注目标注解;
- 解析:通过反射(
Class.getAnnotation())读取注解属性; - 处理:根据注解属性执行逻辑(如 Filter 的拦截路径匹配、顺序排序);
- 执行:将解析后的组件(如 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注解:
- 读取
urlPatterns、dispatcherTypes等属性; - 将注解信息转换为
FilterDef和FilterMap(等价于web.xml的<filter>和<filter-mapping>); - 按
order(或注解扫描顺序)构建 Filter 链,最终传入internalDoFilter()执行。
5.2 注解为什么能被反射读取?
因为注解的@Retention设置为RetentionPolicy.RUNTIME,编译器会将注解信息写入字节码,运行时 JVM 会加载这些信息,反射 API 可通过Class.getAnnotation()读取。
5.3 如何保证 Filter 注解的执行顺序?
- 自定义注解添加
order属性,扫描后按order排序; - Tomcat 的
@WebFilter无order属性,按类名的字母顺序执行; - Spring 的
@Order或@WebFilter结合FilterRegistrationBean可指定顺序。
六、总结:核心知识点回顾
- 注解的本质:元数据,不直接执行逻辑,但可被框架读取,用于动态配置(如 Filter 链构建);
- 运行时注解核心:
@Retention(RetentionPolicy.RUNTIME)+ 反射读取,是@WebFilter等框架注解的基础; - 注解与 Filter 链的结合:框架通过扫描注解构建 Filter 列表,按顺序传入
internalDoFilter(),实现注解化的拦截逻辑。
掌握注解的底层原理,不仅能应对面试,更能在项目中实现 “注解化配置”(如自定义权限注解、日志注解),理解 Spring、Tomcat 等框架的设计思路。
更多推荐

所有评论(0)