前策工代——四个设计模式撑起一个政务Web框架

前、策、工、代——四个设计模式撑起一个政务Web框架

文章目录

  • 前、策、工、代——四个设计模式撑起一个政务Web框架
    • 一、不是学了模式去套,是写了代码回头看
    • 二、Front Controller:一个Servlet接管所有请求
    • 三、Strategy:TypeHandler——多态的威力
    • 四、Factory:TypeHandlerFactory ——注册表模式
    • 五、Proxy:CGLIB 动态代理——IOC容器的秘密武器
    • 六、Template Method:BaseTypeHandler的模板骨架
    • 七、这四个模式不是孤立的
    • 八、结语

一、不是学了模式去套,是写了代码回头看

这个自研框架从2008年第一行代码开始,到2023年系统下线,前后迭代了15年。写的时候没有刻意去"用设计模式"——事实上,很多模式的术语是后来才知道的。但回头看,框架的核心架构恰好对应了四个最经典的 GoF 模式:

模式框架中的位置解决了什么
Front Controllerroute.java所有请求的统一入口和分发
StrategyTypeHandler系列 +responseFactory参数解析和响应输出的可切换策略
FactoryTypeHandlerFactory+BeanFactory对象的创建、注册和依赖注入
ProxyCGLIB 动态代理 +proxyFilter无侵入的横切能力注入

四个模式串起来,就是一个完整的请求处理链路:

请求 → Front Controller(route) → 解析URL找到Method → Factory(BeanFactory) → 获取Controller实例 → Strategy(TypeHandler) → 解析入参类型 → 反射调用方法 → Strategy(responseFactory) → 生成响应

二、Front Controller:一个Servlet接管所有请求

前端控制器的思想很简单:不要让每个JSP/Servlet各自处理请求,用一个入口统一接管

