Java Web框架——自己动手实现一个简化版的Spring IOC框架

一、序

  只要是Java Web的开发者,相信没有不知道大名鼎鼎的Spring框架的。作为一个优秀的开源框架,Spring包含9大组件,如果想要通过源码去理解每一个组件,复杂度和所花费的时间都容易让人望而却步——笔者平时习惯阅读源码,但是在学习Spring这种级别的框架面前显然不是一个好选择。但是问题又来了,不阅读源码,如何深入理解Spring呢?笔者在搜索学习资料的时候在github找到了一个优秀的repo——https://github.com/code4craft/tiny-spring,通过参考其思路,自己动手实现一个简化版的Spring框架(这里只实现核心IOC部分,后续会有文章实现其他部分)。本文假设读者已经对Spring有了大致的理解,不会花太多篇幅介绍Spring,重点在实现上。

二、设计

  首先花几分钟简单看一下Spring3.x的架构,虽然现在Spring都5.x了,但是我们现在是理解核心思路,看以前版本的架构图反而更清晰,下图转自网络:

  Spring含有十几个组件,核心的只有3个——Core,Context和Bean,那么这次我们就实现相对来说比较重要的BeanCore,Context主要是一个起封装功能的作用,影响不大,放到以后完善。首先设计好我们的架构,首先我们需要一个Resource来存储我们需要的Bean信息来源和链接到我们需要的类,也需要一个BeanDefiinition对Bean进行描述和操作,还需要有一个BeanFactory来管理Bean。为了职责分明,再添加一个ResourceLoader对Resource存储的Bean信息进行读取,以及一个对BeanDefinition进行读取的BeanDefinitionReader,大致如下图:

三、实现

  

  1、首先写Resource接口,只规定了一个通过流获取资源getInputStream()方法,方便实现不同的获取资源的方式:

1 package com.joe.io;
2
3 import java.io.InputStream;
4
5 public interface Resource {
6     InputStream getInputStream() throws Exception;
7 }

  

  2、通过具体的类UrlResource实现Resource接口:

 1 package com.joe.io;
 2
 3 import java.io.InputStream;
 4 import java.net.URL;
 5 import java.net.URLConnection;
 6
 7 public class UrlResource implements Resource {
 8
 9     private URL url;
10
11     public UrlResource(URL url) {
12         this.url = url;
13     }
14
15     public InputStream getInputStream() throws Exception {
16         URLConnection urlConnection = url.openConnection();
17         urlConnection.connect();
18         return urlConnection.getInputStream();
19     }
20 }

  这里底层用了java.net包的URL以及相关类去操作资源的获取,核心方法就一个getInputStream(),这里通过URLConnection去获取资源,读者想了解底层细节可以自行google。那么作为入参的URL是由谁传入的呢?自然是我们接下来要介绍的ResourceLoader。

  

  3、负责读取Resource的资源加载器ResourceLoader类:

 1 package com.joe.io;
 2
 3 import java.net.URL;
 4
 5 public class ResourceLoader {
 6
 7     public Resource getResource(String location) {
 8         URL url = this.getClass().getClassLoader().getResource(location);
 9         return new UrlResource(url);
10     }
11 }

  这里很清晰地看到,getResource()方法把接收到的String作为参数,通过获取当前的类加载器,调用ClassLoader.getResource(String location)方法将返回的URL传递给我们的Resource(同上,ClassLoader相关api原理和本文关系不大,想学习的读者的自行google),至此资源加载器的功能就完成了。

  

  4、描述Bean的BeanDefinition类:

 1 package com.joe;
 2
 3 public class BeanDefinition {
 4     private Object bean;// 对象实例
 5
 6     private Class beanClass;//bean的类型
 7
 8     private String beanClassName;//类名
 9
10     private PropertyValues propertyValues;
11
12     public Object getBean() {
13         return bean;
14     }
15
16     public void setBean(Object bean) {
17         this.bean = bean;
18     }
19
20     public Class getBeanClass() {
21         return beanClass;
22     }
23
24     public void setBeanClass(Class beanClass) {
25         this.beanClass = beanClass;
26     }
27
28     public String getBeanClassName() {
29         return beanClassName;
30     }
31
32     public void setBeanClassName(String beanClassName) {
33         this.beanClassName = beanClassName;
34         try {
35             this.beanClass = Class.forName(beanClassName);
36         } catch (ClassNotFoundException e) {
37             e.printStackTrace();
38         }
39     }
40
41     public PropertyValues getPropertyValues() {
42         if (propertyValues == null) {
43             propertyValues = new PropertyValues();
44         }
45         return propertyValues;
46     }
47
48     public void setPropertyValues(PropertyValues propertyValues) {
49         this.propertyValues = propertyValues;
50     }
51 }

  BeanDefinition为了对Bean进行描述和初始化,所以需要持有以下变量:

  1) Object bean,用来存储Bean,也就是具体的对象实例;

  2) Class beanClass,用来存储bean的Class类型;

  3) Class beanClassName,用来存储bean的具体类名;

  4) PropertyValues propertyValues,用来随着bean一起注入IOC容器的类变量。

  前三个变量没什么好说的,PropertyValues主要用来存储类变量,我们稍后对其进行实现。

  

  5、定义好了描述Bean,为了职责分明,我们还需要对一个其进行读取的BeanDefinitonReader接口:

  

