SpringBoot应用——项目搭建指北


一、工程结构规范

在这里插入图片描述1、数据持久层:底层 MySQL 、 Oracle 、 Hbase 等进行数据交互
2、各个层级有自己的数据封装实体类:DO、DTO、VO、query等等
3、dao层引入数据库信息,除了实体类,还包含mapper
4、从jar引入角度看
manager引入dao,service引入manager
web(controller层)引入service
对外接口api层引入service,但是建议不引入controller层
5、从调用关系看,就是如图的调用层级关系
6、common通用包
应该由dao层引入,这样各个模块都可以使用,里面包含各种工具类、日志、lombok等通用的东西

二、创建springBoot项目

1、Idea中,我们可以直接使用Maven创建

如果是gitLab或者其他托管网站新建的项目,是空的,我们clone后,需要自己创建一个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>

    <!--组织GAV坐标-->
    <groupId>com.rj</groupId>

    <!--项目名称-->
    <artifactId>bootest2</artifactId>

    <!--项目版本-->
    <version>1.0.0-SNAPSHOT</version>

    <!--打包格式,pom表示有子模块,该pom为父模块,一般maven默认是jar包-->
    <packaging>pom</packaging>
    
</project>

然后,点击IDEA右边边栏的maven图标,点击+号,将这个pom文件导入即可。

2、在大的外层模块中继续创建各种子模块
在这里插入图片描述在这里插入图片描述

后记:在IDEA工程中,右下角是否为UTF-8,不是的话需要在settings->encoding搜索修改,然后在File->other settings->encoding搜索修改,这样保证其他项目新建的时候也是utf-8
在这里插入图片描述
一般这样就可以了,另外就是注意,如果还是报错,而且是xml文件解析报错,注意如下修改:<?xml version="1.0" encoding="UTF-8" ?>改为<?xml version="1.0" encoding="UTF8" ?>

3、根据工程结构规范,进行包引入
<!--这是service层,由于没有manager,则需要直接引入common和dao-->
<dependencies>
    <dependency>
        <groupId>com.rj</groupId>
        <artifactId>common</artifactId>
        <version>${project.version}</version>
    </dependency>
    <dependency>
        <groupId>com.rj</groupId>
        <artifactId>dao</artifactId>
        <version>${project.version}</version>
    </dependency>
</dependencies>

<!--这是api层-->
<!--可以将项目中其他模块jar包引入,版本号与当前模块的parent一致-->
<dependency>
    <groupId>com.rj</groupId>
    <artifactId>service</artifactId>
    <version>${project.version}</version>
</dependency>
4、引入springBoot依赖,进行版本控制
  • 1、版本的控制必须在父模块的pom文件中控制,即SpringBootTest模块
<!--版本号控制-->
<properties>
    <spring-boot-parent.version>2.0.1.RELEASE</spring-boot-parent.version>
	<spring-boot-mybatis.version>2.0.0</spring-boot-mybatis.version>
	<mybatis-plus-boot-starter>3.4.2</mybatis-plus-boot-starter>
	<mybatis-plus-generator>3.4.0</mybatis-plus-generator>
	<hutool-all.version>5.6.6</hutool-all.version>
	<spring-boot-druid.version>1.1.10</spring-boot-druid.version>
	<mysql.version>5.1.29</mysql.version>
	<lombok.version>1.16.10</lombok.version>
	<junit4.version>4.12</junit4.version>
	<java.version>1.8</java.version>
	<maven-source-plugin.version>2.1.2</maven-source-plugin.version>
	<maven-compiler-plugin.version>2.3.2</maven-compiler-plugin.version>
	<maven-deploy-plugin.version>2.7</maven-deploy-plugin.version>
	<maven-surefire-plugin.version>2.6</maven-surefire-plugin.version>
	<jackson-version>2.9.5</jackson-version>
</properties>

