spring是一个非常流行的技术框架,其中spring mvc组件在其中非常重要的地位,主要面要客户端提供服务,我们今天来手写一个简化版的mvc,且包括ioc部分,主要利用servlet机制来实现,类的关系如下:
准备注解类,类于spring的@Autowired、@Service、@Controller、@RequestMapping、@RequestParam
public CSAutowired { | |
String value() default ""; | |
} | |
public CSController { | |
String value() default ""; | |
} | |
public CSRequestMapping { | |
String value() default ""; | |
} | |
public CSRequestParam { | |
String value() default ""; | |
} | |
public CSService { | |
String value() default ""; | |
} |
准备service interface
public interface IDemoService { | |
public String get(String name); | |
} |
准备service实现类,利用@CSService
public class DemoService implements IDemoService { | |
public String get(String name) { | |
return "My name is "+name; | |
} | |
} |
准备对外服务的类,主要利用@CSController注解
"/demo") | (|
public class DemoAction { | |
private IDemoService demoService; | |
"/query") | (|
public void query(HttpServletRequest req, HttpServletResponse resp, "name") String name){ ( | |
String result=demoService.get(name); | |
try { | |
resp.getWriter().write(result); | |
} catch (IOException exception) { | |
exception.printStackTrace(); | |
} | |
} | |
"/add") | (|
public void add(HttpServletRequest req, HttpServletResponse resp, "aa") Integer a, ("b") Integer b){ ( | |
try { | |
resp.getWriter().write(a+"+"+b+"="+(a+b)); | |
} catch (IOException exception) { | |
exception.printStackTrace(); | |
} | |
} | |
"/remove") | (|
public void remove(HttpServletRequest req, HttpServletResponse resp, "id") Integer id){ ( | |
try { | |
resp.getWriter().write("id="+id); | |
} catch (IOException exception) { | |
exception.printStackTrace(); | |
} | |
} | |
} |
准备servlet
主要实现了以下功能:
1).根据@CSController对外服务的url如何mapping到具体方法 doHandlerMap
2).service和controller bean的管理 iocBeans
3).如何实列化bean doInstance
4).如何获取url中参数值 doDispatch中
5).找到需要加载的class doScanner
6).如何自动autowired doAutoWried
public class CSDispatchServlet extends HttpServlet { | |
public static String urlPattern="/custom"; | |
private void doDispatch(HttpServletRequest request,HttpServletResponse response) throws Exception{ | |
String url=request.getRequestURI(); | |
String contextPath=request.getContextPath(); | |
url=url.replace(urlPattern,""); | |
if(!handlerMap.containsKey(url)){ | |
response.getWriter().write(" not found!"); | |
return; | |
} | |
Method method=handlerMap.get(url); | |
Annotation[][] methodParameterAnnotations= method.getParameterAnnotations(); | |
Parameter[] methodParameters= method.getParameters(); | |
Annotation[][] paramerterAnnotations=method.getParameterAnnotations(); | |
ArrayList<Object> methodParameterValues=new ArrayList<Object>(); | |
Map<String,String[]> requestParams= request.getParameterMap(); | |
int parmeterCnt=; | |
for(Parameter parameter:methodParameters){ | |
if(parameter.getType()==HttpServletRequest.class ){ | |
methodParameterValues.add(request); | |
}else if(parameter.getType()==HttpServletResponse.class){ | |
methodParameterValues.add(response); | |
}else { | |
String methodParamName=""; | |
if(paramerterAnnotations[parmeterCnt].length>) { | |
Annotation annotation= paramerterAnnotations[parmeterCnt][]; | |
if(annotation instanceof CSRequestParam) { | |
methodParamName = ((CSRequestParam) annotation).value(); | |
} | |
} | |
if("".equals(methodParamName.trim())){ | |
methodParamName=parameter.getName(); | |
} | |
String value=""; | |
//String value=Arrays.toString(requestParams.get(methodParamName)); | |
if(requestParams.get(methodParamName).length>) | |
value=Arrays.toString(requestParams.get(methodParamName)); | |
else if(requestParams.get(methodParamName).length==) | |
value= requestParams.get(methodParamName)[]; | |
else | |
value=""; | |
if(parameter.getType()==String.class) | |
methodParameterValues.add(value); | |
else if(parameter.getType()==Integer.class) { | |
try { | |
methodParameterValues.add(Integer.parseInt(value)); | |
} catch (Exception e){ | |
methodParameterValues.add(); | |
} | |
}else { | |
//可以扩展复杂类型转换 | |
} | |
} | |
parmeterCnt++; | |
} | |
String beanName=this.genBeanName(method.getDeclaringClass().getSimpleName()); | |
method.invoke(this.iocBeans.get(beanName), methodParameterValues.toArray()); | |
} | |
private String genBeanName(String beanName){ | |
if(beanName.length()>) | |
beanName=beanName.substring(,0).toLowerCase()+beanName.substring(1); | |
else | |
beanName=beanName.toLowerCase(); | |
return beanName; | |
} | |
@Override | |
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { | |
this.doPost(req,resp); | |
} | |
@Override | |
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { | |
try { | |
this.doDispatch(req,resp); | |
} catch (Exception exception) { | |
exception.printStackTrace(); | |
} | |
} | |
private ArrayList<String> classs=new ArrayList<String>(); | |
private ConcurrentHashMap<String,Object> iocBeans=new ConcurrentHashMap<String,Object>(); | |
private ConcurrentHashMap<String,Method> handlerMap=new ConcurrentHashMap<String,Method>(); | |
private void doInstance() { | |
try { | |
for (String className : classs) { | |
if (!className.contains(".")) continue; | |
Class<?> clazz = Class.forName(className); | |
String beanName=""; | |
if (clazz.isAnnotationPresent(CSController.class)) { | |
CSController controller = clazz.getAnnotation(CSController.class); | |
beanName=controller.value(); | |
}else if(clazz.isAnnotationPresent(CSService.class)){ | |
CSService service=clazz.getAnnotation(CSService.class); | |
beanName=service.value(); | |
}else { | |
continue; | |
} | |
Object instance=clazz.newInstance(); | |
if("".equals(beanName.trim())) | |
beanName=clazz.getSimpleName(); | |
beanName=genBeanName(beanName); | |
iocBeans.put(beanName,instance); | |
if(clazz.isAnnotationPresent(CSService.class)){ | |
for(Class c: clazz.getInterfaces()){ | |
if(iocBeans.containsKey(c.getName())) continue; | |
iocBeans.put(c.getName(),instance); | |
} | |
} | |
} | |
}catch (Exception e){ | |
e.printStackTrace(); | |
} | |
} | |
private void doAutoWried(){ | |
for(Object o:iocBeans.values()){ | |
if(o==null) continue; | |
Class clazz=o.getClass(); | |
if(clazz.isAnnotationPresent(CSService.class) || clazz.isAnnotationPresent(CSController.class)){ | |
Field[] fields=clazz.getDeclaredFields(); | |
for(Field f:fields){ | |
if(!f.isAnnotationPresent(CSAutowired.class)) continue; | |
CSAutowired autowired=f.getAnnotation(CSAutowired.class); | |
String beanName=autowired.value(); | |
if("".equals(beanName)) beanName=f.getType().getName(); | |
f.setAccessible(true); | |
try{ | |
Object o=iocBeans.get(beanName); | |
f.set(o,iocBeans.get(beanName)); | |
}catch (IllegalAccessException e){ | |
e.printStackTrace(); | |
} | |
} | |
} | |
} | |
} | |
private void doHandlerMap(ServletConfig config){ | |
for(Object o:iocBeans.values()){ | |
if(!o.getClass().isAnnotationPresent(CSController.class)) continue; | |
String baseUrl=""; | |
if(o.getClass().isAnnotationPresent(CSRequestMapping.class)){ | |
CSRequestMapping requestMapping=o.getClass().getAnnotation(CSRequestMapping.class); | |
baseUrl=requestMapping.value(); | |
} | |
for(Method method: o.getClass().getMethods()){ | |
if(method.isAnnotationPresent(CSRequestMapping.class)) { | |
CSRequestMapping requestMapping=method.getAnnotation(CSRequestMapping.class); | |
String url=baseUrl+requestMapping.value().replaceAll("/+","/"); | |
String contextPath=config.getServletContext().getContextPath(); | |
this.handlerMap.put(url,method); | |
} | |
} | |
} | |
} | |
@Override | |
public void init(ServletConfig config) throws ServletException { | |
InputStream is=null; | |
try{ | |
System.out.println("custom servlet init........"); | |
/* | |
Properties configContext=new Properties(); | |
is=this.getClass().getClassLoader().getResourceAsStream(config.getInitParameter("contextConfigLocation")); | |
configContext.load(is); | |
String scanPackage=configContext.getProperty("scanPackage"); | |
*/ | |
Enumeration<String> enumerations= config.getInitParameterNames(); | |
while (enumerations.hasMoreElements()){ | |
System.out.println(enumerations.nextElement()); | |
} | |
doScanner("com.mesui.spring.custom"); | |
doInstance(); | |
doAutoWried(); | |
doHandlerMap( config); | |
}catch (Exception exception){ | |
exception.printStackTrace(); | |
}finally { | |
} | |
} | |
private void doScanner(String scanPackage){ | |
URL url= this.getClass().getClassLoader().getResource("") ; | |
String filePath=""; | |
try { | |
filePath= URLDecoder.decode( url.getPath(),"UTF-")+"/"+scanPackage.replaceAll("\\.","/"); | |
} catch (UnsupportedEncodingException e) { | |
e.printStackTrace(); | |
} | |
File classDir=new File(filePath); | |
for(File file:classDir.listFiles()){ | |
if(file.isDirectory()){ | |
doScanner(scanPackage+"."+file.getName()); | |
}else if(!file.getName().endsWith(".class")) { | |
continue; | |
} | |
if(!file.isDirectory()) { | |
String clzzName = (scanPackage + "." + file.getName().replace(".class", "")); | |
//map.put(clzzName,null); | |
classs.add(clzzName); | |
} | |
} | |
} | |
} |
在利用spring的configuration类初始化servlet
这边为了方便进行偷懒,这样/custom/下的服务按照自已逻辑对对外服务,不按照spring mvc的进行,另外自已可以tomcat的web.xml中标记servlet完全脱离spring
public class MybatisPlusConfig { | |
public ServletRegistrationBean CustomServlet(){ | |
return new ServletRegistrationBean(new CSDispatchServlet(),CSDispatchServlet.urlPattern+"/*"); | |
} | |
} |
测试
结论
从上面的例子中我们可以看到自已写一个mvc也很方便,不是什么难事,但是这个只是用于学习,毕竟spring是一个体系,我们自已不可能将所有内容重新写一遍,但是自已写着玩有助于对spring mvc和IOC的理解。