当前位置: 首页 > news >正文

Spring MVC拦截器与过滤器的区别详解

1. 拦截器与过滤器的定义

1.1 拦截器(Interceptor)的定义

Spring MVC拦截器是Spring框架提供的一个组件,用于在请求处理过程中对特定的请求进行拦截和处理。拦截器基于面向切面编程(AOP)思想,可以对控制器的方法执行前后插入自定义逻辑。拦截器主要作用于Spring MVC的处理器(即Controller)上,当请求到达Controller之前和之后,拦截器可以执行一些预处理和后处理操作。

拦截器的核心功能是在不修改业务代码的情况下,为请求处理添加额外的功能,如权限验证、日志记录、事务管理等。拦截器通过实现HandlerInterceptor接口来创建,它提供了三个关键方法:preHandle()postHandle()afterCompletion(),分别在请求处理的不同阶段被调用。

1.2 过滤器(Filter)的定义

过滤器是Servlet规范的一部分,属于Java Web应用的基础组件。它是一个可以动态拦截请求和响应的组件,能够在请求到达服务器资源之前和响应返回客户端之后执行一些处理逻辑。过滤器不依赖于特定的框架,可以用于任何遵循Servlet规范的应用程序。

过滤器的核心功能是对HTTP请求和响应进行预处理和后处理,如字符编码设置、敏感字符过滤、CORS处理等。过滤器通过实现Filter接口来创建,它提供了三个方法:init()doFilter()destroy(),分别在过滤器初始化、处理请求和销毁时被调用。

1.3 核心概念对比
特性拦截器(Interceptor)过滤器(Filter)
所属规范Spring MVC框架Servlet规范
作用范围Spring MVC控制器所有HTTP请求(包括静态资源)
生命周期每次请求触发单例,容器启动/销毁时调用
调用方式基于反射机制基于函数回调机制
依赖关系依赖Spring上下文不依赖框架,独立于Spring

2. 实现原理对比

2.1 拦截器的实现机制

Spring MVC拦截器的实现基于反射机制AOP思想。当请求到达DispatcherServlet后,会通过HandlerMapping找到对应的处理器(Controller)和拦截器链。拦截器链中的拦截器按照配置顺序依次执行preHandle()方法,如果所有拦截器都返回true,则请求继续处理;如果有拦截器返回false,则请求被拦截,不再执行后续的拦截器和Controller方法。

拦截器的三个方法执行顺序如下:

  1. preHandle():在Controller方法执行前调用
  2. postHandle():在Controller方法执行后、视图渲染前调用
  3. afterCompletion():在整个请求处理完成后调用(包括异常处理)

拦截器的实现类需要实现HandlerInterceptor接口,该接口定义了三个方法:

public interface HandlerInterceptor extends BeanNameAware {// 预处理方法,在Controller方法执行前调用default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {return true;}// 后处理方法,在Controller方法执行后、视图渲染前调用default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}// 请求完成后调用(包括异常情况)default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}
2.2 过滤器的实现机制

过滤器的实现基于Servlet规范函数回调机制。当请求到达服务器时,Servlet容器(如Tomcat)会按照配置顺序调用所有匹配的过滤器的doFilter()方法。每个过滤器在doFilter()方法中可以对请求进行预处理,然后通过FilterChain传递给下一个过滤器或目标资源,最后对响应进行后处理。

过滤器的实现类需要实现Filter接口,该接口定义了三个方法:

public interface Filter {// 初始化方法,容器启动时调用一次void init(FilterConfig filterConfig) throws ServletException;// 处理请求方法,每次请求时调用void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;// 销毁方法,容器销毁时调用一次void destroy() throws ServletException;
}
2.3 底层原理对比
特性拦截器(Interceptor)过滤器(Filter)
技术基础Spring MVC框架Servlet容器
调用流程由DispatcherServlet调用由Servlet容器调用
执行阶段Controller处理阶段请求到达Servlet容器后立即执行
作用对象Spring MVC处理器所有HTTP请求
生命周期每次请求触发单例,容器启动/销毁时调用
依赖关系依赖Spring上下文不依赖框架,独立于Spring

3. 配置方式及代码示例

3.1 拦截器的配置方式
3.1.1 注解配置方式

在Spring MVC 5.x版本中,可以通过实现WebMvcConfigurer接口并重写addInterceptors()方法来配置拦截器:

@Configuration
public class WebConfig implements WebMvcConfigurer {// 自定义拦截器@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 配置拦截器,指定拦截路径和排除路径registry.addInterceptor(loginInterceptor).addPathPatterns("/**")       // 拦截所有路径.excludePathPatterns("/login", "/css/**", "/images/**"); // 排除特定路径}
}

代码说明