<!--这个是版本控制管理,不会引入jar包,而只是声明名称和版本,具体的引用在子模块-->
<dependencyManagement>
    <dependencies>
        <!--引入需要的springBoot组件,需要指定版本号${version}-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring-boot-parent.version}</version>
        </dependency>
        <!--log日志,内置的是logback-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
            <version>${spring-boot-parent.version}</version>
        </dependency>
	
		<!--Mybatis+baomidou+framemarker-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${spring-boot-mybatis.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus-boot-starter}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
            <version>${spring-boot-parent.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>${mybatis-plus-generator}</version>
        </dependency>
		<!--Mybatis+baomidou+framemarker-->
		
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${spring-boot-parent.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${spring-boot-druid.version}</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool-all.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit4.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
    		<groupId>com.fasterxml.jackson.core</groupId>
    		<artifactId>jackson-core</artifactId>
    		<version>${jackson-version}</version>
		</dependency>
		<dependency>
    		<groupId>com.fasterxml.jackson.core</groupId>
    		<artifactId>jackson-databind</artifactId>
    		<version>${jackson-version}</version>
		</dependency>
		<dependency>
    		<groupId>com.fasterxml.jackson.core</groupId>
    		<artifactId>jackson-annotations</artifactId>
    		<version>${jackson-version}</version>
		</dependency>
    </dependencies>
</dependencyManagement>

使用dependencyManagement和直接使用dependencies的最大区别,在于,前者只是声明要使用什么依赖以及对应版本号,但不会真正导入依赖,即使飘红也没关系;后者会真正导入依赖。
所以,使用前者,我们就可以在子模块中根据子模块的实际情况进行实际的jar包引入;而后者,则会在各个子模块中都会全部引入,这是很不合规范的

  • 2、在子模块中根据实际情况真实引入jar包依赖
<!--这是api层,我们只需要引入spring-boot-starter-web的jar包-->
<dependencies>
    <!--子模块根据需要实际引入jar包-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-test</artifactId>
	</dependency>
</dependencies>
在这里插入图片描述同理,可以在dao层引入数据库、mybatis、mybatis-plus等对应的jar包
在common可以引入日志依赖,根据传递依赖原则,在顶层的api层都有依赖进来。
5、配置文件

在web或api层的resource目录中配置yml文件,注意命名规范:application.yml

#dataSource
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/mydb3?useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
    druid:
      initial-size: 3
      min-idle: 3
      max-active: 10
      max-wait: 60000
      filter:
        stat:
          log-slow-sql: true
          slow-sql-millis: 1

  #环境:env就是pom中的环境选择标签
  profiles:
    active: ${env}

#tomcat端口号
server:
  port: 8089
  servlet:
    context-path: /rj
  tomcat:
    uri-encoding: utf-8

#mybatis
mybatis-plus:
  type-aliases-package: com.rj.domain
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:com/rj/*/mapper/*.xml

#log日志配置
logging:
  config: classpath:logback.xml

同级位置配置对应的开发或者生成环境配置文件,注意明明规范application-dev.ymlapplication-pro.yml
并在父pom文件中配置环境选择

<!-- 开发环境,默认激活 -->
<profiles>
    <profile>
    	<!--id要和env中的一致-->
        <id>dev</id>
        <properties>
        	<!--这个只是针对的文件后缀,即application-后面的-->
            <env>dev</env>
        </properties>
        <!--默认环境 -->
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
    </profile>
    <!-- 生产环境 -->
    <profile>
        <id>pro</id>
        <properties>
            <env>pro</env>
        </properties>
    </profile>
</profiles>

同级位置引入我们的日志配置logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <!-- 日志路径 -->
    <property name="log_dir" value="/export/Logs/iceStatistic"/>

    <!-- 控制台 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern><!--%black(控制台-)--> %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{10}) - %cyan(%msg%n)
            </pattern>
        </encoder>
    </appender>

    <!-- debug日志 -->
    <appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log_dir}/debug.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log_dir}/debug-%d{yyyy-MM-dd-HH}.log</fileNamePattern>
            <maxHistory>48</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %thread %X{invokeNo} %logger{40} %msg%n</pattern>
        </encoder>
    </appender>

    <!-- info日志 -->
    <appender name="info" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log_dir}/info.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log_dir}/info-%d{yyyy-MM-dd-HH}.log</fileNamePattern>
            <maxHistory>72</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %thread %X{invokeNo} %logger{40} %msg%n</pattern>
        </encoder>
    </appender>

    <!-- error日志 -->
    <appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log_dir}/error.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log_dir}/error-%d{yyyy-MM-dd-HH}.log</fileNamePattern>
            <maxHistory>72</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %thread %X{invokeNo} %logger{40} %msg%n</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter"><!-- 只打印错误日志 -->
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <logger name="DEBUG_LOG" level="debug" additivity="false">
        <appender-ref ref="debug"/>
    </logger>

    <logger name="INFO_LOG" level="info" additivity="false">
        <appender-ref ref="info"/>
    </logger>

    <logger name="ERROR_LOG" level="error" additivity="false">
        <appender-ref ref="error"/>
    </logger>

    <logger name="com.jd.flight" level="debug" additivity="false">
        <appender-ref ref="stdout"/>
        <appender-ref ref="debug"/>
    </logger>
    <root level="info">
        <appender-ref ref="stdout"/>
        <appender-ref ref="info"/>
        <appender-ref ref="error"/>
    </root>
</configuration>
6、springBoot启动类

在web或api层的java/com/rj/下创建启动类

@SpringBootApplication
@MapperScan(value = "com.rj.*.mapper")
//@ComponentScan(value = "com.*") //扫描其他包
public class AppStart {
    public static void main(String[] args) {
        SpringApplication.run(AppStart.class,args);
    }
}

springBootApplication是自动装配,MapperScan是对mapper文件的扫描

由于springBoot内置tomcat,直接启动这个类,就可以将项目起来

springBoot的测试类

@SpringBootTest(classes = AppStart.class)
@RunWith(SpringRunner.class)
@Slf4j
public class StudentTest {
	
	//这个是在yml文件中配置的,虽然yml是冒号分隔,但是这里还是需要使用“.”来调用
    @Value("${vipuser.name}")
    private String name;

    @Test
    public void testStudent() {
        log.error("name = {}", name);
    }
}
7、在父pom中需要配置打包build和插件
<!--build-->
<!-- Package as an executable jar -->
<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
            <!--可以访问的资源文件路径-->
            <includes>
                <include>application.yml</include>
                <include>application-${env}.yml</include>
                <include>*.txt</include>
                <include>**/*.xml</include>
                <include>**/messages*.properties</include>
                <include>**/*.csv</include>
                <include>**/*.zip</include>
                <include>**/*.ftl</include>
                <include>*.properties</include>
            </includes>
        </resource>
    </resources>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-deploy-plugin</artifactId>
            <version>${maven-deploy-plugin.version}</version>
            <configuration>
                <uniqueVersion>false</uniqueVersion>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>${maven-compiler-plugin.version}</version>
            <configuration>
                <failOnError>true</failOnError>
                <verbose>true</verbose>
                <fork>true</fork>
                <compilerArgument>-nowarn</compilerArgument>
                <source>${java.version}</source>
                <target>${java.version}</target>
                <!--必须指定编码为utf-8,否则对于有中文注释的,会出现文件解析错误-->
                <encoding>UTF-8</encoding>
            </configuration>
        </plugin>
        <!-- 打包时生成源码 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>${maven-source-plugin.version}</version>
            <executions>
                <execution>
                    <id>attach-sources</id>
                    <goals>
                        <goal>jar</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <!-- 跳过单元测试 -->
        <plugin>
        	<groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>${maven-surefire-plugin.version}</version>
            <configuration>
                <skip>true</skip>
            </configuration>
        </plugin>
    </plugins>
</build>

spring-boot-maven-plugin.versionjava.version版本也可以在父pom中控制

8、mybatis自动生成工具类1

(选用) 根据数据库表自动生成domain、mapper、mapper.xml、service和controller

//需要引入`mybatis-plus-generator`和`spring-boot-starter-freemarker`

package codegenerate;

import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.springframework.util.StringUtils;

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

/**
 * 根据数据库表自动生成domain/mapper/service/controller
 */
@RunWith(JUnit4.class)
public class CodeGenerator {

    /**
     * 运行测试生成
     */
    @Test
    public void doGenerator() {
        generator("user",  "student");
    }

    /**
     * 自动生成的方法
     * @param nodeName 业务模块的名称,比如用户user、商品good...,同名会追加类
     * @param tables 表名,可多个,建议单个生成
     */
    @SuppressWarnings("all")
    private static void generator(String nodeName, String... tables) {
        final String fNodeName = StringUtils.isEmpty(nodeName) ? "test" : nodeName;

        //代码生成器
        AutoGenerator mpg = new AutoGenerator();

        //局配置
        GlobalConfig gc = new GlobalConfig();
        
        //获取工程的绝对路径
        final String parentPath = "D:\\Java\\ProjectJ\\springBootTest";
        gc.setAuthor("rj");
        gc.setOpen(false);
        gc.setSwagger2(false);
        mpg.setGlobalConfig(gc);

        //数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/mydb3?useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true");
        dsc.setDriverName("com.mysql.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("root");
        mpg.setDataSource(dsc);

        //包配置: src\main\java\com\rj\domain
        PackageConfig pc = new PackageConfig();

        //生成文件夹
        Map<String, String> configPathInfo = new HashMap(8) {
            {
                put(ConstVal.XML_PATH, parentPath + "\\dao\\src\\main\\java\\com\\rj\\"+fNodeName+"\\mapper");
                put(ConstVal.MAPPER_PATH, parentPath + "\\dao\\src\\main\\java\\com\\rj\\"+fNodeName+"\\mapper");
                put(ConstVal.ENTITY_PATH, parentPath + "\\dao\\src\\main\\java\\com\\rj\\domain\\"+fNodeName+"\\entity");
                put(ConstVal.SERVICE_PATH, parentPath + "\\service\\src\\main\\java\\com\\rj\\"+fNodeName+"\\service");
                put(ConstVal.SERVICE_IMPL_PATH, parentPath + "\\service\\src\\main\\java\\com\\rj\\"+fNodeName+"\\service\\impl");
                put(ConstVal.CONTROLLER_PATH, parentPath + "\\api\\src\\main\\java\\com\\rj\\"+fNodeName+"\\controller");
            }
        };

        //生成对应的类
        pc.setPathInfo(configPathInfo);
        pc.setParent("com.rj");
        pc.setXml(fNodeName+".mapper");
        pc.setMapper(fNodeName+".mapper");
        pc.setEntity("domain."+fNodeName+".entity");
        pc.setService(fNodeName+".service");
        pc.setServiceImpl(fNodeName+".service.impl");
        pc.setController(fNodeName+".controller");
        mpg.setPackageInfo(pc);

        //策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        strategy.setEntityLombokModel(true);
        strategy.setInclude(tables);

        //表前缀,根据具体情况修改
        strategy.setTablePrefix("");
        mpg.setStrategy(strategy);
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }
}
9、mybatis自动生成工具类2

不使用baomidou的框架,避免过大的侵入性,我们可以使用org.mybatis自己的自动生成框架。
为了避免过多jar包冲突,我们直接引入原生jar,对应的配置如下

<!--版本控制-->
<!--mybatis + mybatis-generator + pageHelper + tk.mybatis-->
<spring-boot-mybatis.version>2.0.0</spring-boot-mybatis.version>
<mybatis-generator-core.version>1.3.5</mybatis-generator-core.version>
<pagehelper.version>4.1.6</pagehelper.version>
<tk-mybatis-mapper.version>4.1.5</tk-mybatis-mapper.version>
<!--mybatis + mybatis-generator + pageHelper + tk.mybatis-->

<!--引入-->
<!--mybatis + mybatis-generator + pageHelper + tk.mybatis-->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>${spring-boot-mybatis.version}</version>
</dependency>
<!--根据数据库自动生成mybatis文件-->
<dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>${mybatis-generator-core.version}</version>
    <scope>compile</scope>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>${pagehelper.version}</version>
</dependency>
<!--mapper继承类,可以有封装号的单表查询方法-->
<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper</artifactId>
    <version>${tk-mybatis-mapper.version}</version>
</dependency>
<!--mybatis + mybatis-generator + pageHelper + tk.mybatis-->

这里注意:mybatis-spring-boot-starter中已经引入了org.mybatismybatis-spring,这里无须重新引入。
对应的yml文件也需要修改

#mybatis
mybatis:
  type-aliases-package: com.rj.domain
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath*:com/rj/mapper/xml/*.xml

网上大部分都是xml+maven插件自动生成,这里我们可以使用java代码实现

@RunWith(JUnit4.class)
public class CodeGenerator {

    /**
     * 执行生成代码
     */
    @Test
    public void codeGenerator() throws InvalidConfigurationException, InterruptedException, SQLException, IOException {
        List<String> warnings = new ArrayList<>();
        Configuration config = getGeneratorConfig("user", "id");
        DefaultShellCallback callback = new DefaultShellCallback(true);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null);
    }

    /**
     * 获取自动生成的配置
     * @param tableName 表名
     * @param indexId 主键列名称
     */
    private static Configuration getGeneratorConfig(String tableName, String indexId) {

        //获取工程的绝对路径
        final String parentPath = "D:\\Projects\\gongyinglian\\test\\bootest2";

        Context context = new Context(ModelType.CONDITIONAL);
        context.setId("defaultContext");
        context.setTargetRuntime("MyBatis3Simple");

        /*添加属性*/
        context.addProperty("javaFileEncoding", "UTF-8");
        context.addProperty("beginningDelimiter","`");
        context.addProperty("endingDelimiter","`");
        context.addProperty("javaFormatter","org.mybatis.generator.api.dom.DefaultJavaFormatter");//格式化java代码
        context.addProperty("xmlFormatter","org.mybatis.generator.api.dom.DefaultXmlFormatter");//格式化xml代码

        /*
           自动识别数据库关键字,默认false,如果设置为true,根据SqlReservedWords中定义的关键字列表;一般保留默认值,遇到数据库关键字(Java关键字),
           使用columnOverride覆盖
         */
        context.addProperty("autoDelimitKeywords","true");

        //引入tk.mybatis.mapper.common.Mapper插件和属性配置
        PluginConfiguration pluginConfiguration = new PluginConfiguration();
        pluginConfiguration.setConfigurationType("tk.mybatis.mapper.generator.MapperPlugin");
        pluginConfiguration.addProperty("mappers", "tk.mybatis.mapper.common.Mapper");
        context.addPluginConfiguration(pluginConfiguration);

        /*注释生成器配置*/
        CommentGeneratorConfiguration commentGeneratorConfig = new CommentGeneratorConfiguration();
        commentGeneratorConfig.addProperty("suppressAllComments", "true");
        commentGeneratorConfig.addProperty("suppressDate","true");
        context.setCommentGeneratorConfiguration(commentGeneratorConfig);

        /*JDBC连接信息配置*/
        JDBCConnectionConfiguration jdbcConnectionConfig = new JDBCConnectionConfiguration();
        jdbcConnectionConfig.setDriverClass("com.mysql.jdbc.Driver");

        //注意代码配置中JDBC连接字符串中的参数分隔符不需要再像xml配置文件中那样使用转义符
        jdbcConnectionConfig.setConnectionURL("jdbc:mysql://localhost:3306/userDatabase?useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true");
        jdbcConnectionConfig.setUserId("root");
        jdbcConnectionConfig.setPassword("root");
        jdbcConnectionConfig.addProperty("nullCatalogMeansCurrent", "true");//MySQL无法识别table标签中schema类的配置,所以在URL上指明目标数据库,并追加nullCatalogMeansCurrent属性为true
        jdbcConnectionConfig.addProperty("remarksReporting", "true");//针对oracle数据库无法读取表和字段备注
        jdbcConnectionConfig.addProperty("useInformationSchema", "true");//针对mysql数据库无法读取表和字段备注
        context.setJdbcConnectionConfiguration(jdbcConnectionConfig);

        JavaTypeResolverConfiguration javaTypeResolverConfiguration = new JavaTypeResolverConfiguration();

        //是否使用bigDecimal, false可自动转化以下类型(Long, Integer, Short, etc.)
        javaTypeResolverConfiguration.addProperty("forceBigDecimals","false");
        context.setJavaTypeResolverConfiguration(javaTypeResolverConfiguration);

        /*domain生成器配置*/
        JavaModelGeneratorConfiguration javaModelGeneratorConfig = new JavaModelGeneratorConfiguration();
        javaModelGeneratorConfig.setTargetProject(parentPath + "\\dao\\src\\main\\java");//目标项目(源码主路径)
        javaModelGeneratorConfig.setTargetPackage("com.rj.domain");//目标包(Model类文件存放包)
        javaModelGeneratorConfig.addProperty("enableSubPackages","false");
        javaModelGeneratorConfig.addProperty("trimStrings","true");
        context.setJavaModelGeneratorConfiguration(javaModelGeneratorConfig);

        /*SqlMapper生成器配置(*Mapper.xml类文件),要javaClient生成器类型配合*/
        SqlMapGeneratorConfiguration sqlMapGeneratorConfig = new SqlMapGeneratorConfiguration();
        sqlMapGeneratorConfig.setTargetProject(parentPath + "\\dao\\src\\main\\java");//目标项目(源码主路径)
        sqlMapGeneratorConfig.setTargetPackage("com.rj.mapper.xml");//目标包(*Mapper.xml类文件存放包)
        sqlMapGeneratorConfig.addProperty("enableSubPackages","false");
        context.setSqlMapGeneratorConfiguration(sqlMapGeneratorConfig);

        /*JavaClient生成器配置(*Mapper.java类文件)*/
        JavaClientGeneratorConfiguration javaClientGeneratorConfig = new JavaClientGeneratorConfiguration();
        javaClientGeneratorConfig.setConfigurationType("XMLMAPPER");//JavaClient生成器类型(主要有ANNOTATEDMAPPER、MIXEDMAPPER、XMLMAPPER,要Context的TargetRuntime配合)
        javaClientGeneratorConfig.setTargetProject(parentPath + "\\dao\\src\\main\\java");//目标项目(源码主路径)
        javaClientGeneratorConfig.setTargetPackage("com.rj.mapper");//目标包(*Mapper.java类文件存放包)
        javaClientGeneratorConfig.addProperty("enableSubPackages","false");
        context.setJavaClientGeneratorConfiguration(javaClientGeneratorConfig);

        /*表生成配置*/
        TableConfiguration tableConfig = new TableConfiguration(context);
        tableConfig.setTableName(tableName);
        tableConfig.setCountByExampleStatementEnabled(true);
        tableConfig.setUpdateByExampleStatementEnabled(true);
        tableConfig.setDeleteByExampleStatementEnabled(true);
        tableConfig.setInsertStatementEnabled(true);
        tableConfig.setDeleteByPrimaryKeyStatementEnabled(true);
        GeneratedKey generatedKey = new GeneratedKey(indexId, "MySql", true, null);//设置主键列和生成方式
        tableConfig.setGeneratedKey(generatedKey);
        context.addTableConfiguration(tableConfig);
        Configuration config = new Configuration();
        config.addContext(context);
        return config;
    }
}

注意:
@SpringbootApplication的位置配置@MapperSacan的,我们不再使用spring的,而是使用tk.mybatisimport tk.mybatis.spring.annotation.MapperScan;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;

@SpringBootApplication
@MapperScan("com.jd.mapper")
public class AppStart {
    public static void main(String[] args) {
        SpringApplication.run(AppStart.class, args);
    }
}
10、关于Jackson的工具类
public class JackSonUtil {

    /**
     * 日志
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(JackSonUtil.class);

    /**
     * 将对象序列化
     */
    public static String objToJson(Object obj) {
        ObjectMapper mapper = new ObjectMapper();
        String json = null;
        try {
            json = mapper.writeValueAsString(obj);
        } catch (Exception e) {
            LOGGER.error("序列化失败", e);
        }
        return json;
    }

    /**
     * json串反序列化
     */
    public static <T> T jsonToObj(String json, TypeReference<T> typeReference) {
        ObjectMapper mapper = new ObjectMapper();
        T t = null;
        try {
            t = mapper.readValue(json, typeReference);
        } catch (Exception e) {
            LOGGER.error("", e);
        }
        return t;
    }
}
11、线程池+关于@Configuration@Bean
方式1
//@Configuration 创建自己的线程池,并交给spring管理
@Configuration
public class ThreadPoolConfig {

    /**
     * 核心线程数:使用yml配置文件注入
     */
    @Value("${thread.core.size}")
    private int corePoolSize;

    /**
     * 最大线程数
     */
    @Value("${thread.max.size}")
    private int maxPoolSize;

    /**
     * 最大等待时间(秒)
     */
    @Value("${thread.alive.second}")
    private int keepAlive;

    /**
     * 线程队列数
     */
    @Value("${thread.queue.size}")
    private int queueSize;

    @Bean("threadPoolExecutor")
    public ThreadPoolTaskExecutor getExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        //核心线程数:线程池创建时候初始化的线程数
        executor.setCorePoolSize(corePoolSize);

        //最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        executor.setMaxPoolSize(maxPoolSize);

        //允许线程的空闲时间(秒):当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
        executor.setKeepAliveSeconds(keepAlive);

        //缓冲队列数量:用来缓冲执行任务的队列
        executor.setQueueCapacity(queueSize);

        //线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,
        // 当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;
        // 如果执行程序已关闭,则会丢弃该任务
        executor.setThreadNamePrefix("myThread-");//设置线程号前缀
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }
}

