if 判断太多,不想写重复代码怎么办?

😀 事件经过


有一天,一如既往的在公司摸鱼,突然,某位好基友在基友群十分认真了问了句:安仔,我有好多的 if 判断,有没有其他办法解决呀?


这能难倒身经百战的安仔么?我不加思索立马回他:switch 啊。


没办法,乐于助人毕竟是我行走江湖的招牌啊。😉


😀 思考


不过说真的,安仔在很多上线的项目中发现也有海量的 if 判断,如下形式:

if(type == 1) {
	业务1();
}
if(type == 2) {
	业务2();
}
if(type == 3) {
	业务3();
}
......

可是安仔思前想后,觉得 switch 也并不是最好的解决办法呀,毕竟代码量还是在那,各位平时如何解决大量 if 的判断呢?(不是真用的 switch 吧??)


😀 调查


有个特别牛逼的开源框架叫做 SpringMVC 给了我极大的灵感,传说它有个叫 HandlerMapping 的家伙能帮助我们找到匹配 Http Method 和 URI 的方法逻辑。也就是 @RequestMapping 注解里面的 method 和 value 属性。


那么我们在 Controller 中写了那么多方法,这个 HandlerMapping 家伙是怎么通过一个 Http 请求找到那个符合的方法呢?不会是通过 if 一个一个判断吧?


安仔决定给大家破这个案。苦苦翻完了 SpringMVC 全部代码,可是并没见着大量 的 if 代码。果然,这案子还是有点难度,暴力破解失败!!😭


难倒还有什么玄机不成,让我们采访一下老大哥 DispatcherServlet , 听说他是 SpingMVC 的核心。


在我们的严刑逼问下,发现一切怀疑都指向了DispatcherServlet 的 doDispatch() 方法,并在这个方法中我们发现了 getHandler() 方法充满了嫌疑。


	// 这个方法可以说是 DispatcherServlet 的核心,看明白了有助于理解 SpringMVC
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 获取Handler 重点 
				mappedHandler = getHandler(processedRequest);      
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}
	//  以下代码省略

通过对 getHandler() 的走访,发现了重要的蛛丝马迹,它通过 mapping 对象的 getHandler() 方法获取了一个处理器链。这个 mapping 对象就是本案的的核心 HandlerMapping*,真相也许马上水落石出。让我们继续调查下去。

	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);    // 重点这里
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

遗憾的是 HandlerMapping 是一个接口,我们需要在运行时找到它的实现类对象,才能继续排查。经过断点运行,一切指向了 HandlerMapping 的间接子类 RequestMappingHandlerMapping 。但在 RequestMappingHandlerMapping 中没有找到 getHandler() 方法的实现,而在它的间接父抽象类 AbstractHandlerMapping 找到了 getHandler() 方法的实现:


	@Override
	@Nullable
	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		// 获取 handler **重点**
		Object handler = getHandlerInternal(request);
		if (handler == null) {
			handler = getDefaultHandler();
		}
		if (handler == null) {
			return null;
		}
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}

		// 获取执行链 (HandlerExecutionChain 里面包含了 handler 和 拦截器 list )
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);  

		if (logger.isTraceEnabled()) {
			logger.trace("Mapped to " + handler);
		}
		else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
			logger.debug("Mapped to " + executionChain.getHandler());
		}

		if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
			CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			config = (config != null ? config.combine(handlerConfig) : handlerConfig);
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}

		return executionChain;
	}

我们知道一般 HandlerMapping 会返回一个 HandlerExecutionChain 执行链 ,里面包含我们需要的 handler 和一群拦截器(包含我们自己定义的拦截器),之后 HandlerAdapter 执行 Handler 时会帮你处理拦截器的代码。所以接下来就是探秘 **getHandlerInternal(request)**方法了。


