Java技术仔 发表于 2021-8-11 16:30:10

Spring源码学习 |简单使用Spring与SpringMVC

实战:Spring + SpringMVC

上一篇对于搭建web项目步骤介绍得比较详细了,本文就简朴的列举一下关键的配置:
项目布局

spring-demo    │    ├─src    │├─main    ││├─java    │││└─cn    │││      └─dingyufan    │││          └─demo    │││            └─spring    │││                  ├─controller    │││                  │   └─HelloController.java    │││                  │    │││                  └─service    │││                        └─HelloService.java    │││    ││├─resources    ││└─webapp    ││      └─WEB-INF    ││         ├─spring-beans.xml    ││         ├─spring-mvc.xml    ││         └─web.xml    ││    │└─test    │      ├─java    │      └─resources    │    └─pom.xml复制代码依赖包

日常使用都是springboot,现在回到自己动手找依赖包的时候,也是有些不风俗了= =。
    4.0.0    cn.dingyufan    spring-demo    1.0-SNAPSHOT    spring-demo    war            1.8      1.8      5.2.10.RELEASE                            javax.servlet            javax.servlet-api            4.0.1                                    org.springframework            spring-core            ${spring.version}                            org.springframework            spring-beans            ${spring.version}                            org.springframework            spring-context            ${spring.version}                                    org.springframework            spring-web            ${spring.version}                            org.springframework            spring-webmvc            ${spring.version}                                                    org.apache.maven.plugins                maven-war-plugin                2.4                        复制代码编码

我们这里就简朴的写一个Controller和一个Service。
package cn.dingyufan.demo.spring.controller;import cn.dingyufan.demo.spring.service.HelloService;import org.springframework.beans.BeansException;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;@RestControllerpublic class HelloController implements ApplicationContextAware {    private ApplicationContext applicationContext;    @Autowired    private HelloService helloService;    @GetMapping("/hello")    public ResponseEntity hello(HttpServletRequest request) {      return ResponseEntity.ok("Hello Spring!");    }    @GetMapping("/check")    public ResponseEntity check(HttpServletRequest request) {      return ResponseEntity.ok(String.valueOf(helloService.hashCode()));    }    @Override    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {      System.out.println("HelloController注入applicationContext =>" + applicationContext.getDisplayName());      this.applicationContext = applicationContext;    }}复制代码package cn.dingyufan.demo.spring.service;import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Service;public class HelloService implements ApplicationContextAware {    private ApplicationContext applicationContext;    @Override    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {      System.out.println("HelloService注入applicationContext =>" + applicationContext.getDisplayName());      this.applicationContext = applicationContext;    }}复制代码可以看到我们代码中都实现了 ApplicationContextAware 接口,简朴解释一下这个接口的作用。这个接口实在是继承于 Aware 接口,作用就是可以通过set方法注入applicationContext实例。实在有一系列类似的接口,同理比如 BeanFactoryAware ,可以通过set方法注入beanFactory,还有很多在此不一一举例。在Controller和Service注入applicationContext的目的,就是对比一下两者获取到的容器的异同,后面会细说。
web.xml配置