//我们也可以使用yml配置文件注入参数
/**
 * 核心线程数
 */
@Value("${mythread.core.size}")
private int CORE_POOL_SIZE;

//调用线程池:这里使用的是@Autowired,所以会根据类名去找,而与我们指定的bean名称id无关
//如果我们使用@Resource,则必须将这个类的名字与我们指定的@Bean的名称id一致
@Autowired
private ThreadPoolTaskExecutor threadpool1324251;
@Resource
private ThreadPoolTaskExecutor threadPoolExcutor;

//这样就可以直接使用线程池了
方式2

此外,我们还可以使用@ConfigurationProperties

#myThreadPool-config2
my:
  threadpool2:
    config:
      coreSize: 5
      maxSize: 10
      queueSize: 20
      aliveSconds: 300
@Data
@Configuration

//必须都是小写,且成员变量与yml配置中的一致,且必须和@Configuration一起用
@ConfigurationProperties(prefix = "my.threadpool2.config") 
public class ThreadPoolConfigParam {

    /**
     * 核心线程
     */
    private int coreSize;

    /**
     * 最大线程
     */
    private int maxSize;

    /**
     * 队列
     */
    private int queueSize;

    /**
     * 存活秒数
     */
    private int aliveSconds;
}

直接引入参数配置线程池