1 package com.joe;
2
3 public interface BeanDefinitionReader {
4     void loadBeanDefinitions(String location) throws Exception;
5 }

  定义一个方法供IOC容器在初始化的时候从location指定的位置进行资源以及Bean的加载,在这里我们只实现xml加载方式。

  6、AbstractBeanDefinitionReader,在我们实现具体的通过xml进行Bean加载器之前,为了通用性(实际场景,不可能只通过xml去解析Bean资源),我们实现一个抽象的Bean加载器类:

 

 1 package com.joe;
 2
 3 import java.util.HashMap;
 4 import java.util.Map;
 5
 6 import com.joe.io.ResourceLoader;
 7
 8 public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {
 9
10     private Map<String, BeanDefinition> registry;
11
12     private ResourceLoader resourceLoader;
13
14     public AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
15         this.registry = new HashMap<String, BeanDefinition>();
16         this.resourceLoader = resourceLoader;
17     }
18
19     public Map<String, BeanDefinition> getRegistry() {
20         return registry;
21     }
22
23     public ResourceLoader getResourceLoader() {
24         return resourceLoader;
25     }
26 }

  抽象类除了持有Map<String, BeanDefinition>以方便Bean的注册外,还持有一个我们之前实现的资源加载器ResourceLoader,稍后介绍资源加载器的作用。

  

  7、XmlBeanDefinitionReader,这部分内容涉及比较多的细节,比如dom解析文件节点等操作,可以选择性跳过,理解思路即可:

 1 package com.joe.xml;
 2
 3 import java.io.InputStream;
 4
 5 import javax.xml.parsers.DocumentBuilder;
 6 import javax.xml.parsers.DocumentBuilderFactory;
 7
 8 import org.w3c.dom.Document;
 9 import org.w3c.dom.Element;