  • @Configuration:标记该类为配置类
  • WebMvcConfigurer:实现该接口可以自定义Spring MVC配置
  • InterceptorRegistry:用于注册拦截器的注册表
  • addPathPatterns():指定需要拦截的路径模式
  • excludePathPatterns():指定不需要拦截的路径模式
  • addInterceptor():添加自定义拦截器到注册表中
3.1.2 XML配置方式

在Spring MVC的XML配置文件中,可以通过以下方式配置拦截器:

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsd"><!-- 配置拦截器 --><bean id="loginInterceptor" class="com.exampleinterceptorasr" /><!-- 配置拦截器映射 --><mvc:interceptors><mvc:interceptor><mvc:mapping path="/**" /><exclude><exclude-mapping path="/login" /><exclude-mapping path="/css/**" /><exclude-mapping path="/images/**" /></exclude><ref bean="loginInterceptor" /></mvc:interceptor></mvc:interceptors></beans>

代码说明

  • <bean>:定义拦截器实例
  • <mvc:interceptors>:配置拦截器链
  • <mapping>:指定需要拦截的路径模式
  • <exclude-mapping>:指定不需要拦截的路径模式
  • <ref>:引用已定义的拦截器实例
3.2 过滤器的配置方式
3.2.1 注解配置方式

在Servlet 3.0及以上版本中,可以通过@WebFilter注解配置过滤器:

@Component
@WebFilter(urlPatterns = "/*", filterName = "encodingFilter")
public class EncodingFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("过滤器初始化完成");}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// 设置请求和响应的编码request.setCharacterEncoding("UTF-8");response.setContentType("text/html;charset=utf-8");System.out.println("过滤器预处理请求:" + ((HttpServletRequest) request)..getRequestURI());// 传递给下一个过滤器或Servletchain.doFilter(request, response);System.out.println("过滤器后处理响应:" + ((HttpServletRequest) request)..getRequestURI());}@Overridepublic void destroy() {System.out.println("过滤器销毁完成");}
}

代码说明

  • @WebFilter:定义过滤器的配置信息
  • urlPatterns:指定需要拦截的URL模式
  • init():过滤器初始化方法,容器启动时调用一次
  • doFilter():处理请求方法,每次请求时调用
  • chain.doFilter():将请求传递给下一个过滤器或目标资源
  • destroy():过滤器销毁方法,容器销毁时调用一次
3.2.2 XML配置方式

web.xml中配置过滤器:

<filter><filter-name>encodingFilter</filter-name><filter-class>com.example EncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param>
</filter><filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>

代码说明

  • <filter>:定义过滤器
  • <filter-name>:过滤器名称
  • <filter-class>:过滤器实现类
  • <init-param>:定义初始化参数
  • <filter-mapping>:定义过滤器映射
  • <url-pattern>:指定需要拦截的URL模式
3.3 实际应用场景示例
3.3.1 拦截器应用场景

权限验证拦截器:检查用户是否登录,是否有访问特定资源的权限。

@Component
public class AuthInterceptor implements HandlerInterceptor {// 在Controller方法执行前调用@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 从Session中获取用户信息Object user = request sessions.getAttribute("user");String requestURI = request.getRequestURI();// 如果请求的是登录页面,则直接放行if (requestURI.startsWith("/login")) {return true;}// 如果用户未登录,则重定向到登录页面if (user == null) {response.sendRedirect("/login");return false;}// 如果用户已登录,但访问的资源需要权限验证// 这里可以访问Spring上下文中的Service@Autowiredprivate UserService userService;// 获取用户权限UserVO loginedUser = (UserVO) user;if (!loginedUser.hasAuthorityByURI(requestURI)) {response.sendRedirect("/unauthorized");return false;}// 放行请求return true;}// 在Controller方法执行后、视图渲染前调用@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {// 可以修改ModelAndView中的内容// 例如添加全局信息到视图if (modelAndView != null) {modelAndView.addObject("globalMessage", "欢迎访问本系统");}}// 在整个请求处理完成后调用@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 清理资源或记录日志// 无论请求是否成功,都会执行System.out.println("请求处理完成:" + request.getRequestURI());}
}

日志记录拦截器:记录请求处理时间和其他相关信息。

@Component
public class LoggingInterceptor implements HandlerInterceptor {private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 记录请求开始时间long startTime = System.currentTimeMillis();request.setAttribute(" startTime", startTime);// 记录请求信息logger.info("请求开始:URI={}", request.getRequestURI());return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView MVC) throws Exception {// 记录请求处理时间long startTime = (Long) request.getAttribute(" startTime");long processingTime = System.currentTimeMillis() - startTime;logger.info("请求处理时间:URI={}, 毫秒数={}", request.getRequestURI(), processingTime);// 如果需要修改ModelAndView中的内容// 例如添加处理时间到视图if (modelAndView != null) {MVC.addObject("processingTime", processingTime + "毫秒");}}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 记录请求完成信息logger.info("请求完成:URI={}, 状态码={}", request.getRequestURI(), response.getStatus());// 如果发生异常,记录异常信息if (ex != null) {logger.error("请求处理异常:URI={}, 异常={}", request.getRequestURI(), ex.getMessage());}}
}

事务管理拦截器:在Controller方法执行前后开启和提交事务。

@Component
public class TransactionInterceptor implements HandlerInterceptor {@Autowiredprivate PlatformTransactionManager transactionManager;private TransactionStatus transactionStatus;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 开启事务transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView MVC) throws Exception {// 提交事务transactionManager.commit(transactionStatus);}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 如果发生异常,回滚事务if (ex != null) {transactionManager.rollback(transactionStatus);}}
}
3.3.2 过滤器应用场景

字符编码过滤器:统一设置请求和响应的字符编码。

@Component
@WebFilter(urlPatterns = "/*", filterName = "encodingFilter")
public class EncodingFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {// 读取初始化参数String encoding = filterConfig.getInitParameter("encoding");System.out.println("过滤器初始化:字符编码=" + encoding);}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// 设置请求和响应的编码request.setCharacterEncoding("UTF-8");response.setContentType("text/html;charset=utf-8");// 传递给下一个过滤器或Servletchain.doFilter(request, response);}@Overridepublic void destroy() {System.out.println("过滤器销毁");}
}

CORS过滤器:处理跨域资源共享。

@Component
@WebFilter(urlPatterns = "/*", filterName = "corsFilter")
public classCorsFilter implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// 转换为HttpServletResponse和HttpServletRequestHttpServletResponse httpRes = (HttpServletResponse) response;HttpServletRequest httpReq = (HttpServletRequest) request;// 设置允许跨域的头部信息httpRes addHeader("Access-Control-Allow-Origin", "*");httpRes.addHeader("Access-Control-Allow-Credentials", "true");httpRes.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");httpRes.addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");httpRes.addHeader("Access-Control-Max-Age", "3600");// 如果是预检请求(OPTIONS),直接返回if ("OPTIONS".equals(httpReq.getMethod())) {httpRes.setStatus(HttpServletResponse.SC_OK);return;}// 否则继续处理请求chain.doFilter(request, response);}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {// 初始化代码}@Overridepublic void destroy() {// 销毁代码}
}

敏感词过滤器:过滤请求中的敏感内容。

@Component
@WebFilter(urlPatterns = "/*", filterName = "sensitiveWordFilter")
public class SensitiveWordFilter implements Filter {private Set<String> sensitiveWords = new HashSet<>(Arrays.asList("敏感词1", "敏感词2", "敏感词3"));@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// 包装请求,替换可能包含敏感词的参数wrapRequest((HttpServletRequest) request, (HttpServletResponse) response);// 继续传递请求chain.doFilter(request, response);}private void wrapRequest(HttpServletRequest request, HttpServletResponse response) {// 创建包装类,替换参数值SensitiveWordWrapper wrapper = new SensitiveWordWrapper(request);// 替换request对象try {request = wrapper;} catch (Exception e) {e.printStackTrace();}}// 请求包装类public static class SensitiveWordWrapper extends HttpServletRequestWrapper {private Map<String, String[]> parameters = new HashMap<>();public SensitiveWordWrapper(HttpServletRequest request) {super(request);// 读取原始参数Map<String, String[]> map = request.getParameterMap();for (Map.Entry<String, String[]> entry : map.entrySet()) {String key = entry.getKey();String[] values = entry.getValue();// 过滤敏感词String[] filteredValues = filterValues(values);// 存储过滤后的参数parameters.put(key, filteredValues);}}private String[] filterValues(String[] values) {if (values == null) {return null;}String[] filteredValues = new String[values.length];for (int i = 0; i < values.length; i++) {String value = values[i];if (value != null) {for (String word : sensitiveWords) {value = value.replace(word, "*".repeat(word.length()));}}filteredValues[i] = value;}return filteredValues;}@Overridepublic Map<String, String[]> getParameterMap() {return parameters;}@Overridepublic String[] getParameterValues(String name) {return getParameterMap().get(name);}@Overridepublic String getParameter(String name) {String[] values = getParameterValues(name);return values != null && values.length > 0 ? values[0] : null;}}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {// 初始化敏感词列表// 可以从配置文件或数据库读取}@Overridepublic void destroy() {// 销毁代码}
}

4. 执行顺序及相互关系

4.1 请求处理流程中的执行顺序

Spring MVC框架中的请求处理流程可以概括为以下几个步骤:

  1. 客户端发送HTTP请求
  2. 过滤器链依次处理请求(按配置顺序)
  3. 请求到达DispatcherServlet(前端控制器)
  4. 拦截器链preHandle()方法依次处理请求(按配置顺序)
  5. 调用Controller方法处理请求
  6. 拦截器链postHandle()方法反向处理请求(按配置顺序的反向)
  7. 视图渲染
  8. 拦截器链afterCompletion()方法反向处理请求(按配置顺序的反向)
  9. 过滤器链依次处理响应(按配置顺序的反向)

执行顺序图

客户端请求↓
过滤器1的doFilter()↓
过滤器2的doFilter()↓
...↓
过滤器N的doFilter()↓
DispatcherServlet↓
拦截器1的preHandle()↓
拦截器2的preHandle()↓
...↓
拦截器N的preHandle()↓
Controller方法执行↓
拦截器N的postHandle()↓
...↓
拦截器1的postHandle()↓
视图渲染↓
拦截器N的afterCompletion()↓
...↓
拦截器1的afterCompletion()↓
过滤器N的后处理↓
...↓
过滤器1的后处理↓
响应返回客户端
4.2 多拦截器/过滤器的执行顺序

过滤器执行顺序

  • web.xml中声明的过滤器,按照声明顺序依次执行
  • 使用注解@WebFilter声明的过滤器,按照类名的字典顺序执行
  • 如果同时使用XML和注解配置过滤器,XML配置的过滤器优先于注解配置的过滤器执行

拦截器执行顺序

  • WebMvcConfigurer.addInterceptors()方法中注册的拦截器,按照注册顺序依次执行preHandle()方法
  • postHandle()afterCompletion()方法按照注册顺序的反向执行
4.3 拦截器与过滤器的相互关系

拦截器和过滤器可以同时使用,形成更复杂的请求处理链。它们的关系可以总结为以下几点:

  1. 作用范围不同:过滤器作用于所有HTTP请求(包括静态资源、非Spring MVC处理的请求),而拦截器仅作用于Spring MVC框架处理的Controller请求。

  2. 执行顺序不同:过滤器在拦截器之前执行,过滤器链处理完请求后才会到达拦截器链。

  3. 生命周期不同:过滤器是单例模式,init()destroy()方法在整个应用生命周期中只执行一次,而拦截器的preHandle()postHandle()afterCompletion()方法在每次请求处理时都会执行。

  4. 功能互补:过滤器适合处理全局性的HTTP请求/响应操作(如字符编码、CORS、敏感词过滤),拦截器适合处理与业务逻辑相关的请求处理(如权限验证、日志记录、事务管理)。

  5. 依赖关系不同:过滤器不依赖于Spring框架,可以在任何Servlet容器中使用;拦截器需要Spring MVC环境支持,可以访问Spring上下文中的组件和服务。

5. 功能差异对比

5.1 作用对象差异

拦截器

  • 仅作用于Spring MVC框架处理的请求
  • 可以访问Spring上下文中的组件和服务
  • 可以获取Controller方法信息(如方法名、参数等)

过滤器

  • 作用于所有HTTP请求(包括静态资源、非Spring MVC处理的请求)
  • 仅能访问原始的HTTP请求/响应对象
  • 无法获取Spring上下文中的组件和服务
5.2 生命周期差异

拦截器

  • preHandle()postHandle()afterCompletion()方法在每次请求处理时都会执行
  • 通常为单例模式,但也可以配置为原型模式
  • 不需要实现init()destroy()方法,由Spring容器管理生命周期

过滤器

  • init()destroy()方法在整个应用生命周期中只执行一次
  • doFilter()方法在每次请求处理时都会执行
  • 必须实现init()doFilter()destroy()三个方法
  • 始终为单例模式
5.3 依赖框架差异

拦截器

  • 依赖于Spring MVC框架
  • 可以使用Spring的依赖注入(如@Autowired
  • 可以访问Spring上下文中的组件和服务
  • 可以使用Spring的AOP特性

过滤器

  • 不依赖于任何框架,是Servlet规范的一部分
  • 无法使用Spring的依赖注入
  • 仅能访问原始的HTTP请求/响应对象
  • 可以在任何Servlet容器中使用
5.4 灵活性差异

拦截器

  • 更灵活,可以访问Spring上下文中的组件和服务
  • 支持细粒度控制,可以针对特定的Controller或方法进行拦截
  • 可以在请求处理的不同阶段(前、后、完成)插入自定义逻辑
  • 可以使用@Order注解或实现Ordered接口控制拦截器的执行顺序

过滤器

  • 较为简单,仅能对HTTP请求/响应进行预处理和后处理
  • 无法访问Spring上下文中的组件和服务
  • 作用范围较广,通常针对URL模式或请求类型进行过滤
  • 执行顺序由过滤器链的配置决定,灵活性较低

6. 开发选型建议

6.1 选择拦截器的场景

需要与Spring上下文深度集成:如果需要访问Spring容器中的组件和服务(如Service、DAO等),拦截器是更好的选择。例如权限验证拦截器中需要调用用户服务来验证权限。

仅需处理特定的Controller请求:如果只需要拦截Spring MVC框架处理的Controller请求,而不需要处理静态资源或其他类型的请求,拦截器更合适。

需要细粒度控制请求处理流程:如果需要在请求处理的不同阶段(前、后、完成)插入自定义逻辑,并且需要根据条件决定是否放行请求,拦截器提供了更灵活的控制。

示例场景

  • 用户登录状态验证
  • 请求日志记录
  • 事务管理
  • 方法执行时间统计
  • 视图渲染前的模型数据修改
6.2 选择过滤器的场景

需要处理所有HTTP请求:如果需要对所有请求(包括静态资源、非Spring MVC处理的请求)进行处理,过滤器是唯一的选择。

需要直接操作HTTP请求/响应对象:如果需要修改HTTP请求头、响应头或内容,而不需要与Spring上下文集成,过滤器更简单直接。

需要在请求到达Spring MVC之前执行逻辑:如果某些逻辑需要在请求到达Spring MVC框架之前执行(如字符编码设置、CORS处理),过滤器是必要的。

示例场景

  • 字符编码设置
  • 跨域资源共享(CORS)
  • 敏感词过滤
  • 请求头修改
  • 响应压缩
6.3 两者结合使用的场景

全局预处理与业务逻辑处理:可以使用过滤器处理全局性的HTTP请求/响应操作(如字符编码、CORS),然后使用拦截器处理与业务逻辑相关的请求处理(如权限验证、日志记录)。

性能优化:对于需要频繁访问的静态资源,可以使用过滤器排除路径(如/css/**/images/**)来减少性能开销,而拦截器可以专注于处理业务请求。

复杂请求处理流程:对于需要多层处理的请求(如先进行安全检查,再进行业务验证),可以结合使用过滤器和拦截器,形成更完整的请求处理链。

示例场景

  • 过滤器处理字符编码,拦截器处理权限验证
  • 过滤器处理CORS,拦截器处理请求日志
  • 过滤器处理敏感词过滤,拦截器处理事务管理

总结

Spring MVC拦截器和过滤器是两种不同的请求处理机制,各有其适用场景。过滤器作用于Servlet容器级别,对所有HTTP请求生效,适合处理全局性的HTTP请求/响应操作;拦截器作用于Spring MVC框架级别,仅对Controller请求生效,适合处理与业务逻辑相关的请求处理。

在实际开发中,可以根据具体需求选择合适的组件:

  • 如果需要处理所有HTTP请求(包括静态资源),或者需要直接操作HTTP请求/响应对象,选择过滤器。
  • 如果需要与Spring上下文深度集成,或者需要细粒度控制请求处理流程,选择拦截器。
  • 对于复杂的请求处理流程,可以结合使用过滤器和拦截器,形成更完整的处理链。

通过理解两者的区别和联系,可以更合理地设计和实现Web应用的请求处理逻辑,提高应用的安全性和性能。

http://www.lryc.cn/news/612805.html

相关文章:

  • Ubuntu24.04的“errors from xkbcomp are not fatal to the X server”终极修复方案
  • Ethereum:如何优雅部署 NPM 包中的第三方智能合约?
  • SpringBoot学习日记 Day5:解锁企业级开发核心技能
  • 90-基于Flask的中国博物馆数据可视化分析系统
  • 8- 知识图谱 — 应用案例怎么 “落地” 才有效?构建流程与行业实践全解析
  • LoRaWAN的网络拓扑
  • Kong vs. NGINX:从反向代理到云原生网关的全景对比
  • PCL提取平面上的圆形凸台特征
  • 阿里系bx_et加密分析
  • 构造函数:C++对象初始化的核心机制
  • 天猫商品评论API技术指南
  • uni-app X能成为下一个Flutter吗?
  • Flutter报错...Unsupported class file major version 65
  • C# 异步编程(async_await特性的结构)
  • PyTorch 核心三件套:Tensor、Module、Autograd
  • `/dev/vdb` 是一个新挂载的 4TB 硬盘,但目前尚未对其进行分区和格式化。
  • vscode 打开设置
  • Flutter 三棵树
  • 【物联网】基于树莓派的物联网开发【25】——树莓派安装Grafana与Influxdb无缝集成
  • CentOS 7 下通过 Anaconda3 运行llm大模型、deepseek大模型的完整指南
  • 人工智能的20大应用
  • 从Centos 9 Stream 版本切换到 Rocky Linux 9
  • 360纳米AI、实在Agent、CrewAI与AutoGen……浅析多智能体协作系统
  • 构建在 OpenTelemetry eBPF 基础之上:详解 Grafana Beyla 2.5 新特性
  • 【0基础3ds Max】菜单栏介绍
  • 多模态融合(Multimodal Fusion)
  • PCIe Base Specification解析(九)
  • mapbox进阶,mapbox-gl-draw绘图插件扩展,绘制新增、编辑模式支持点、线、面的捕捉
  • 什么是SpringBoot
  • Shuffle SOAR使用学习经验