@Configuration
public class ThreadPoolConfig {

    /**
     * 配置参数
     */
    @Resource
    private ThreadPoolConfigParam param;

    /**
     * 获取线程池
     */
    @Bean("threadPoolTask")
    public ThreadPoolTaskExecutor getExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(param.getCoreSize());
        taskExecutor.setMaxPoolSize(param.getMaxSize());
        taskExecutor.setQueueCapacity(param.getQueueSize());
        taskExecutor.setKeepAliveSeconds(param.getAliveSconds());
        taskExecutor.setThreadNamePrefix("threadPool2-");
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return taskExecutor;
    }
}
方式3

在配置内部注入参数bean,将配置文件引入

@Configuration
public class ThreadPoolConfig {

	/**
     * 注入参数bean并将配置文件内容引入
     */
    @Bean
    @ConfigurationProperties(prefix = "my.threadpool2.config")
    public ThreadPoolConfigParam getParam() {
        return new ThreadPoolConfigParam();
    }

    /**
     * 获取线程池
     */
    @Bean("threadPoolTask")
    public ThreadPoolTaskExecutor getExecutor() {
        ThreadPoolConfigParam param = getParam();
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(param.getCoreSize());
        taskExecutor.setMaxPoolSize(param.getMaxSize());
        taskExecutor.setQueueCapacity(param.getQueueSize());
        taskExecutor.setKeepAliveSeconds(param.getAliveSconds());
        taskExecutor.setThreadNamePrefix("threadPool2-");
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return taskExecutor;
    }
}
12、排包

