Solon 的過濾器 Filter 和兩種攔截器 Handler、 Interceptor

劉之西東發表於2021-05-17

在web開發中,過濾器、攔截器是經常用到的功能。它可以幫我們限制流量、驗證是否登陸、記錄日誌以及統計執行效率等等。

今天主要交流一下 Solon 框架中的過濾器和攔截器。

Solon 是什麼框架?

Solon 是一個外掛式的 Java 微型開發框架。強調,剋制 + 簡潔 + 開放的原則;力求,更小、更快、更自由的體驗。支援:RPC、REST API、 MVC、Micro service、WebSocket、Socket 等多種開發模式。

一、Solon 的過濾器

Solon 是一個 Servelt 無關的開發框架,所以有自己專屬的 Filter,但與 Servelt 的 Filter 功能相差不大。另外,Solon 是一個多訊號源的開發框架,所以 Filter 對 Http、Socket、WebSocket 的請求訊號統統有效。

//介面程式碼
@FunctionalInterface
public interface Filter {
    void doFilter(Context ctx, FilterChain chain) throws Throwable;
}

Solon Filter 是最根級的、最粗顆料度的過濾手段。它不能選擇路徑過濾,只能對所有的請求進行過濾。如果需要僅對某路徑處理,需要程式碼內控制,這是與 Servelt Filter 的一大區別。

一個限流的示例:
public class DemoApp{
    public static void main(String[] args){
        SolonApp app = Solon.start(DemoApp.class, args);
        
       app.filter((ctx, chain)->{
            try(AutoCloseable entry = Limiter.entry()){
                chain.doFilter(ctx);
            }catch (Exception e){
                ctx.output("伺服器有點忙,請稍後再試");
            }
        });
    }
}
也可以用元件的形式申明:
@Component
public class BreakerFilter implements Filter {
    @Override
    public void doFilter(Context ctx, FilterChain chain) throws Throwable {
        try(AutoCloseable entry = Limiter.entry()){
            chain.doFilter(ctx);
        }catch (Exception e){
            ctx.output("伺服器有點忙,請稍後再試");
        }
    }
}

Solon Filter 絕大部份的工作,都可以由 Solon 攔截器 Handler 完成。

二、Solon 的攔截器

Solon 中攔截器分為兩種。一是 Handler,爭對請求地址與上下文物件的攔截;一是 Interceptor,對 Bean 的 Method 進行攔截。

1、Handler(Context 攔截器)

Solon 對web請求處理的本質,即是對 Context 的一路攔截處理並最終輸出。這一路的攔截處理可稱之為攔截鏈,攔截鏈上每個處理節點,即為 Context 攔截器,每個攔截器即為 Handler 。

//介面程式碼
@FunctionalInterface
public interface Handler {
    void handle(Context context) throws Throwable;
}

Handler 在順位上可分為:前置攔截器(可以多個)、中置攔截器(最多一個)、後置攔截器(可以多個),提供了三段攔截能力。在使用上又有三種模式可選,具體如下程式碼:

使用模式一:純手寫模式(這種模式,可以偷偷為控制器加點東西)
public class DemoApp{
    public static void main(String[] args){
        SolonApp app = Solon.start(DemoApp.class, args);
        
        //中置攔截處理
        app.get("/hello",c->c.output("Hello world!"));
        
        
        
        //前置攔截處理(驗證Token)
        app.before("/hello",c->{
            if(c.header("Token") == null){
                //如果沒有Token則中止後續處理
                c.setHandled(true);
            }
        });

        //前置攔截處理(記錄Log)-- 攔截鏈,可以形成一種"裝配"的感覺
        app.before("/hello",c->{
            System.out.println("記錄日誌");
        });

        //後前置攔截處理
        app.after("/hello",c->{
            System.out.println("記錄時間消耗");
        });
    }
}
使用模式二:控制器編寫模式(這種模式比較有透明度,自己給自己加點料)
@Controller
public class DemoController {

    //前置攔截處理(驗證Token)
    @Mapping(value = "hello", before = true)
    public void helloBef1(Context c) {
        if (c.header("Token") == null) {
            //如果沒有Token則中止後續處理
            c.setHandled(true);
        }
    }