10 import org.w3c.dom.Node;
11 import org.w3c.dom.NodeList;
12
13 import com.joe.AbstractBeanDefinitionReader;
14 import com.joe.BeanDefinition;
15 import com.joe.BeanReference;
16 import com.joe.PropertyValue;
17 import com.joe.io.ResourceLoader;
18
19 public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
20
21     public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
22         super(resourceLoader);
23     }
24
25     public void loadBeanDefinitions(String location) throws Exception {
26         InputStream inputStream = getResourceLoader().getResource(location).getInputStream();
27         doLoadBeanDefinitions(inputStream);
28     }
29
30     protected void doLoadBeanDefinitions(InputStream inputStream) throws Exception {
31         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
32         DocumentBuilder docBuilder = factory.newDocumentBuilder();
33         Document doc = docBuilder.parse(inputStream);
34         // 解析bean
35         registerBeanDefinitions(doc);
36         inputStream.close();
37     }
38
39     public void registerBeanDefinitions(Document doc) {
40         Element root = doc.getDocumentElement();
41
42         parseBeanDefinitions(root);
43     }
44
45     protected void parseBeanDefinitions(Element root) {
46         NodeList nl = root.getChildNodes();
47         for (int i = 0; i < nl.getLength(); i++) {
48             Node node = nl.item(i);
49             if (node instanceof Element) {
50                 Element ele = (Element) node;
51                 processBeanDefinition(ele);
52             }
53         }
54     }
55
56     protected void processBeanDefinition(Element ele) {
57         String name = ele.getAttribute("id");
58         String className = ele.getAttribute("class");
59         BeanDefinition beanDefinition = new BeanDefinition();
60         processProperty(ele, beanDefinition);
61         beanDefinition.setBeanClassName(className);
62         getRegistry().put(name, beanDefinition);
63     }
64
65     private void processProperty(Element ele, BeanDefinition beanDefinition) {
66         NodeList propertyNode = ele.getElementsByTagName("property");
67         for (int i = 0; i < propertyNode.getLength(); i++) {
68             Node node = propertyNode.item(i);
69             if (node instanceof Element) {
70                 Element propertyEle = (Element) node;
71                 String name = propertyEle.getAttribute("name");
72                 String value = propertyEle.getAttribute("value");
73                 if (value != null && value.length() > 0) {
74                     beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
75                 } else {
76                     String ref = propertyEle.getAttribute("ref");
77                     if (ref == null || ref.length() == 0) {
78                         throw new IllegalArgumentException("Configuration problem: <property> element for property ‘"
79                                 + name + "‘ must specify a ref or value");
80                     }
81                     BeanReference beanReference = new BeanReference(ref);
82                     beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference));
83                 }
84             }
85         }
86     }
87 }

  这个代码稍微多点,大致上就干了这么几件事:

  1)加载xml:初始化时,把指定的xml文件(这里是xml,也可以是其他资源)加载进IOC容器,转化成流inputStream后存储到org.w3c.dom.Document,这里用到的org.w3c包里的对象比较多,分别有Document,Element,Node等。谁负责加载呢?自然就是我们之前定义的资源加载器ResourceLoader了,这时候,之前提到的抽象资源加载器类AbstractBeanDefinitionReader持有的ResourceLoader就发挥作用了,正是通过这个ResourceLoader完成一系列上述操作,忘了的读者可以回去看其实现。

  2)获取xml中的各个节点:由1中获取到的Document对象中获得其Element元素,Element描述了xml文件的各个定义bean的节点,也可以说是一个Element代表了一个xml中bean相关的标签,形如<bean id="...", class="...">以及其包括的内容。

  3)注入bean:对各个Element元素遍历,获取标签中定义为id和class的内容,存储为name和className,name在5中讲解,className为完整描述了包路径的类名。

  4)注入类变量:针对每个Element创建一个BeanDefinition,遍历Element包含的所有<property>标签(此标签定义了类变量),获取到类变量名以及希望注入的值,添加到BeanDefinition持有的PropertyValues中去。如果类变量是引用类型,则进行另外的处理,这里就不展开讲了,有兴趣的读者自行阅读源码。

  5)初始化BeanDefiniton部分属性:完成变量的注入后,我们对BeanDefinition持有的其他类变量进行初始化,这里就用到3)中存储的类名name,通过Class.forName(name)获取到其类型,这样BeanDefiniton的beanClassName和beanClass也得到了初始化(还有bean的初始化呢?请带着疑问接着看下去),这里把name和BeanDefiniton注册到抽象资源加载器类AbstractBeanDefinitionReader持有的Map中。至此,资源加载器初始化完成,可以供bean工厂获取了。那么接下来要实现的自然是bean工厂。

  8、BeanFactory接口

 1 package com.joe.factory;
 2
 3 import com.joe.BeanDefinition;
 4
 5 public interface BeanFactory {
 6
 7     Object getBean(String name);
 8
 9     void registerBeanDefinition(String name, BeanDefinition beanDefinition) throws Exception;
10 }

  一个获取Bean的getBean(),一个负责从资源加载器中获取并注册Bean的registerBeanDefinition()。

  

  9、同样的考虑了通用性,实现了其抽象类:

 1 package com.joe.factory;
 2
 3 import java.util.concurrent.ConcurrentHashMap;
 4
 5 import com.joe.BeanDefinition;
 6
 7 public abstract class AbstractBeanFactory implements BeanFactory {
 8
 9     private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
10
11     public Object getBean(String name) {
12         return beanDefinitionMap.get(name).getBean();
13     }
14
15     public void registerBeanDefinition(String name,
16             BeanDefinition beanDefinition) throws Exception {
17         // 何时设置beanDefinition的其他属性beanClass,beanClassName?——在BeanDefinitionReader加载xml文件的时候set(初始化的时候)
18         //测试用例指定要获取的beanClassName
19         Object bean = doCreateBean(beanDefinition);//beanDefinition.getBeanClass().newInstance()
20         beanDefinition.setBean(bean);
21         beanDefinitionMap.put(name, beanDefinition);
22     }
23
24     abstract Object doCreateBean(BeanDefinition beanDefinition) throws Exception;
25
26 }

  抽象类持有负责存储注册bean的ConcurrentHashMap,关于ConcurrentHashMap的内容也不展开了,读者自行google关于它的内容。这个map的键为String的类名,值为BeanDefinition对象。这里创建类实例(交给实现类实现doCreateBean()去做),并且设置到BeanDefiniton中,至此,BeanDefiniton的所有属性都初始化完毕。可以注册到BeanDefinition工厂中了,beanDefinitonMap.put()方法注册完成后,bean工厂的初始化也正式宣告完成,可以等待代码获取bean了(工厂可以正式开始量产“对象”)。

  

  10、AutowiredCapableBenafactory,第9中提到的如何创建类实例,我们通过实现doCreateBean()自行决定如何创建:

 1 package com.joe.factory;
 2
 3 import java.lang.reflect.*;
 4
 5 import com.joe.*;
 6
 7 public class AutowiredCapableBeanFactory extends AbstractBeanFactory {
 8
 9     @Override
10     Object doCreateBean(BeanDefinition beanDefinition) throws Exception {
11         Object bean = beanDefinition.getBeanClass().newInstance();
12         beanDefinition.setBean(bean);
13         applyPropertyValues(bean, beanDefinition);
14         return bean;
15     }
16
17     void applyPropertyValues(Object bean, BeanDefinition mdb) throws Exception {
18         for (PropertyValue propertyValue : mdb.getPropertyValues().getPropertyValues()) {
19             Field field = bean.getClass().getDeclaredField(propertyValue.getName());
20             field.setAccessible(true);
21             field.set(bean, propertyValue.getValue());
22         }
23     }
24
25 }

  doCreateBena通过反射相关API创建实例bean,并设置给BeanDefiniton的bean属性,然后通过遍历BeanDefiniton的变量ProperyValues,还是通过反射,为变量设置从xml文件注入的值(回忆下刚刚资源加载器从如何从xml解析各个节点元素的值)。

  

  11、之前提到用于描述类变量的PropertyValues和PropertyValue,注意两者的差别,两者配合使用,联系比较紧密,就两个类一起贴出来了。