这项操作非常重要,由于包冲突,导致项目启动失败,这里推荐使用插件Maven Helper进行包冲突排除,建议将低版本的jar包排除掉

项目启动会有错误,大多数问题是jar包冲突,不要慌张,可在在idea使用shift+alt+ctrl+n进行文件搜索,结合网上对问题的解决,确定是哪个jar包冲突导致,然后排包,注意版本号,maven-helper提示的需要排除的红色依赖,不一定是要排除的,原则上应该是排除低版本;另外,spring生态的,尤其是boot-start/core等,保证版本号一致

另外,由于springBoot的集成,我们可以搜索需要使用的jar包是否已经被引入,如果版本合适,我们就不需要再单独引入了,否则需要单独引入需要版本,并排除boot集成的版本

使用springBoot的jar包的时候,如果除了应用spring-boot-web-starter以外,还引入其他的组件jar包,建议使用原生,不必要使用boot-starter集成的,以避免太多的冲突

13、springBoot配置拦截器

SpringBoot的WebMvcConfigurer接口提供了很多针对过去springMVC控制层的非web.xml配置方法。

首先创建两个拦截器配置,一个是权限校验,一个是usrPin的处理。

/**
 * 权限拦截校验配置
 * 必须实现HandlerInterceptor的拦截器接口
 */
