springboot教程:从零开始搭建一个springboot项目

前言:该文章不是入门教程,需要读者对spring,spring-mvc,spring-security,spring-boot有相对深入的了解。该文章的目的是帮助那些对spring-boot有相对深入了解的人快速搭建一个spring-boot项目。

说实话,如果不是spring-boot项目可以通过 ‘java -jar *’ 的方式来运行的话,我个人是不偏向使用spring-boot来开发项目的。个人感觉,如果不是对spring-mvc有相对深入了解的话,使用spring-boot这么一个黑盒工具会有很多问题。就算是那些对spring-mvc有相对多了解的人,在一开始使用spring-boot的时候也会有一种无从下手的感觉。当然了,如果对spring-mvc熟悉之后又学会了spring-boot,其实也会感觉省去很多麻烦,毕竟,spring-boot自己也说了:Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.

本文所涉及的那些文件我打了一个包,当成了本文的资源,本意是让大家可以0积分下载,有个方便的地方直接开始,但是目前csdn不支持手动设置积分,默认就是5积分,所以大家看需求下载吧。那个资源包里的文件跟本文所涉及的文件是一样的,本文就是根据那个资源包里的文件来写的。本文所涉及开发工具为eclipse,项目结构为maven普通java项目,也就是那个maven-archetype-quickstart的选项。


一、项目结构概览

如果按照该文章一步一步来,最终搭出来的项目,结构大致如下:
springboot教程:从零开始搭建一个springboot项目_第1张图片


二、对上图中的每个文件进行解释
1、App.java
package com.szq.base;

import org.springframework.boot.SpringApplication;

import com.szq.base.config.AppConfig;

public class App {
    public static void main(String[] args) {
        SpringApplication.run(AppConfig.class, args);
    }
}

这个类没什么需要解释的,spring-boot项目标准启动方式,固定这么写就行了。

2、AppConfig.java
package com.szq.base.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@SpringBootApplication(scanBasePackages = { "com.szq.base.controller", "com.szq.base.service.impl", "com.szq.base.springcomponent" })
@ServletComponentScan
@MapperScan("com.szq.base.dao")
public class AppConfig extends SpringBootServletInitializer implements WebMvcConfigurer {
    @Bean
    protected WebSecurityConfigurerAdapter getWebSecurityConfigurerAdapter() {
        return new WebSecurityConfigurerAdapter() {

            @Override
            protected void configure(HttpSecurity http) throws Exception {
                //禁用spring-security的csrf功能
                http.csrf().disable();
                //启用spring-security的cors功能,这里这样写,不加其他代码,则默认调用spring-mvc的cors相关功能
                http.cors();
                //防止spring-security拦截spring-boot actuator的相关请求
                http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests().anyRequest().permitAll();
            }
        };
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowedOrigins("*");
    }

}