package com.joe;

import java.util.ArrayList;
import java.util.List;

public class PropertyValues {

    private final List<PropertyValue> propertyValueList = new ArrayList<PropertyValue>();

    public PropertyValues() {}

    public void addPropertyValue(PropertyValue pv) {
        this.propertyValueList.add(pv);
    }

    public List<PropertyValue> getPropertyValues() {
        return this.propertyValueList;
    }
}

package com.joe;

public class PropertyValue {

    private final String name;

    public String getName() {
        return name;
    }

    public Object getValue() {
        return value;
    }

    private final Object value;

    public PropertyValue(String name, Object value) {
        this.name = name;
        this.value = value;
    }
}

  12、为了读者能跑通UnitTest,也给出相关的其他类以及xml文件。

  用于注入引用类型变量的BeanReference实现:

 1 package com.joe;
 2
 3 public class BeanReference {
 4
 5     private String name;
 6
 7     private Object bean;
 8
 9     public BeanReference(String name) {
10         this.name = name;
11     }
12
13     public String getName() {
14         return name;
15     }
16
17     public void setName(String name) {
18         this.name = name;
19     }
20
21     public Object getBean() {
22         return bean;
23     }
24
25     public void setBean(Object bean) {
26         this.bean = bean;
27     }
28 }

  xml文件:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
 4        xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
 5        xsi:schemaLocation="
 6     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
 7     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
 8     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
 9     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
