实现原理
springmvc是spring推出的面向前后端的模块框架,springMVC主要原理是根据浏览器发送的URL请求通过核心类dispatcherServlet通过配置的映射关系匹配到对应的servlet类即controller层的controller类,然后controller类会调用service层的serviceImpl实现类处理业务逻辑,如果有操作数据库的操作就会调用dao层的类(不管是jdbc,mybatis还是hibernate),然后controller类通过处理获得的数据返回指定的视图和模型(当然通过@responsebody返回json\xml 等),通过视图解析器找到对应的视图并把数据注入到视图中返回给浏览器。
当然我们手写springmvc就不需要这么复杂的。首先我们要理解把我们项目放到Tomcat里面,当Tomcat启动时会加载项目中的web.xml文件,通过我们指定的初始化核心类DispatcherServlet,然后我们把加载配置信息、扫描项目中所有的类、实例化Javabean、绑定URL请求与对应controller类的方法的映射关系放到servlet的初始化方法里,在类的初始化时就处理完这些事情。 还有对请求调用的service方法进行处理,我这里在controller类中对service的调用时利用自定义注解的方式注入的。所以要在方法中对注解进行判断,并把绑定的实现类赋给对应的引用。利用反射原理调用对应的方法。
这里需要注意的是,spring的控制反转IOC容器的原理其实就是利用map集合把需要实例对象放入到map中,需要的时候就从map中拿,这些都是在初始化的时候就已经完成的,而不需要再我们的业务代码中去new一个对象的。减少耦合性。
代码现实
- 实现自定义注解
@LZLAutowired 注入对应的实例bean
@LZLController 定义controller组件
@LZLRequestMapping 绑定请求url和对应的类和方法
@LZLRequestParam 请求参数
@LZLService 定义service实现类组件
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LzlAutowired
{
String value() default "";
}
- 实现对应的controller和service类
@LzlController
@LzlRequestMapping("/lzl")
public class OrderController
{
@LzlAutowired
OrderService orderService;
@LzlRequestMapping("/query")
public void queryOrder(HttpServletRequest request,HttpServletResponse response,@LzlRequestParam("orderId")String orderId) {
String order=orderService.queryOrder(orderId);
System.out.println(order);
PrintWriter writer;
try
{
writer = response.getWriter();
writer.write(order);
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
- 对应的业务实体类
public class Order
{
private String orderId;
private String orderName;
private String price;
private Integer count;
private String address;
public String getOrderId()
{
return orderId;
}
public void setOrderId(String orderId)
{
this.orderId = orderId;
}
public String getOrderName()
{
return orderName;
}
public void setOrderName(String orderName)
{
this.orderName = orderName;
}
public String getPrice()
{
return price;
}
public void setPrice(String price)
{
this.price = price;
}
public Integer getCount()
{
return count;
}
public void setCount(Integer count)
{
this.count = count;
}
public String getAddress()
{
return address;
}
public void setAddress(String address)
{
this.address = address;
}
@Override
public String toString()
{
return "Order [orderId=" + orderId + ", orderName=" + orderName + ", price=" + price + ", count=" + count
+ ", address=" + address + "]";
}
}
- 核心类DispatcherServlet
public class DispatcherServlet extends HttpServlet
{
/**
* IOC容器,存放实例bean
*/
private Map<String, Object> beans=new HashMap<>();
/**
* @RequestMapping和@Autowired的绑定关系
*/
private Map<String, Method> handlerMap=new HashMap<>();
/**
* URL和controller的绑定关系,这里的object对象必须是IOC容器的对象,必须是单实例的
*/
private Map<String, Object> controllerMap=new HashMap<>();
/**
* 项目中的所有类
*/
private List<String> classNames=new ArrayList<>();
private String basePackage;
@Override
public void init(ServletConfig config)
throws ServletException
{
super.init(config);
doInit(config);
doScan(basePackage);
doInstance();
doHandler();
}
/**
*
* 加载配置
*
* @author lzl
* @param config
*/
public void doInit(ServletConfig config) {
basePackage=config.getInitParameter("contextConfigLocation");
}
/**
*
* 扫描包中的所有类
*
* @author lzl
* @param location
*/
public void doScan(String location) {
String path="/"+location.replaceAll("\\.", "/");
//这里需要优化,觉得可以用相对路径来取
File dir=new File("D:\\SpaceOfSTS\\lzlSpringMVC\\src"+path);
for(File file:dir.listFiles()) {
if(file.isDirectory()) {
doScan(location+"."+file.getName());
}else {
String className=location+"."+file.getName().replace(".java", "");
classNames.add(className);
}
}
}
/**
*
* 实例化所有的Javabean
*
* @author lzl
*/
public void doInstance() {
if(classNames.isEmpty()) {
return;
}
for(String className:classNames) {
try
{
Class<?> clazz=Class.forName(className);
if(clazz.isAnnotationPresent(LzlController.class)) {
Object object=clazz.newInstance();
beans.put(toLowerFirstWord(clazz.getSimpleName()), object);
}else if(clazz.isAnnotationPresent(LzlService.class)) {
Object object=clazz.newInstance();
beans.put(toLowerFirstWord(clazz.getSimpleName()), object);
}else {
continue;
}
}
catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
continue;
}
}
}
/**
*
* 绑定url和对应的方法
*
* @author lzl
*/
public void doHandler() {
String classPath="";
String methodPatrh="";
if(beans.isEmpty()) {
return;
}
for(Entry<String, Object> entry:beans.entrySet()) {
Object object=entry.getValue();
Class<?> clazz=object.getClass();
if(!clazz.isAnnotationPresent(LzlController.class)) {
continue;
}
//OrderService属性的注入,遇到的坑
Field[] fields=clazz.getDeclaredFields();
for(Field field:fields) {
if(field.isAnnotationPresent(LzlAutowired.class)) {
field.setAccessible(true);
String value=field.getAnnotation(LzlAutowired.class).value();
if(value==null||value.length()==0) {
value=field.getName()+"Impl";
}
try
{
field.set(object, beans.get(value));
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
if(clazz.isAnnotationPresent(LzlRequestMapping.class)) {
classPath=clazz.getAnnotation(LzlRequestMapping.class).value();
}
Method[] methods=clazz.getMethods();
for(Method method:methods) {
if(!method.isAnnotationPresent(LzlRequestMapping.class)) {
continue;
}
methodPatrh=method.getAnnotation(LzlRequestMapping.class).value();
handlerMap.put(classPath+methodPatrh, method);
try
{
controllerMap.put(classPath+methodPatrh, object);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
}
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) {
doPost(req, resp);
}
public void doPost(HttpServletRequest req, HttpServletResponse resp) {
String url=req.getRequestURI();
String contextPath=req.getContextPath();
url=url.replace(contextPath, "").replaceAll("/+", "/");
if(!this.handlerMap.containsKey(url)){
try
{
resp.getWriter().write("404 NOT FOUND!");
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return;
}
Method method=handlerMap.get(url);
Class<?>[] paramTypes=method.getParameterTypes();
Parameter[] parameters=method.getParameters();
int count=parameters.length;
Object[] params=new Object[count];
for(int i=0;i<count;i++) {
if(paramTypes[i].getSimpleName().equalsIgnoreCase("HttpServletRequest")) {
params[i]=req;
continue;
}
if(paramTypes[i].getSimpleName().equalsIgnoreCase("HttpServletResponse")) {
params[i]=resp;
continue;
}
if(parameters[i].isAnnotationPresent(LzlRequestParam.class)) {
String value=parameters[i].getAnnotation(LzlRequestParam.class).value();
params[i]=req.getParameter(value);
continue;
}
}
try
{
method.invoke(this.controllerMap.get(url),params);
}
catch (Exception e)
{
e.printStackTrace();
}
}
/**
*
* 把首字母改为小写
*
* @author lzl
* @param name
* @return
*/
private String toLowerFirstWord(String name){
char[] charArray = name.toCharArray();
charArray[0] += 32;
return String.valueOf(charArray);
}
}
代码太多太占篇幅,这里只写部分的,具体可以去我的GitHub上下载代码:https://github.com/liuzongliang0202/lzlSpringMVC
小建议
有可能我们写完项目,感觉没啥错误自信满满的,然而一实际运行却打击人心。我写这个工程花了两天,第一天写完,第二天一直在找bug 。。。 利用tomcat debug的时候你可以把Tomcat的启动时间调长一些,方便定位问题。另一个问题是,获取配置文件的时候,会有绝对路径和相对路径,这里需要注意,其实我已没搞懂,就用了绝对路径。。。剩下的就是你们去实现自己的springMVC了!