这个类是spring-boot项目配置类,虽然spring-boot支持多个配置类,但是, 个人建议:只使用这一个配置类,将所有配置集中管理是比较好的。 这个类是个重头戏,解释如下:

  • @SpringBootApplication: 这个注解是spring-boot的注解,原文链接如下:https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/htmlsingle/#using-boot-using-springbootapplication-annotation。大致意思就是这个注解是其他几个注解功能的合集,注解的 scanBasePackages 属性对应spring-mvc的component-scan配置,配置spring扫描bean的路径。
  • @ServletComponentScan: 这个注解可以让spring模拟servlet3.1的自动扫描功能,自动扫描项目中配置有@WebServlet,@WebFilter,@WebListener和其他一些注解的类,具体自行百度。这个注解主要的功能就是在spring-boot项目中向servlet容器添加servlet,filter和listener。很多人在使用spring-boot进行项目开发时,不知道怎么向servlet容器添加这三样东西,因为很多人使用的是spring-boot内置容器,不像传统war包部署那样,有一个单独的容器来让人任意调配,这个注解的功能就是向servlet容器(不论是spring-boot内置容器还是传统war包部署那样)添加servlet,filter和listener。还有其他一些向spring-boot添加servlet,filter和listener的方法,但是比较麻烦,所以不建议使用。原文链接如下:https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/htmlsingle/#howto-add-a-servlet-filter-or-listener-using-scanning。个人建议配置上这个注解,免得用得到的时候不知道怎么找资料。
  • @MapperScan: 这个注解是mybatis的注解,用来扫描 MyBatis mapper interfaces ,对应MapperScannerConfigurer的basePackage属性配置。具体可以参考我的这篇mybatis教程:https://blog.csdn.net/lianjunzongsiling/article/details/74783674。
  • SpringBootServletInitializer: 重头戏1,spring-boot必须并且仅有一个(同时也如上所述,推荐spring-boot项目只有一个配置类)配置类继承 SpringBootServletInitializer ,这样的话跟下文的某些配置搭配起来,打出来的 war 包既可以通过 java -jar base.war 的形式运行,也可以像传统war包部署那样,部署在一个servlet容器里。
  • WebMvcConfigurer: 实现这个接口的目的是通过override这个接口的不同的方法,对spring-mvc的相关功能进行不同的配置,如上述代码中,addCorsMappings就是通过override的WebMvcConfigurer方法来配置spring-mvc的 cors 功能,对应的就是spring-mvc的cors配置。spring-mvc的cors原文链接如下:https://docs.spring.io/spring/docs/4.3.15.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#_xml_namespace。
  • getWebSecurityConfigurerAdapter: 这个方法的返回值是一个 WebSecurityConfigurerAdapter 类型,并且这个方法有一个 @Bean 注解,表明该方法会生成一个spring的bean。 WebSecurityConfigurerAdapter 类型bean的作用是配置spring-security的功能,可以通过override相关的方法来配置spring-security的不同的功能,如上述代码中 ‘http.csrf().disable();’ 禁用了spring-security的 csrf 功能,‘http.cors();’ 启用了spring-security的 cors 功能(注意,这里就这样写,不要添加其他spring-security的cors的相关代码,则会默认调用spring-mvc的cors功能,原文链接如下:https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#cors)。‘http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests().anyRequest().permitAll();’ 则防止了spring-security拦截spring-boot的actuator的相关请求。

3、AppConstants.java
package com.szq.base.constants;

public interface AppConstants {
    String APP_AUTORELOADABLE_FILE_NAME = "app-autoreloadable.properties";
}

这个类没什么解释的,一个常量类。


4、ReturnConstants.java
package com.szq.base.constants;

import java.util.HashMap;
import java.util.Map;

public class ReturnConstants {
    public static final Map<String, Object> SUCCESS;
    public static final Map<String, Object> FAIL;
    public static final Map<String, Object> SYSTEM_ERROR;
    static {
        SUCCESS = new HashMap<>(2);
        SUCCESS.put("code", 0);
        SUCCESS.put("msg", "success");
        FAIL = new HashMap<>(2);
        FAIL.put("code", 1);
        FAIL.put("msg", "fail");
        SYSTEM_ERROR = new HashMap<>(2);
        SYSTEM_ERROR.put("code", 2);
        SYSTEM_ERROR.put("msg", "System error");
    }
}

这个类没什么解释的,也是一个常量类。


5、ApiController.java
package com.szq.base.controller.api;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.configuration2.ImmutableConfiguration;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.szq.base.constants.AppConstants;
import com.szq.base.constants.ReturnConstants;
import com.szq.base.service.UserService;
import com.szq.base.springcomponent.GlobalHttpLinkPool;
import com.szq.base.springcomponent.GlobalPropertiesConfig;

/**
 * 常规spring-mvc中的controller层,这个controller有个比较特殊的地方就是实现了ApplicationContextAware借口,下文有介绍
 * @author Administrator
 * @comment 
 */
@RestController
@RequestMapping(value = "/api", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class ApiController implements ApplicationContextAware {
    private ApplicationContext applicationContext;
    /**
     * 全局单例工具类,读取配置文件,基于Apache Commons Configuration库
     * 该读取工具会自动重载配置文件而无需重启服务器
     * 可以将一些开关类型的变量配置在一个配置文件中,用该工具类来读取
     * 从而实现在不停机的前提下改变服务的功能
     * 该项目所用配置文件名为:app-autoreloadable.properties
     */
    @Autowired
    private GlobalPropertiesConfig globalPropertiesConfig;
    /**
     * 全局单例工具类,用来发送http请求,基于Apache HttpClient库
     */
    @Autowired
    private GlobalHttpLinkPool globalHttpLinkPool;
    /**
     * 常规spring-mvc中的service
     */
    @Autowired
    private UserService userService;

    /*
     * 通过实现ApplicationContextAware借口,获取applicationContext
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 很多基于spring-boot的开发者不知道怎么手动优雅的关闭spring-boot项目
     * 当然了,在实际生产环境中是不需要考虑优雅关闭spring-boot项目的,因为如果是通过'java -jar'的方式运行的
     * 直接'ctrl + c'就行,如果是传统war包方式部署的spring-boot项目,则servlet容器有自己的关闭方式
     * 但是,如果是在开发的时候呢?如何优雅的关闭spring-boot项目呢?在eclipse中,只有一个terminate按钮,是没有stop按钮的!
     * 其实方法很简单,就是下面这个接口中所描述的方法。
     * @return
     * @throws Exception 
     */
    @RequestMapping(value = "/shutdown", method = RequestMethod.GET)
    private Map<String, Object> appShutdown() throws Exception {
        //一定要另起一个线程,不然该方法可能无法正常执行完毕
        new Thread(new Runnable() {

            @Override
            public void run() {
                //spring-boot项目退出,固定这么写就行了
                System.exit(SpringApplication.exit(applicationContext));
            }
        }).start();
        return ReturnConstants.SUCCESS;
    }

    /**
     * 常规接口,测试数据库操作是否正确
     * @return
     * @throws Exception 
     */
    @RequestMapping(value = "/user/save", method = RequestMethod.GET)
    private Map<String, Object> saveUser() throws Exception {
        String userName = "userName1";
        String userPassword = "userPassword1";
        userService.saveUser(userName, userPassword);
        return ReturnConstants.SUCCESS;
    }

    /**
     * 常规接口,测试全局配置文件读取工具是否正常工作
     * @return
     * @throws Exception 
     */
    @RequestMapping(value = "/property/test", method = RequestMethod.GET)
    private Map<String, Object> testPropertyFunction() throws Exception {
        ImmutableConfiguration configuration = globalPropertiesConfig.getConfiguration(AppConstants.APP_AUTORELOADABLE_FILE_NAME);
        Map<String, Object> rm = new HashMap<>(ReturnConstants.SUCCESS);
        {
            rm.put("data", configuration.getString("msg"));
        }
        return rm;
    }

    /**
     * 常规接口,测试全局http请求工具是否正常工作
     * @return
     * @throws Exception 
     */
    @RequestMapping(value = "/httplinkpool/test", method = RequestMethod.GET)
    private Map<String, Object> testHttpLinkPoolFunction() throws Exception {
        CloseableHttpClient closeableHttpClient = globalHttpLinkPool.getCloseableHttpClient();
        HttpPost post = new HttpPost("https://www.baidu.com");
        CloseableHttpResponse response = closeableHttpClient.execute(post);
        String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
        Map<String, Object> rm = new HashMap<>(ReturnConstants.SUCCESS);
        {
            rm.put("data", responseString);
        }
        return rm;
    }
}

这个类也没什么好解释的,都在代码中的注释里。其中那个优雅关闭spring-boot项目的方法算是一个干货吧。


6、PageController.java
package com.szq.base.controller.page;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 常规spring-mvc中的controller层,用来返回页面
 * @author Administrator
 * @comment 
 */
@Controller
public class PageController {
    /**
     * 工具方法,从请求url中提取页面名称,
     * @param servletPath
     * @return 
     */
    private String getHTMLName(String servletPath) {
        String pageName = servletPath.substring(1);
        if (pageName.endsWith(".html")) {
            pageName = pageName.substring(0, pageName.length() - 5);
        }
        return pageName;
    }

    /**
     * 常规接口,测试freemarker是否正常工作
     * @param request
     * @return
     * @throws Exception 
     */
    @RequestMapping("/freemarkerIndex.html")
    private String getIndexPage(HttpServletRequest request) throws Exception {
        return getHTMLName(request.getServletPath());
    }
}

没什么要解释的,都在代码注释里。


7、UserDAO .java
package com.szq.base.dao;

import com.szq.base.dio.UserDIO;

/**
 * 常规spring-mvc的dao层,这个接口会被mybatis自动扫描
 * 对应于AppConfig.java配置类中的那个@MapperScan("com.szq.base.dao")配置
 * @author Administrator
 * @comment 
 */
public interface UserDAO {
    int saveUser(UserDIO user);
}

这个也没什么解释的,看代码注释。


8、UserDIO.java
package com.szq.base.dio;

public class UserDIO {
    private int id;
    private String userName;
    private String userPassword;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPassword() {
        return userPassword;
    }

    public void setUserPassword(String userPassword) {
        this.userPassword = userPassword;
    }

    @Override
    public String toString() {
        return "UserDIO [id=" + id + ", userName=" + userName + ", userPassword=" + userPassword + "]";
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + id;
        result = prime * result + ((userName == null) ? 0 : userName.hashCode());
        result = prime * result + ((userPassword == null) ? 0 : userPassword.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        UserDIO other = (UserDIO) obj;
        if (id != other.id)
            return false;
        if (userName == null) {
            if (other.userName != null)
                return false;
        } else if (!userName.equals(other.userName))
            return false;
        if (userPassword == null) {
            if (other.userPassword != null)
                return false;
        } else if (!userPassword.equals(other.userPassword))
            return false;
        return true;
    }

}

没什么解释的,就是一个bean。


9、UserService.java
package com.szq.base.service;

public interface UserService {
    int saveUser(String userName, String userPassword) throws Exception;
}

没什么解释的。


10、UserServiceImpl.java
package com.szq.base.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.szq.base.dao.UserDAO;
import com.szq.base.dio.UserDIO;
import com.szq.base.service.UserService;

/**
 * 常规spring-mvc的service层,没什么特殊的地方
 * @author Administrator
 * @comment 
 */
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDAO userDAO;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public int saveUser(String userName, String userPassword) throws Exception {
        UserDIO user = new UserDIO();
        {
            user.setUserName(userName);
            user.setUserPassword(userPassword);
        }
        int i = 0;
        for (int j = 0; j < 20; j++) {
            //这个if是为了测试上面的@Transactional注解是否起到了作用
            if (j == 5) {
                throw new IllegalStateException("-- transaction exception and global exception handler test");
            }
            userDAO.saveUser(user);
        }
        return i;
    }

}

没什么解释。


11、GlobalExceptionHandler.java
package com.szq.base.springcomponent;

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import com.alibaba.fastjson.JSON;
import com.szq.base.constants.ReturnConstants;

/**
 * 这个类会被spring自动扫描
 * 对应于AppConfig配置类的@SpringBootApplication(scanBasePackages = { "com.szq.base.controller", "com.szq.base.service.impl", "com.szq.base.springcomponent" })配置
 * 全局统一异常处理,请仔细看ApiController类,里面的每个接口都直接把异常给抛出了
 * 如:private Map appShutdown() throws Exception
 * 那个抛出的异常就会跑到这里进行统一的处理
 * @author Administrator
 * @comment 
 */
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
    private Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());

    @ExceptionHandler(Exception.class)
    protected final ResponseEntity<String> handleNonSpringStandardException(Exception e) {
        logger.warn("", e);
        Map<String, Object> rm = new HashMap<>(ReturnConstants.SYSTEM_ERROR);
        {
            rm.put("err_msg", e.getMessage());
        }
        return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON_UTF8).body(JSON.toJSONString(rm));
    }
}

