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格式
这里先声明我们的调用url格式
{
https://127.0.0.1/我的项目/user/2_4/list/1/4/化妆品
user代表访问模块名指向了UserController
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) ,只有解耦
阅读全文
0 0
- SpringMVC IOC DI接口版本管理(迭代版)
- SpringMVC学习-->控制反转(IoC)与依赖注入(DI
- IoC/DI(转载)
- Spring(四):IOC,DI
- Spring(1)IOC/DI
- Spring基础(IOC&DI)
- Ioc&&DI
- IOC/DI
- IOC&DI
- DI/IOC
- IoC/DI
- 让SpringMVC支持可版本管理的Restful接口
- spring 学习笔记(2)--IOC(DI)
- Spring(二):IOC和DI
- spring入门案例(IoC和DI)
- spring mvc 扫描注解(ioc,di)
- IoC框架(依赖注入 DI)
- Spring回顾(一)IoC & DI
- h264 rtp FU-A
- 6.7—排序—Sort Colors
- 【基础算法练习】【最短路+图的遍历+最小生成树】水题 C+ E+ F-----AC题
- 迪杰斯特拉最短路径算法
- PAT 1002. A+B for Polynomials
- SpringMVC IOC DI接口版本管理(迭代版)
- 7.1—查找—Sear for a Range
- HDU 1535 Invitation Cards(多源点到单源点,dijkstra/spfa)
- 闲记
- typedef
- python 安装superset:error: Setup script exited with error: command 'gcc' failed with exit status 1
- 小白如何成长为优秀的系统设计专家?
- 7.2—查找—Sear Insert Position
- 剑指offer-39.二叉树的深度