10
11     <bean id="helloWorldService" class="com.joe.HelloWorldServiceImpl">
12         <property name="text" value="Hello Joe"></property>
13     </bean>
14
15 </beans>

 

  13:单元测试文件:

  测试类BeanFactoryTest:

 1 package com.joe;
 2
 3 import static org.junit.Assert.*;
 4
 5 import java.util.Map;
 6
 7 import org.junit.Test;
 8
 9 import com.joe.factory.AbstractBeanFactory;
10 import com.joe.factory.AutowiredCapableBeanFactory;
11 import com.joe.io.ResourceLoader;
12 import com.joe.xml.XmlBeanDefinitionReader;
13
14 public class BeanFactoryTest {
15
16     @Test
17     public void test() throws Exception {
18         //初始化......
19         XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
20         xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");
21
22         AbstractBeanFactory beanFactory = new AutowiredCapableBeanFactory();
23         for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {
24             beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
25         }
26         //初始化完毕,获取想要的bean
27         HelloWorldService helloWorldService = (HelloWorldService)beanFactory.getBean("helloWorldService");
28         helloWorldService.helloWorld();
29     }
30
31 }

  测试用服务类以及其接口(一起贴出来了):

package com.joe;
public class HelloWorldServiceImpl implements HelloWorldService {

    private String text;

    public void setText(String text) {
        this.text = text;
    }

    @Override
    public void helloWorld() {
        System.out.println(text);
    }

}

package com.joe;
public interface HelloWorldService {

    void helloWorld();
}

四、总结

  Spring作为一个通用的Web框架,其代码组织方式非常值得我们借鉴。接口、抽象类、实现类之间都很好的体现了开闭原则,各个类职责清晰,可以说是工厂模式,模板方法等等设计模式的一个很好的实践。这个简化版Spring,代码量虽然不大,但是也正因为这样,更适合新手理解和实践。

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Monaco }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Monaco }

原文地址:https://www.cnblogs.com/junqiao/p/9502255.html

时间: 2024-07-30 11:20:42

Java Web框架——自己动手实现一个简化版的Spring IOC框架的相关文章

java web程序 上机考试做一个登陆注册程序

大二期末 java web.用到数据库,jdbc.myeclipse实现用户的注册,登陆 并且不能出现500错误,用户不能重复注册.当用户任意点击时也不能出现500错误! 这里.我只写注册成功的页面.这个不让用户重复注册 当时老师对我各种扣分.可后来.我问他的时候,他说不出来.我不是那个第一个提交作业的学生,可我的分数却比他低,我的功能比他多一个好吧 无所谓啊  谁叫我那么背时,再也不想问了.特别烦躁了 registerOk.jsp <body> <% String name=reque

Spring4- 01 - Spring框架简介及官方压缩包目录介绍- Spring IoC 的概念 - Spring hello world环境搭建