@Service
@Slf4j
public class AuthorityInceptorConfig implements HandlerInterceptor {

    /**
     * 前置校验
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("访问controller前开始进行权限校验....");
        return true;
    }
}

/**
 * 错误码校验:当访问返回401错误码的时候,直接跳转错误页
 */
@Service
@Slf4j
public class ErrorCodeInterceptor implements HandlerInterceptor {

    /**
     * 后置校验
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        if(response.getStatus()==401){
            
            //如果是重定向,必须写【redirect】,否则默认是具体页面,而不是重定向到另一个controller
            modelAndView.setViewName("redirect:/common/error401"); 
        }
    }
}

最后将两个权限配置注入到WebMvcConfigurer

/**
 * 登录配置:
 * 实现【WebMvcConfigurer】接口
 * 该接口提供了很多的可以重写的方法,比如拦截器和主页设置
 */
@Configuration
public class LoginConfig implements WebMvcConfigurer {

    /**
     * 将权限校验拦截器注入
     */
    @Resource
    private AuthorityInceptorConfig authorityInceptorConfig;

    /**
     * 错误码校验
     */
    @Resource
    private ErrorCodeInterceptor errorCodeInterceptor;

    /**
     * 重写拦截器方法即可配置
     * @param registry 注册拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        InterceptorRegistration registrationOfAuthor = registry.addInterceptor(authorityInceptorConfig);
        registrationOfAuthor.addPathPatterns("/**"); //对所有路径拦截

        /*
         * 当然,我们可以继续配置多个registration
         */
        InterceptorRegistration registrationOfError = registry.addInterceptor(errorCodeInterceptor);
        registrationOfError.addPathPatterns("/**");
    }
}

注意:需要在springBoot启动位置配置注解:@ServletComponentScan(basePackages = {"com.jd.config.interceptor"})

打印结果:
[http-nio-8089-exec-1] INFO c.j.c.i.AuthorityInceptorConfig - 访问controller前开始进行权限校验…

14、SpringBoot主页设置

如果你使用的是SpringBoot自带的模板引擎thymeleaf,那么必须保证pom版本一致。

"/**
 * 登录controller
 */
@Controller
@Slf4j
public class LoginController {

