基于注解的环境搭建

一、创建Maven项目

 

配置webapp(SpringMVC)

选中这个之后出现这个页面(设置web资源的目录,也就是我们属性的webapp所在的位置,它默认的位置并不正确,所有我们要修改一下),修改为 src\main\webapp(没有这个文件夹就新建)

因为我们现在搭建的注解版,所以就不需要web.xml(点击-号把默认的删除)

最终的项目目录

二、pom.xml依赖配置

<?xml version="1.0" encoding="UTF-8"?>
<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>com.codedot</groupId>
    <artifactId>SpringAnno</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <!-- JDK版本 -->
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!-- Spring 5.1.6 要求JDK必须是1.8以上-->
        <spring.version>5.1.16.RELEASE</spring.version>
    </properties>

    <dependencies>
        <!-- 核心容器 之 spring-core -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- 核心容器 之 spring-beans -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- 核心容器 之 spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- 核心容器 之 spring-context-support -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- 核心容器 之 spring-expression -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- Spring Web -->
        <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>

        <!-- 否则 Error:(9,7) java: 错误: 无法访问ServletException-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>

        <!-- Spring Test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- Junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version><!--此处需要注意的是,spring5 及以上版本要求 junit 的版本必须是 4.12 及以上,否则用不了-->
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>
            <!-- 没有web.xml文件的情况下构建WAR, 否则打包时会报错 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <!--如果想在没有web.xml文件的情况下构建WAR,请设置为false。-->
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

要使用spring的事务,需要引入:

<!-- Spring 事务支持 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>${spring.version}</version>
</dependency>

要使用spring的JdbcTemplate等功能,需要引入:

<!-- Spring JDBC -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${spring.version}</version>
</dependency>

三、搭建Spring和SpringMVC环境

继承AbstractAnnotationConfigDispatcherServletInitializer,创建启动器
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

/**
 * @Description AbstractAnnotationConfigDispatcherServletInitializer 是在spring-webmvc包下的
 */
public class SpringInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    //Spring容器:等同于applicationContext.xml
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    //SpringMVC容器 :等同于springmvc.xml
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMVCConfig.class};
    }

    // SpringMVC 的DispatcherServlet拦截规则
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}
 Spring容器配置
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;

/**
 * 这个是Spring容器,相当于ApplicationContext.xml,负责扫描相关的service和dao,排除controller的扫描
 * 数据源、事务等均在这里配置
 */
@ComponentScan(value = {"com.codedot"},
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}),
                @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {RestController.class})})
@Configuration
public class SpringConfig {

}
SpringMVC容器配置
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
 * SpringMVC容器,负责扫描controller,声明视图解析规则、静态资源处理规则、拦截器等.
 * 在Spring5.0后,原来继承WebMvcConfigurerAdapter过期,提供了替代类 WebMvcConfigurationSupport, 但是在使用
 * WebMvcConfigurationSupport的时候,自定义的视图解析器不能正常使用。
 * 所以改成实现 WebMvcConfigurer 接口,spring 5.0后要使用Java8,而在Java8中接口是可以有default方法的,不用非得实现,
 * 所以这个类就没必要了。
 */
@EnableWebMvc
@ComponentScan(value = {"com.codedot.ctrl"},
        includeFilters={@ComponentScan.Filter(type= FilterType.ANNOTATION,value={Controller.class}),
                @ComponentScan.Filter(type= FilterType.ANNOTATION,value={RestController.class})},useDefaultFilters = false)
@Configuration
public class SpringMVCConfig implements WebMvcConfigurer{

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/WEB-INF/views/", ".jsp");
    }
}
在webapp/WEB-INF/views目录下创建index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>首页</title>
</head>
<body>
    Hello World!!!
</body>
</html>
新建controller
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HelloController {

    @GetMapping("fromIndex")
    public String helloIndex(){
        return "index";
    }
}
配置tomcat部署项目,访问http://ip:port/上下文/fromIndex

注意:这里要特别注意上下文的设置。

四、原理解析

为什么ServletContainerInitializer取代web.xml实现零配置?