    //前置攔截處理(記錄Log)
    @Mapping(value = "hello", before = true)
    public void helloBef2(Context c) {
        System.out.println("記錄日誌");
    }

    //中置攔截處理
    @Get
    @Mapping("hello")
    public String hello() {
        return "Hello world!";
    }

    //後前置攔截處理
    @Mapping(value = "hello", after = true)
    public void helloAft1(Context c) {
        System.out.println("記錄時間消耗");
    }
}
使用模式三:註解模式(通過:@Before、@After 註解附加;這種模式比較有裝配感)
//
//1. 三個攔截處理
//
public class HelloBef1Handler implements Handler {
    @Override
    public void handle(Context c) throws Throwable {
        if (c.header("Token") == null) {
            //如果沒有Token則中止後續處理
            c.setHandled(true);
        }
    }
}

public class HelloBef1Handler implements Handler {
    @Override
    public void handle(Context c) throws Throwable {
        if (c.header("Token") == null) {
            //如果沒有Token則中止後續處理
            c.setHandled(true);
        }
    }
}

public class HelloBef2Handler implements Handler {
    @Override
    public void handle(Context c) throws Throwable {
        System.out.println("記錄日誌");
    }
}

//
// 2.通過註解,附加在Action上
//
@Controller
public class DemoController {
    //此注入,也可附加在控制器類上
    @After({HelloAft1Handler.class})
    @Before({HelloBef1Handler.class, HelloBef2Handler.class})
    @Get
    @Mapping("hello")
    public String hello() {
        return "Hello world!";
    }
}

2、Interceptor(Method 攔截器)

Interceptor 攔截的目標是方法,所以被代理的 Bean Method。

//介面程式碼
@FunctionalInterface
public interface Interceptor {
    Object doIntercept(Invocation inv) throws Throwable;
}

Interceptor 同樣有三種使用模式。

使用模式一:手寫模式
//定義一個攔截器
public class TranInterceptor implements Interceptor {
    @Override
    public Object doIntercept(Invocation inv) throws Throwable{
        ValHolder val0 = new ValHolder();

        Tran anno = inv.method().getAnnotation(Tran.class);
        TranExecutorImp.global.execute(anno, () -> {
            val0.value = inv.invoke();
        });

        return val0.value;
    }
}

//定義一個註解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Tran {
}

//註冊一個環境處理到Aop容器
Aop.context().beanAroundAdd(Tran.class, new TranInterceptor(), 120);

//使用
@Service
public class UserService{
    //通過@Tran,實現攔截並新增事務支援
    @Tran
    public void addUser(User user){
        userMapper.insert(user);
    }
}
使用模式二:通過註解橋接模式(通過:@Around 註解橋接一個攔截器)
//定義一個攔截器
public class TranInterceptor implements Interceptor {
    @Override
    public Object doIntercept(Invocation inv) throws Throwable{
        ValHolder val0 = new ValHolder();

        Tran anno = inv.method().getAnnotation(Tran.class);
        TranExecutorImp.global.execute(anno, () -> {
            val0.value = inv.invoke();
        });

        return val0.value;
    }
}

//定義一個註解(通過@Aroud 關聯一個攔截器)
@Around(value = TranInterceptor.class, index = 120))
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Tran {
}

//使用
@Service
public class UserService{
    //通過@Tran,實現攔截並新增事務支援
    @Tran
    public void addUser(User user){
        userMapper.insert(user);
    }
}
使用模式三:直接註解模式(通過:@Around 註解直接申明攔截器)
//定義一個攔截器
public class TranInterceptor implements Interceptor {
    @Override
    public Object doIntercept(Invocation inv) throws Throwable{
        ValHolder val0 = new ValHolder();

        Tran anno = inv.method().getAnnotation(Tran.class);
        TranExecutorImp.global.execute(anno, () -> {
            val0.value = inv.invoke();
        });

        return val0.value;
    }
}

//使用
@Service
public class UserService{
    @Around(value = TranInterceptor.class, index = 120))
    public void addUser(User user){
        userMapper.insert(user);
    }
}

附:專案地址

附:入門示例

相關文章