手把手搭建 零配置文件的spring项目

Posted by zjhChester on January 31, 2020

手把手搭建 零配置文件的spring项目(java Configuration 代替xml编写配置文件,并脱离web.xml启动web项目)

前言:

要知道,在自我学习的过程中都有一个过渡的阶段,作为刚刚大三的学生,深有体会,书本和课程大纲教授的是基于spring2.5和tomcat3.0以下版本的ssm项目结构,相当于是最基础的一代版本,然而在目前最前沿的技术springboot面前,是跨越了几十个版本的,在掌握了最基础的ssm结构的时候,就想要去解除springboot的东西,内心就会有一些疑问,比如:他是如何做到零配置,怎么做到零web.xml启动web项目,怎么做到默认优于配置的,这篇文章希望带给你有所帮助。

再次之前我也是找了各大论坛,各大社区,去解答疑惑,最后还是落眼在spring.io官网,我发现官网提供的就是我想要的那种效果,

https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html

官网里给了一个比较直观的方式去初始化咱们的spring:

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletCxt) {

        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
        ac.register(AppConfig.class);
        ac.refresh();

        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(ac);
        ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}

通过一个类去实现WebApplicationInitializer接口,并且重写onStartup方法来启动咱们的spring

可以看到源码里的注释,第一个是加载springweb应用的配置(就是咱们的ioc容器和mvc的配置),第二个是初始化并注册一个DispatcherServlet通过new的方式。

熟悉springweb应用加载顺序的同学肯定很容易就知道了这里代替了咱们传统smm中的哪一部分:

1、加载springweb应用的配置(mvc和ioc)

<!--通过dispatcherservlet加载mvc的配置-->
<init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath*:spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
<!--通过监听器初始化ioc容器-->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:beans.xml</param-value>
  </context-param>

2、初始化并注册一个DispatcherServlet

 <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      </servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

官网给出的那一大串代码其实就是代替了以上两段web.xml内的配置文件,那官网是如何做到这么简短的呢?这里就需要提到一个名词java Configuration。

java Configuration:

口头翻译即为java配置,其实就是用java代码去代替xml,使spring纯java加载。

这里其实会有一个误区,不知道刚刚学完ssm的小伙伴会不会有同样的问题,java Configuration 和 注解,会不会想到是同一个东西,刚刚说到了Configuration其实就是用java代码代替xml,注解确实也是起到了同样的作用,但是注解和Configuration 是两个东西,这里我举个例子:

//这是用注解的方式去
@Component
class A{
    public A MethodName(){
        return new A();
    }
}
//这是Configuration
@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

可以看到两者是不同的两个东西,在spring官网也是有同样的区分别:

Basic Concepts: @Bean and @Configuration

AnnotationConfigApplicationContext

所以不要进入这个误区了,好了,最基础的介绍到此结束,咱们开始正式进入零配置的解读。

正文解读:

咱们先回顾一下基于xml配置的ssm结构

1、spring.xml 用于扫描包和整合mybatis,包含了数据源,sqlSessionFactoryBean等等

2、springmvc.xml 用于mvc配置,拦截器,静态资源映射,视图解析器等等

3、web.xml 用于加载以上两个配置文件

OK回顾完毕,然后我们看看,如何用javaconfiguration的方式吧上述全都给代替掉,

在前言中,我也是说道了spring.io官网中给出了web.xml代替的方式即实现WebApplicationInitializer接口,重写onStartup方法,咱们先把源码搬过来,一条一条的读:

public class MyWebApplicationInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletCxt) {
//        web.xml 中的 listener 初始化
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
//        listener中的加载spring.xml的配置文件内容
        ac.register(MyApp.class);
//        用于初始化spring配置文件 web项目可用可不用
        ac.refresh();
        //创建servlet容器
        DispatcherServlet dispatcherServlet = new DispatcherServlet(ac);
//        创建前端控制器去名字为dispatcherServlet  <servlet>标签
        ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcherServlet", dispatcherServlet);
//        <随服务器启动加载这个servlet
        registration.setLoadOnStartup(1);
//        <servlet-mapping>里面的url-patten
        registration.addMapping("/*");
}