一. Spring 框架简介及官方压缩包目录介绍 主要发明者:Rod Johnson 轮子理论推崇者: 2.1 轮子理论:不用重复发明轮子. 2.2 IT 行业:直接使用写好的代码. Spring 框架宗旨:不重新发明技术,让原有技术使用起来更加方便. Spring 几大核心功能 4.1 IoC/DI控制反转/依赖注入 4.2 AOP面向切面编程 4.3 声明式事务. Spring 框架runtime 5.1 test: spring 提供测试功能 5.2 Core Container:核心容器

从零开始手写 spring ioc 框架,深入学习 spring 源码

IoC Ioc 是一款 spring ioc 核心功能简化实现版本,便于学习和理解原理. 创作目的 使用 spring 很长时间,对于 spring 使用非常频繁,实际上对于源码一直没有静下心来学习过. 但是 spring 源码存在一个问题,那就是过于抽象,导致学习起来成本上升. 所以本项目由渐入深,只实现 spring 的核心功能,便于自己和他人学习 spring 的核心原理. spring 的核心 Spring 的核心就是 spring-beans,后面的一切 spring-boot,spr

[原创]java WEB学习笔记12:一个简单的serlet连接数据库实验

本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱好者,互联网技术发烧友 微博:伊直都在0221 QQ:951226918 ---------------------------------

Java Web项目--使用Servlet生成一个页面

为了生成一个servlet对应的网页.我们需要新建一个web.xml,其中将会放置servlet的相关信息.web.xml文件放置在WebContent/WEB-INF/目录下.(我们在Eclipe中新建一个Dynamic Web Project的时候一直点"next"的话会有提示"Generate web.xml deployment descriptor",勾选该选项会默认生成一个web.xml文件).我们需要配置两对映射关系,一对是servlet名和他对应的s

Java Web项目--使用JSP生成一个页面

我们使用了servlet生成了一个网页,但是可以看到使用servlet生成网页必须将网页的内容全部嵌入到Java代码当中,不是很方便.所以有没有什么办法是将Java代码嵌入到html代码中,而不是像servlet这样将html代码嵌入到Java代码中的呢.答案是使用JSP.JSP是使用类似"<% %>"括起来的一段嵌入到html中的Java代码.我们下面将展示使用一个jsp页面显示一个网页的效果.JSP页面的开头需要加上一行"<%@ page content

为初学者提供一个基本Struts+Spring+Mybatis框架的搭建(主要实现登录注册):配置struts+spring篇

在struts基础上配置spring 1.同样的先导入spring的包(标志spring的包和用于spring和struts整合的包)如下: 另外,要把struts和spring结合起来还需要这个jar包:      这些包都可以从官网上下载的struts和spring的包中找到2. 2.导入jar包后要配置文件,让struts和spring真正的融合起来,也让系统真正知道spring的存在.为此,我们要配置两个三个文件: Spring 本身的配置文件applicationContext.xml

自己实现spring IOC框架(超详细)

1.IOC简介: 控制反转即IoC (Inversion of Control),所谓控制反转就是应用本身不负责依赖对象的创建及对象之间关系的维护,而是由外部容器负责的.这样控制权就由应用转移到了外部容器,控制权的转移就是所谓反转. IoC是一个很大的概念,可以用不同的方式来实现.例如:<1>依赖查找(Dependency Lookup):容器提供回调接口和上下文环境给组件.EJB和Apache Avalon都使用这种方式.<2>依赖注入(Dependency Injection)

初学 Java Web 开发,请远离各种框架,从 Servlet 开发

Web框架是开发者在使用某种语言编写Web应用服务端时关于架构的最佳实践.很多Web框架是从实际的Web项目抽取出来的,仅和Web的请求和响应处 理有关,形成一个基础,在开发别的应用项目的时候则可以从这个剥离出来的基础做起,让开发者更关注更具体的业务问题,而不是Web的请求和响应的控制. 框架很多,但套路基本类似,帮你隐藏很多关于 HTTP 协议细节内容,专注功能开发. 但对一个初学者来说,过早的接触框架往往是事倍功半!同样一个问题,换一种框架你可能需要从头开始研究. 下面是针对初学 Java