getHandlerInternal(request) 方法在 AbstractUrlHandlerMappingAbstractHandlerMethodMapping 中各有实现,顾名思义,一个是针对 URL 的,一个是针对方法的。我们康康针对 URL 的吧!

	@Override
	@Nullable
	protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
		// 获取 path
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
		request.setAttribute(LOOKUP_PATH, lookupPath);
		// 找 handler ,【重点】
		Object handler = lookupHandler(lookupPath, request);
		if (handler == null) {
			// We need to care for the default handler directly, since we need to
			// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
			Object rawHandler = null;
			if ("/".equals(lookupPath)) {
				rawHandler = getRootHandler();
			}
			if (rawHandler == null) {
				rawHandler = getDefaultHandler();
			}
			if (rawHandler != null) {
				// Bean name or resolved handler?
				if (rawHandler instanceof String) {
					String handlerName = (String) rawHandler;
					rawHandler = obtainApplicationContext().getBean(handlerName);
				}
				validateHandler(rawHandler, request);
				handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
			}
		}
		return handler;
	}

接下来就是继续看 lookupHandler(lookupPath, request) 方法了。方法有点长,截取了一部分

	@Nullable
	protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
		// Direct match?   原注释:直接匹配?   从 handlerMap 中直接获取
		Object handler = this.handlerMap.get(urlPath);
		if (handler != null) {
			// Bean name or resolved handler?
			if (handler instanceof String) {
				String handlerName = (String) handler;
				handler = obtainApplicationContext().getBean(handlerName);
			}
			validateHandler(handler, request);
			return buildPathExposingHandler(handler, urlPath, urlPath, null);
		}

		// Pattern match?   原注释:匹配匹配? 从 handlerMap 中匹配获取
		List<String> matchingPatterns = new ArrayList<>();
		for (String registeredPattern : this.handlerMap.keySet()) {
			if (getPathMatcher().match(registeredPattern, urlPath)) {
				matchingPatterns.add(registeredPattern);
			}
			else if (useTrailingSlashMatch()) {
				if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
					matchingPatterns.add(registeredPattern + "/");
				}
			}
		}
 	......

看到这里,相信你明白一些了,SpringMVC 就是把 URL 和 Handler 在初始化时放在了一个 Map(handlerMap) 中,获取的时候直接从这个 Map 中获取。那么,SpringMVC 是如何放入这个 Map 的呢?让我们康康初始化的过程。这次我直接放核心代码吧,源码粘贴多了容易晕。


protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
		// 上面省略一下代码
		Object mappedHandler = this.handlerMap.get(urlPath);
		if (mappedHandler != null) {
			if (mappedHandler != resolvedHandler) {
				throw new IllegalStateException(
						"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
						"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
			}
		}
		else {
			if (urlPath.equals("/")) {
				if (logger.isTraceEnabled()) {
					logger.trace("Root mapping to " + getHandlerDescription(handler));
				}
				setRootHandler(resolvedHandler);
			}
			else if (urlPath.equals("/*")) {
				if (logger.isTraceEnabled()) {
					logger.trace("Default mapping to " + getHandlerDescription(handler));
				}
				setDefaultHandler(resolvedHandler);
			}
			else {
				// 康康这里哦,放进去了吧
				this.handlerMap.put(urlPath, resolvedHandler);
				if (logger.isTraceEnabled()) {
					logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
				}
			}
		}
	}

SpringMVC 在初始化时通过 反射 获取到我们写的 Controller 中的方法,把合理的(在调用这个方法的方法里进行了判断)放入上面提到的 Map 中,为了证明这个过程,我追踪到 AbstractHandlerMethodMapping 的下面这个方法:

	protected void detectHandlerMethods(Object handler) {
		Class<?> handlerType = (handler instanceof String ?
				obtainApplicationContext().getType((String) handler) : handler.getClass());

		if (handlerType != null) {
			Class<?> userType = ClassUtils.getUserClass(handlerType);
			Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
					(MethodIntrospector.MetadataLookup<T>) method -> {
						try {
							return getMappingForMethod(method, userType);
						}
						catch (Throwable ex) {
							throw new IllegalStateException("Invalid mapping on handler class [" +
									userType.getName() + "]: " + method, ex);
						}
					});
			if (logger.isTraceEnabled()) {
				logger.trace(formatMappings(userType, methods));
			}
			methods.forEach((method, mapping) -> {
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
				// 下面这句代码就是注册了
				registerHandlerMethod(handler, invocableMethod, mapping);
			});
		}
	}

