preface

The last article wrote the way to build web projects using servlets, this article will look at how to do web projects after Spring. This article will use Spring + SpringMVC to build the demo.

Actual combat: Spring + SpringMVC

Since the previous article covered the steps to set up a Web project in more detail, this article briefly lists the key configurations. The full project can be obtained from Spring-XML-demo · Dingyufan/learning-Spring-Code Cloud-Open Source China (gitee.com)

The project structure

Pom. XML dependency

Daily use is springboot, now back to find their own dependencies on the package, is also a little not used to = =.


      
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.dingyufan.learnspring</groupId>
    <artifactId>spring-xml-demo</artifactId>
    <version>1.0 the SNAPSHOT</version>
    <name>spring-xml-demo</name>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.target>11</maven.compiler.target>
        <maven.compiler.source>11</maven.compiler.source>
        <spring.version>5.2.12. RELEASE</spring.version>
    </properties>

    <dependencies>
        <! -- Using Tomcat 10 -->
        <! Jakarta. Servlet </groupId> <artifactId> Jakarta. Servlet-api </artifactId> <version>5.0.0</version>  <scope>provided</scope> </dependency> -->

        <! For compatibility with SpringMVC, use javax.servlet. As of 5.2.12.RELEASE, SpringMVC does not support Jakarta. Servlet -->
        <! -- Using Tomcat 9 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
        
        <! - JDK11 need -- -- >
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.0</version>
            </plugin>
        </plugins>
    </build>
</project>
Copy the code

coding

Let’s simply write a Controller and a Service.

package cn.dingyufan.learnspring.springxmldemo.controller;

import cn.dingyufan.learnspring.springxmldemo.service.HelloService;
import javax.servlet.http.HttpServletRequest;
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;

@RestController
public class HelloController implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Autowired
    private HelloService helloService;


    @GetMapping("/hello")
    public ResponseEntity<String> hello(HttpServletRequest request) {
        return ResponseEntity.ok("Hello Spring!");
    }

    @GetMapping("/check")
    public ResponseEntity<String> check(HttpServletRequest request) {
        return ResponseEntity.ok(String.valueOf(helloService.hashCode()));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("HelloController injects applicationContext =>" + applicationContext.getDisplayName());
        this.applicationContext = applicationContext; }}Copy the code
package cn.dingyufan.learnspring.springxmldemo.service;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class HelloService implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("HelloService injections applicationContext =>" + applicationContext.getDisplayName());
        this.applicationContext = applicationContext; }}Copy the code

As you can see, the ApplicationContextAware interface is implemented in our code. Briefly explain what this interface does. This interface inherits from the Aware interface and is used to inject applicationContext instances through the set method. There are a number of similar interfaces, such as BeanFactoryAware, that can be injected into beanFactory through the set method. The purpose of injecting the applicationContext into the Controller and Service is to compare and contrast the containers they get, as discussed later.

The web.xml configuration

In the previous article, we configured the listener, context-param, filter, and servlet in web.xml. You’ll also need to configure this with Spring, but it’s a little different.


      
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">


    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>


    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>WEB-INF/spring-beans.xml</param-value>
    </context-param>


    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/ *</url-pattern>
    </filter-mapping>


    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>WEB-INF/spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/ *</url-pattern>
    </servlet-mapping>

</web-app>
Copy the code

In this web.xml, we configured the same thing, but the related classes were not written ourselves, but provided by Spring. This section describes the components.

ContextLoaderListener

ContextLoaderListener is a listener provided by the Spring-Web package. HelloListener implements the ServletContextListener interface, and rewrites the contextInitialized() and contextDestroyed() methods of the interface. By calling the initWebApplicationContext (), create and initialize a applicationContext.

It follows that after the server is started and the ServletContext is initialized, the Spring container (applicationContext) will be built and initialized using the ContextLoaderListener. This is the entry point to start the Spring container in your Web project.

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}

	@Override
	public void contextInitialized(ServletContextEvent event) {
                // As the name implies, an applicationContext is created and initialized
		initWebApplicationContext(event.getServletContext());
	}

	@Override
	public void contextDestroyed(ServletContextEvent event) { closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); }}Copy the code

CharacterEncodingFilter

This Filter is not required. It is only common to use this filter to unify the request encoding. This filter is configured for comparison with the ones I wrote above.

Through his inheritance relationship, we can see that he and our previous RandomFilter, also implemented the Filter interface. The core doFilter() method is implemented by the parent OncePerRequestFilter class. The parent OncePerRequestFilter writes its own logic in doFilter() and calls the doFilterInternal() method. This method is empty in OncePerRequestFilter and is left to subclasses to implement their own logic. This approach is a design pattern, the template method pattern, common in Spring source code.