这个类算是一个干货,与其在每个接口中处理异常,不如写这么一个类全局统一处理异常,能少写很多代码。原文链接如下:
https://docs.spring.io/spring/docs/4.3.15.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#mvc-ann-controller-advice。文档写的可能不是很清楚,大致意思是@RestControllerAdvice注解的类里面可以使用@ExceptionHandler注解,@ExceptionHandler又可以用来处理controller的异常,综合一下就可以整出来这么一个全局统一异常处理的工具来。


12、GlobalHttpLinkPool.java
package com.szq.base.springcomponent;

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.net.ssl.SSLContext;

import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.springframework.stereotype.Component;

/**
 * http请求工具,作为spring的一个单例bean来使用,会被spring自动扫描
 * 对应于AppConfig配置类的@SpringBootApplication(scanBasePackages = { "com.szq.base.controller", "com.szq.base.service.impl", "com.szq.base.springcomponent" })配置
 * @author Administrator
 * @comment 
 */
@Component
public class GlobalHttpLinkPool {
    private PoolingHttpClientConnectionManager connectionManager;

    @PostConstruct
    private void initMethod() {
        TrustStrategy trustStrategy = new TrustStrategy() {

            @Override
            public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                //返回true就会信任所有凭证,某些应用只是需要https的加密传输功能而不需要进行实际的认证,这时候就需要return true
                return true;
            }
        };
        SSLContext sslContext = null;
        try {
            sslContext = SSLContextBuilder.create().loadTrustMaterial(trustStrategy).build();
        } catch (Exception e) {
            //出异常,connectionManager置null
            connectionManager = null;
        }
        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext);
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.INSTANCE).register("https", sslConnectionSocketFactory).build();
        connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        connectionManager.setMaxTotal(500); //设置连接池总大小
        connectionManager.setDefaultMaxPerRoute(50); //设置每个URL最多可同时有多少个连接
        connectionManager.setValidateAfterInactivity(1000); //设置检测失效连接的时间间隔,单位:毫秒
    }

    @PreDestroy
    private void destroyMethod() {
        connectionManager.shutdown();
    }

    /**
     * 获取连接客户端
     * @return 
     */
    public CloseableHttpClient getCloseableHttpClient() {
        return HttpClients.custom().setConnectionManager(connectionManager).build();
    }
}

