Solon

七、过滤器、路由拦截器、处理器、拦截器

</> markdown

Web处理,会经过四个路段:过滤器->路由拦截器->处理器->拦截器。可通过 《请求处理过程示意图》 了解。

1、过滤器,全局请求的管控(Filter)[环绕式]

过滤器,一般用于:

  • 全局的 Web 请求异常处理(包括 '静态' 与 '动态' 等...请求)
  • 性能记时
  • 响应状态调整
  • 上下文日志记录
  • 链路跟踪等...
@Slf4j
@Component(index = 0) //index 为顺序位(不加,则默认为0)
public class AppFilter implements Filter {
    @Override
    public void doFilter(Context ctx, FilterChain chain) throws Throwable {
        //1.开始计时(用于计算响应时长)
        long start = System.currentTimeMillis();
        try {
            //2.记录请求数据日志
            log.info("Request data: {}", getRequestData(ctx)); //getRequestData 需要自己写,把 ctx 里的请求数据转为 string
            
            //3.执行处理
            chain.doFilter(ctx);

            //3.2.未处理设为404状态
            if(!ctx.getHandled()){
                ctx.status(404);
            }
            
            //3.3.404状态的定制(也可对别的状态处理)
            if (ctx.status() == 404) {
                ctx.output("没有:(");
            }
            
            //4.记录响应数据日志
            log.info("Response data: {}", getResponseData(ctx.result)); //getResponseData 需要自己写,把 ctx 里的请求数据转为 string
        } catch (Throwable e) {
            //4.异常捕捉与控制(并标为500错误)
            ctx.status(500);
                    
            log.error("{}", e);
        }

        //5.获得接口响应时长
        long times = System.currentTimeMillis() - start;
        System.out.println("用时:"+ times);
    }
}  

再例如,如果你想把 "/" 转为静态文件 "/index.html" 上:

@Component(index = 0) //index 为顺序位(不加,则默认为0)
public class AppFilter implements Filter {
    @Override
    public void doFilter(Context ctx, FilterChain chain) throws Throwable {
        if("/".equals(ctx.pathNew())){ //ContextPathFilter 就是类似原理实现的
            ctx.pathNew("/index.html");
        }

        chain.doFilter(ctx);
    }
}  

//
//也可以在控制器做类似处理(不过,会多一轮处理)
//
@Controller
public class HomeController {
    @Mapping("/")
    public void home(Context ctx) {
        //内部跳转到 /index.htm
        ctx.forward("/index.htm");
    }
}

2、路由拦截器,全局路由的拦截(RouterInterceptor)[环绕式]

RouterInterceptor 和 Filter 差不多。区别有二:1. 只对路由器范围内的处理进行拦截,对静态资源无效;2. 增加了Mvc处理结果值的提交确认(即可以修改返回结果)。

  • 当不存在此路由时,mainHandler 为 null
  • 通过对 mainHandler 类型检测,可以判断是不是 Action 类型

例1:

@Component
public class GlobalTransInterceptor implements RouterInterceptor {
    @Inject
    private TransService transService;

    /**
     * 拦截处理(包围式拦截) //和过滤器的 doFilter 类似,且只对路由器范围内的处理有效
     */
    @Override
    public void doIntercept(Context ctx, Handler mainHandler, RouterInterceptorChain chain) throws Throwable {
        //提示:如果有需要,可以获取 Action。进而获取控制器和函数
        //Action action = (mainHandler instanceof Action ? (Action) mainHandler : null); 
        //
        //提示:这里和 doFilter 差不多...
        chain.doIntercept(ctx, mainHandler);
    }

     /**
     * 提交结果( render 执行前调用)//不要做太复杂的事情
     */
    @Override
    public Object postResult(Context ctx, Object result) throws Throwable {
        //提示:此处只适合做结果类型转换
        if (result != null && !(result instanceof Throwable) && ctx.action() != null) {
            result = transService.transOneLoop(result, true);
        }

        return result;
    }
}

例2:(设定路径匹配模式)

@Component
public class AdminAuthInterceptorImpl implements RouterInterceptor {
    @Inject
    private AuthService authService;
    
    /**
    * 路径限制规则
    */
    @Override
    public PathRule pathPatterns() {
        return new PathRule().include("/admin/**").exclude("/admin/login");
    }

    /**
     * 拦截处理(包围式拦截) //和过滤器的 doFilter 类似,且只对路由器范围内的处理有效
     */
    @Override
    public void doIntercept(Context ctx, Handler mainHandler, RouterInterceptorChain chain) throws Throwable {
        if(authService.isLogined(ctx)){
            chain.doIntercept(ctx, mainHandler);
        }else{
           ctx.render(Result.failure(401,"账号未登录"));
        }
    }
}

3、处理器,针对请求路径的处理(Handler)[责任链式]

  • 作用在全局路由器的写法(作用域可大可小)

处理器分为:

类型说明
前置处理器一般做些验证或检测工作,有一定的拦阻效果
主处理器默认都是这种
后置处理器一般是做些补充工作
// handler模式(申明一个:前置处理器)
//
Solon.app().before(-1, "/hello/", ctx->{
    if(ctx.param("name") == null){    
        ctx.setHandled(true);    //如果没有name, 则终止处理
    }
});

// controller模式(申明一个:主处理器)
//
@Controller
public class HelloController  {
    @Mapping("/hello/")   
    public void handle(Context ctx, String name) {
        
    }
}
  • 以注解形式,作用在具体控制器的写法(作用域较小;也可以注在基类上)

更多参考::《@Before、@After 用法说明(AOP)》

4、拦截器,对Method拦截(Interceptor)[环绕式]

只有被动态代理的Bean,才能对Method进行拦截。一般用于切面开发,用注解做为切点配合起来用。比如缓存控制注解@Cache、事务控制注解@Tran等。

更多参考:《切面与环绕拦截(AOP)》《@Around 使用说明(AOP)》