So the CharacterEncodingFilter’s logic is in its own doFilterInternal() method. The specific logic is to determine whether to modify the encoding of request and response according to local variables without the purpose of interception.

public class CharacterEncodingFilter extends OncePerRequestFilter {
	@Nullable
	private String encoding;
	private boolean forceRequestEncoding = false;
	private boolean forceResponseEncoding = false;

	public CharacterEncodingFilter(a) {}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(a) {
		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(a) {
		return this.forceRequestEncoding;
	}

	public void setForceResponseEncoding(boolean forceResponseEncoding) {
		this.forceResponseEncoding = forceResponseEncoding;
	}

	public boolean isForceResponseEncoding(a) {
		return this.forceResponseEncoding;
	}


	@Override
	protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
                // CharacterEncodingFilterj CharacterEncodingFilterj
		String encoding = getEncoding();
		if(encoding ! =null) {
			if (isForceRequestEncoding() || request.getCharacterEncoding() == null) {
				request.setCharacterEncoding(encoding);
			}
			if(isForceResponseEncoding()) { response.setCharacterEncoding(encoding); } } filterChain.doFilter(request, response); }}Copy the code

As another quick note, we see two init-params in the configuration of the filter in web.xml. You can see that this is actually the corresponding member variable in his source code. The parent class GenericFilterBean assigns the configured value to the corresponding field in the init() method.

DispatcherServlet

The DispatcherServlet is an essential component of SpringMVC and is provided by the Spring-WebMVC package. You can tell by its class name that it is a Servlet, and it does implement the Servlet interface. It acts as an entry point for all SpringMVC requests. It involves a lot of content, here we only want to understand his several functions

  • Create and initialize a WebApplicationContext
  • Forwarding forwarding requests to Handler(Controller) based on requestMapping

The web.xml configuration for DispatcherServlet also has init-param, which configures the location of the SpringMVC configuration file; There is also a load-on-startup configuration, which indicates the load order and allows servlets to create Servlet instances as soon as the ServletContext is initialized, or if the ServletContext is not configured or negative, the Servlet instances are created on request as described above.

context-param

There is a context-param configuration in web.xml, which is essentially the Spring configuration file location. Context-param is loaded into the ServletContext, and ContextLoaderListener reads the value of this configuration in the ServletContext when it creates the Spring container applianceContext in the method. And assign a value to the applicationContext so that the Spring container can find the correct location when it reads the configuration later.

In web. XML we have contextConfigLocation configured in context-param and contextConfigLocation configured in DispatcherServlet. What’s the difference?

In fact, when using Spring + SpringMVC, Spring creates a Root WebApplicationContext that manages Spring beans; SpringMVC also creates a container, WebApplicationContext, which is a child of Root WebApplicationContext and manages Controller-related beans. Why do you do that? My understanding is that it’s easy to decouple. For example, if we choose Spring + Structs2, Spring still has a Root WebApplicationContext container to manage beans, and the controller layer is managed by Structs2. So that either Spring or SpringMVC can be taken out separately and combined flexibly.

The spring configuration

We use both manual definition and scanning to register beans. How the XML configuration becomes a Bean object will be explained later. You just have to know how to define it here.

spring-beans.xml

The way beans are configured using raw XML.


      
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <! Define a bean-->
    <bean id="helloService" class="cn.dingyufan.learnspring.springxmldemo.service.HelloService"/>
    
</beans>
Copy the code

spring-mvc.xml

Configure beans by scanning


      
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    
    <mvc:annotation-driven/>
    <! -- Scan the beans under base-package -->
    <mvc:component-scan base-package="cn.dingyufan.learnspring.springxmldemo.controller"/>
    
</beans>
Copy the code

Run the validation

When Tomcat is started, the console prints the two bean injected applicationContext, which you can see are different Spring containers.

Request localhost:8080/hello, return successfully

Localhost :8080/check returns hashCode, indicating that helloService injection was successful.

conclusion

The startup sequence of web projects using Spring + SpringMVC is as follows:

  1. The server starts, creates the ServletContext, and reads web.xml
  2. The Listener instance is created and ContextLoaderListener is notified to create a Spring container and initialize it
  3. Creating a Filter Instance
  4. Create Servlet instance, DispatcherServlet create a Spring subcontainer and initialize it, do MVC configuration

The web services in this article have many similarities and differences from the previous article. However, it can be seen that the underlying Spring + SpringMVC is actually a set of servlets. It expands many functions on the basis of servlets, and then forms a set of concepts and theories. So Spring source code is not a mystery, learning Spring source code is to learn how Spring is designed such a set of frameworks. Know what it is, and then know why. Learn what source code looks like, and then think about why it looks like that. In the next article we’ll look at exactly what Spring container initialization does.