没什么解释的,一个工具类。


13、GlobalPropertiesConfig.java
package com.szq.base.springcomponent;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.annotation.PreDestroy;

import org.apache.commons.configuration2.ConfigurationUtils;
import org.apache.commons.configuration2.ImmutableConfiguration;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.ReloadingFileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;
import org.apache.commons.configuration2.builder.fluent.PropertiesBuilderParameters;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.reloading.PeriodicReloadingTrigger;
import org.springframework.stereotype.Component;

/**
 * 配置文件读取工具,会自动重载配置文件,作为spring的一个单例bean来使用,会被spring自动扫描
 * 对应于AppConfig配置类的@SpringBootApplication(scanBasePackages = { "com.szq.base.controller", "com.szq.base.service.impl", "com.szq.base.springcomponent" })配置
 * @author Administrator
 * @comment 
 */
@Component
public class GlobalPropertiesConfig {
    private Map<String, BuilderAndTrigger> btMap = new HashMap<>();

    @PreDestroy
    private void destroyMethod() {
        Set<Entry<String, BuilderAndTrigger>> entries = btMap.entrySet();
        for (Entry<String, BuilderAndTrigger> entry : entries) {
            entry.getValue().getTrigger().shutdown();
        }
    }

    /**
     * 获取配置
     * @param fileName 配置文件名,配置文件放在类目录下
     * @return 
     * @throws ConfigurationException 
     */
    public ImmutableConfiguration getConfiguration(final String fileName) throws ConfigurationException {
        BuilderAndTrigger bt = btMap.get(fileName);
        PropertiesConfiguration configuration = null;
        if (bt == null) { //builder不存在,生成新builder
            ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration> builder = createConfiguration(fileName);
            configuration = builder.getConfiguration(); //检测builder是否成功生成,未成功生成则这里会抛出异常
            PeriodicReloadingTrigger trigger = new PeriodicReloadingTrigger(builder.getReloadingController(), null, 5, TimeUnit.SECONDS); //声明配置重载触发器,5秒钟检测一次重载
            trigger.start(); //启动触发器
            btMap.put(fileName, new BuilderAndTrigger(builder, trigger)); //将文件名和对应的builder和trigger保存到btMap
        } else {
            configuration = bt.getBuilder().getConfiguration(); //若builder存在,则直接取该builder
        }
        return ConfigurationUtils.unmodifiableConfiguration(configuration); //返回不可修改的configuration
    }