不仅初始化放入 Map 时使用了反射获取每个 Handler,被 Adapter 调用时也使用反射调用的,最终追踪到了InvocableHandlerMethod中 的 doInvoke() :


	@Nullable
	protected Object doInvoke(Object... args) throws Exception {
		ReflectionUtils.makeAccessible(getBridgedMethod());
		try {
			//  这里反射调了我们自己写的方法逻辑 即 method.invoke()
			return getBridgedMethod().invoke(getBean(), args);
		}
		// 以下代码省略
	}

😀 破案


总的的来说,就是 SpringMVC 的 HandlerMapping 在初始化时会通过反射拿到我们的个Handler(也就是我们 Controller 中的自己写的方法 Method ),放到一个 handlerMap 中进行,URL 为 key,handler 为 value,在获取时直接从 Map 中获取 Method,在 Adapter 调用时通过 method.invoke() 方法反射调用。


😀 总结


SpingMVC 避免大量 if 判断的原理在于提前通过反射建立 条件 => 方法 的规则,并将这个规则用 map 存起来,用 map 存主要因为反射的效率偏低(特别是 setAccessible(true) 即开启安全检查,效率更低),核心在于反射来动态做判断可以避免大量 if 。


😀 实践


前段时间,我自己在写一个聊天的应用,后端使用 netty 来处理 socket 的连接,前端使用了 websocket 通信,对于每一个来自前端的消息,我需要先判断是正常聊天消息还是其他消息(比如心跳,添加好友,同意好友等),我为消息设置了 type 字段来区分消息类型,可是我需要进行消息类型的判断,如果我使用 if 来判断,那将是个灾难,因为我无法确定有多少种类型。


于是我效仿 SpingMVC 这种 Mapping 反射动态判断的思想,完成了判断,以下是部分代码逻辑(伪代码不可运行):


<1> 设计一个注解用于标注到自定义的方法上:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited   // 子类可以继承
public @interface Handle {
	//  通过这个类型来判断
    String type() default "default";
}

<2> 业务使用该注解:


public class MessageHandler {

	// 处理聊天消息的逻辑
    @Handle(type = "chat_message")
    @Override
    public void ChatMessage() {
		// 省略具体逻辑
	}
	
	// 验证token的逻辑
    @Handle(type = "token_verify")
    @Override
    public void tokenVerify() {
    	// 省略具体逻辑
    }

	// 获取好友的逻辑
    @Handle(type = "get_friends")
    @Override
    public void getFriends() {
    	// 省略具体逻辑
	}
	...
}

<2> 设计 Mapping 来动态判断和调用:


@Component
@RequiredArgsConstructor
public class MessageTypeHandlerMapping implements HandlerMapping {

	// 注入业务类
   	private final MessageHandler messageHandler;
	
	// 省略部分代码
	
	/**
     * mapping 处理
     * @param webSocketFrame
     * @param channel
     */
  	@Override
    public void handle(Message message) {
    	String type = message.getType();
    	// getClass() 拿到的是代理类  getSuperclass() 能获取真实的类
        for (Method method : MessageHandler.getClass().getSuperclass().getMethods()) {
        	// 判断这个方法上是否有 Handle 注解
            if(method.isAnnotationPresent(Handle.class)) {
                Handle annotation = method.getAnnotation(Handle.class);
                // 判断注解的类型是否一致
                if (annotation.type().value().equals(type)) {
                    method.setAccessible(true);
                    // 反射调用
                    method.invoke(messageHandler,null);
                    break;
                }
            }
        }
	}
}

当然,安仔这个代码省去了 SpringMVC 中 HandlerMap 和 HandlerAdapter 的设计,大家明白思想就行,不必较真哈 ~
原文: 【好基友问:if 判断太多,不想写重复代码怎么办?】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值