在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);
}
}
附:專案地址
附:入門示例
- Solon 入門教程示例:https://gitee.com/noear/solon_demo
- Solon Rpc 入門教程示例:https://gitee.com/noear/solon_rpc_demo
- Solon Cloud 入門教程示例:https://gitee.com/noear/solon_cloud_demo
- Solon 進階教程示例:https://gitee.com/noear/solon_advance_demo