实现一个类似RequestMapping的分发服务

本文介绍如何利用Spring框架创建自定义服务分发器,通过定义映射注解和请求响应对象,实现类似SpringMVC的灵活服务分发机制,便于服务扩展和维护。

在编写非web的的服务处理程序时,需要根据某个字段值作分发,这样便于分离不同的服务类和方法,也便于以后增加和扩展服务处理逻辑。有了Spring的容器,最简单的方式是一个服务一个类,用相同的接口,然后用字段映射到beanName。

但是有时候需要分发的服务变多,而且重用逻辑也不好分得太散。这时需要类似Spring MVC的分发映射到处理方法的方式。这样灵活性很高,扩展性也好,编写扩展服务起来也方便。其实Spring MVC已经实现了,只要借用这套机制,调用其辅助工具类,很容易搭建自己的服务分发。

本文用一个例子来抛砖引玉。

首先定义我们的映射器,用个类似RequestMapping的注解。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Mapping {
    String value() default "";
}

然后,抽象一组请求和返回对象:

public interface Req {
    String getMappingValue();
}
public class Res {

}

当然你也把请求参数和返回对象定义更贴合自己的业务特点,可以用上包括泛型等等的特性。

先看一个扩展服务,也就是最终分发到的方法样例:

@Service
@Mapping("/demo")
public class DemoServiceImpl implements DemoService {

    @Override
    @Mapping("/test")
    public Res testHandle(Req req) {
        return new Res();
    }
  
   @Override
    @Mapping("/test2")
    public Res test2Handle(Req req) {
        return new Res();
    }
}

和Spring MVC类似,这非常容易理解。最后是靠Req.getMappingValue()返回的值与具体方法的注解中定义的value相匹配而实现分发的。

/demo/test -> DemoServiceImpl.testHandle(Req req)
/demo/test2 -> DemoServiceImpl.test2Handle(Req req)

最后来看分发器,也很简单明了,基本工具类Spring里面都有了:

import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class Dispatcher extends ApplicationObjectSupport implements InitializingBean {

    private Map<String, ReqHandlerMethod> eventHandlerMap = new ConcurrentHashMap<>();

    @Override
    public void afterPropertiesSet() throws Exception {
        initHandlerMethods();
    }

    public Res dispatch(Req req) {
        String mappingValue = req.getMappingValue();
        if (eventHandlerMap.containsKey(mappingValue)) {
            ReqHandlerMethod reqHandlerMethod = eventHandlerMap.get(mappingValue);
            ReflectionUtils.makeAccessible(reqHandlerMethod.bridgeMethod);
            try {
                return (Res) reqHandlerMethod.bridgeMethod.invoke(reqHandlerMethod.handler, req);
            } catch (IllegalAccessException | InvocationTargetException e) {
                // 这里处理调用时发生的异常, 返回出错信息
                // return new Res();
                e.printStackTrace();
                throw new RuntimeException(e);
            }

        } else {
            throw new IllegalArgumentException("未找到处理方法,mappingValue:" + mappingValue);
        }
    }

    private void initHandlerMethods() {
        ApplicationContext ctx = getApplicationContext();
        String[] beanNames = ctx.getBeanNamesForType(Object.class);
        for (String beanName : beanNames) {
            Class<?> handlerType = ctx.getType(beanName);
            // 这里是透过AOP和各种产生的代理类拿到真正用户定义的类
            Class<?> userType = ClassUtils.getUserClass(handlerType);
            if (isHandler(userType)) {
                Map<Method, String> methods = MethodIntrospector.selectMethods(userType,
                        (MethodIntrospector.MetadataLookup<String>) method -> {
                            try {
                                return getMappingForMethod(method, userType);
                            } catch (Throwable ex) {
                                throw new IllegalStateException(
                                        "Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex);
                            }
                        });
                Object bean = ctx.getBean(beanName);
                methods.forEach(((method, s) -> {
                    String mapping = getMappingForMethod(method, userType);
                    registerHandlerMethod(mapping, bean, method);
                }));
            }
        }

    }

    private boolean isHandler(Class<?> userType) {
        return AnnotationUtils.findAnnotation(userType, Mapping.class) != null;
    }

    private String getMapping(AnnotatedElement element) {
        Mapping mapping =
                AnnotatedElementUtils.findMergedAnnotation(element, Mapping.class);
        return mapping.value();
    }

    private String getMappingForMethod(Method method, Class<?> handlerType) {
        String info = getMapping(method);
        if (info != null) {
            String typeInfo = getMapping(handlerType);
            if (typeInfo != null) {
                info = typeInfo + info;
            }
        }
        return info;
    }

    private void registerHandlerMethod(String eventName, Object handler, Method method) {
        ReqHandlerMethod reqHandlerMethod = new ReqHandlerMethod();
        reqHandlerMethod.handler = handler;
        reqHandlerMethod.method = method;
        reqHandlerMethod.bridgeMethod = BridgeMethodResolver.findBridgedMethod(method);
        eventHandlerMap.put(eventName, reqHandlerMethod);
    }

    static class ReqHandlerMethod {
        Object handler;
        Method method;
        Method bridgeMethod;
        Class<? extends Req> reqType;
    }

}

当然可以不实现InitializingBean接口,用@PostConstruct也一样,在容器初始化后调用。

 @PostConstruct
 private void initHandlerMethods() {
 // ...
 }

在实际使用的可以扩展请求参数的转换,典型的请求参数可能来自非HTTP协议的,需要自己定义消息转换器来转换请求和返回对象。另外还有集中的处理包装和服务调用期间处理公用异常。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值