说明
在基础篇当中,我们讲究了 Solon 对 MVC 的支持和提供的基础的Web 能力。在这个章节我们会讲解 Solon 提供了哪些能力可以供我们进行控制。
首先我们来看看 Solon Web 的请求过程,理解 Solon 的基础概念,这样方便我们后续的定制。
请求过程

从图中,我们可以看到在Web 的请求过程中,会经过几个环节:Filter(全局过滤器)->
RouterInterCeptor(路由拦截器)-> Handler(处理器)-> Interceptor(拦截器)。
Context
Solon 采用 Handler + Context 架构,Context 是请求的上下文。 在 Context 中可以获取请求和响应相关的数据,包括请求相关的对象与接口,会话相关的对象与接口,响应相关的对象与接口等。Solon 提供了多种方式获取Context,也为Context 其他了一些常用的接口,更详细的内容可以查看 https://solon.noear.org/article/216 。
Filter
过滤器,可以对所有请求(无论动态还是静态的请求)进行全局的控制。通常用于全局的 Web 请求异常处理,全局的性能记录,全局的响应状态调整,全局的上下文日志记录,全局的链路跟踪等等,当然也可以作用在局部上。
RouterInterceptor
路由拦截器,可以对动态请求进行全局的控制,功能基本与 Filter 一致。主要的区别是:1. 只能对动态请求(路由器范围)进行拦截;2. 可以修改参数和返回结果。
Interceptor
拦截器,拦截器使用切面(AOP)的方式实现,通过定义注解、设置切点、注册拦截器,Solon 将对方法进行拦截,从而增加拦截器方法的执行。拦截器可以用于实现缓存、事务等等功能。
Action
实际的请求方法,注册到路由器的方法(通常是@Mapping 函数,可以通过 Controller 的方式定义,也可以通过 Handler 的方式定义,等等)。Action 本质上是 Handler 和 Class Method 的结合,可以请求方法进行局部的控制。
示例 demo-web02
在 IDEA 中通过 File->New->Modules... 可以创建新的模块 demo-web02,基础代码和配置可以从demo-web01中拷贝过来。
这里我们继续补充demo-web01 ,增加定制化的处理。
- 增加简单的鉴权用于演示的Filter。
- 增加RouterInterceptor,用于演示Filter和RouterInterceptor的区别。
- 增加日志审计的功能用于演示 Interceptor 拦截器。
全局过滤器
使用全局的Filter做简单的 token 验证。这里需要指定Component的注解,也就是统一有Solon 来管理。如果有多个过滤器是可以指定Component的 index 参数,参数越小,越外层,可以理解为优先级越高。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package com.example.demo.web.common.filter;
import com.jfinal.kit.Kv; import lombok.extern.slf4j.Slf4j; import org.dromara.hutool.core.text.StrUtil; import org.noear.snack.ONode; import org.noear.solon.annotation.Component; import org.noear.solon.core.handle.Context; import org.noear.solon.core.handle.Filter; import org.noear.solon.core.handle.FilterChain;
@Slf4j @Component public class GlobalFilter implements Filter { @Override public void doFilter(Context ctx, FilterChain chain) throws Throwable { String path = ctx.path(); log.info("1. GlobalFilter, path: {}", path); if (!path.contains(".")) { String token = ctx.param("token"); if (StrUtil.isBlank(token)) { ctx.outputAsJson(ONode.stringify(Kv.of("state", false))); return; } }
chain.doFilter(ctx); } }
|
路由拦截器
这边只是为了对比和Filter的不同,如果是静态资源请求时,不会进入路由拦截器,也就不会打印对应的日志。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package com.example.demo.web.common.interceptor;
import lombok.extern.slf4j.Slf4j; import org.noear.solon.annotation.Component; import org.noear.solon.core.handle.Context; import org.noear.solon.core.handle.Handler; import org.noear.solon.core.route.RouterInterceptor; import org.noear.solon.core.route.RouterInterceptorChain;
@Slf4j @Component public class DemoRouteInterceptor implements RouterInterceptor { @Override public void doIntercept(Context ctx, Handler mainHandler, RouterInterceptorChain chain) throws Throwable { String path = ctx.path(); log.info("2. DemoRouteInterceptor, path: {}", path);
chain.doIntercept(ctx, mainHandler); } }
|
局部过滤器
局部过滤器可以通过 Addition 注解直接使用,可以通过注解继承的方式使用。
WhitelistFilter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.example.demo.web.common.filter;
import lombok.extern.slf4j.Slf4j; import org.noear.solon.core.handle.Context; import org.noear.solon.core.handle.Filter; import org.noear.solon.core.handle.FilterChain;
@Slf4j public class WhitelistFilter implements Filter { @Override public void doFilter(Context ctx, FilterChain chain) throws Throwable { String path = ctx.path(); log.info("3. WhitelistFilter, path: {}", path); chain.doFilter(ctx); } }
|
Whitelist
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.example.demo.web.common.annotation;
import com.example.demo.web.common.filter.WhitelistFilter; import java.lang.annotation.*; import org.noear.solon.annotation.Addition;
@Addition(WhitelistFilter.class) @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Whitelist {}
|
拦截器
拦截器可以通过 Around 注解直接使用,可以通过注解继承的方式使用。
LogInterceptor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.example.demo.web.common.interceptor;
import lombok.extern.slf4j.Slf4j; import org.noear.solon.core.aspect.Interceptor; import org.noear.solon.core.aspect.Invocation; import org.noear.solon.core.handle.Context;
@Slf4j public class LogInterceptor implements Interceptor { @Override public Object doIntercept(Invocation inv) throws Throwable { Context context = Context.current(); String path = context.path(); log.info("4. LogInterceptor, path: {}", path);
return inv.invoke(); } }
|
OperateLog
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.example.demo.web.common.annotation;
import com.example.demo.web.common.interceptor.LogInterceptor; import java.lang.annotation.*; import org.noear.solon.annotation.Around;
@Around(LogInterceptor.class) @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface OperateLog {}
|
Controller的调整
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| package com.example.demo.web.manage.controller;
import com.example.demo.web.common.annotation.OperateLog; import com.example.demo.web.common.annotation.Whitelist; import com.example.demo.web.common.filter.WhitelistFilter; import com.example.demo.web.common.interceptor.LogInterceptor; import com.example.demo.web.manage.dto.DeptDto; import com.example.demo.web.manage.service.DeptService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import java.util.List; import org.noear.solon.annotation.*; import org.noear.solon.core.handle.Context; import org.noear.solon.core.handle.ModelAndView; import org.noear.solon.core.handle.UploadedFile;
@Controller @Mapping("/dept") @Api("部门管理") public class DeptController { @Inject private DeptService service;
@Mapping("/index") @Get @ApiOperation("浏览部门") @OperateLog @Whitelist public ModelAndView index() { ModelAndView mv = new ModelAndView(); mv.put("depts", service.list()); mv.view("dept.shtm");
return mv; }
@Mapping(value = "list") @Get @ApiOperation("获取列表") @Around(LogInterceptor.class) @Addition(WhitelistFilter.class) public List<DeptDto> list() { return service.list(); } }
|
验证
- 请求 /dept/index,不带token得情况,演示全局的过滤器效果

- 请求 /dept/list?tokent=1,带token得情况,验证请求过程,使用的是 Addition 和 Around 的方式添加局部过滤器和拦截器。


- 请求 /dept/index?tokent=1,带token得情况,验证请求过程,使用注解继承的方式添加局部过滤器和拦截器。


- 请求 /img/wechat.png,验证静态请求不会通过RouterInterceptor。如果静态资源不存在的情况,依然会继续查找动态路由,此时会进入RouterInterceptor。

小结
本章节通过示意图的方式简单描述Solon web的请求过程,并对其中的重要概念做了讲解,通过实例的方式正式了对过滤器,路由拦截器和拦截器的定制。
官网对这些内容做了多篇文章的介绍,可以通过官网进一步的了解这些概念和使用方法。