    /**
     * 移除不再使用的配置
     * @param fileName 
     */
    public void removeConfiguration(final String fileName) {
        BuilderAndTrigger bt = btMap.get(fileName);
        if (bt != null) { //有builder和trigger存在
            bt.getTrigger().shutdown(); //停止trigger
            btMap.remove(fileName); //移除builder和trigger
        }
    }

    /**
     * 根据配置文件名生成新配置
     * @param fileName
     * @return 
     */
    private ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration> createConfiguration(final String fileName) {
        PropertiesBuilderParameters parameters = new Parameters().properties().setFile(new File(fileName));
        return new ReloadingFileBasedConfigurationBuilder<>(PropertiesConfiguration.class).configure(parameters);
    }

    /**
     * @author Administrator 内部类,表示builder和其对应的trigger
     * @comment 
     */
    private static class BuilderAndTrigger {
        private ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration> builder;
        private PeriodicReloadingTrigger trigger;

        public BuilderAndTrigger(ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration> builder, PeriodicReloadingTrigger trigger) {
            this.builder = builder;
            this.trigger = trigger;
        }

        public ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration> getBuilder() {
            return builder;
        }

        public PeriodicReloadingTrigger getTrigger() {
            return trigger;
        }

        @Override
        public String toString() {
            return "BuilderAndTrigger [builder=" + builder + ", trigger=" + trigger + "]";
        }

    }
}