ok,从上文中,咱们可以看到,在onStartup方法中,传入参数是ServletContext,内部的AnnotationConfigWebApplicationContext其实就是初始化ioc容器加载spring的配置文件,然后通过 ac.register(MyApp.class),将MyApp这个类,注册并加载,细心的同学就会发现,其实这个MyApp.class才是最重要的那个配置文件,即基于Configuration的配置文件,这个文件我先丢个源码,这个里头就是涵盖了我们所有的spring,springmvc的配置内容,包括数据源等等

@Configuration//表名是配置类
@EnableWebMvc//标识可以写mvc的配置
@ComponentScan("xyz.zjhwork")//扫描包的路径  相当于<context:component-scan base-package="cn.yunge">
public class MvcConf implements WebMvcConfigurer {
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//        字符转换  包括解决中文乱码
        FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
        fastJsonHttpMessageConverter.setSupportedMediaTypes(MediaType.parseMediaTypes("text/html;charset=utf-8"));
        converters.add(fastJsonHttpMessageConverter);
    }
......

上述文件就是我的MyApp的类,这个名字当然可以随便取,显示申明当前的类是配置类,因为这里我们需要把mvc和spring 的配置丢在一起,所以这里我们还要标明这个类可以写mvc的配置,因此这类里头,我们可以写所以的mvc和spirng的配置,只是要学习一些将xml转换为javaconfiguration的写法。后面我会把这个类放一个基础完整的源码。

ok到这里我们再回顾一下,我们用javaconfiguration代替了那些原来的配置:

1、spring.xml ---》MyApp类

2、springmvc.xml ---》 MyApp类

3、web.xml ----》onStartup方法

好了,到这里相当于我们基于配置方面的东西,已经实现了,但是肯定会有想要知道原因的小伙伴问,凭什么他这里实现一个WebApplicationInitializer接口就能加载配置,原来的tomcat呢,springboot是不需要配置tomcat 的啊,人是直接启动,根本不需要再启动外置服务器,好,下面我就讲讲,以上说出的两个疑问。

WebApplicationInitializer接口:

在官方文档解读中:

In a Servlet 3.0+ environment, you have the option of configuring the Servlet container programmatically as an alternative or in combination with a web.xml file. The following example registers a DispatcherServlet:

这里说到,在servlet3.0+的环境中:

  <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
  </dependency>

你可以通过configuring的方式去代替web.xml配置,并注册一个dispatcherservlet,所以,如果你的servlet版本在3.0+以上,只要你实现了WebApplicationInitializer这个接口就可以注册一个dispatcherservlet,正因为servlet3.0的规范规定,无论你使用的服务器是tomcat还是jetty,在服务器启动的时候都会先执行onStartup这个方法,由此去加载用于编写spring和mvc的配置内容,接下来就是咱们的内置tomcat服务器

内置Tomcat:

首先引入maven坐标

<!--核心包-->
<dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>8.5.33</version>
</dependency>
<!--加上这个不会报错误和异常-->
 <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <version>8.5.33</version>
</dependency>

注意这里的tomcat版本一定要选择好,基于servlet3.0的tomcat是8.0+版本的,所以低于8.0版本的tomcat都是不支持servlet3.0规范的。

导入内嵌tomcat包之后,我们就可以用一个main函数去启动tomcat,这样,我们就不需要外部去开启tomcat然后再部署项目了:

public class ApplicationStarter {
    public static void run(){
        //实例化tomcat
        Tomcat tomcat = new Tomcat();
        //设置端口号为9999
        tomcat.setPort(9999);
        //        标识tomcat启动为webapp
        try {
            tomcat.addWebapp("/1","D://test/");
//            tomcat启动
            tomcat.start();
//            tomcat监听用户接入
            tomcat.getServer().await();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        //启动tomcat
        run();
    }
}

这样的一个类,相当于就是咱们的spring项目的入口启动器,启动web项目就基于这样一个类,这里比较重要的一个地方就是tomcat.addWebapp将当前的服务器启动标识为web项目不然不会去加载咱们的onStartup方法,

到此,咱们的讲解就结束了,其他需要重视的就是在咱们把xml配置文件改写为configurtion的格式的时候注意写法,下面我就把最基础的配置,即MyApp配置类的内容发出来(一步一步敲出来的),拿去用的时候需要把里头的包名和开发自定义的部分的东西修改掉。

package xyz.zjhwork.springApplicationStarter.mvcConf;

import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import xyz.zjhwork.interceptor.LoginInterceptor;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.List;
import java.util.Properties;

@Configuration
@EnableWebMvc
@ComponentScan("xyz.zjhwork")
public class MvcConf implements WebMvcConfigurer {
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//        字符转换  包括解决中文乱码
        FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
        fastJsonHttpMessageConverter.setSupportedMediaTypes(MediaType.parseMediaTypes("text/html;charset=utf-8"));
        converters.add(fastJsonHttpMessageConverter);
    }


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //拦截器注册
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/newException").addPathPatterns("/")
                .addPathPatterns("/userStatus").addPathPatterns("/userExit").addPathPatterns("/newException").addPathPatterns("/myListException").addPathPatterns("/userInfo")
        .addPathPatterns("/isFavByUsernameAndExceptionId").addPathPatterns("/findFavByUsername").addPathPatterns("/deleteFavFromFavByUsernameAndExceptionId").addPathPatterns("/addFavByUsernameAndExceptionId")
        .addPathPatterns("/isAproByUsernameAndExceptionId").addPathPatterns("/addAproByUsernameAndExceptionId").addPathPatterns("/insertComment").addPathPatterns("/findHistoryByUsername").addPathPatterns("/userInfoUpdate")
        ;
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/css/**").addResourceLocations("classpath:/static/css/");
        registry.addResourceHandler("/js/**").addResourceLocations("classpath:/static/js/");
        registry.addResourceHandler("/img/**").addResourceLocations("classpath:/static/img/");
        registry.addResourceHandler("/theme/**").addResourceLocations("classpath:/static/theme/");
//        静态资源存放
        registry.addResourceHandler("/*.html").addResourceLocations("classpath:/static/");

    }

    /**
     * mybatisConf
     *
     * @return
     */
    @Bean("pooledDataSource")
    public DataSource dataSource() {
        //加载db.properties 读取数据库基本信息
        Properties pop = new Properties();
        try {
            pop.load(this.getClass().getClassLoader().getResourceAsStream("db.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        PooledDataSource dataSource = new PooledDataSource();
        try {
            dataSource.setDriver(pop.getProperty("jdbc.driver"));
            dataSource.setUsername(pop.getProperty("jdbc.username"));
            dataSource.setPassword(pop.getProperty("jdbc.password"));
            dataSource.setUrl(pop.getProperty("jdbc.url"));
            dataSource.setDefaultAutoCommit(true);
            dataSource.setPoolMaximumActiveConnections(20);
            dataSource.setPoolMaximumIdleConnections(0);


        } catch (Exception e) {
            e.printStackTrace();
        }
        return dataSource;
    }

    @Bean("sqlSessionFactoryBean")
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws IOException {
        SqlSessionFactory factory;
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        //加载mapper.xml
        ResourcePatternResolver resolver = new ClassPathXmlApplicationContext();
        bean.setMapperLocations(resolver.getResources("classpath*:/daoMappers/*.xml"));
        try {
            factory = bean.getObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return factory;
    }

    @Bean("mapperScannerConfigurer")
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setBasePackage("xyz.zjhwork.dao");
        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactoryBean");
        return mapperScannerConfigurer;
    }
}

结束:

本文结束,不知道小伙伴看到全java代码的spring 有没有兴奋的感觉呢!有问题或者需要帮助的同学联系邮箱zjhChester@gmail.com

1580461697040