    /**
     * 主页
     */
    @RequestMapping("/index")
    private String goToLogin() {
        log.info("重定向到登录");
        return "redirect:/common/login";
    }

    /**
     * 登录主页
     */
    @RequestMapping("/common/login")
    private String login() {
        log.info("跳转到页面");
        return "forward:/index.html";
    }
}

如果你在static静态资源路径下配置了index.html,那么直接使用localhost:8080访问就可以直接访问,因为访问路径为"/"。而如果你是在他路径下配置了首页,那么将访问不到,我们希望能针对"/"进行重定向到"/index"的controller,其功能类似于过去SpringMvc的web.xml配置

<welcome-file-list>
	<welcome-file>index</welcome-file>
</welcome-file-list>

实现方式同样是使用接口WebMvcConfigurer,不过要重写另一个方法addViewControllers

/**
 * 设置主页,任何以"/"访问的都转为"/index"再访问主页
 */
@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addRedirectViewController("/", "index");
    registry.setOrder(Ordered.HIGHEST_PRECEDENCE); //最高级别,过滤的时候最先执行
}

访问localhost:8080打印结果:
—INFO c.j.c.LoginController - 重定向到登录
—INFO c.j.c.LoginController - 跳转到页面
而且地址栏也自动变成了我们想要的:http://localhost:8089/common/login

15、SpringBoot过滤器配置

以往配置springMvc的过滤器配置都是使用的web.xml

<filter>
	<filter-name>sessionFilter</filter-name>
    <!--创建一个SessionFilter继承Filter-->
	<filter-class>com.rj.web.filter.SessionFilter</filter-class>
	<init-param>
        <!--设置排除路径-->
		<param-name>extendUrl</param-name>
		<param-value>
			/index,/common/login
		</param-value>
	</init-param>
</filter>
<filter-mapping>
	<filter-name>sessionFilter</filter-name>
	<url-pattern>/*</url-pattern><!--拦截路径:全部-->
</filter-mapping>

现在同样使用继承Filter方式,但是参数可以通过注解注入。

/**
 * session过滤:
 * 1、本地启动的时候,式springboot先启动,故配置文件内容可以在filter抓取到
 * 2、但是,如果是线上容器,则可能tomcat先启动,然后触发springboot,这将导致nullPointerException
 * 3、所以,我们建议将初始化参数通过【initParams】提前注入,然后在init方法中通过【filterConfig】获取
 */
@Order(1) //第一个过滤器,可以设置多个
@WebFilter(filterName = "sessionFilter", urlPatterns = "/*", initParams = { //通过InitParams将参数提早注入
        @WebInitParam(name = "extendUrl", value = "/index,/common/login"), //排除需要过滤的
        @WebInitParam(name = "sympol", value = "MaSaKi")
})
@Slf4j
public class SessionFilter implements Filter {