一个工具类。


14、UserDAO.xml


<mapper namespace="com.szq.base.dao.UserDAO">
	<insert id="saveUser">
		INSERT INTO t_user ( u_name, u_password )
		VALUES
		(
		#{userName},
		#{userPassword}
		)
	insert>
mapper>

mybatis的sql映射文件。这个文件在mybatis-mapper目录下,这个目录是在app-autoreloadable.properties配置文件中配置的,下文有解释。


15、freemarkerIndex.js
alert('-- freemarkerIndex page js');

测试spring-boot静态资源是否正常加载,这个文件在static目录下,这个目录spring-boot默认静态资源目录。


16、index.html
<html>
<head>
<title>indextitle>
head>
<body>
	<h2>this is index pageh2>
	<script type="text/javascript" src="index.js">script>
body>
html>

同15。


17、index.js
alert('-- this is index page');

同15。


18、freemarkerIndex.html
<html>
<head>
<title>indextitle>
head>
<body>
	<h2>this is freemarker index pageh2>
	
	<p>${300000}p>
	
	<p>${300000000?number_to_datetime}p>
	<script type="text/javascript" src="freemarkerIndex.js">script>
body>
html>

freemarker测试页面,测试freemarker是否正常工作,看注释,有一些干货小技巧。这个文件在templates目录下,这个目录是spring-boot的freemarker starter默认目录。


19、app-autoreloadable.properties
#该配置文件配置会自动重载,无需重启服务器
msg=this is a auto-reload config test msg.1

GlobalPropertiesConfig.java所用的那个配置文件,没什么解释的。


20、application.properties
#这一组配置表示是否开启spring-boot内置的debug和trace级别的日志
debug=false
trace=false

#这一组配置用来配置spring-boot内置servlet容器的一些配置
#配置内置server监听的网络地址,这里配置为0.0.0.0表示监听任意网络地址
server.address=0.0.0.0
server.port=8080
server.tomcat.accesslog.enabled=true