上一篇中,我们在web.xml配置了listener、context-param、filter和servlet。同样引入Spring之后也必要配置这些内容,但是和之前的配置有些许差别。
            org.springframework.web.context.ContextLoaderListener                contextConfigLocation      /WEB-INF/spring-beans.xml                encodingFilter      org.springframework.web.filter.CharacterEncodingFilter                  encoding            UTF-8                            forceEncoding            true                        encodingFilter      /*                dispatcherServlet      org.springframework.web.servlet.DispatcherServlet                  contextConfigLocation            WEB-INF/spring-mvc.xml                1                dispatcherServlet      /*    复制代码在这个web.xml中,我们配置了同样的内容,但是相关的类都不是自己写的,而是Spring提供的。这里对各组件进行介绍。
ContextLoaderListener

ContextLoaderListener是spring-web包提供的一个监听器。打开来看它的源码,发现它和我们上一篇写的HelloListener一样,都实现了 ServletContextListener 接口,并重写了接口的contextInitialized()和contextDestroyed()方法。通过调用initWebApplicationContext(),创建并初始化一个applicationContext。
那么可以得出一个结论,服务器启动之后, ServletContext初始化后,Spring的容器(applicationContext)就会通过监听器(ContextLoaderListener)来构建并初始化 。这里就是Web项目中Spring容器启动的入口。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {        public ContextLoaderListener(WebApplicationContext context) {                super(context);        }        @Override        public void contextInitialized(ServletContextEvent event) {                // 顾名思义,这里会创建并初始化一个applicationContext                initWebApplicationContext(event.getServletContext());        }        @Override        public void contextDestroyed(ServletContextEvent event) {                closeWebApplicationContext(event.getServletContext());                ContextCleanupListener.cleanupAttributes(event.getServletContext());        }}复制代码CharacterEncodingFilter

这个Filter并不是必须项。只是通常情况下会使用这个过滤器来统一request的编码。配置这个过滤器是为了和前文自己写的做个对比。
https://p3.toutiaoimg.com/large/pgc-image/9baa1cb0406e4350af180403c9c8f6da
通过他的继承关系,可以看到他和我们前文RandomFilter一样,也实现了 Filter 接口。核心的doFilter()方法由父类OncePerRequestFilter实现。父类OncePerRequestFilter在doFilter()中写了自己的逻辑,并调用了doFilterInternal()方法。这个方法在OncePerRequestFilter是一个空方法,作用是留给子类实现子类自己的逻辑。这种做法是一种设计模式,模板方法模式,在Spring源码中很常见。
https://p26.toutiaoimg.com/large/pgc-image/3388f0944c0741c2a7f873177ee738a6
所以CharacterEncodingFilter的逻辑就是在自己的doFilterInternal()方法中。具体的逻辑就是根据局部变量,判断是否修改request和response的编码,并无拦截目的。
public class CharacterEncodingFilter extends OncePerRequestFilter {        @Nullable        private String encoding;        private boolean forceRequestEncoding = false;        private boolean forceResponseEncoding = false;        public CharacterEncodingFilter() {      }        public CharacterEncodingFilter(String encoding) {                this(encoding, false);        }        public CharacterEncodingFilter(String encoding, boolean forceEncoding) {                this(encoding, forceEncoding, forceEncoding);        }        public CharacterEncodingFilter(String encoding, boolean forceRequestEncoding, boolean forceResponseEncoding) {                Assert.hasLength(encoding, "Encoding must not be empty");                this.encoding = encoding;                this.forceRequestEncoding = forceRequestEncoding;                this.forceResponseEncoding = forceResponseEncoding;        }        public void setEncoding(@Nullable String encoding) {                this.encoding = encoding;        }        @Nullable        public String getEncoding() {                return this.encoding;        }        public void setForceEncoding(boolean forceEncoding) {                this.forceRequestEncoding = forceEncoding;                this.forceResponseEncoding = forceEncoding;        }        public void setForceRequestEncoding(boolean forceRequestEncoding) {                this.forceRequestEncoding = forceRequestEncoding;        }        public boolean isForceRequestEncoding() {                return this.forceRequestEncoding;        }        public void setForceResponseEncoding(boolean forceResponseEncoding) {                this.forceResponseEncoding = forceResponseEncoding;        }        public boolean isForceResponseEncoding() {                return this.forceResponseEncoding;        }        @Override        protected void doFilterInternal(                        HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)                        throws ServletException, IOException {                // CharacterEncodingFilterj具体逻辑                String encoding = getEncoding();                if (encoding != null) {                        if (isForceRequestEncoding() || request.getCharacterEncoding() == null) {                                request.setCharacterEncoding(encoding);                        }                        if (isForceResponseEncoding()) {                                response.setCharacterEncoding(encoding);                        }                }                filterChain.doFilter(request, response);        }}复制代码还有一点必要简朴解释一下,我们看到web.xml中filter的配置中有两个init-param。可以发现这个实在就是对应他源码中成员变量对应的。父类GenericFilterBean会在init()方法中,将配置的值赋值给对应的字段。
DispatcherServlet

DispatcherServlet是SpringMVC必不可少的组件,它是由spring-webmvc包提供。从它的类名就可以知道它就是一个Servlet,它也确实实现了 Servlet 接口。它可以算是SpringMVC所有请求的入口。它涉及的内容非常多,在这里我们只要先明白他的几个作用

[*]创建并初始化一个WebApplicationContext
[*]转发根据requestMapping转发请求给Handler(Controller)
https://p6.toutiaoimg.com/large/pgc-image/8f208729b9d34f1580489d44147e7f43
DispatcherServlet相关的web.xml配置也有init-param,配置了SpringMVC配置文件的位置;还有一个load-on-startup配置,这个配置的作用就是表示加载顺序,能让Servlet在ServletContext初始化后就依次创建Servlet实例,如果不配置或配置负数,则是像上文说的有请求时再创建Servlet实例。
context-param

在web.xml中有一个context-param配置,这个配置的实在就是Spring的配置文件位置。context-param会加载到ServletContext中,ContextLoaderListener在方法中创建Spring容器applianceContext的时候,会读取ServletContext中这项配置的值,并给applicationContext赋值,这样后期Spring容器读取配置时就可以找到正确的位置。
在web.xml我们在context-param中配置了contextConfigLocation,DispatcherServlet中也配置了contextConfigLocation,两者有什么区别呢?
实际上,在使用Spring + SpringMVC的时候,Spring会创建一个Root WebApplicationContext,管理Spring的Bean;SpringMVC也会创建一个容器WebApplicationContext,它是Root WebApplicationContext的子容器,管理Controller相关Bean。为什么要这么做呢?我的理解是,这样可以很方便解耦。比如我们选择Spring + Structs2,Spring依然有一个Root WebApplicationContext容器用以管理Bean,而控制器层由Structs2管理。 这样Spring或是SpringMVC都可以单独拿出来,机动组合。
spring配置

我们使用手动定义、扫描两种方式来注册Bean。后面会讲授xml配置是怎么样变成Bean对象的。在这只要知道怎么样可以定义就可以了。
spring-beans.xml

使用原始的xml配置bean的方式。
            复制代码spring-mvc.xml

使用扫描的方式配置bean
                  复制代码运行验证

启动tomcat,控制台打印了两个bean注入的applicationContext,可以看出是不同的Spring容器。
https://p9.toutiaoimg.com/large/pgc-image/7b5dc4ed51a149fb9f2b2f23d2896739
请求 localhost:8080/spring-demo/hello,乐成返回
https://p3.toutiaoimg.com/large/pgc-image/834c6bda6866453580c611c32840698b
请求 localhost:8080/spring-demo/check,返回了hashcode,阐明helloService注入乐成。
https://p6.toutiaoimg.com/large/pgc-image/24ad6003fe8b433fb2e87b252d33cf59
结语

本文我们使用Spring + SpringMVC来搭建了一个web服务,和前一篇相比有很多相似之处,也有很多不同之处。但是可以看出,Spring + SpringMVC底层实在也是Servlet那一套,它是在Servlet的基础上,扩充了许多功能,然后自身形成了一套概念、理论。所以Spring源码实在并不神秘,学习Spring源码,就是学习Spring是如何设计这样一套框架的。知其然,然后知其所以然。先了解源码是什么样,然后思考源码为什么是这样。
页: [1]
查看完整版本: Spring源码学习 |简单使用Spring与SpringMVC