学习java web开发的小伙伴,对spring MVC 不会陌生的。最近没事看了一下spring的源码,了解了一些框架的实现方法,再结合网上一些资料,自己编写了一个简易的MVC框架,现将记录下开发工程。
一、首先,我们先来介绍一下Spring的三个阶段,配置阶段、初始化阶段和运行阶段
配置阶段:主要是完成application.properties配置和Annotation配置。
初始化阶段:主要是加载并解析配置信息,然后,初始化IOC容器,完成容器的DI操作,已经完成HandlerMapping的初始化。
运行阶段:主要是完成Spring容器启动以后,完成用户请求的内部调度,并返回响应结果。
二、配置阶段
我采用的是maven管理项目。先来看pom.xml文件中的配置,只引用了servlet-api的依赖。
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
在web.xml文件中配置以下信息:
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<servlet>
<servlet-name>spring MVC</servlet-name>
<servlet-class>com.xiao.servlet.MyDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLoaction</param-name>
<param-value>application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring MVC</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
application.properties 配置
scanPackage=com.xiao.demo
接下来,配置注解。
创建MyController 注解:
package com.xiao.annotation;
import java.lang.annotation.*;
/**
* 注解在类上
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {
String value() default "";
}
创建MyService 注解:
package com.xiao.annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyService {
String value() default "";
}
创建MyRequestMapping注解:
package com.xiao.annotation;
import java.lang.annotation.*;
/**
* 注解在类和方法上
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
String value() default "";
}
创建MyAutowired注解:
package com.xiao.annotation;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAutowired {
String value() default "";
}
创建MyRequestParam注解:
package com.xiao.annotation;
import java.lang.annotation.*;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {
/**
* 表示参数的别名,必填
* @return
*/
String value();
}
三、初始化、运行阶段
创建GPDispatcherServlet类并继承HttpServlet,重写init()、doGet()和doPost()方法。
package com.xiao.servlet;
import com.xiao.annotation.MyAutowired;
import com.xiao.annotation.MyController;
import com.xiao.annotation.MyRequestMapping;
import com.xiao.annotation.MyService;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
/**
* @author xiaoxiaoping
*/
public class MyDispatcherServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* 跟web.xml中param-name的值一致
*/
private static final String LOCATION = "contextConfigLoaction";
/**
* 保存所有的配置信息
*/
private Properties properties = new Properties();
/**
* 核心IOC容器,保存所有初始化的bean
*/
private Map<String ,Object> ioc = new HashMap<String,Object>();
/**
* 保存所有的URL和方法的映射关系
*/
private Map<String, Method> handelerMapping = new HashMap<String,Method>();
/**
* 保存所有臊面到的相关类名
*/
private List<String> classNames = new ArrayList<String>();
public MyDispatcherServlet() { super(); }
/**
* 初始化,加载配置文件
* @param config
* @throws ServletException
*/
@Override
public void init(ServletConfig config) throws ServletException {
//加载配置文件
doLoadConfig(config.getInitParameter(LOCATION));
//扫描所有相关类
doScanner(properties.getProperty("scanPackage"));
//初始化所有相关类的实例,并保存到IOC容器
doInstance();
//依赖注入
doAutowired();
//构造HandleMapping
initHandlerMapping();
//等待请求,匹配URL,定位方法,反射调用执行
//调用doGet或者doPost方法
//提示信息
System.out.println("xiaoMvc is init");
}
private void initHandlerMapping() {
if(ioc.isEmpty()){return;}
for(Map.Entry<String,Object> entry : ioc.entrySet()){
Class <?> clazz = entry.getValue().getClass();
if(!clazz.isAnnotationPresent(MyController.class)){continue;}
String baseUrl = "";
//获取controller的url配置
if(clazz.isAnnotationPresent(MyRequestMapping.class)){
MyRequestMapping requestMapping = clazz.getAnnotation(MyRequestMapping.class);
baseUrl = requestMapping.value();
}
Method [] methods = clazz.getMethods();
for(Method method : methods){
if(!method.isAnnotationPresent(MyRequestMapping.class)){continue;}
MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
String url = ("/"+baseUrl+"/"+requestMapping.value()).replaceAll("/+", "/");
handelerMapping.put(url,method);
System.out.println("mapped"+url+" , "+method);
}
}
}
private void doAutowired() {
if(ioc.isEmpty()){return;}
for(Map.Entry<String,Object> entry : ioc.entrySet()){
Field[] fields = entry.getValue().getClass().getDeclaredFields();
for(Field field : fields){
if(!field.isAnnotationPresent(MyAutowired.class)){continue;}
MyAutowired autowired = field.getAnnotation(MyAutowired.class);
String beanName = autowired.value();
if("".equals(beanName.trim())){
beanName = lowerFirstCase(field.getType().getSimpleName());
}
//设置私有属性的访问权限
field.setAccessible(true);
try {
field.set(entry.getValue(),ioc.get(beanName));
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
}
private void doInstance() {
if(classNames.size()==0){return;}
try {
for(String className : classNames) {
Class<?> clazz = Class.forName(className);
if(clazz.isAnnotationPresent(MyController.class)){
//默认将首字母小写作为beanName
String beanName = lowerFirstCase(clazz.getSimpleName());
ioc.put(beanName,clazz.newInstance());
}else if(clazz.isAnnotationPresent(MyService.class)){
MyService service = clazz.getAnnotation(MyService.class);
String beanName = service.value();
//如果用户设置了名字,就用用户自己设置的
if("".equals(beanName)){
beanName = lowerFirstCase( clazz.getSimpleName() );
}
ioc.put(beanName,clazz.newInstance());
}else{
continue;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void doScanner(String scanPackage) {
URL url = this.getClass().getClassLoader().getResource("/"+scanPackage.replaceAll("\\.","/"));
File dir = new File(url.getFile());
for(File file : dir.listFiles()){
if(file.isDirectory()){
doScanner(scanPackage+"."+file.getName());
}else{
classNames.add(scanPackage+"."+file.getName().replace(".class", ""));
}
}
}
private void doLoadConfig(String location) {
//把web.xml中的contextConfigLocation对应value值的文件加载到流里面
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(location);
try {
//用Properties文件加载文件里的内容
properties.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}finally {
//关流
if(null!=resourceAsStream){
try {
resourceAsStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@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 {
doDispatch(req,resp);
} catch (Exception e) {
e.printStackTrace();
resp.getWriter().write("500!! Server Exception");
}
}
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
if(this.handelerMapping.isEmpty()){ return; }
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replace(contextPath,"").replaceAll("/+","/");
if(!this.handelerMapping.containsKey(url)){
resp.getWriter().write("404 not Found");
return;
}
Method method = this.handelerMapping.get(url);
Class<?>[] parameterTypes = method.getParameterTypes();
//获取请求的参数
Map<String,String[]> parameterMap = req.getParameterMap();
//保存参数值
Object[] paramValues = new Object[parameterTypes.length];
for(int i = 0;i < parameterTypes.length; i++){
Class parameterType = parameterTypes[i];
if(parameterType == HttpServletRequest.class){
paramValues[i] = req;
continue;
}else if(parameterType == HttpServletResponse.class){
paramValues[i] = resp;
continue;
}else if(parameterType == String.class){
for(Map.Entry<String,String[]> param : parameterMap.entrySet()){
String value =Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
paramValues[i]=value;
}
}
}
try {
String beanName = lowerFirstCase(method.getDeclaringClass().getSimpleName());
method.invoke(this.ioc.get(beanName), paramValues);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
System.out.println("此处接收被调用方法内部未被捕获的异常");
Throwable t = e.getTargetException();// 获取目标异常
t.printStackTrace();
}
}
private String lowerFirstCase(String str){
char [] chars = str.toCharArray();
chars [0] += 32;
return String.valueOf(chars);
}
}
四、测试实例
service类
package com.xiao.demo.service.impl;
import com.xiao.annotation.MyService;
@MyService
public class TestServiceImpl{
public String query(String name, String age) {
return name+"年龄"+age;
}
}
controller类
package com.xiao.demo;
import com.xiao.annotation.*;
import com.xiao.demo.service.impl.TestServiceImpl;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@MyController
@MyRequestMapping("/test")
public class HelloController {
@MyAutowired
TestServiceImpl testService;
@MyRequestMapping("/hello")
public void test1(HttpServletRequest request, HttpServletResponse response,
@MyRequestParam("param") String param){
System.out.println( testService.query("xiaoping","30"));
try {
response.getWriter().write( " param:"+param);
} catch (IOException e) {
e.printStackTrace();
}
}
}
至此,一个简单的spring mvc 简易框架完成。
在此给出源码地址:https://gitee.com/longsis/xiaoMVC.git