#这一组配置表示spring-boot-starter-freemarker的相关属性
#这个配置表示freemarker模板文件的后缀
spring.freemarker.suffix=.html
#这个配置表示freemarker数字显示格式
spring.freemarker.settings.number_format=#
#这个配置表示freemarker日期时间显示格式
spring.freemarker.settings.datetime_format=yyyy-MM-dd HH:mm:ss

#这一组配置表示spring-boot内置文件上传功能的相关属性
#奇怪的是,spring-boot不支持使用commons fileupload库,推荐使用内置的文件上传功能,而spring-mvc则拥抱commons fileupload库,不知道为什么,可能是这两个spring项目的开发人员理念不一致吧
#原文链接分别如下:
#spring-boot:https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/htmlsingle/#howto-multipart-file-upload-configuration
#spring-mvc:https://docs.spring.io/spring/docs/4.3.15.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#mvc-multipart-resolver-commons
#这个配置表示上传的每个文件的最大大小
spring.servlet.multipart.max-file-size=1MB
#这个配置表示每个请求的最大大小
spring.servlet.multipart.max-request-size=10MB

#这组配置表示阿里巴巴druid数据库连接池的配置,粘贴复制改改账户名密码和URL,拿来用就行了,每个项的意思参考我这篇文章
#https://blog.csdn.net/lianjunzongsiling/article/details/80112971
spring.datasource.druid.username=root
spring.datasource.druid.password=123456
spring.datasource.druid.url=jdbc:mysql://localhost:3306/learn?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=TRUE&serverTimezone=UTC
spring.datasource.druid.initial-size=1
spring.datasource.druid.max-active=200
spring.datasource.druid.min-idle=1
spring.datasource.druid.max-wait=60000
spring.datasource.druid.validation-query=select 1;
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.time-between-eviction-runs-millis=25200000
spring.datasource.druid.min-evictable-idle-time-millis=3600000

