SpringMVC IOC DI接口版本管理(迭代版)

来源:互联网 发布:儿童初学画画软件 编辑:程序博客网 时间:2024/06/18 10:32

前言


之前写过一篇文章

《SpringMVC 接口版本管理/IP访问控制/ANT打包发布到LINUX》 

后来总觉得其中DI, IOC管理没有贯彻下来,主要原因是以为中间用了反射,来对Controller进行调用,反射又脱离了spring动态代理的生态体系。

最近针对此方面做了一定的修改和调整。

如果对下面内容不理解请先看上面所述的文章


修订


Controller

请先打开上面所述文章的实例,进行比对查看
以下为修改后的基本文件路径


IBaseController

依然是提供了一个统一的接口,为所有具体的Controller提供了管理
package com.api.controller.factory;public interface IBaseController {}

RoomController1_1

对于带版本号的Controller我们使用了@service进行注解,其隐式的name表现为@Service(value="roomController1_1")
当然他的父级也做了@Service,其value默认为roomController1_0
@Servicepublic class RoomController1_1 extends RoomController1_0  {

IController

default是新版增加的接口,我们是用java8 default来进行描述。 
package com.api.controller.factory.version;import com.api.controller.factory.IBaseController;/** * service版本接口 *  * @author Allen 2017年5月25日 * */public interface IController {public IBaseController pushController(); //1.0提供了3个基础方法public IBaseController roomController();public IBaseController userController();public default IBaseController videoController(){ //1.1需要用到一个新方法return null;}        //N.N依然使用default建立方法}

Controller1_0

IController的实现类及实现类的派生类这里作为版本列表的存在,声明了某版本下包含的具体Controller类
package com.api.controller.factory.version;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.stereotype.Service;import com.api.controller.factory.IBaseController;/**  * @author Allen 2017年5月18日  */@Servicepublic class Controller1_0 implements IController {@Autowired@Qualifier("pushController1_0")private IBaseController pushController1_0;@Autowired@Qualifier("roomController1_0")private IBaseController roomController1_0;@Autowired@Qualifier("userController1_0")private IBaseController userController1_0; @Overridepublic IBaseController pushController() { // TODO Auto-generated method stubreturn pushController1_0;}@Overridepublic IBaseController roomController() {// TODO Auto-generated method stubreturn roomController1_0;}@Overridepublic IBaseController userController() {// TODO Auto-generated method stubreturn userController1_0;} }

Controller1_1

1.1继承了1.0只对1.1的新Controller进行描述
例如我们还有1.2  ~ 1.N 也只需对新版本的XXController1_N进行修饰,老版本的则依赖继承关系向下兼容
譬如Controller1_1按照下面Class内容 ,我调用UserController则会去父类寻找1.0版的,但是我只需让客户端维护一个1.1的版本号即可,
1.0的想要调用1.1的则肯定是无法找到,也做到了低版本无法访问高版本的隔离
package com.api.controller.factory.version;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.stereotype.Service;import com.api.controller.factory.IBaseController;/** * version1.1 *  * @author Allen 2017年5月18日  */@Servicepublic class Controller1_1 extends Controller1_0 {@Autowired@Qualifier("roomController1_1")private IBaseController RoomController1_1;@Autowired@Qualifier("videoController1_1")private IBaseController VideoController1_1; @Overridepublic IBaseController roomController() {// TODO Auto-generated method stubreturn RoomController1_1;}@Overridepublic IBaseController videoController() {return VideoController1_1;} }
再来看改动较多的CoreController,这个CoreController才是所有Controller中为一个有@Controller注释的对外的Controller,可以看做一个访问负责人
我们只摘选有调整的CoreController代码
这里先声明我们的调用url格式
{
  https://127.0.0.1/我的项目/user/2_4/list/1/4/化妆品
user代表访问模块名指向了UserController
2_4代表了访问版本号指向了 Controller2_4
1,4,化妆品代表参数
}
         /** * 多版本代理指向Controller *  * @throws Exception */@ResponseBody@RequestMapping("/**")  //相应各种参数,当然在类头部定义了@RequestMapping(value = "/{domain}/{version}")public Object execute(@PathVariable String domain, @PathVariable String version, String callback) {Object ob = req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);if (ob == null) // 参数为空return LiveValue.execute(State.NPE, callback);String val = ob.toString();val = val.substring(val.indexOf(version) + version.length() + 1); //拆出参数中的版本号if (Arrays.asList(DOMAINS).indexOf(domain) == -1) //确保访问的模块再定义的DOMAINS模块名预设数组中                //final String[] DOMAINS = new String[] { ROOM, USER, PUSH, VIDEO, PULL };return LiveValue.execute(State.DOMAINNOTFOUND, callback);else {try {                                //旧版文章中这里使用getClass去抽象工厂反射,现在我们通过spring提供的反射类                                //得到Controller+version.java 也就是得到 Controller1_0 或 Controller1_N                                 //当然bean为null会返回版本错误Object bean = reflectBean.get(Type.CONTROLLER, version); if (bean == null) {return LiveValue.execute(State.VERSIONERROR, callback);}analysis(val, version); //这里是从请求的"/**" 中解析出参数int flag = targetMethod(bean, req, domain);//去反射调用具体指向的类(主要说这个)return bindResponse(callback, ob, flag);//根据结果返回不同的信息} catch (SecurityException e) {return LiveValue.execute(State.NPE, callback);} catch (NoSuchMethodException | IllegalArgumentException e) {return LiveValue.execute(State.DOMAINNOTFOUND, callback);} catch (Exception e) {Log.printlog(RoomController1_0.class, e);System.out.println(StringUtils.append("[domain]", domain, "[version]", version, "[params]",req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE).toString()));return LiveValue.execute(State.EXCEPTION, callback);}}} 
       /** * 执行目标函数 *  * @param clazz * @param method * @return * @throws Exception */        //ReflectionUtils是springframwork.util包下提供的反射工具,目的是在spring动态代理的对象中进行反射并无损的得到bean         //下面以 /user/1_0/list/2  的请求url来做分析private int targetMethod(Object bean, HttpServletRequest req, String domain) throws Exception {//获取IController中domain指向的Controller成员                //bean是"controller1_0",domain是user,bindControllerMethod是将user与常量Controller进行append得到userController的methodName                //最终controllerMethod返回的是controller1_0中的userController方法Method controllerMethod= ReflectionUtils.findMethod(bean.getClass(),bindControllerMethod(domain));//执行获取到的Controller成员                //得到了method只有执行才能得到其返回的class                //参数的意思为从  bean: controller1_0 中去执行 userController方法Class<?> controllerClazz=ReflectionUtils.invokeMethod(controllerMethod, bean).getClass();                //返回的必然是UserController1_0然后去遍历其方法,寻找我们要调用的预先得到的methodNameMethod[] methods=controllerClazz.getMethods();//Method[] methods = bean.getClass().getMethod(domain,null).getClass().getMethods();// 找到目标method并配置参数类型classfor (Method m : methods) {// 通过注解判断是否匹配访问domainif (m.isAnnotationPresent(RequestAlias.class)&& m.getAnnotation(RequestAlias.class).value().equals(methodName)) {if (!ipControl(getRemoteHost(req), controllerClazz, m)) {// ip不匹配访问权限return -1;}                                //成功获取了method,再次执行                                //得到 UserController1_0的类名,得到其bean,然后调用其method,传入params即可result = ReflectionUtils.invokeMethod(m,reflectBean.get(controllerClazz.getSimpleName()), obParam);return 0;}}return -2;}

reflectBean

提供了从spring中取获取bean
这里就不说了结合上面使用就是获取bean,然后首字母小写
package com.api.util.spring;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import com.api.modules.Conf;import com.api.util.StringUtils;/** * controllerBean获取 *  * @author Allen 2017年5月24日 * */@Componentpublic class ReflectBean {/** * 为了dubbox和spring动态代理的繁衍 有了下面的声明。 *  * 旧版本设计如下 http://blog.csdn.net/crazyzxljing0621/article/details/72723823 */@Autowiredprivate SpringContextsUtil springContextsUtil;/** *  * @param strs *             * @return */public Object get(Type type, String  version) { Object ob = null;if (!version.matches(Conf.PATTERN_COMPLIE_VERSION)|| (ob = springContextsUtil.getBean(StringUtils.append(type.str(), version))) == null) {return null;// 版本错误}return ob;}/** * 直接返回一个反射bean * @param beanName * @return */public Object get(String beanName) {return springContextsUtil.getBean(toLowerCaseFirstOne(beanName));}/** * 首字母改小写 * @param s * @return */private String toLowerCaseFirstOne(String s) {if (Character.isLowerCase(s.charAt(0)))return s;elsereturn (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();}public enum Type {                //这里为什么还有一个service因为service层也是有版本列表管理的SERVICE("service"), CONTROLLER("controller");private String str;private Type(String str) {this.str = str;}public String str() {return this.str;}}}
package com.api.util.spring;import org.springframework.beans.BeansException;import org.springframework.beans.factory.NoSuchBeanDefinitionException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;/** * 获取Bean 必须是ApplicationContextAware的实现类 * @author Allen 2017年7月26日 * */@Componentpublic class SpringContextsUtil implements ApplicationContextAware {    private ApplicationContext applicationContext;    public Object getBean(String beanName) {    try {            return applicationContext.getBean(beanName);} catch (NoSuchBeanDefinitionException e) {return null;}    }    public <T> T getBean(String beanName, Class<T> clazs) {        return clazs.cast(getBean(beanName));    }    @Override    public void setApplicationContext(ApplicationContext applicationContext)            throws BeansException {        this.applicationContext = applicationContext;    }}
看看上面
我们请求一个接口一个版本
通过CoreController进行验证,得到版本列表的bean,然后去bean下面寻找对应的版本列表实现
从实现中得到具体的 XXController1_N。
当有新的版本接入
譬如增加1.3版本,特性是增加了userController中的一个方法
只需要建立一个Controller1_3 extends Controller1_2
在其中生命 UserController1_3。
当然UserController1_3 extends UserController1_2
是不是很简单。

Service

Service也是这样去改造简单说下
XXController调用某service利用serviceFactory来传递版本,指明调用的函数。 
return serviceFactory.iVersion(version).iUser().save(roomStreamKey, userName, roomPassword);

serviceFactory是什么?

提供了获取bean的钩子
@Componentpublic class ServiceFactory {@Autowiredprivate ReflectBean reflectBean;public IService iVersion(String version) throws Exception {Object ob = reflectBean.get(Type.SERVICE, version);if (ob == null)throw new ApiVersionException();return (IService) ob;}}

总结

修改后重新定义了controller,service,dao的DI,调整了IOC

再说说调用过程,CoreController反射到版本列表,版本列表根据继承与version得到method,method返回XXControllerX_Y,再反射到其method
新增版本的时候只需要继承上一个版本即可,每个新的版本为他建立一个统一的版本列表即可,Controller4_5 extends Controller 4_4 是不是向下兼容了大笑
这里没有if,switch,没有@requestMapping(/1_0) ,只有解耦