tomcat自动扫描web项目的WEB-INF/classes或WEB-INF/lib/*.jar的META-INF/services/javax.servlet.ServletContainerInitializer文件,调用其onStartup方法,参数webAppInitializerClasses为@HandlesTypes指定接口的所有实现类。因为spring-web.jar,所以开发过程中,只需定义一个类实现org.springframework.web.WebApplicationInitializer接口就可以了。

javax.servlet.ServletContainerInitializer接口

在基于注解的servlet开发中,ServletContainerInitializer接口用于代替web.xml。它只有一个方法:onStartup,可以在其中注册servlet、拦截器(Filter)、监听器(Listener)这三大组件。另外,ServletContainerInitializer还可以使用@HandlesTypes在onStartup方法的参数列表中注入感兴趣的类。servlet容器启动时,会扫描每个jar包的项目根目录下的/META-INF/services/javax.servlet.ServletContainerInitializer文件,执行这个文件中指定的ServletContainerInitializer接口的实现类的onStartup方法。

org.springframework.web包提供的ServletContainerInitializer实现类

org.springframework.web包的/META-INF/services/javax.servlet.ServletContainerInitializer文件指定了ServletContainerInitializer接口的实现类:SpringServletContainerInitializer。源码如下:

package org.springframework.web;

@HandlesTypes(WebApplicationInitializer.class)  //(1)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

    @Override
    public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {

        List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();

        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                // Be defensive: Some servlet containers provide us with invalid classes,
                // no matter what @HandlesTypes says...
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                        WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer) waiClass.newInstance());  //(2)
                    }
                    catch (Throwable ex) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
            return;
        }

        servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
        AnnotationAwareOrderComparator.sort(initializers);
        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);  //(3)
        }
    }

}

(1) 通过@HandlesType注解在onStartup方法的参数列表中注入感兴趣的类,即WebApplicationInitializer;

(2) 将WebApplicationInitializer的每个实现类,都新建一个实例,并放入initializers列表中;

(3) 遍历initializers列表,对每个WebApplicationInitializer实例执行其onStartup方法。

那么问题来了:WebApplicationInitializer有哪些实现类,是用来干什么的?

WebApplicationInitializer的实现类及其功能

WebApplicationInitializer的实现类有很多,重点看一下AbstractAnnotationConfigDispatcherServletInitializer

package org.springframework.web.servlet.support;

public abstract class AbstractAnnotationConfigDispatcherServletInitializer
        extends AbstractDispatcherServletInitializer {

    @Override
    @Nullable
    protected WebApplicationContext createRootApplicationContext() {
        Class<?>[] configClasses = getRootConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
            context.register(configClasses);
            return context;
        }
        else {
            return null;
        }
    }

    @Override
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        Class<?>[] configClasses = getServletConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            context.register(configClasses);
        }
        return context;
    }

    @Nullable
    protected abstract Class<?>[] getRootConfigClasses();

    @Nullable
    protected abstract Class<?>[] getServletConfigClasses();

}

这个类提供了两个方法的实现,以及两个抽象方法供子类继承

(1) createRootApplicationContext:创建根容器;

(2) createServletApplicationContext:创建servlet容器;

(3) getRootConfigClasses:抽象类,用于注册根容器的配置类,相当于applicationContext.xml;

(4) getServletConfigClasses:抽象的类,用于注册servlet容器的配置类,相当于springmvc.xml;

另外,它还从AbstractDispatcherServletInitializer类继承了getServletMappings方法,用于注册servlet的映射。

因此,我们可以自定义一个WebApplicationInitializer的实现类,继承AbstractAnnotationConfigDispatcherServletInitializer;在servlet容器启动时,会创建spring根容器和servlet容器,代替web.xml配置文件。同时,我们可以看到,在基于注解的springmvc开发中,真正用于代替web.xml的是WebApplicationInitializer,而并不是ServletContainerInitializer,这与本文开头提到的基于注解的servlet开发有些区别。

根容器和servlet容器

根容器用于管理@Service、@Repository等业务逻辑层和数据库交互层组件;

servlet容器用于管理@Controller、视图解析器、拦截器等跟页面处理有关的组件。

 

posted @ 2020-07-13 22:46  codedot  阅读(217)  评论(0编辑  收藏  举报