#这组配置表示mybatis相关配置,mybatis有一个spring-boot的starter,这个配置就是给那个starter用的
#这个配置表示mybatis的sql映射文件的位置,支持ant形式
mybatis.mapper-locations=mybatis-mapper/*.xml
#这个配置表示mybatis的type alias的位置,具体参考我的这篇文章
#https://blog.csdn.net/lianjunzongsiling/article/details/74783674
mybatis.type-aliases-package=com.szq.base.dso

application.properties是spring-boot的默认配置文件,每个配置的意思请看上面配置中的注释。干货:server.address=0.0.0.0,表示监听所有网络地址。有些人将server.address配置成localhost或者127.0.0.1,这样的话springboot内置服务器就只会监听配置的那个固定的地址,导致别人无法访问。


21、log4j2-spring.xml

<Configuration status="WARN" monitorInterval="5">
	<Properties>
		<Property name="logDir" value="logs/base/" />
		<Property name="logFileName" value="app" />
		<Property name="genericPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
		<Property name="genericFilePattern" value="%d{yyyy-MM-dd}-%i" />
	Properties>
	<Appenders>
		<Console name="consoleAppender" target="SYSTEM_OUT">
			<PatternLayout pattern="${genericPattern}" />
		Console>
		<RollingRandomAccessFile name="rollingRandomAccessFileAppender" fileName="${logDir}/${logFileName}.log" filePattern="${logDir}/${logFileName}-${genericFilePattern}.log" append="true">
			<PatternLayout pattern="${genericPattern}" />
			<Policies>
				<TimeBasedTriggeringPolicy interval="1" />
				<SizeBasedTriggeringPolicy size="100 MB" />
			Policies>
			<DefaultRolloverStrategy max="1000000" compressionLevel="9" />
		RollingRandomAccessFile>
	Appenders>
	<Loggers>
		<Root level="INFO">
			<AppenderRef ref="consoleAppender" />
		Root>
	Loggers>
Configuration>

log4j2的配置文件,log4j2的默认配置文件名原本是log4j2.xml,但是spring-boot推荐使用log4j2-spring.xml这个名字,原文链接如下:https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/htmlsingle/#boot-features-custom-log-configuration。log4j2的教程可以参考我这篇文章:https://blog.csdn.net/lianjunzongsiling/article/details/78848844。


22、pom.xml
<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.0modelVersion>

	<groupId>com.szqgroupId>
	<artifactId>baseartifactId>
	<version>0.0.1-SNAPSHOTversion>
	<packaging>warpackaging>

	<name>basename>
	<url>http://maven.apache.orgurl>

	<properties>
		
		<maven-jar-plugin.version>3.1.1maven-jar-plugin.version>
	properties>

	<parent>
		<groupId>org.springframework.bootgroupId>
		<artifactId>spring-boot-starter-parentartifactId>
		<version>2.1.6.RELEASEversion>
	parent>

	<dependencies>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starterartifactId>
			<exclusions>
				
				<exclusion>
					<groupId>org.springframework.bootgroupId>
					<artifactId>spring-boot-starter-loggingartifactId>
				exclusion>
			exclusions>
		dependency>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-webartifactId>
		dependency>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-tomcatartifactId>
			
			<scope>providedscope>
		dependency>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-testartifactId>
			<scope>testscope>
		dependency>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-securityartifactId>
		dependency>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-log4j2artifactId>
		dependency>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-devtoolsartifactId>
			<optional>trueoptional>
		dependency>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-actuatorartifactId>
		dependency>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-freemarkerartifactId>
		dependency>

		
		<dependency>
			<groupId>com.alibabagroupId>
			<artifactId>druid-spring-boot-starterartifactId>
			<version>1.1.18version>
		dependency>

		
		<dependency>
			<groupId>org.mybatis.spring.bootgroupId>
			<artifactId>mybatis-spring-boot-starterartifactId>
			<version>2.1.0version>
		dependency>

		
		<dependency>
			<groupId>mysqlgroupId>
			<artifactId>mysql-connector-javaartifactId>
		dependency>

		
		<dependency>
			<groupId>com.alibabagroupId>
			<artifactId>fastjsonartifactId>
			<version>1.2.58version>
		dependency>

		
		<dependency>
			<groupId>org.apache.httpcomponentsgroupId>
			<artifactId>httpclientartifactId>
		dependency>
		
		<dependency>
			<groupId>org.apache.commonsgroupId>
			<artifactId>commons-configuration2artifactId>
			<version>2.5version>
		dependency>
		
		<dependency>
			<groupId>commons-beanutilsgroupId>
			<artifactId>commons-beanutilsartifactId>
			<version>1.9.3version>
		dependency>
		
		<dependency>
			<groupId>commons-iogroupId>
			<artifactId>commons-ioartifactId>
			<version>2.6version>
		dependency>

	dependencies>
	<build>
		<finalName>basefinalName>
		<plugins>
			<plugin>
				
				<groupId>org.springframework.bootgroupId>
				<artifactId>spring-boot-maven-pluginartifactId>
			plugin>
		plugins>
	build>
project>

pom.xml文件,里面有必要的注释。通过修改packaging属性为 jar 或者 war ,可以分别打出来jar包或者war包。其实,按照这个pom.xml的配置,加上前文中的重头戏1,打出来的 war 包既可以通过 ‘java -jar *.war’ 包的形式来运行,也可以像传统的war包部署那样来运行,也就是说 jar 包这种形式是不需要的,因为按照本文章所介绍, jar 包有的功能,war 包都有,jar 包没有的功能,war 包也有。


如果你浏览了本文章,按照章节一中图片所示的结构创建了项目,然后把章节二中的代码和配置文件都对应地粘贴复制保存好,那么你现在就有了一个功能齐备的可以直接用来开发的项目了,这个项目整合了常用框架,有现成配置文件读取工具和http请求工具,有现成的spring-security整合,可以直接通过override相关接口的相关方法来配置spring-mvc和spring-security的相关功能,数据库操作功能也是现成的,事务支持也是现成的。如果该文章让你感觉很困惑,那么你需要从基础的部分学习spring,然后再来看本文章。


原创不易,转帖请注明出处----ShiZhongqi

你可能感兴趣的:(springboot教程:从零开始搭建一个springboot项目)