    /**
     * 新建一个list,初始化的时候将需要排除校验的url放入,在销毁的时候销毁
     */
    private Set<String> extendUrls = Sets.newHashSet();

    /**
     * 初始化
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String extendUrl = filterConfig.getInitParameter("extendUrl"); //获取注解配置的initParam
        extendUrls = Sets.newHashSet(Arrays.asList(extendUrl.split(","))); //初始化extencUrls
        log.info("初始化排除过滤的 URL = {}", JackSonUtil.objToJson(extendUrls));
    }

    /**
     * 执行过滤
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = null;
        HttpServletResponse res = null;
        if (request instanceof HttpServletRequest) {
            req = (HttpServletRequest) request;
        } else {
            log.error("不是http请求!" );
            return;
        }

        if (response instanceof HttpServletResponse) {
            res = (HttpServletResponse) response;
        } else {
            log.error("不是http响应!" );
            return;
        }

        //获取请求路径
        String servletPath = req.getServletPath();
        log.info("获取到的请求路径, servletPath = {}", servletPath);

        //包含,直接继续
        if (extendUrls.contains(servletPath)) {
            chain.doFilter(req, res);
        } else {
            log.info("进行进一步过滤...");
            chain.doFilter(req, res);
        }
    }

    /**
     * 销毁
     */
    @Override
    public void destroy() {
        log.info("销毁extendUrls的set集合");
        extendUrls.clear();
    }
}

需要在SpringBoot启动类上打注解:@ServletComponentScan(basePackages = {"com.jd.config.interceptor","com.jd.config.filter"})

访问localhost:8080打印结果:
c.j.c.f.SessionFilter - 初始化排除过滤的 URL = ["/common/login","/index"]
c.j.c.f.SessionFilter - 获取到的请求路径, servletPath = /index

16、Spring的异步监听机制

类似于消息队列,但是在内存处理的,并且不能像消息队列那样可以限流削峰,可以说与开线程一样,但是可以解耦,对于数据的并行处理非常有用。

我们有一个销售商品的接口,主线任务是销售,副线任务是比较销售额:

/**
 * 商品销售的实现类
 */
@Service
@Slf4j
public class GoodSaleServiceImpl implements GoodSaleService {

    /**
     * spring 应用上下文
     */
    @Resource
    private ApplicationContext applicationContext;

    /**
     * 进行销售
     * 两份商品销售,最后需要异步发送消息去进行销售额的比较
     */
    @Override
    public void saling() {

        //苹果手机销售
        Goods apple = new Goods();
        apple.setGoodName("iPhone12Pro");
        apple.setGoodPrice("10000"); //一部一万块
        int appleSaleNum = 100; //销售了一百部

        //华为手机销售
        Goods huawei = new Goods();
        huawei.setGoodPrice("7000");
        huawei.setGoodName("Mate70s");
        int huaweiSaleNum = 1000;
        log.info("苹果销售数量是{}部, 华为手机销量是{}部", appleSaleNum, huaweiSaleNum);

        //使用spring的监听推送消息
        applicationContext.publishEvent(new PriceComparatorEvent(
                apple,
                huawei,
                appleSaleNum,
                huaweiSaleNum));
    }
}

我们需要创建一个监听类PriceComparatorEvent,Spring也是通过类型去判断由谁监听。

/**
 * 销售额比较事件:spring的监听就是通过这个类来确认接受者是谁
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PriceComparatorEvent {

    /**
     * 苹果货品
     */
    private Goods apple;

    /**
     * 华为货品
     */
    private Goods huawei;

    /**
     * 苹果销量
     */
    private int appleNum;

    /**
     * 华为销量
     */
    private int huaweiNum;
}

最后我们需要设置一个监听者去异步执行逻辑

/**
 * 价格比较监听者
 */
@Component //确保springboot扫描到
@Slf4j
public class PriceComparatorListener {

    /**
     * 进行事件监听
     */
    @Async("threadPoolUtil") //异步监听,建议使用自己的线程池,我设置的线程号前缀是【myThread-】
    @EventListener //监听注解,将根据参数类型查找监听者
    public void onEventListening(PriceComparatorEvent priceComparatorEvent) {
        log.info("开始比价处理-----");
        Goods apple = priceComparatorEvent.getApple();
        int appleNum = priceComparatorEvent.getAppleNum();
        Goods huawei = priceComparatorEvent.getHuawei();
        int huaweiNum = priceComparatorEvent.getHuaweiNum();
        log.info("苹果销售额是{}元, 华为销售额是{}元",
                Integer.valueOf(apple.getGoodPrice())*appleNum,
                Integer.valueOf(huawei.getGoodPrice())*huaweiNum);
    }
}

同样,我们需要在SpringBoot启动类加注解允许异步,否则就只是解耦,还是同步的调用:@EnableAsync

打印结果,走的是单独的自定义线程:
[myThread-0] INFO c.j.l.PriceComparatorListener - 苹果销售额是1000000元, 华为销售额是7000000元

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值