route.java继承HttpServlet,配置/*拦截所有请求。核心代码在service方法里:

publicvoidservice(HttpServletRequestrequest,HttpServletResponseresponse){// 1. 从URL解析出方法和BeanMethodMapmothodmap=praserHandler(request);// 2. 解析入参——支持 DataCenter、newDataStore、List、HashMap、基本类型paramValues=praserInParameter(request,response,mothodmap);// 3. 从BeanFactory获取Controller实例Objectts=BeanFactory.getBean(mothodmap.getBeanid());// 4. 反射调用obj=method1.invoke(ts,paramValues);// 5. 输出响应——根据返回类型选择不同的输出策略responseFactory.getInstance().getTypeHandler(mothodmap.getReturnHandler()).parse(obj,request,response);}

URL 到方法的映射是启动时建立的。BeanFactory在初始化阶段扫描所有带@responseMapping注解的方法,用 ASM 读取字节码拿到参数名,拼成handlerMap

// URL: /api/user/save → Bean: userController → Method: savehandlerMap.put("/api/user/save",methodMap);

运行时,praserHandlerrequest.getRequestURI()中提取路径,直接查handlerMap。一个 HashMap 的 get 操作,比任何 XML 解析、注解扫描都快。

三、Strategy:TypeHandler——多态的威力

前端发来的参数五花八门:有时候是整个 JSON 对象(DataCenter),有时候是数组(List),有时候是单个字符串(int/String),有时候是上传文件(HashMap)。如果每个 Controller 方法都自己解析参数,代码重复到崩溃。

TypeHandler 系列用策略模式解决了这个问题:

publicinterfaceTypeHandler<T>{publicTparse(Objectobject)throwsTypeException;publicTparse(Objectobject,Classcls)throwsTypeException;publicTparse(Objectobject,Stringname,Classcls)throwsTypeException;}

每个类型一个实现:

TypeHandler处理的类型怎么解析
StringTypeHandlerString直接 toString
IntegerTypeHandlerint/IntegerparseInt,提供默认值
DateTypeHandlerDateparseDate,null时返回null
DataCenterTypeHandlerDataCenterJSON反序列化
ListTypeHandlerListJSONArray → 反射实例化
newDataStoreTypeHandlernewDataStoreJSON → DataStore
InputStreamTypeHandlerHashMap解析文件上传的multipart

praserInParameter里,根据方法的参数类型从工厂取出对应的 TypeHandler:

Classcls=mothodmap.getParametertypeList().get(i);TypeHandlerhandler=TypeHandlerFactory.newInstance().getTypeHandler(cls);paramValues[i]=handler.parse(rawData,mothodmap.getParameterNameList().get(i),...);

业务方法不需要知道参数是怎么来的——它只需要声明(DataCenter dc, HttpServletRequest req, HttpServletResponse res),框架自动把 JSON 转成 DataCenter,把 Request/Response 直接注入。

同样的策略模式也用在响应输出上。responseFactory根据方法注解的@responseType选择策略:

  • JSONresponse:输出 JSON 字符串
  • URLresponse:跳转到 JSP 页面
  • FileResponse:输出文件下载流

一个请求进来,策略链从头到尾:解析策略 → 业务逻辑 → 输出策略。

四、Factory:TypeHandlerFactory ——注册表模式

策略模式需要一个工厂来管理策略的注册和查找:

publicfinalclassTypeHandlerFactory{privateMap<Object,TypeHandler<?>>typeHandlerMap;privatestaticTypeHandlerFactoryfactory;privateTypeHandlerFactory(){typeHandlerMap=newHashMap<Object,TypeHandler<?>>();}publicstaticTypeHandlerFactorynewInstance(){if(factory==null){factory=newTypeHandlerFactory();factory.register(String.class,newStringTypeHandler());factory.register(int.class,newIntegerTypeHandler(0));factory.register(Integer.class,newIntegerTypeHandler(null));factory.register(Date.class,newDateTypeHandler(null));factory.register(DataCenter.class,newDataCenterTypeHandler());factory.register(List.class,newListTypeHandler());factory.register(HashMap.class,newInputStreamTypeHandler());// ... 16种类型}returnfactory;}publicTypeHandler<?>getTypeHandler(ClassjavaType){TypeHandlertypeHandler=typeHandlerMap.get(javaType);returntypeHandler==null?newUserObjectTypeHandler():typeHandler;}}

设计要点:

单例:全局只有一个工厂实例,避免重复注册。

懒加载初始化:第一次调用newInstance()时注册所有 TypeHandler,后续直接返回。

兜底策略:如果找不到精确匹配的 TypeHandler,返回UserObjectTypeHandler——它用反射处理任何自定义业务对象。框架不会因为不认识的类型就崩溃。

注册表可扩展:如果要加一个新类型(比如BigDecimal),一行代码搞定:

factory.register(BigDecimal.class,newBigDecimalTypeHandler());

五、Proxy:CGLIB 动态代理——IOC容器的秘密武器

BeanFactory 不仅是 Factory 模式,它还是一个小型 IOC 容器。启动时扫描@bean注解的类,自动实例化并注入依赖:

publicBeanFactory(Stringurlpackage){// Step 1: 扫描包下所有类Set<Class<?>>list=findClassInPackage.getClasses(urlpackage);for(Class<?>cl:list){if(cl.isAnnotationPresent(bean.class)){bean d=(bean)cl.getAnnotation(bean.class);if(cl.isAnnotationPresent(aoppoint.class)){// 需要AOP → 创建CGLIB代理map.put(d.id(),createproxy(cl));}else{// 不需要AOP → 直接实例化map.put(d.id(),cl.newInstance());}}}// Step 2: 注入属性(@property注解)for(Class<?>cl:list){if(cl.isAnnotationPresent(bean.class)){getField(cl,map.get(...));}}}

这里面代理模式起关键作用:带@aoppoint的类不走直接实例化,而是创建 CGLIB 代理:

privateObjectcreateproxy(Class<?>cls){Callback[]callbacks=newCallback[2];callbacks[0]=newdoProxy();// 走拦截链callbacks[1]=newnoProxy();// 直接透传Enhancerenhancer=newEnhancer();enhancer.setSuperclass(cls);enhancer.setCallbacks(callbacks);proxyFilter filter=newproxyFilter();filter.setFilerList(cls.getAnnotation(aoppoint.class).filter());enhancer.setCallbackFilter(filter);// 决定哪些方法走doProxyreturnenhancer.create();}

CGLIB 生成一个目标类的子类,所有方法调用都先经过CallbackFilter判断:在 filterList 里的方法走doProxy(责任链拦截),不在的走noProxy(直接透传)。这样,业务对象的使用者完全不知道它是一个代理——它看起来就是普通的UserController,但事务、日志、监控已经在代理层自动执行了。

六、Template Method:BaseTypeHandler的模板骨架

BaseTypeHandler用了模板方法模式。所有具体 TypeHandler 继承它,只实现valueOf,其他逻辑全部复用:

publicabstractclassBaseTypeHandler<T>implementsTypeHandler<T>{// 模板方法:定义骨架publicTparse(Objectobject,TdefaultValue)throwsTypeException{if(isnull(object)){// ① 空值检查returndefaultValue;}if(getType().isInstance(object)){// ② 类型匹配return(T)object;}try{returnvalueOf(object);// ③ 调用子类实现}catch(Exceptione){thrownewTypeException(...);}}// 子类只需要实现这个publicabstractTvalueOf(Objectobject)throwsTypeException;}

StringTypeHandler的实现只有两行:

publicclassStringTypeHandlerextendsBaseTypeHandler<String>{publicStringvalueOf(Objectobject){returnString.valueOf(object);}}

DataCenterTypeHandler的实现也只多了 JSON 解析:

publicclassDataCenterTypeHandlerextendsBaseTypeHandler<DataCenter>{publicDataCentervalueOf(Objectobject)throwsTypeException{DataCenterdc=newDataCenter();dc.fromJson((String)object);returndc;}}

空值处理、类型比较、异常包装——这些在每个 TypeHandler 里都要做的事,被BaseTypeHandler统一做了。子类只关心"怎么把一个 Object 变成 T"。

七、这四个模式不是孤立的

把它们放回一个完整的请求来看:

1. 浏览器发 POST /api/person/save,Body里是JSON ↓ 2. route.service() 拦截请求(Front Controller) ↓ 3. praserHandler() 从URL提取 /api/person/save → 查handlerMap → 拿到MethodMap ↓ 4. praserInParameter() 遍历方法的参数类型 → TypeHandlerFactory.getTypeHandler(DataCenter.class) → DataCenterTypeHandler(Factory + Strategy) → 解析JSON为DataCenter对象 → TypeHandlerFactory.getTypeHandler(HttpServletRequest.class) → 直接注入request ↓ 5. BeanFactory.getBean("personController") → 取实例(如果是@bean+@aoppoint,返回CGLIB代理)(Factory + Proxy) ↓ 6. method.invoke(proxyInstance, params) → 实际调用走doProxy责任链 ↓ 7. 返回DataCenter → responseFactory.getTypeHandler(JSONresponse.class).parse(dc, req, res)(Strategy)

每一步都对应一个模式,每个模式都解决了一个在系统全生命周期中反复出现的需求。

八、结语

学设计模式的时候,书上给的例子往往是"鸭子"、“咖啡店”、“停车场”。看完觉得懂了,但真到项目里不知道怎么用。

这个框架的代码是反过来的——先有业务需求(怎么统一接收请求、怎么解析五花八门的参数、怎么无侵入地加事务),然后自然长出了这些模式。Front Controller 不是"学了才用"的,是"所有请求都打到同一个 Servlet 上"这件事做久了,回头看发现它叫 Front Controller。

所以学模式的真正方法不是背 GoF 20多个模式的定义,是先写足够多的代码,然后在自己的代码里认出那些反复出现的结构。认出来一次,这个模式就再也不会忘了。