boot入门思想 spring_Spring Boot 入门

boot入门思想 spring_Spring Boot 入门_第1张图片

SpringBoot 基本应用

约定优于配置

约定优于配置(Convention over Configuration),又称按约定编程,是一种软件设计范式。

本质上是说,系统、类库或框架应该假定合理的默认值,而非要求提供不必要的配置。比如说模型中有一个名为 User 的类,那么数据库中对应的表就会默认命名为 User。只有在偏离这一个约定的时候,例如想要将该表命名为 person,才需要写有关这个名字的配置。

比如平时架构师搭建项目就是限制软件开发随便写代码,制定出一套规范,让开发人员按统一的要求进行开发编码测试之类的,这样就加强了开发效率与审查代码效率。所以说写代码的时候就需要按要求命名,这样统一规范的代码就有良好的可读性与维护性了。

约定优于配置简单来理解,就是遵循约定。

Spring Boot 是所有基于 Spring 开发的项目的起点。Spring Boot 的设计是为了尽可能快的跑起来 Spring 应用程序并且尽可能减少配置文件。

SpringBoot 概念

Spring 优缺点分析

优点:Spring 是 Java 企业版(Java Enterprise Edition,JEE,也称 J2EE)的轻量级代替品。无需开发重量级的 Enterprise Java Bean - EJB,Spring 为企业级 Java 开发提供了一种相对简单的方法,通过依赖注入和面向切面编程,用简单的 Java 对象(Plain Old Java Object,POJO)实现了 EJB 的功能。

缺点:虽然 Spring 的组件代码是轻量级的,但它的配置却是重量级的。一开始,Spring 用 XML 配置,而且是很多 XML 配 置。Spring 2.5 引入了基于注解的组件扫描,这消除了大量针对应用程序自身组件的显式 XML 配置。Spring 3.0 引入 了基于 Java 的配置,这是一种类型安全的可重构配置方式,可以代替 XML。

所有这些配置都代表了开发时的损耗。因为在思考 Spring 特性配置和解决业务问题之间需要进行思维切换,所以编写配置挤占了编写应用程序逻辑的时间。和所有框架一样,Spring 实用,但与此同时它要求的回报也不少。

除此之外,项目的依赖管理也是一件耗时耗力的事情。在环境搭建时,需要分析要导入哪些库的坐标,而且还需要分析导入与之有依赖关系的其他库的坐标,一旦选错了依赖的版本,随之而来的不兼容问题就会严重阻碍项目的开发进度。SSM 整合:Spring、Spring MVC、Mybatis、Spring-Mybatis 整合包、数据库驱动,引入依赖的数量繁多、容易存在版本冲突。

Spring Boot 解决上述 Spring 的问题

SpringBoot 对上述 Spring 的缺点进行的改善和优化,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期。

起步依赖:

起步依赖本质上是一个 Maven 项目对象模型 (Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。 
简单的说,起步依赖就是将具备某种功能的依赖坐标打包到一起,并提供一些默认的功能。

自动配置:

springboot 的自动配置,指的是 springboot 会自动将一些配置类的 bean 注册进 ioc 容器,在需要的地方使用 @autowired 或者 @resource 等注解来使用它。
“自动”的表现形式就是只需要引想用功能的包,相关的配置完全不用管,springboot 会自动注入这些配置 bean,直接使用这些 bean 即可。

Springboot 可以简单、快速、方便地搭建项目;对主流开发框架的无配置集成;极大提高了开发、部署效率。

SpringBoot 入门案例

案例需求:请求 Controller 中的方法,并将返回值响应到页面。

1) 依赖管理

pom.xml



    org.springframework.bootgroupId>
    spring-boot-starter-parentartifactId>
    2.2.2.RELEASEversion>
parent>


    
    
        org.springframework.bootgroupId>
        spring-boot-starter-webartifactId>
    dependency>
dependencies>



    
        
            org.springframework.bootgroupId>
            spring-boot-maven-pluginartifactId>
        plugin>
    plugins>
build>
2) 启动类

com.renda.SpringBootDemo1Application

/**
 * SpringBoot 的启动类通常放在二级包中,
 * 比如:com.renda.SpringBootDemo1Application。
 * 因为 SpringBoot 项目在做包扫描,
 * 会扫描启动类所在的包及其子包下的所有内容。
 *
 * @author Renda Zhang
 * @since 2020-10-28 23:11
 */
@SpringBootApplication // 标识当前类为 SpringBoot 项目的启动类
public class SpringBootDemo1Application {

    public static void main(String[] args) {
        // 样板代码
        SpringApplication.run(SpringBootDemo1Application.class, args);
    }

}
3) Controller

com.renda.controller.HelloController

@RestController
@RequestMapping("/hello")
public class HelloController {

    @RequestMapping("/boot")
    public String helloSpringBoot() {
        return "Hello Spring Boot";
    }

}

SpringBoot 快速构建

案例需求:请求 Controller 中的方法,并将返回值响应到页面。

1)使用 Spring Initializr 方式构建 Spring Boot 项目

本质上说,Spring Initializr 是一个 Web 应用,它提供了一个基本的项目结构,能够快速构建一个基础的 Spring Boot 项目。

注意使用快速方式创建 Spring Boot 项目时,所在主机须在联网状态下;本质上是在开发工具执行各项参数后,由 Spring 提供的 URL 所对应的服务器生成, IDEA 将服务器生成的 SpringBoot 项目下载到本地的工作空间中。

Project SDK 用于设置创建项目使用的 JDK 版本,这里,使用之前初始化设置好的 JDK 版本即可;在 Choose Initializr Service URL 下使用默认的初始化服务地址 https://start.spring.io 进行 Spring Boot 项目创建。

创建完成后的 pom 文件



    4.0.0modelVersion>
    
        org.springframework.bootgroupId>
        spring-boot-starter-parentartifactId>
        2.2.2.RELEASEversion>
         
    parent>
    com.rendagroupId>
    springbootdemo2artifactId>
    0.0.1-SNAPSHOTversion>
    springbootdemo2name>
    Demo project for Spring Bootdescription>

    
        11java.version>
    properties>

    
        
            org.springframework.bootgroupId>
            spring-boot-starter-webartifactId>
        dependency>

        
            org.springframework.bootgroupId>
            spring-boot-starter-testartifactId>
            testscope>
            
                
                    org.junit.vintagegroupId>
                    junit-vintage-engineartifactId>
                exclusion>
            exclusions>
        dependency>
    dependencies>

    
        
            
                org.springframework.bootgroupId>
                spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>

project>

创建完成后,可以删除自动生成的 .mvn.gitignoreHELP.mdmvnwmvnw.cmd

创建好的 Spring Boot 项目结构如图:

/src/main/java/
    com.renda.Springbootdemo2Application - 项目主程序启动类
/src/main/resources/
    static - 静态资源文件夹
    templates - 模板页面文件夹
    application.properties - 全网配置文件
/src/test/java/
    com.renda.Springbootdemo2ApplicationTests - 项目测试类

使用 Spring Initializr 方式构建的 Spring Boot 项目会默认生成项目启动类、存放前端静态资源和页面的文件夹、编写项目配置的配置文件以及进行项目单元测试的测试类。

2)创建一个用于 Web 访问的 Controller

注意:确保项目启动类 Springbootdemo2Application 在 com.renda 包下。

com.renda.controller.HelloController

@RestController
@RequestMapping("/hello")
public class HelloController {

    @RequestMapping("/boot")
    public String hello() {
        return "What's up! Spring Boot!";
    }

}
3) 运行项目

application.properties 中修改 tomcat 的默认端口号:

server.port=8888

运行主程序启动类 Springbootdemo2Application,项目启动成功后,在控制台上会发现 Spring Boot 项目默认启动的端口号为 8888,此时,可以在浏览器上访问 http://localhost:8888/hello/boot。

页面输出的内容是 “What's up! Spring Boot!”,至此,构建 Spring Boot 项目就完成了。

单元测试与热部署

单元测试

开发中,每当完成一个功能接口或业务方法的编写后,通常都会借助单元测试验证该功能是否正确。

1)添加 spring-boot-starter-test 测试依赖启动器,在项目的 pom.xml 文件中添加 spring-boot-starter-test 测试依赖启动器,示例代码如下 :


    org.springframework.bootgroupId>
    spring-boot-starter-testartifactId>
    testscope>
    
        
            org.junit.vintagegroupId>
            junit-vintage-engineartifactId>
        exclusion>
    exclusions>
dependency>

注意:使用 Spring Initializr 方式搭建的 Spring Boot 项目,会自动加入 spring-boot-starter-test 测试依赖启动器,无需再手动添加。

2)编写单元测试类和测试方法:

/**
 *  SpringJUnit4ClassRunner.class: Spring 运行环境
 *  JUnit4.class: JUnit 运行环境
 *  SpringRunner.class: Spring Boot 运行环境
 */
@RunWith(SpringRunner.class) // @RunWith: 运行器
@SpringBootTest // 标记为当前类为 SpringBoot 测试类,加载项目的 ApplicationContext 上下文环境
class Springbootdemo2ApplicationTests {

    @Autowired
    private HelloController helloController;

    @Test
    void contextLoads() {
        String result = helloController.hello();
        System.out.println(result);
    }

}

上述代码中,先使用 @Autowired 注解注入了 HelloController 实例对象,然后在 contextLoads() 方法中调用了 HelloController 类中对应的请求控制方法 hello(),并输出打印结果。

热部署

在开发过程中,通常会对一段业务代码不断地修改测试,在修改之后往往需要重启服务,有些服务需要加载很久才能启动成功,这种不必要的重复操作极大的降低了程序开发效率。为此,Spring Boot 框架专门提供了进行热部署的依赖启动器,用于进行项目热部署,而无需手动重启项目 。

热部署:在修改完代码之后,不需要重新启动容器,就可以实现更新。

使用步骤:

1)添加 spring-boot-devtools 热部署依赖启动器。

在 Spring Boot 项目进行热部署测试之前,需要先在项目的 pom.xml 文件中添加 spring-boot-devtools 热部署依赖启动器:



    org.springframework.bootgroupId>
    spring-boot-devtoolsartifactId>
dependency>

由于使用的是 IDEA 开发工具,添加热部署依赖后可能没有任何效果,接下来还需要针对 IDEA 开发工具进行热部署相关的功能设置。

2)开启 IDEA 的自动编译。

选择 IDEA 工具界面的 【File】->【Settings】 选项,打开 Compiler 面板设置页面。

选择 Build 下的 Compiler 选项,在右侧勾选 “Build project automatically” 选项将项目设置为自动编译,单击 【Apply】->【OK】 按钮保存设置。

3)开启 IDEA 的在项目运行中自动编译的功能。

在项目任意页面中使用组合快捷键 “Ctrl+Shift+Alt+/” 打开 Maintenance 选项框,选中并打开 Registry 页面。

列表中找到 “compiler.automake.allow.when.app.running”,将该选项后的 Value 值勾选,用于指定 IDEA 工具在程序运行过程中自动编译,最后单击 【Close】 按钮完成设置。
热部署效果测试

启动 Springbootdemo2Application http://localhost:8888/hello/boot

页面原始输出的内容是 “What's up! Spring Boot!”。

为了测试配置的热部署是否有效,接下来,在不关闭当前项目的情况下,将 HelloController 类中的请求处理方法 hello() 的返回值修改为 “Hello! Spring Boot!” 并保存,查看控制台信息会发现项目能够自动构建和编译,说明项目热部署生效。

再次刷新浏览器,输出了 “Hello! Spring Boot!”,说明项目热部署配置成功。

全局配置文件

全局配置文件能够对一些默认配置值进行修改。Spring Boot 使用一个 application.properties 或者 application.yaml 的文件作为全局配置文件,该文件存放在 src/main/resource 目录或者类路径的 /config,一般会选择 resource 目录。

Spring Boot 配置文件的命名及其格式:

  • application.properties

  • application.yaml

  • application.yml

`application.properties` 配置文件

引入相关依赖:


    org.springframework.bootgroupId>
    spring-boot-starter-jdbcartifactId>
dependency>


    mysqlgroupId>
    mysql-connector-javaartifactId>
    6.0.6version>
dependency>

使用 Spring Initializr 方式构建 Spring Boot 项目时,会在 resource 目录下自动生成一个空的 application.properties 文件,Spring Boot 项目启动时会自动加载 application.properties 文件。

# 修改 tomcat 的版本号
server.port=8080
# 定义数据库的连接信息 JdbcTemplate
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/renda01
spring.datasource.username=root
spring.datasource.password=password

可以在 application.properties 文件中定义 Spring Boot 项目的相关属性,当然,这些相关属性可以是系统属性、环境变量、命令参数等信息,也可以是自定义配置文件名称和位置。

预先准备了两个实体类文件,后续会演示将 application.properties 配置文件中的自定义配置属性注入到 Person 实体类的对应属性中。

1)先在项目的 com.renda 包下创建一个 pojo 包,并在该包下创建两个实体类 Pet 和 Person。

/**
 * 宠物类
 *
 * @author Renda Zhang
 * @since 2020-10-29 1:03
 */
public class Pet {
    // 品种
    private String type;
    // 名称
    private String name;
    // setter and getter, toString ...
}

/**
 * 人类
 *
 * @author Renda Zhang
 * @since 2020-10-29 1:04
 */
@Component
/**
 * 将配置文件中所有以 person 开头的配置信息注入当前类中
 * 前提 1:必须保证配置文件中 person.xx 与当前 Person 类的属性名一致
 * 前提 2:必须保证当前 Person 中的属性都具有 set 方法
 */
@ConfigurationProperties(prefix = "person")
public class Person {
    // id
    private int id;
    // 名称
    private String name;
    // 爱好
    private List hobby;
    // 家庭成员
    private String[] family;
    private Map map;
    // 宠物
    private Pet pet;
    // setter and getter, toString ...
}

@ConfigurationProperties(prefix = "person") 注解的作用是将配置文件中以 person 开头的属性值通过 setXX() 方法注入到实体类对应属性中。

@Component 注解的作用是将当前注入属性值的 Person 类对象作为 Bean 组件放到 Spring 容器中,只有这样才能被 @ConfigurationProperties 注解进行赋值。

2)打开项目的 resources 目录下的 application.properties 配置文件,在该配置文件中编写需要对 Person 类设置的配置属性。

# 自定义配置信息注入到 Person 对象中
person.id=100
person.name=张人大
## list
person.hobby=看书,写代码,吃饭
## String[]
person.family=兄弟,父母
## map
person.map.k1=v1
person.map.k2=v2
## pet 对象
person.pet.type=dog
person.pet.name=旺财

3)查看 application.properties 配置文件是否正确,同时查看属性配置效果,打开通过 IDEA 工具创建的项目测试类,在该测试类中引入 Person 实体类 Bean,并进行输出测试。

@RunWith(SpringRunner.class)
@SpringBootTest
class Springbootdemo2ApplicationTests {

    @Autowired
    private Person person;

    @Test
    void showPersonInfo() {
        System.out.println(person);
    }

}

可以看出,测试方法 showPersonInfo() 运行成功,同时正确打印出了 Person 实体类对象。至此,说明 application.properties 配置文件属性配置正确,并通过相关注解自动完成了属性注入。

4)解决浏览器请求出现中文乱码问题。

调整文件编码格式:Settings -> Editor -> File Encodings -> 确保 Global Encoding 和 Project Encoding 为 UTF-8,修改 Default ecoding for properties files 为 UTF-8 并勾选 Transparent native-to-ascii conversion。

application.properties 文件中增加配置:

# 解决中文乱码
server.tomcat.uri-encoding=UTF-8
spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
`application.yaml` 配置文件

YAML 文件格式是 Spring Boot 支持的一种 JSON 文件格式,相较于传统的 Properties 配置文件,YAML 文件以数据为核心,是一种更为直观且容易被电脑识别的数据序列化格式。application.yaml 配置文件的工作原理和 application.properties 是一样的,只不过 yaml 格式配置文件看起来更简洁一些。

  • YAML 文件的扩展名可以使用 .yml 或者 .yaml

  • application.yml 文件使用 key: value 格式配置属性,使用缩进控制层级关系。

SpringBoot 的三种配置文件是可以共存的:application.propertiesapplication.yamlapplication.yml

1)value 值为普通数据类型(例如数字、字符串、布尔等)

当 YAML 配置文件中配置的属性值为普通数据类型时,可以直接配置对应的属性值,同时对于字符串类型的属性值,不需要额外添加引号,示例代码如下:

server:
  port: 8080
  servlet:
    context-path: /hello

2)value 值为数组和单列集合

当 YAML 配置文件中配置的属性值为数组或单列集合类型时,主要有两种书写方式:缩进式写法和行内式写法。

其中,缩进式写法还有两种表示形式,示例代码如下:

person: 
  hobby: 
    - play
    - read
    - sleep 

或者使用如下示例形式:

person:
  hobby:
    play,
    read,
    sleep

上述代码中,在 YAML 配置文件中通过两种缩进式写法对 person 对象的单列集合(或数组)类型的爱好 hobby 赋值为 play、read 和 sleep。其中一种形式为 “- 属性值”,另一种形式为多个属性值之前加英文逗号分隔;注意,最后一个属性值后不要加逗号。

person: 
  hobby: [play,read,sleep]

通过上述示例对比发现,YAML 配置文件的行内式写法更加简明、方便。另外,包含属性值的中括号 “[]” 还可以进一步省略,在进行属性赋值时,程序会自动匹配和校对。

3)value 值为 Map 集合和对象

当 YAML 配置文件中配置的属性值为 Map 集合或对象类型时,YAML 配置文件格式同样可以分为两种书写方式 - 缩进式写法和行内式写法。

其中,缩进式写法的示例代码如下:

person: 
  map: 
    k1: v1
    k2: v2

对应的行内式写法示例代码如下:

person:
  map: {k1: v1,k2: v2}

在YAML配置文件中,配置的属性值为Map集合或对象类型时,缩进式写法的形式按照YAML文件格式编写即可,而行内式写法的属性值要用大括号“{}”包含。

接下来,在 Properties 配置文件演示案例基础上,注释掉 Properties 中跟 Person 相关的注入,然后通过配置 application.yaml 配置文件对 Person 对象进行赋值,具体使用如下。

在项目的 resources 目录下,新建一个 application.yaml 配置文件,在该配置文件中编写为 Person 类设置的配置属性:

person:
  id: 1000
  name: 张人大
  hobby:
    - 跑步
    - 瑜伽
    - 游泳
  family:
    - 张小明
    - 李小红
  map:
    k1: 这是 k1 对应的 value
    k2: 这是 k2 对应的 value
  pet:
    type: dog
    name: 金毛

再次执行测试。

可以看出,测试方法 showPersonInfo() 同样运行成功,并正确打印出了 Person 实体类对象。

需要说明的是,本次使用 application.yaml 配置文件进行测试时需要提前将 application.properties 配置文件中编写的配置注释,这是因为 application.properties 配置文件会覆盖 application.yaml 配置文件。

配置文件属性值的注入

spring-boot-starter-parent 的 pom 文件可以知道配置文件的优先级从低到高如下:


    **/application*.ymlinclude>
    **/application*.yamlinclude>
    **/application*.propertiesinclude>
includes>

使用 Spring Boot 全局配置文件设置属性时:

如果配置属性是 Spring Boot 已有属性,例如服务端口 server.port,那么 Spring Boot 内部会自动扫描并读取这些配置文件中的属性值并覆盖默认属性。

如果配置的属性是用户自定义属性,例如刚刚自定义的 Person 实体类属性,还必须在程序中注入这些配置属性方可生效。

Spring Boot 支持多种注入配置文件属性的方式,下面来介绍如何使用注解 @ConfigurationProperties@Value 注入属性。

使用 `@ConfigurationProperties` 注入属性

Spring Boot 提供的 @ConfigurationProperties 注解用来快速、方便地将配置文件中的自定义属性值批量注入到某个 Bean 对象的多个对应属性中。假设现在有一个配置文件,如果使用 @ConfigurationProperties 注入配置文件的属性,示例代码如下:

@Component
@ConfigurationProperties(prefix = "person")
public class Person {
    private int id;
    private String name;
    private List hobby;
    private String[] family;
    private Map map;
    private Pet pet;
    // setter and getter, toString ...
}

上述代码使用 @Component@ConfigurationProperties(prefix = “person”) 将配置文件中的每个属性映射到 person 类组件中。

使用 `@Value` 注入属性

@Value 注解是 Spring 框架提供的,用来读取配置文件中的属性值并逐个注入到 Bean 对象的对应属性中,Spring Boot 框架从 Spring 框架中对 @Value 注解进行了默认继承,所以在 Spring Boot 框架中还可以使用该注解读取和注入配置文件属性值。使用 @Value 注入属性的示例代码如下:

@Component
public class Student {
    @Value("${person.id}")
    private int number;
    // getter setter toString ...
}

上述代码中,使用 @Component@Value 注入 Person 实体类的id属性。其中,@Value 不仅可以将配置文件的属性注入 Person 的 id 属性,还可以直接给 id 属性赋值,这点是@ConfigurationProperties 不支持的。

1)在 com.renda.pojo 包下新创建一个实体类 Student,并使用 @Value 注解注入属性:

@Component
public class Student {

    @Value("${person.id}")
    private String number;

    @Value("${person.name}")
    private String name;

    // getter setter toString ...
}

Student 类使用 @Value 注解将配置文件的属性值读取和注入。

从上述示例代码可以看出,使用 @Value 注解方式需要对每一个属性注入设置,同时又免去了属性的 setXX() 方法。

2)再次打开测试类进行测试:

@RunWith(SpringRunner.class)
@SpringBootTest
class Springbootdemo2ApplicationTests {

    @Autowired
    private Student student;

    @Test
    public void showStudentInfo(){
        System.out.println(student);
    }

}

可以看出,测试方法 showStudentInfo() 运行成功,同时正确打印出了 Student 实体类对象。需要说明的是,本示例中只是使用 @Value 注解对实例中 Student 对象的普通类型属性进行了赋值演示,而 @Value 注解对于包含 Map 集合、对象以及 YAML 文件格式的行内式写法的配置文件的属性注入都不支持,如果赋值会出现错误。

自定义配置

Spring Boot 免除了项目中大部分的手动配置,对于一些特定情况,可以通过修改全局配置文件以适应具体生产环境,可以说,几乎所有的配置都可以写在 application.yml 文件中,Spring Boot 会自动加载全局配置文件从而免除手动加载的烦恼。但是,如果自定义配置文件,Spring Boot 是无法识别这些配置文件的,此时就需要手动加载。

使用 `@PropertySource` 加载配置文件

对于这种加载自定义配置文件的需求,可以使用 @PropertySource 注解来实现。@PropertySource 注解用于指定自定义配置文件的具体位置和名称。

如果需要将自定义配置文件中的属性值注入到对应类的属性中,可以使用 @ConfigurationProperties 或者 @Value 注解进行属性值注入。

1)打开 Spring Boot 项目的 resources 目录,在项目的类路径下新建一个 my.properties 自定义配置文件,在该配置文件中编写需要设置的配置属性。

# 对实体类对象 Product 进行属性配置
product.id=99
product.name=小米

2)在 com.renda.pojo 包下新创建一个配置类 Product,提供 my.properties 自定义配置文件中对应的属性,并根据 @PropertySource 注解的使用进行相关配置。

@Component
@PropertySource("classpath:my.properties")
@ConfigurationProperties(prefix = "product")
public class Product {
    private int id;
    private String name;
    // getter setter toString ...
}

主要是一个自定义配置类,通过相关注解引入了自定义的配置文件,并完成了自定义属性值的注入。针对示例中的几个注解,具体说明如下:

  • @PropertySource("classpath:my.properties") 注解指定了自定义配置文件的位置和名称,此示例表示自定义配置文件为 classpath 类路径下的 my.properties 文件。

  • @ConfigurationProperties(prefix = "product") 注解将上述自定义配置文件 my.properties 中以 product 开头的属性值注入到该配置类属性中。

3)进行测试。

@RunWith(SpringRunner.class)
@SpringBootTest
class Springbootdemo2ApplicationTests {

    @Autowired
    private Product product;

    @Test
    public void showProductInfo() {
        System.out.println(product);
    }

}
使用 `@Configuration` 编写自定义配置类

在 Spring Boot 框架中,推荐使用配置类的方式向容器中添加和配置组件。

在 Spring Boot 框架中,通常使用 @Configuration 注解定义一个配置类,Spring Boot 会自动扫描和识别配置类,从而替换传统 Spring 框架中的 XML 配置文件。

当定义一个配置类后,还需要在类中的方法上使用 @Bean 注解进行组件配置,将方法的返回对象注入到 Spring 容器中,并且组件名称默认使用的是方法名,当然也可以使用 @Bean 注解的 name 或 value 属性自定义组件的名称。

1)在项目下新建一个 com.renda.config 包,并在该包下新创建一个类 MyService,该类中不需要编写任何代码:

package com.renda.service;
public class MyService {
}

创建了一个空的 MyService 类,而该类目前没有添加任何配置和注解,因此还无法正常被 Spring Boot 扫描和识别。

2)在项目的 com.renda.config 包下,新建一个类 MyConfig,并使用 @Configuration 注解将该类声明一个配置类,内容如下:

@Configuration // 标识当前类是一个配置类,SpringBoot 会扫描该类,将所有标识 @Bean 注解的方法的返回值注入的容器中
public class MyConfig {

    @Bean // 注入的名称就是方法的名称,注入的类型就是返回值的类型
    public MyService myService(){
        return new MyService();
    }

    @Bean("service_")
    public MyService myService2(){
        return new MyService();
    }

}

MyConfig 是 @Configuration 注解声明的配置类(类似于声明了一个 XML 配置文件),该配置类会被 Spring Boot 自动扫描识别;使用 @Bean 注解的 myService() 方法,其返回值对象会作为组件添加到了 Spring 容器中(类似于 XML 配置文件中的标签配置),并且该组件的 id 默认是方法名 myService

3)测试类:

@RunWith(SpringRunner.class)
@SpringBootTest
class Springbootdemo2ApplicationTests {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void testConfig(){
        System.out.println(applicationContext.containsBean("myService"));
        System.out.println(applicationContext.containsBean("service_"));
    }

}

上述代码中,先通过 @Autowired 注解引入了 Spring 容器实例 ApplicationContext,然后在测试方法 testConfig() 中测试查看该容器中是否包括 id 为 myServiceservice_ 的组件。

从测试结果可以看出,测试方法 testConfig() 运行成功,显示运行结果为 true,表示 Spirng 的 IOC 容器中也已经包含了 id 为 myServiceservice_的实例对象组件,说明使用自定义配置类的形式完成了向 Spring 容器进行组件的添加和配置。

SpringBoot 原理深入及源码剖析

依赖管理

在 Spring Boot 入门程序中,项目 pom.xml 文件有两个核心依赖,分别是 spring-boot-starter-
parent 和 spring-boot-starter-web,关于这两个依赖的相关介绍具体如下:

spring-boot-starter-parent 依赖

    org.springframework.bootgroupId>
    spring-boot-starter-parentartifactId>
    2.2.2.RELEASEversion>
    
parent>

上述代码中,将 spring-boot-starter-parent 依赖作为 Spring Boot 项目的统一父项目依赖管理,并将项目版本号统一为 2.2.2.RELEASE,该版本号根据实际开发需求是可以修改的。

使用 “Ctrl+鼠标左键” 进入并查看 spring-boot-starter-parent 底层源文件,发现 spring-boot-
starter-parent 的底层有一个父依赖 spring-boot-dependencies,核心代码具体如下


    org.springframework.bootgroupId>
    spring-boot-dependenciesartifactId>
    2.2.2.RELEASEversion>
    ../../spring-boot-dependenciesrelativePath>
parent>

继续查看 spring-boot-dependencies 底层源文件,核心代码具体如下:


    5.15.11activemq.version>
    ...
    8.2.0solr.version>
    2.2.2.RELEASEspring-amqp.version>
    4.2.1.RELEASEspring-batch.version>
    2.0.7.RELEASEspring-cloud-connectors.version>
    Moore-SR3spring-data-releasetrain.version>
    5.2.2.RELEASEspring-framework.version>
    1.0.2.RELEASEspring-hateoas.version>
    5.2.2.RELEASEspring-integration.version>
    2.3.4.RELEASEspring-kafka.version>
    2.3.2.RELEASEspring-ldap.version>
    2.0.4.RELEASEspring-restdocs.version>
    1.2.4.RELEASEspring-retry.version>
    5.2.1.RELEASEspring-security.version>
    Corn-RELEASEspring-session-bom.version>
    3.0.8.RELEASEspring-ws.version>
    3.28.0sqlite-jdbc.version>
    ${jakarta-mail.version}sun-mail.version>
    3.0.11.RELEASEthymeleaf.version>
    2.0.1thymeleaf-extras-data-attribute.version>
    ...
properties>

从 spring-boot-dependencies 底层源文件可以看出,该文件通过标签对一些常用技术框架的依赖文件进行了统一版本号管理,例如 activemq、spring、tomcat 等,都有与 Spring Boot 2.2.2 版本相匹配的版本,这也是 pom.xml 引入依赖文件不需要标注依赖文件版本号的原因。

需要说明的是,如果 pom.xml 引入的依赖文件不是 spring-boot-starter-parent 管理的,那么在 pom.xml 引入依赖文件时,需要使用标签指定依赖文件的版本号。

spring-boot-starter-web 依赖

spring-boot-starter-parent 父依赖启动器的主要作用是进行版本统一管理;项目运行依赖的 JAR 包是从 Spring Boot Starters 中导入。

查看 spring-boot-starter-web 依赖文件源码,核心代码具体如下:


    
        org.springframework.bootgroupId>
        spring-boot-starterartifactId>
        2.2.2.RELEASEversion>
        compilescope>
    dependency>
    
        org.springframework.bootgroupId>
        spring-boot-starter-jsonartifactId>
        2.2.2.RELEASEversion>
        compilescope>
    dependency>
    
        org.springframework.bootgroupId>
        spring-boot-starter-tomcatartifactId>
        2.2.2.RELEASEversion>
        compilescope>
    dependency>
    
        org.springframework.bootgroupId>
        spring-boot-starter-validationartifactId>
        2.2.2.RELEASEversion>
        compilescope>
        
            
                tomcat-embed-elartifactId>
                org.apache.tomcat.embedgroupId>
            exclusion>
        exclusions>
    dependency>
    
        org.springframeworkgroupId>
        spring-webartifactId>
        5.2.2.RELEASEversion>
        compilescope>
    dependency>
    
        org.springframeworkgroupId>
        spring-webmvcartifactId>
        5.2.2.RELEASEversion>
        compilescope>
    dependency>
dependencies>

从上述代码可以发现,spring-boot-starter-web 依赖启动器的主要作用是提供 Web 开发场景所需的底层所有依赖。

正是如此,在 pom.xml 中引入 spring-boot-starter-web 依赖启动器时,就可以实现 Web 场景开发,而不需要额外导入 Tomcat 服务器以及其他 Web 依赖文件等。当然,这些引入的依赖文件的版本号还是由 spring-boot-starter-parent 父依赖进行的统一管理。

Spring Boot Starters:

https://github.com/spring-projects/spring-boot/tree/v2.1.0.RELEASE/spring-boot-project/spring-boot-starters

https://mvnrepository.com/search?q=starter

Spring Boot 除了提供有上述介绍的 Web 依赖启动器外,还提供了其他许多开发场景的相关依赖,可以打开 Spring Boot 官方文档,搜索 “Starters” 关键字查询场景依赖启动器。

需要说明的是,Spring Boot 官方并不是针对所有场景开发的技术框架都提供了场景启动器,例如数据库操作框架 MyBatis、阿里巴巴的 Druid 数据源等,Spring Boot 官方就没有提供对应的依赖启动器。为了充分利用 Spring Boot 框架的优势,在 Spring Boot 官方没有整合这些技术框架的情况下,MyBatis、Druid 等技术框架所在的开发团队主动与 Spring Boot 框架进行了整合,实现了各自的依赖启动器,例如 mybatis-spring-boot-starter、druid-spring-boot-starter 等。在 pom.xml 文件中引入这些第三方的依赖启动器时,切记要配置对应的版本号。

自动配置

概念:能够在添加 jar 包依赖的时候,自动配置一些组件的相关配置,无需手动配置或者只需要少量手动配置就能运行编写的项目。

Spring Boot 应用的启动入口是 @SpringBootApplication 注解标注类中的 main() 方法。

@SpringBootApplication:SpringBoot 应用标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 就应该运行这个类的 main() 方法启动 SpringBoot 应用。

进入到 @SpringBootApplication 内:

// 注解的适用范围, Type 表示注解可以描述在类、接口、注解或枚举中
@Target(ElementType.TYPE)
// 表示注解的生命周期,Runtime 运行时
@Retention(RetentionPolicy.RUNTIME)
// 表示注解可以记录在 javadoc 中
@Documented
// 表示可以被子类继承该注解
@Inherited
// 标明该类为 Spring Boot 配置类
@SpringBootConfiguration
// 启动自动配置功能
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

    // 根据 class 来排除特定的类,使其不能加入 spring 容器,传入参数 value 类型是 class 类型。
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class>[] exclude() default {};

    // 根据 classname 来排除特定的类,使其不能加入 spring 容器,传入参数 value 类型是class的全类名字符串数组。
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};

    // 指定扫描包,参数是包名的字符串数组。
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    // 扫描特定的包,参数类似是 Class 类型数组。
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class>[] scanBasePackageClasses() default {};

    ...   
}

从上述源码可以看出,@SpringBootApplication 注解是一个组合注解,前面 4 个是注解的元数据信息, 主要看后面 3 个注解:@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan 三个核心注解。

@SpringBootConfiguration 注解

@SpringBootConfiguration: SpringBoot 的配置类,标注在某个类上,表示这是一个  SpringBoot 的配置类。

查看 @SpringBootConfiguration 注解源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    ...
}

从上述源码可以看出,@SpringBootConfiguration 注解内部有一个核心注解 @Configuration,该注解是 Spring 框架提供的,表示当前类为一个配置类(XML 配置文件的注解表现形式),并可以被组件扫描器扫描。由此可见,@SpringBootConfiguration 注解的作用与 @Configuration 注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过 @SpringBootConfiguration 是被 Spring Boot 进行了重新封装命名而已。

`@EnableAutoConfiguration` 注解

@EnableAutoConfiguration:开启自动配置功能,以前需要手动配置的东西,现在由  SpringBoot 自动配置,这个注解就是 Springboot 能实现自动配置的关键。

查看该注解内部查看源码信息,核心代码具体如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 自动配置包
@AutoConfigurationPackage
// Spring 的底层注解 @Import,给容器中导入一个组件;导入的组件是AutoConfigurationPackages.Registrar.class
@Import(AutoConfigurationImportSelector.class)
// 告诉 SpringBoot 开启自动配置功能,这样自动配置才能生效。
public @interface EnableAutoConfiguration {

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    // 返回不会被导入到 Spring 容器中的类
    Class>[] exclude() default {};

    // 返回不会被导入到 Spring 容器中的类名
    String[] excludeName() default {};

}

可以发现它是一个组合注解,  Spring  中有很多以 Enable 开头的注解,其作用就是借助 @Import 来收集并注册特定场景相关的 Bean ,并加载到 IOC 容器。@EnableAutoConfiguration 就是借助 @Import 来收集所有符合自动配置条件的 bean 定义,并加载到 IoC 容器。

@AutoConfigurationPackage 注解

查看 @AutoConfigurationPackage 注解内部源码信息,核心代码具体如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

从上述源码可以看出,@AutoConfigurationPackage 注解的功能是由 @Import 注解实现的,它是 spring 框架的底层注解,它的作用就是给容器中导入某个组件类,例如 @Import(AutoConfigurationPackages.Registrar.class),它就是将 Registrar 这个组件类导入到容器中,可查看 Registrar 类中 registerBeanDefinitions 方法,这个方法就是导入组件类的具体实现:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
      

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        register(registry, new PackageImport(metadata).getPackageName());
    }

    @Override
    public Set determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImport(metadata));
    }

}

从上述源码可以看出,在 Registrar 类中有一个 registerBeanDefinitions() 方法,使用 Debug 模式启动项目, 可以看到选中的部分就是 com.renda。也就是说,@AutoConfigurationPackage 注解的主要作用就是将主程序类所在包及所有子包下的组件到扫描到 spring 容器中。

因此在定义项目包结构时,要求定义的包结构非常规范,项目主程序启动类要定义在最外层的根目录位置,然后在根目录位置内部建立子包和类进行业务开发,这样才能够保证定义的类能够被组件扫描器扫描。

@Import({AutoConfigurationImportSelector.class}) 注解

AutoConfigurationImportSelector 这个类导入到 Spring 容器中,AutoConfigurationImportSelector 可以帮助 Springboot 应用将所有符合条件的 @Configuration 配置都加载到当前 SpringBoot 创建并使用的 IOC 容器 ApplicationContext 中。

继续研究 AutoConfigurationImportSelector 这个类,通过源码分析这个类中是通过selectImports 这个方法告诉 springboot 都需要导入那些组件:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    // 获得自动配置元信息,需要传入 beanClassLoader 这个类加载器
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
        .loadMetadata(this.beanClassLoader);
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                                                                              annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

深入研究 loadMetadata 方法 :

// 文件中为需要加载的配置类的类路径
protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

private AutoConfigurationMetadataLoader() {
}

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
    return loadMetadata(classLoader, PATH);
}

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
    try {
        // 读取 spring-boot-autoconfigure-2.1.5.RELEASE.jar 包中的 spring-autoconfigure-metadata.properties 的信息生成 url
        Enumeration urls = (classLoader != null) ? classLoader.getResources(path)
            : ClassLoader.getSystemResources(path);
        Properties properties = new Properties();// 解析 urls 枚举对象中的信息封装成 properties 对象并加载while (urls.hasMoreElements()) {
            properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
        }// 根据封装好的 properties 对象生成 AutoConfigurationMetadata 对象并返回return loadMetadata(properties);
    }catch (IOException ex) { throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
    }
}

AutoConfigurationImportSelectorgetAutoConfigurationEntry 方法:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
      
    // 判断 EnabledAutoConfiguration 注解有没有开启, 默认开启
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // 获得注解的属性信息
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 获取默认支持的自动配置类列表
    List configurations = getCandidateConfigurations(annotationMetadata, attributes);// 去重
    configurations = removeDuplicates(configurations);// 去除一些多余的配置类,根据 @EnabledAutoConfiguration 的 exclusions 属性进行排除
    Set exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);// 根据 pom 文件中加入的依赖文件筛选中最终符合当前项目运行环境对应的自动配置类
    configurations = filter(configurations, autoConfigurationMetadata);// 触发自动配置导入监听事件
    fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);
}

深入 getCandidateConfigurations 方法:

这个方法中有一个重要方法 loadFactoryNames,这个方法是让 SpringFactoryLoader 去加载一些组件的名字。

protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
      
    /**
     * loadFactoryNames:
     * 这个方法需要传入两个参数 getSpringFactoriesLoaderFactoryClass() 和 
     * getBeanClassLoader()。
     * getSpringFactoriesLoaderFactoryClass() 这个方法返回的是
     * @EnableAutoConfiguration.class。
     * getBeanClassLoader() 这个方法返回的是 beanClassLoader 类加载器
     */
    List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct.");return configurations;
}/**
 * Return the class used by { @link SpringFactoriesLoader} to load configuration
 * candidates.
 * @return the factory class
 */protected Class> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class;
}
...@Overridepublic void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader;
}protected ClassLoader getBeanClassLoader() { return this.beanClassLoader;
}

继续点开 loadFactoryNames 方法:

public static List loadFactoryNames(Class> factoryType, @Nullable ClassLoader classLoader) {
      
    // 获取出入的键
    String factoryTypeName = factoryType.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap result = cache.get(classLoader);if (result != null) { return result;
    }try { // 如果类加载器不为 null,则加载类路径下 spring.factories 文件,将其中设置的配置类的全路径信息封装为 Enumeration 类对象
        Enumeration urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();// 循环 Enumeration 类对象,根据相应的节点信息生成 Properties 对象,通过传入的键获取值,在将值切割为一个个小的字符串转化为 Array,加入方法 result 集合中while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryTypeName, factoryImplementationName.trim());
                }
            }
        }
        cache.put(classLoader, result);return result;
    }catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

上面代码需要读取的 FACTORIES_RESOURCE_LOCATION 最终路径的长这样,而这个是 spring 提供的一个工具类:

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

它其实是去加载一个外部的文件,而这文件是在 spring-boot-autoconfigure-2.2.2.RELEASE.jar 包下的 META-INF/spring.factories,打开 spring.factories 文件:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
...

@EnableAutoConfiguration 就是从 classpath 中搜寻 META-INF/spring.factories 配置文件,并将其中 org.springframework.boot.autoconfigure.EnableutoConfiguration 对应的配置项通过反射 Java Refletion 实例化为对应的标注了 @Configuration 的 JavaConfig 形式的配置类,并加载到 IOC 容器中。

以入门项目为例,在项目中加入了 Web 环境依赖启动器,对应的 WebMvcAutoConfiguration 自动配置类就会生效,打开该自动配置类会发现,在该配置类中通过全注解配置类的方式对 Spring MVC 运行所需环境进行了默认配置,包括默认前缀、默认后缀、视图解析器、MVC 校验器等。而这些自动配置类的本质是传统 Spring MVC 框架中对应的 XML 配置文件,只不过在 Spring Boot 中以自动配置类的形式进行了预先配置。因此,在 Spring Boot 项目中加入相关依赖启动器后,基本上不需要任何配置就可以运行程序,当然,也可以对这些自动配置类中默认的配置进行更改。

总结:

因此 springboot 底层实现自动配置的步骤是:

1. springboot 应用启动。

2. @SpringBootApplication 起作用。

3. @EnableAutoConfiguration。

4. @AutoConfigurationPackage:这个组合注解主要是 @Import(AutoConfigurationPackages.Registrar.class),它通过将 Registrar 类导入到容器中,而 
Registrar 类作用是扫描主配置类同级目录以及子包,并将相应的组件导入到 springboot 创建管理的容器中。

5. @Import(AutoConfigurationImportSelector.class):它通过将AutoConfigurationImportSelector 类导入到容器中;AutoConfigurationImportSelector 类作用是通过 selectImports 方法执行的过程中,会使用内部工具类 SpringFactoriesLoader,查找 classpath 上所有 jar 包中的 META-INF/spring.factories 进行加载,实现将配置类信息交给 SpringFactory 加载器进行一系列的容器创建过程。
@ComponentScan 注解

@ComponentScan 注解具体扫描的包的根路径由 Spring Boot 项目主程序启动类所在包位置决定,在扫描过程中由前面介绍的 @AutoConfigurationPackage 注解进行解析,从而得到 Spring Boot 项目主程序启动类所在包的具体位置。

总结
@SpringBootApplication 的注解的功能简单来说就是 3 个注解的组合注解:

|- @SpringBootConfiguration
   // 通过 javaConfig 的方式来添加组件到 IOC 容器中
   |- @Configuration
|- @EnableAutoConfiguration
   // 自动配置包,与 @ComponentScan 扫描到的添加到 IOC
   |- @AutoConfigurationPackage
   // 到 META-INF/spring.factories 中定义的 bean 添加到 IOC 容器中
   |- @Import(AutoConfigurationImportSelector.class) 
// 包扫描
|- @ComponentScan

SpringBoot 数据访问

Spring Boot 整合 MyBatis

MyBatis 是一款优秀的持久层框架,Spring Boot 官方虽然没有对 MyBatis 进行整合,但是 MyBatis 团队自行适配了对应的启动器,进一步简化了使用 MyBatis 进行数据的操作。

因为 Spring Boot 框架开发的便利性,所以实现 Spring Boot 与数据访问层框架(例如 MyBatis)的整合非常简单,主要是引入对应的依赖启动器,并进行数据库相关参数设置即可。

基础环境搭建
1)数据准备

在 MySQL 中,先创建了一个数据库 springbootdata,然后创建了两个表 t_articlet_comment 并向表中插入数据。其中评论表 t_commenta_id 与文章表 t_article 的主键 id 相关联。

# 创建数据库
CREATE DATABASE IF NOT EXISTS springbootdata DEFAULT CHARACTER SET utf8;
# 选择使用数据库
USE springbootdata;

# 创建表 t_article 并插入相关数据
DROP TABLE IF EXISTS t_article;
CREATE TABLE t_article
(
    id      int(20) NOT NULL AUTO_INCREMENT COMMENT '文章id',
    title   varchar(200) DEFAULT NULL COMMENT '文章标题',
    content longtext COMMENT '文章内容',
    PRIMARY KEY (id)
) ENGINE = InnoDB AUTO_INCREMENT = 2 DEFAULT CHARSET=utf8;

INSERT INTO t_article VALUES (1, 'Spring Boot 基础入门', '从入门到精通讲解...');
INSERT INTO t_article VALUES (2, 'Spring Cloud 基础入门', '从入门到精通讲解...');

# 创建表 t_comment 并插入相关数据
DROP TABLE IF EXISTS t_comment;
CREATE TABLE t_comment
(
    id      int(20) NOT NULL AUTO_INCREMENT COMMENT '评论id',
    content longtext COMMENT '评论内容',
    author  varchar(200) DEFAULT NULL COMMENT '评论作者',
    a_id    int(20)      DEFAULT NULL COMMENT '关联的文章id',
    PRIMARY KEY (id)
) ENGINE = InnoDB AUTO_INCREMENT = 3 DEFAULT CHARSET=utf8;
INSERT INTO t_comment VALUES (1, '很全、很详细', 'lucy', 1);
INSERT INTO t_comment VALUES (2, '赞一个', 'tom', 1);
INSERT INTO t_comment VALUES (3, '很详细', 'eric', 1);
INSERT INTO t_comment VALUES (4, '很好,非常详细', '张三', 1);
INSERT INTO t_comment VALUES (5, '很不错', '李四', 2);
2)创建项目,引入相应的启动器

使用 Spring Initializr 来初始化项目。

项目名:springbootmybatis

包名:com.renda

启动器:SQL 的 MyBatis Framework、MySQL Driver,Web 的 Spring Web

3)编写与数据库表 t_comment 和 t_article 对应的实体类 Comment 和 Article

com.renda.pojo.Comment

public class Comment {
      
    private Integer id;
    private String content;
    private String author;
    private Integer aId;
    // getter setter toString ...
}

com.renda.pojo.Article

public class Article {
      
    private Integer id;
    private String title;
    private String content;
    // getter setter toString ...
}
4)编写配置文件

application.properties  更名为 application.yml。在 application.properties 配置文件中进行数据库连接配置:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC&characterEncoding=UTF-8
    username: root
    password: password
注解方式整合 Mybatis

需求:实现通过 ID 查询 Comment 信息。

1)创建一个对 t_comment 表数据操作的接口 CommentMapper

com.renda.mapper.CommentMapper

public interface CommentMapper {
      
    @Select("select * from t_comment where id = #{id}")
    Comment findById(Integer id);
}
2)在 Spring Boot 项目启动类上添加 @MapperScan("xxx") 注解

com.renda.SpringbootmybatisApplication

@SpringBootApplication
@MapperScan("com.renda.bootmybatis.mapper") //执行扫描mapper的包名
public class SpringbootmybatisApplication {

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

}
3)编写测试方法

导入 Junit 的依赖,增加测试方法:

com.renda.SpringbootmybatisApplicationTests

@RunWith(SpringRunner.class)
@SpringBootTest
class SpringbootmybatisApplicationTests {

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    @Autowired
    private CommentMapper commentMapper;

    @Test
    void findCommentById() {
        Comment comment = commentMapper.findById(1);
        System.out.println(comment);
    }

}

控制台中查询的 Comment 的 aId 属性值为 null,没有映射成功。这是因为编写的实体类 Comment 中使用了驼峰命名方式将 t_comment 表中的 a_id 字段设计成了 aId 属性,所以无法正确映射查询结果。

为了解决上述由于驼峰命名方式造成的表字段值无法正确映射到类属性的情况,可以在 Spring Boot 全局配置文件 application.yml 中添加开启驼峰命名匹配映射配置,示例代码如下:

mybatis:
  configuration:
    # 开启驼峰命名匹配映射
    map-underscore-to-camel-case: true
配置文件的方式整合 MyBatis

第一、二步骤使用 Free Mybatis plugin 插件生成:使用 IDEA 连接 Database,然后选中要自动生成代码的表,右键 -> mybatis-generator -> 按照需求输入信息,点击 ok。

1)创建一个用于对数据库表 t_article 数据操作的接口 ArticleMapper
public interface ArticleMapper {
      
    int deleteByPrimaryKey(Integer id);

    int insert(Article record);

    int insertSelective(Article record);

    Article selectByPrimaryKey(Integer id);

    int updateByPrimaryKeySelective(Article record);

    int updateByPrimaryKey(Article record);
}
2)创建 XML 映射文件

resources 目录下创建一个统一管理映射文件的包 mapper,并在该包下编写与 ArticleMapper 接口方应的映射文件 ArticleMapper.xml


mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

  
    
    
    
  resultMap>
  
    id, title, content
  sql>
  
    select 
    
    from t_article
    where id = #{id,jdbcType=INTEGER}
  select>
  
    delete from t_article
    where id = #{id,jdbcType=INTEGER}
  delete>
  
    insert into t_article (title, content)
    values (#{title,jdbcType=VARCHAR}, #{content,jdbcType=VARCHAR})
  insert>
  
    insert into t_article
    
      
        title,
      if>
      
        content,
      if>
    trim>
    
      
        #{title,jdbcType=VARCHAR},
      if>
      
        #{content,jdbcType=VARCHAR},
      if>
    trim>
  insert>
  
    update t_article
    
      
        title = #{title,jdbcType=VARCHAR},
      if>
      
        content = #{content,jdbcType=VARCHAR},
      if>
    set>
    where id = #{id,jdbcType=INTEGER}
  update>
  
    update t_article
    set title = #{title,jdbcType=VARCHAR},
      content = #{content,jdbcType=VARCHAR}
    where id = #{id,jdbcType=INTEGER}
  update>
mapper>
3)配置 XML 映射文件路径

在项目中编写的 XML 映射文件,Spring Boot 并无从知晓,所以无法扫描到该自定义编写的 XML 配置文件,还必须在全局配置文件 application.yml 中添加 MyBatis 映射文件路径的配置,同时需要添加实体类别名映射路径,示例代码如下:

mybatis:
  configuration:
    # 开启驼峰命名匹配映射
    map-underscore-to-camel-case: true
  # 加载 resources/mapper 文件夹下的所有的 xml 文件
  mapper-locations: classpath:mapper/*.xml
  # 配置 XML 映射文件中指定的实体类别名路径
  type-aliases-package: com.renda.pojo
4)编写单元测试进行接口方法测试
@RunWith(SpringRunner.class)
@SpringBootTest
class SpringbootmybatisApplicationTests {

    @Autowired
    private ArticleMapper articleMapper;

    @Test
    void findArticleById() {
        Article article = articleMapper.selectByPrimaryKey(1);
        System.out.println(article);
    }

}

Spring Boot 整合 Redis

1)添加Redis依赖包

在项目的 pom.xml 中添加如下:



    org.springframework.bootgroupId>
    spring-boot-starter-data-redisartifactId>
dependency>
2)配置 Redis 数据库连接

在 application.yml 中配置redis数据库连接信息,如下:

spring:
  redis:
    # Redis 服务器地址
    host: 192.168.186.128
    # Redis 服务器连接端口
    port: 6379
    jedis:
      pool:
        # 连接池最大连接数(使用负值表示没有限制)
        max-active: 18
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: 3000
        # 连接池中的最大空闲连接
        max-idle: 20
        # 连接池中的最小空闲连接
        min-idle: 2
    # 连接超时时间(毫秒)
    timeout: 3000
    # Redis 数据库索引(默认为 0)
    database: 0
3)编写 Redis 操作工具类

将 RedisTemplate 实例包装成一个工具类,便于对 redis 进行数据操作。

package com.renda.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @author Renda Zhang
 * @since 2020-10-30 1:00
 */
@Component
public class RedisUtils {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 读取缓存
     */
    public Object get(final String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 写入缓存
     */
    public boolean set(String key, Object value) {
        boolean result = false;
        try {
            redisTemplate.opsForValue().set(key, value, 1, TimeUnit.DAYS);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 更新缓存
     */
    public boolean getAndSet(final String key, String value) {
        boolean result = false;
        try {
            redisTemplate.opsForValue().getAndSet(key, value);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 删除缓存
     */
    public boolean delete(final String key) {
        boolean result = false;
        try {
            redisTemplate.delete(key);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}
4)测试

写一个测试用例类来完成对 redis 的整合。

@RunWith(SpringRunner.class)
@SpringBootTest
class SpringbootmybatisApplicationTests {

    // 写入,key:1,value:mysql 数据库中 id 为 1 的 article 记录
    @Autowired
    private RedisUtils redisUtils;

    @Test
    void writeRedis() {
        redisUtils.set("1", articleMapper.selectByPrimaryKey(1));
        System.out.println("success");
    }

    @Test
    void readRedis() {
        Article article = (Article) redisUtils.get("1");
        System.out.println(article);
    }

}

SpringBoot 视图技术

支持的视图技术

前端模板引擎技术的出现,使前端开发人员无需关注后端业务的具体实现,只关注自己页面的呈现效果即可,并且解决了前端代码错综复杂的问题、实现了前后端分离开发。Spring Boot 框架对很多常用的模板引擎技术(如:FreeMarker、Thymeleaf、Mustache 等)提供了整合支持。

Spring Boot 不太支持常用的 JSP 模板,并且没有提供对应的整合配置,这是因为使用嵌入式 Servlet 容器的 Spring Boot 应用程序对于 JSP 模板存在一些限制 :

  • 在 Jetty 和 Tomcat 容器中,Spring Boot 应用被打包成 war 文件可以支持 JSP。但 Spring Boot 默认使用嵌入式 Servlet 容器以 JAR 包方式进行项目打包部署,这种 JAR 包方式不支持 JSP。

  • 如果使用 Undertow 嵌入式容器部署 Spring Boot 项目,也不支持 JSP 模板。(Undertow  是红帽公司开发的一款基于 NIO 的高性能 Web 嵌入式服务器)

  • Spring Boot 默认提供了一个处理请求路径 “/error” 的统一错误处理器,返回具体的异常信息。使用 JSP 模板时,无法对默认的错误处理器进行覆盖,只能根据 Spring Boot 要求在指定位置定制错误页面。

Thymeleaf

Thymeleaf 是一种现代的基于服务器端的 Java 模板引擎技术,也是一个优秀的面向 Java 的 XML、XHTML、HTML5 页面模板,它具有丰富的标签语言、函数和表达式,在使用 Spring Boot 框架进行页面设计时,一般会选择 Thymeleaf 模板。

Thymeleaf 语法

在 HTML 页面上使用 Thymeleaf 标签,Thymeleaf 标签能够动态地替换掉静态内容,使页面动态展示。

为了更直观的认识 Thymeleaf,下面展示一个在 HTML 文件中嵌入了 Thymeleaf 的页面文件,示例代码如下:

html>

    
        
        
        Thymeleaftitle><br>    head><br>    <body><br>        <p th:text="${hello}">Hello Thymeleafp><br>    body><br>html><br></code></pre> <p>上述代码中,<code>“xmlns:th="http://www.thymeleaf.org"</code> 用于引入 Thymeleaf 模板引擎标签,使用关键字 <code>th</code> 标注标签是 Thymeleaf 模板提供的标签,其中,<code>th:href="@{/css/gtvg.css}"</code> 用于引入外联样式文件,<code>th:text="${hello}"</code> 用于动态显示标签文本内容。</p> <p>常用标签:</p> <ul> <li><p><code>th:insert</code> - 布局标签,替换内容到引入的文件</p></li> <li><p><code>th:replace</code> - 页面片段包含(类似 JSP 中的 include 标签)</p></li> <li><p><code>th:each</code> - 元素遍历(类似 JSP 中的 <code>c:forEach</code> 标签)</p></li> <li><p><code>th:if</code> - 条件判断,如果为真</p></li> <li><p><code>th:unless</code> - 条件判断,如果为假</p></li> <li><p><code>th:switch</code> - 条件判断,进行选择性匹配</p></li> <li><p><code>th:case</code> - 条件判断,进行选择性匹配</p></li> <li><p><code>th:value</code> - 属性值修改,指定标签属性值</p></li> <li><p><code>th:href</code> - 用于设定链接地址</p></li> <li><p><code>th:src</code> - 用于设定链接地址</p></li> <li><p><code>th:text</code> - 用于指定标签显示的文本内容</p></li> </ul> <p>标准表达式:</p> <ul> <li><p>变量表达式-  <code>${…}</code></p></li> <li><p>选择变量表达式 - <code>*{…}</code></p></li> <li><p>消息表达式 - <code>#{…}</code></p></li> <li><p>链接 URL 表达式 - <code>@{…}</code></p></li> <li><p>片段表达式 - <code>~{…}</code></p></li> </ul> <h6><span style="font-weight:bold;">变量表达式 `${…}`</span></h6> <p>变量表达式 <code>${...}</code> 主要用于获取上下文中的变量值,示例代码如下:</p> <pre class="has"><code><p th:text="${title}">这是标题p><br></code></pre> <p>示例使用了 Thymeleaf 模板的变量表达式 <code>${...}</code> 用来动态获取 P 标签中的内容,如果当前程序没有启动或者当前上下文中不存在 title 变量,该片段会显示标签默认值“这是标题”;如果当前上下文中存在 title 变量并且程序已经启动,当前 P 标签中的默认文本内容将会被 title 变量的值所替换,从而达到模板引擎页面数据动态替换的效果。</p> <p>同时,Thymeleaf 为变量所在域提供了一些内置对象,具体如下所示:</p> <pre class="has"><code>#ctx:上下文对象<br>#vars:上下文变量<br>#locale:上下文区域设置<br>#request:(仅限 Web Context)HttpServletRequest 对象<br>#response:(仅限 Web Context)HttpServletResponse 对象<br>#session:(仅限 Web Context)HttpSession 对象<br>#servletContext:(仅限 Web Context)ServletContext 对象<br></code></pre> <p>结合上述内置对象的说明,假设要在 Thymeleaf 模板引擎页面中动态获取当前国家信息,可以使用 <code>#locale</code> 内置对象,示例代码如下:</p> <pre class="has"><code>The locale country is: <span th:text="${#locale.country}">Chinaspan><br></code></pre> <p>上述代码中,使用 <code>th:text="${#locale.country}"</code> 动态获取当前用户所在国家信息,其中标签内默认内容为 China,程序启动后通过浏览器查看当前页面时,Thymeleaf 会通过浏览器语言设置来识别当前用户所在国家信息,从而实现动态替换。</p> <h6><span style="font-weight:bold;">选择变量表达式 `*{…}`</span></h6> <p>选择变量表达式和变量表达式用法类似,一般用于从被选定对象而不是上下文中获取属性值,如果没有选定对象,则和变量表达式一样,示例代码如下:</p> <pre class="has"><code><div th:object="${book}"><br>    <p>titile: <span th:text="*{title}">标题span>.p><br>div><br></code></pre> <p><code>*{title}</code> 选择变量表达式获取当前指定对象 book 的 title 属性值。</p> <h6><span style="font-weight:bold;">消息表达式 `#{…}`</span></h6> <p>消息表达式 <code>#{...}</code> 主要用于 Thymeleaf 模板页面国际化内容的动态替换和展示,使用消息表达式 <code>#{...}</code> 进行国际化设置时,还需要提供一些国际化配置文件。</p> <h6><span style="font-weight:bold;">链接表达式 `@{…}`</span></h6> <p>链接表达式 <code>@{...}</code> 一般用于页面跳转或者资源的引入,在 Web 开发中占据着非常重要的地位,并且使用也非常频繁,示例代码如下:</p> <pre class="has"><code><a th:href="@{http://localhost:8080/order/details(orderId=${o.id})}">viewa> <br><a th:href="@{/order/details(orderId=${o.id},pid=${p.id})}">viewa><br></code></pre> <p>上述代码中,链接表达式 <code>@{...}</code> 分别编写了绝对链接地址和相对链接地址。在有参表达式中,需要按照 <code>@{路径(参数名称=参数值,参数名称=参数值...)}</code> 的形式编写,同时该参数的值可以使用变量表达式来传递动态参数值。</p> <h6><span style="font-weight:bold;">片段表达式 `~{…}`</span></h6> <p>片段表达式 <code>~{...}</code> 用来标记一个片段模板,并根据需要移动或传递给其他模板。其中,最常见的用法是使用 <code>th:insert</code> 或 <code>th:replace</code> 属性插入片段,示例代码如下:</p> <pre class="has"><code><div th:insert="~{thymeleafDemo::title}">div><br></code></pre> <p>上述代码中,使用 <code>th:insert</code> 属性将 <code>title</code> 片段模板引用到该标签中。<code>thymeleafDemo</code> 为模板名称,Thymeleaf 会自动查找 <code>/resources/templates/</code> 目录下的 <code>thymeleafDemo</code> 模板,title 为片段名称。</p> <h5><span style="font-weight:bold;">基本使用</span></h5> <p>1) Thymeleaf 模板基本配置</p> <p>首先在 Springbootdemo2 项目中使用 Thymeleaf 模板,首先必须保证引入 Thymeleaf 依赖,示例代码如下:</p> <pre class="has"><code><dependency><br>    <groupId>org.springframework.bootgroupId><br>    <artifactId>spring-boot-starter-thymeleafartifactId><br>dependency><br></code></pre> <p>其次,在全局配置文件中配置 Thymeleaf 模板的一些参数。一般 Web 项目都会使用下列配置,示例代码如:</p> <pre class="has"><code>spring:<br>  thymeleaf:<br>    # 在开发阶段,为了验证建议关闭缓存<br>    cache: true<br>    # 模板编码<br>    encoding: UTF-8<br>    # 应用于模板的模板模式<br>    mode: HTML5<br>    # 指定模板页面存放路径<br>    prefix: classpath:/templates/<br>    # 指定模板页面名称的后缀<br>    suffix: .html<br></code></pre> <p>上述配置中,<code>spring.thymeleaf.cache</code> 表示是否开启 Thymeleaf 模板缓存,默认为 true,在开发过程中通常会关闭缓存,保证项目调试过程中数据能够及时响应;<code>spring.thymeleaf.prefix</code> 指定了 Thymeleaf 模板页面的存放路径,默认为<code>classpath:/templates/</code>;<code>spring.thymeleaf.suffix</code> 指定了 Thymeleaf 模板页面的名称后缀,默认为 <code>.html</code>。</p> <p>2)静态资源的访问</p> <p>开发 Web 应用时,难免需要使用静态资源。Spring boot 默认设置了静态资源的访问路径。</p> <p>使用 Spring Initializr 方式创建的 Spring Boot 项目,默认生成了一个 resources 目录,在 resources 目录中的 public、resources、static 三个子目录下,Spring boot 默认会挨个从public、resources、static 里面查找静态资源。</p> <h5><span style="font-weight:bold;">完成数据的页面展示</span></h5> <p>1)创建 Spring Boot 项目,引入 Thymeleaf 依赖。</p> <p>2)编写配置文件。</p> <p>编辑 application.yml 全局配置文件,在该文件中对 Thymeleaf 模板页面的数据缓存进行设置。</p> <pre class="has"><code># thymeleaf 页面缓存设置(默认为 true),开发中方便调试应设置为 false,上线稳定后应保持默认 true<br>spring:<br>  thymeleaf:<br>    cache: false<br></code></pre> <p>使用 <code>spring.thymeleaf.cache=false</code> 将 Thymeleaf 默认开启的缓存设置为了 false,用来关闭模板页面缓存。</p> <p>3)创建 web 控制类</p> <p>在项目中创建名为 <code>com.renda.controller</code> 的包,并在该包下创建一个用于前端模板页面动态数据替换效果测试的访问实体类 <code>LoginController</code>。</p> <pre class="has"><code>@Controller<br>public class LoginController { <br><br>    @RequestMapping("/toLogin")<br>    public String toLoginView(Model model){ <br>        model.addAttribute("currentYear", Calendar.getInstance().get(Calendar.YEAR));<br>        return "login"; // resources/templates/login.html<br>    }<br><br>}<br></code></pre> <p><code>toLoginView()</code> 方法用于向登录页面 <code>login.html</code> 跳转,同时携带了当前年份信息 <code>currentYear</code>。</p> <p>4)创建模板页面并引入静态资源文件。</p> <p>在 <code>classpath:/templates/</code> 目录下引入一个用户登录的模板页面 <code>login.html</code>。</p> <pre class="has"><code>html><br><html lang="en" xmlns:th="http://www.thymeleaf.org"><br><head><br>    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><br>    <meta name="viewport" content="width=device-width, initial-scale=1,shrink-to-fit=no"><br>    <title>用户登录界面title><br>    <link th:href="@{../static/css/bootstrap.min.css}" rel="stylesheet"><br>    <link th:href="@{../static/css/signin.css}" rel="stylesheet"><br>head><br><body class="text-center"><br><br><form class="form-signin"><br>    <img class="mb-4" th:src="@{../static/img/login.png}" width="72" height="72"><br>    <h1 class="h3 mb-3 font-weight-normal">请登录h1><br>    <input type="text" class="form-control"th:placeholder="用户名" required="" autofocus=""><br>    <input type="password" class="form-control"th:placeholder="密码" required=""><br>    <div class="checkbox mb-3"><br>        <label><br>            <input type="checkbox" value="remember-me"> 记住我<br>        label><br>    div><br>    <button class="btn btn-lg btn-primary btn-block" type="submit" >登录button><br>    <p class="mt-5 mb-3 text-muted">© <span th:text="${currentYear}">2019span>-<span th:text="${currentYear}+1">2020span>p><br>form><br>body><br>html><br></code></pre> <p>通过 <code>xmlns:th="http://www.thymeleaf.org</code> 引入了 Thymeleaf 模板标签;</p> <p>使用 <code>th:href</code> 和 <code>th:src</code> 分别引入了两个外联的样式文件和一个图片;</p> <p>使用 <code>th:text</code> 引入了后台动态传递过来的当前年份 <code>currentYear</code>。</p> <p>5)效果测试</p> <p>可以看出,登录页面 <code>login.html</code> 显示正常,在页面底部动态显示了当前日期 <code>2020-2021</code>,而不是文件中的静态数字 <code>2019-2020</code>。这进一步说明了 Spring Boot 与 Thymeleaf 整合成功,完成了静态资源的引入和动态数据的显示。</p> <h3><span style="font-weight:bold;">SpringBoot 实战演练</span></h3> <p>实战技能补充:lombok</p> <pre class="has"><code><dependency><br>    <groupId>org.projectlombokgroupId><br>    <artifactId>lombokartifactId><br>    <version>1.18.12version><br>    <br>    <scope>providedscope><br>dependency><br></code></pre> <p>需求:实现用户的 CRUD 功能</p> <p>初始化数据库信息:</p> <pre class="has"><code>DROP TABLE IF EXISTS `user`;<br>CREATE TABLE `user`<br>(<br>    id int(11) NOT NULL AUTO_INCREMENT COMMENT '用户id',<br>    username varchar(100) DEFAULT NULL COMMENT '用户名',<br>    password varchar(100) DEFAULT NULL COMMENT '密码',<br>    birthday varchar(100) DEFAULT NULL COMMENT '生日',<br>    PRIMARY KEY (id)<br>) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET=utf8;<br>INSERT INTO `user` VALUES (1, 'zhangsan', '123', '2020-10-1');<br>INSERT INTO `user` VALUES (2, 'lisi', '123', '2020-10-2');<br>INSERT INTO `user` VALUES (3, 'wangwu', '123', '2020-10-10');<br>INSERT INTO `user` VALUES (4, 'yuanjing', '123', '2020-10-11');<br></code></pre> <h4><span style="font-weight:bold;">1)创建 springboot 工程</span></h4> <p>使用 Spring Initializr 新建一个工程 <code>springbootuser</code>,选择依赖:Developer Tools -> Lombok,Web -> Spring Web,SQL -> [MyBatis Framework、MySQL Driver]。</p> <h4><span style="font-weight:bold;">2)编辑 pom.xml</span></h4> <pre class="has"><code><br><dependency><br>    <groupId>com.alibabagroupId><br>    <artifactId>druidartifactId><br>    <version>1.1.3version><br>dependency><br></code></pre> <h4><span style="font-weight:bold;">3)User 实体类编写</span></h4> <p>使用 FreeMyBatis 生成实体类。</p> <p>使用 FreeMyBatis 生成 UserMapper 相关的代码。</p> <p><code>com.renda.pojo.User</code></p> <pre class="has"><code>@Data // Lombok 自动生成 getter 和 setter<br>public class User implements Serializable { <br>    /**<br>     * 用户id<br>     */<br>    private Integer id;<br><br>    /**<br>     * 用户名<br>     */<br>    private String username;<br><br>    /**<br>     * 密码<br>     */<br>    private String password;<br><br>    /**<br>     * 生日<br>     */<br>    private String birthday;<br><br>    private static final long serialVersionUID = 1L;<br>}<br></code></pre> <h4><span style="font-weight:bold;">4)UserMapper 编写及 xml 文件</span></h4> <p><code>com.renda.mapper.UserMapper</code></p> <pre class="has"><code>public interface UserMapper { <br>    int deleteByPrimaryKey(Integer id);<br><br>    int insert(User record);<br><br>    int insertSelective(User record);<br><br>    User selectByPrimaryKey(Integer id);<br><br>    int updateByPrimaryKeySelective(User record);<br><br>    int updateByPrimaryKey(User record);<br>}<br></code></pre> <p><code>src\main\resources\mapper\UserMapper.xml</code></p> <pre class="has"><code><?xml  version="1.0" encoding="UTF-8"?><br>mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><br><mapper namespace="com.renda.mapper.UserMapper"><br>  <resultMap id="BaseResultMap" type="com.renda.pojo.User"><br>    <id column="id" jdbcType="INTEGER" property="id" /><br>    <result column="username" jdbcType="VARCHAR" property="username" /><br>    <result column="password" jdbcType="VARCHAR" property="password" /><br>    <result column="birthday" jdbcType="VARCHAR" property="birthday" /><br>  resultMap><br>  <sql id="Base_Column_List"><br>    id, username, `password`, birthday<br>  sql><br>  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap"><br>    select <br>    <include refid="Base_Column_List" /><br>    from user<br>    where id = #{id,jdbcType=INTEGER}<br>  select><br>  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer"><br>    delete from user<br>    where id = #{id,jdbcType=INTEGER}<br>  delete><br>  <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.renda.pojo.User" useGeneratedKeys="true"><br>    insert into user (username, `password`, birthday<br>      )<br>    values (#{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, #{birthday,jdbcType=VARCHAR}<br>      )<br>  insert><br>  <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.renda.pojo.User" useGeneratedKeys="true"><br>    insert into user<br>    <trim prefix="(" suffix=")" suffixOverrides=","><br>      <if test="username != null"><br>        username,<br>      if><br>      <if test="password != null"><br>        `password`,<br>      if><br>      <if test="birthday != null"><br>        birthday,<br>      if><br>    trim><br>    <trim prefix="values (" suffix=")" suffixOverrides=","><br>      <if test="username != null"><br>        #{username,jdbcType=VARCHAR},<br>      if><br>      <if test="password != null"><br>        #{password,jdbcType=VARCHAR},<br>      if><br>      <if test="birthday != null"><br>        #{birthday,jdbcType=VARCHAR},<br>      if><br>    trim><br>  insert><br>  <update id="updateByPrimaryKeySelective" parameterType="com.renda.pojo.User"><br>    update user<br>    <set><br>      <if test="username != null"><br>        username = #{username,jdbcType=VARCHAR},<br>      if><br>      <if test="password != null"><br>        `password` = #{password,jdbcType=VARCHAR},<br>      if><br>      <if test="birthday != null"><br>        birthday = #{birthday,jdbcType=VARCHAR},<br>      if><br>    set><br>    where id = #{id,jdbcType=INTEGER}<br>  update><br>  <update id="updateByPrimaryKey" parameterType="com.renda.pojo.User"><br>    update user<br>    set username = #{username,jdbcType=VARCHAR},<br>      `password` = #{password,jdbcType=VARCHAR},<br>      birthday = #{birthday,jdbcType=VARCHAR}<br>    where id = #{id,jdbcType=INTEGER}<br>  update><br>mapper><br></code></pre> <h4><span style="font-weight:bold;">5)UserService 接口及实现类编写</span></h4> <p><code>com.renda.service.UserService</code></p> <pre class="has"><code>public interface UserService { <br><br>    /**<br>     * 查询所有<br>     */<br>    List queryAll();<br><br>    /**<br>     * 通过 ID 查询<br>     */<br>    User findById(Integer id);<br><br>    /**<br>     * 新增<br>     */<br>    void insert(User user);<br><br>    /**<br>     * 通过 ID 删除<br>     */<br>    void deleteById(Integer id);<br><br>    /**<br>     * 修改<br>     */<br>    void update(User user);<br><br>}<br></code></pre> <p><code>com.renda.service.impl.UserServiceImpl</code></p> <pre class="has"><code>@Service<br>public class UserServiceImpl implements UserService { <br><br>    @Autowired<br>    private UserMapper userMapper;<br><br>    @Override<br>    public List queryAll() { <br>        return userMapper.queryAll();<br>    }<br><br>    @Override<br>    public User findById(Integer id) { <br>        return userMapper.selectByPrimaryKey(id);<br>    }<br><br>    @Override<br>    public void insert(User user) { <br>        // 将除 id 外所有的列都拼接入 SQL 语句<br>        // userMapper.insert(user);<br>        // 只将不为空的列才拼接入 SQL 语句(优先使用,减少高并发下数据传输)<br>        userMapper.insertSelective(user);<br>    }<br><br>    @Override<br>    public void deleteById(Integer id) { <br>        userMapper.deleteByPrimaryKey(id);<br>    }<br><br>    @Override<br>    public void update(User user) { <br>        userMapper.updateByPrimaryKeySelective(user);<br>    }<br><br>}<br></code></pre> <h4><span style="font-weight:bold;">6)UserController 编写</span></h4> <p><code>com.renda.controller.UserController</code></p> <pre class="has"><code>/**<br> * restful 格式进行访问<br> * 查询:GET<br> * 新增: POST<br> * 更新:PUT<br> * 删除: DELETE<br> *<br> * @author Renda Zhang<br> * @since 2020-10-31 1:36<br> */<br>@RestController<br>@RequestMapping("/user")<br>public class UserController { <br><br>    @Autowired<br>    private UserService userService;<br><br>    /**<br>     * 查询所有<br>     */<br>    @GetMapping("/query")<br>    public List queryAll(){ <br>        return userService.queryAll();<br>    }<br><br>    /**<br>     * 通过 ID 查询<br>     */<br>    @GetMapping("/query/{id}")<br>    public User queryById(@PathVariable Integer id){ <br>        return userService.findById(id);<br>    }<br><br>    /**<br>     * 删除<br>     */<br>    @DeleteMapping("/delete/{id}")<br>    public String delete(@PathVariable Integer id){ <br>        userService.deleteById(id);<br>        return "删除成功";<br>    }<br><br>    /**<br>     * 新增<br>     */<br>    @PostMapping("/insert")<br>    public String insert(User user){ <br>        userService.insert(user);<br>        return "新增成功";<br>    }<br><br>    /**<br>     * 修改<br>     */<br>    @PutMapping("/update")<br>    public String update(User user){ <br>        userService.update(user);<br>        return "修改成功";<br>    }<br><br>}<br></code></pre> <h4><span style="font-weight:bold;">7)全局配置文件 application.yml</span></h4> <p>重命名 <code>application.properties</code> 为 <code>application.yml</code></p> <p><code>src\main\resources\application.yml</code></p> <pre class="has"><code># 服务器配置<br>server:<br>  port: 8090<br><br>spring:<br>  # 数据源配置<br>  datasource:<br>    name: druid<br>    type: com.alibaba.druid.pool.DruidDataSource<br>    url: jdbc:mysql://localhost:3306/springbootdata?characterEncoding=utf-8&serverTimezone=UTC<br>    username: root<br>    password: password<br><br># 整合 MyBatis<br>mybatis:<br>  # 声明 MyBatis 文件所在的位置<br>  mapper-locations: classpath:mapper/*Mapper.xml<br></code></pre> <h4><span style="font-weight:bold;">8)启动类</span></h4> <p><code>com.renda.SpringbootuserApplication</code></p> <pre class="has"><code>@SpringBootApplication<br>// 使用的 Mybatis, 扫描 com.renda.mapper<br>@MapperScan("com.renda.mapper")<br>public class SpringbootuserApplication { <br><br>    public static void main(String[] args) { <br>        SpringApplication.run(SpringbootuserApplication.class, args);<br>    }<br><br>}<br></code></pre> <h4><span style="font-weight:bold;">10)使用 Postman 测试</span></h4> <ul> <li><p>GET <code>http://localhost:8090/user/query</code></p></li> <li><p>GET <code>http://localhost:8090/user/query/1</code></p></li> <li><p>POST <code>http://localhost:8090/user/insert?username=renda&password=123456&birthday=1995-12-27</code></p></li> <li><p>PUT <code>http://localhost:8090/user/update?username=RendaZhang&password=00000&birthday=1997-12-27&id=5</code></p></li> <li><p>DELETE <code>http://localhost:8090/user/delete/5</code></p></li> </ul> <h3><span style="font-weight:bold;">Spring Boot 项目部署</span></h3> <p>需求:将 Spring Boot 项目使用 maven 指令打成 jar 包并运行测试。</p> <h4><span style="font-weight:bold;">分析</span></h4> <p>1)添加打包组件将项目中的资源、配置、依赖包打到一个 jar 包中;可以使用 maven 的 package 命令。</p> <p>2)部署:<code>java -jar 包名</code></p> <h4><span style="font-weight:bold;">步骤实现</span></h4> <p>确保 pom.xml 文件中有如下的打包组件:</p> <pre class="has"><code><build><br>    <plugins><br>        <br>        <plugin><br>            <groupId>org.springframework.bootgroupId><br>            <artifactId>spring-boot-maven-pluginartifactId><br>        plugin><br>    plugins><br>build><br></code></pre> <p>部署运行:</p> <pre class="has"><code>java -jar springbootuser-0.0.1-SNAPSHOT.jar<br></code></pre> </div> </div> </div> </div> </div> </div> </div> <!--PC和WAP自适应版--> <div id="SOHUCS" sid="1384698058687729664"></div> <script type="text/javascript" src="/views/front/js/chanyan.js"></script> <!-- 文章页-底部 动态广告位 --> <div class="youdao-fixed-ad" id="detail_ad_bottom"></div> </div> <div class="col-md-3"> <div class="row" id="ad"> <!-- 文章页-右侧1 动态广告位 --> <div id="right-1" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_1"> </div> </div> <!-- 文章页-右侧2 动态广告位 --> <div id="right-2" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_2"></div> </div> <!-- 文章页-右侧3 动态广告位 --> <div id="right-3" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_3"></div> </div> </div> </div> </div> </div> </div> <div class="container"> <h4 class="pt20 mb15 mt0 border-top">你可能感兴趣的:(boot入门思想,spring)</h4> <div id="paradigm-article-related"> <div class="recommend-post mb30"> <ul class="widget-links"> <li><a href="/article/1835513570171908096.htm" title="底层逆袭到底有多难,不甘平凡的你准备好了吗?让吴起给你说说" target="_blank">底层逆袭到底有多难,不甘平凡的你准备好了吗?让吴起给你说说</a> <span class="text-muted">造命者说</span> <div>底层逆袭到底有多难,不甘平凡的你准备好了吗?让吴起给你说说我叫吴起,生于公元前440年的战国初期,正是群雄并起、天下纷争不断的时候。后人说我是军事家、政治家、改革家,是兵家代表人物。评价我一生历仕鲁、魏、楚三国,通晓兵家、法家、儒家三家思想,在内政军事上都有极高的成就。周安王二十一年(公元前381年),因变法得罪守旧贵族,被人乱箭射死。我出生在卫国一个“家累万金”的富有家庭,从年轻时候起就不甘平凡</div> </li> <li><a href="/article/1835510025561403392.htm" title="《投行人生》读书笔记" target="_blank">《投行人生》读书笔记</a> <span class="text-muted">小蘑菇的树洞</span> <div>《投行人生》----作者詹姆斯-A-朗德摩根斯坦利副主席40年的职业洞见-很短小精悍的篇幅,比较适合初入职场的新人。第一部分成功的职业生涯需要规划1.情商归为适应能力分享与协作同理心适应能力,更多的是自我意识,你有能力识别自己的情并分辨这些情绪如何影响你的思想和行为。2.对于初入职场的人的建议,细节,截止日期和数据很重要截止日期,一种有效的方法是请老板为你所有的任务进行优先级排序。和老板喝咖啡的好</div> </li> <li><a href="/article/1835506868877881344.htm" title="每日一题——第八十九题" target="_blank">每日一题——第八十九题</a> <span class="text-muted">互联网打工人no1</span> <a class="tag" taget="_blank" href="/search/C%E8%AF%AD%E8%A8%80%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1%E6%AF%8F%E6%97%A5%E4%B8%80%E7%BB%83/1.htm">C语言程序设计每日一练</a><a class="tag" taget="_blank" href="/search/c%E8%AF%AD%E8%A8%80/1.htm">c语言</a> <div>题目:在字符串中找到提取数字,并统计一共找到多少整数,a123xxyu23&8889,那么找到的整数为123,23,8889//思想:#include#include#includeintmain(){charstr[]="a123xxyu23&8889";intcount=0;intnum=0;//用于临时存放当前正在构建的整数。boolinNum=false;//用于标记当前是否正在读取一个整</div> </li> <li><a href="/article/1835504723210366976.htm" title="第四天旅游线路预览——从换乘中心到喀纳斯湖" target="_blank">第四天旅游线路预览——从换乘中心到喀纳斯湖</a> <span class="text-muted">陟彼高冈yu</span> <a class="tag" taget="_blank" href="/search/%E5%9F%BA%E4%BA%8EGoogle/1.htm">基于Google</a><a class="tag" taget="_blank" href="/search/earth/1.htm">earth</a><a class="tag" taget="_blank" href="/search/studio/1.htm">studio</a><a class="tag" taget="_blank" href="/search/%E7%9A%84%E6%97%85%E6%B8%B8%E8%A7%84%E5%88%92%E5%92%8C%E9%A2%84%E8%A7%88/1.htm">的旅游规划和预览</a><a class="tag" taget="_blank" href="/search/%E6%97%85%E6%B8%B8/1.htm">旅游</a> <div>第四天:从贾登峪到喀纳斯风景区入口,晚上住宿贾登峪;换乘中心有4路车,喀纳斯①号车,去喀纳斯湖,路程时长约5分钟;将上面的的行程安排进行动态展示,具体步骤见”Googleearthstudio进行动态轨迹显示制作过程“、“Googleearthstudio入门教程”和“Googleearthstudio进阶教程“相关内容,得到行程如下所示:Day4-2-480p</div> </li> <li><a href="/article/1835503712899002368.htm" title="linux中sdl的使用教程,sdl使用入门" target="_blank">linux中sdl的使用教程,sdl使用入门</a> <span class="text-muted">Melissa Corvinus</span> <a class="tag" taget="_blank" href="/search/linux%E4%B8%ADsdl%E7%9A%84%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/1.htm">linux中sdl的使用教程</a> <div>本文通过一个简单示例讲解SDL的基本使用流程。示例中展示一个窗口,窗口里面有个随机颜色快随机移动。当我们鼠标点击关闭按钮时间窗口关闭。基本步骤如下:1.初始化SDL并创建一个窗口。SDL_Init()初始化SDL_CreateWindow()创建窗口2.纹理渲染存储RGB和存储纹理的区别:比如一个从左到右由红色渐变到蓝色的矩形,用存储RGB的话就需要把矩形中每个点的具体颜色值存储下来;而纹理只是一</div> </li> <li><a href="/article/1835495644123459584.htm" title="Day1笔记-Python简介&标识符和关键字&输入输出" target="_blank">Day1笔记-Python简介&标识符和关键字&输入输出</a> <span class="text-muted">~在杰难逃~</span> <a class="tag" taget="_blank" href="/search/Python/1.htm">Python</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/%E5%A4%A7%E6%95%B0%E6%8D%AE/1.htm">大数据</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/1.htm">数据分析</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/1.htm">数据挖掘</a> <div>大家好,从今天开始呢,杰哥开展一个新的专栏,当然,数据分析部分也会不定时更新的,这个新的专栏主要是讲解一些Python的基础语法和知识,帮助0基础的小伙伴入门和学习Python,感兴趣的小伙伴可以开始认真学习啦!一、Python简介【了解】1.计算机工作原理编程语言就是用来定义计算机程序的形式语言。我们通过编程语言来编写程序代码,再通过语言处理程序执行向计算机发送指令,让计算机完成对应的工作,编程</div> </li> <li><a href="/article/1835493626688401408.htm" title="Python快速入门 —— 第三节:类与对象" target="_blank">Python快速入门 —— 第三节:类与对象</a> <span class="text-muted">孤华暗香</span> <a class="tag" taget="_blank" href="/search/Python%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/1.htm">Python快速入门</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a> <div>第三节:类与对象目标:了解面向对象编程的基础概念,并学会如何定义类和创建对象。内容:类与对象:定义类:class关键字。类的构造函数:__init__()。类的属性和方法。对象的创建与使用。示例:classStudent:def__init__(self,name,age,major):self.name&#</div> </li> <li><a href="/article/1835488955101966336.htm" title="C++菜鸟教程 - 从入门到精通 第二节" target="_blank">C++菜鸟教程 - 从入门到精通 第二节</a> <span class="text-muted">DreamByte</span> <a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a> <div>一.上节课的补充(数据类型)1.前言继上节课,我们主要讲解了输入,输出和运算符,我们现在来补充一下数据类型的知识上节课遗漏了这个知识点,非常的抱歉顺便说一下,博主要上高中了,更新会慢,2-4周更新一次对了,正好赶上中秋节,小编跟大家说一句:中秋节快乐!2.int类型上节课,我们其实只用了int类型int类型,是整数类型,它们存贮的是整数,不能存小数(浮点数)定义变量的方式很简单inta;//定义一</div> </li> <li><a href="/article/1835480678255390720.htm" title="2019-01-19" target="_blank">2019-01-19</a> <span class="text-muted">王小康KK</span> <div>姓名:王康公司:扬州市方圆建筑工程有限公司2018年3月16日~3月18日上海361期《六项精进》感谢二组学员【日精进打卡第307天】【知~学习】《六项精进》大纲3遍共862遍《大学》通篇3遍共860遍《六项精进》全书40页【经典名句】思想决定行为,行为决定习惯,习惯决定性格,性格决定命运。【行~实践】一、修身:(对自己个人)1、践行六项精进的理念。二、齐家:(对家庭和家人)1、和女朋友视频聊天。</div> </li> <li><a href="/article/1835479758033481728.htm" title="SpringBlade dict-biz/list 接口 SQL 注入漏洞" target="_blank">SpringBlade dict-biz/list 接口 SQL 注入漏洞</a> <span class="text-muted">文章永久免费只为良心</span> <a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%BA%93/1.htm">数据库</a> <div>SpringBladedict-biz/list接口SQL注入漏洞POC:构造请求包查看返回包你的网址/api/blade-system/dict-biz/list?updatexml(1,concat(0x7e,md5(1),0x7e),1)=1漏洞概述在SpringBlade框架中,如果dict-biz/list接口的后台处理逻辑没有正确地对用户输入进行过滤或参数化查询(PreparedSta</div> </li> <li><a href="/article/1835476350190841856.htm" title="ExpRe[25] bash外的其它shell:zsh和fish" target="_blank">ExpRe[25] bash外的其它shell:zsh和fish</a> <span class="text-muted">tritone</span> <a class="tag" taget="_blank" href="/search/ExpRe/1.htm">ExpRe</a><a class="tag" taget="_blank" href="/search/bash/1.htm">bash</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/ubuntu/1.htm">ubuntu</a><a class="tag" taget="_blank" href="/search/shell/1.htm">shell</a> <div>文章目录zsh基础配置实用特性插件`autojump`语法高亮自动补全fish优点缺点时效性本篇撰写时间为2021.12.15,由于计算机技术日新月异,博客中所有内容都有时效和版本限制,具体做法不一定总行得通,链接可能改动失效,各种软件的用法可能有修改。但是其中透露的思想往往是值得学习的。本篇前置:ExpRe[10]Ubuntu[2]准备神秘软件、备份恢复软件https://www.cnblogs</div> </li> <li><a href="/article/1835475216491442176.htm" title="STM32中的计时与延时" target="_blank">STM32中的计时与延时</a> <span class="text-muted">lupinjia</span> <a class="tag" taget="_blank" href="/search/STM32/1.htm">STM32</a><a class="tag" taget="_blank" href="/search/stm32/1.htm">stm32</a><a class="tag" taget="_blank" href="/search/%E5%8D%95%E7%89%87%E6%9C%BA/1.htm">单片机</a> <div>前言在裸机开发中,延时作为一种规定循环周期的方式经常被使用,其中尤以HAL库官方提供的HAL_Delay为甚。刚入门的小白可能会觉得既然有官方提供的延时函数,而且精度也还挺好,为什么不用呢?实际上HAL_Delay中有不少坑,而这些也只是HAL库中无数坑的其中一些。想从坑里跳出来还是得加强外设原理的学习和理解,切不可只依赖HAL库。除了延时之外,我们在开发中有时也会想要确定某段程序的耗时,这就需要</div> </li> <li><a href="/article/1835469798838988800.htm" title="Python实现简单的机器学习算法" target="_blank">Python实现简单的机器学习算法</a> <span class="text-muted">master_chenchengg</span> <a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E5%8A%9E%E5%85%AC%E6%95%88%E7%8E%87/1.htm">办公效率</a><a class="tag" taget="_blank" href="/search/python%E5%BC%80%E5%8F%91/1.htm">python开发</a><a class="tag" taget="_blank" href="/search/IT/1.htm">IT</a> <div>Python实现简单的机器学习算法开篇:初探机器学习的奇妙之旅搭建环境:一切从安装开始必备工具箱第一步:安装Anaconda和JupyterNotebook小贴士:如何配置Python环境变量算法初体验:从零开始的Python机器学习线性回归:让数据说话数据准备:从哪里找数据编码实战:Python实现线性回归模型评估:如何判断模型好坏逻辑回归:从分类开始理论入门:什么是逻辑回归代码实现:使用skl</div> </li> <li><a href="/article/1835468833360539648.htm" title="父母教育孩子的方式,将影响孩子一生" target="_blank">父母教育孩子的方式,将影响孩子一生</a> <span class="text-muted">树英教育</span> <div>为什么有些孩子总是充满自信与快乐?独立、有主见又坚强?而有些孩子却自卑、胆怯,软弱又过度依赖父母?为什么有些孩子总是健康、阳光又富于创造力?而有些孩子却悲观、孤僻又思想空乏?一个孩子的行为取决于孩子的思想,思想取决于环境和自己的认知,认知取决于教育。父母是孩子人生中的第一位教育者,父母养育孩子的方式,将决定他们人生的高度,影响他们的一生。网络图,侵权即删优秀的父母就像园丁,既要浇水施肥,又要修剪杂</div> </li> <li><a href="/article/1835464242648674304.htm" title="【韩玲】领读小组2月21日打卡文集合" target="_blank">【韩玲】领读小组2月21日打卡文集合</a> <span class="text-muted">9ce517ee104c</span> <div>【输出者】健芳【打卡素材】对财富说是Day50【作者】[澳]奥南朵【标题】让努力看得见【字数】7931建立新信念做事情失败的原因都由我们自己无意识的旧有的信念去掌控着。故步自封,没让自己去更新迭代自己的信念。建立新的信念,相信自己的财富会越来越多。2改掉坏习惯以前的懒床、刷手机、煲剧、这些都是封锁自己思想的坏习惯,以为这样就可以让自己过得充实。其实真的不是,而是带给自己一种伤害,阻碍自己努力上进的</div> </li> <li><a href="/article/1835460785443270656.htm" title="2019考研 | 西交大软件工程" target="_blank">2019考研 | 西交大软件工程</a> <span class="text-muted">笔者阿蓉</span> <div>本科背景:某北京211学校电子信息工程互联网开发工作两年录取结果:全日制软件工程学院分数:初试350+复试笔试80+面试85+总排名:100+从五月份开始脱产学习,我主要说一下专业课和复试还有我对非全的一些看法。【数学100+】张宇,张宇,张宇。跟着张宇学习,入门视频刷一遍,真题刷两遍,错题刷三遍。书刷N多遍。从视频开始学习,是最快的学习方法。5-7月份把主要是数学学好,8-9月份开始给自己每个周</div> </li> <li><a href="/article/1835458199755517952.htm" title="spring如何整合druid连接池?" target="_blank">spring如何整合druid连接池?</a> <span class="text-muted">惜.己</span> <a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/junit/1.htm">junit</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%BA%93/1.htm">数据库</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/idea/1.htm">idea</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a><a class="tag" taget="_blank" href="/search/xml/1.htm">xml</a> <div>目录spring整合druid连接池1.新建maven项目2.新建mavenModule3.导入相关依赖4.配置log4j2.xml5.配置druid.xml1)xml中如何引入properties2)下面是配置文件6.准备jdbc.propertiesJDBC配置项解释7.配置druid8.测试spring整合druid连接池1.新建maven项目打开IDE(比如IntelliJIDEA,Ecl</div> </li> <li><a href="/article/1835454795712917504.htm" title="esp32开发快速入门 8 : MQTT 的快速入门,基于esp32实现MQTT通信" target="_blank">esp32开发快速入门 8 : MQTT 的快速入门,基于esp32实现MQTT通信</a> <span class="text-muted">z755924843</span> <a class="tag" taget="_blank" href="/search/ESP32%E5%BC%80%E5%8F%91%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/1.htm">ESP32开发快速入门</a><a class="tag" taget="_blank" href="/search/%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">服务器</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C/1.htm">网络</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a> <div>MQTT介绍简介MQTT(MessageQueuingTelemetryTransport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联</div> </li> <li><a href="/article/1835451142843232256.htm" title="Armv8.3 体系结构扩展--原文版" target="_blank">Armv8.3 体系结构扩展--原文版</a> <span class="text-muted">代码改变世界ctw</span> <a class="tag" taget="_blank" href="/search/ARM-TEE-Android/1.htm">ARM-TEE-Android</a><a class="tag" taget="_blank" href="/search/armv8/1.htm">armv8</a><a class="tag" taget="_blank" href="/search/%E5%B5%8C%E5%85%A5%E5%BC%8F/1.htm">嵌入式</a><a class="tag" taget="_blank" href="/search/arm%E6%9E%B6%E6%9E%84/1.htm">arm架构</a><a class="tag" taget="_blank" href="/search/%E5%AE%89%E5%85%A8%E6%9E%B6%E6%9E%84/1.htm">安全架构</a><a class="tag" taget="_blank" href="/search/%E8%8A%AF%E7%89%87/1.htm">芯片</a><a class="tag" taget="_blank" href="/search/Trustzone/1.htm">Trustzone</a><a class="tag" taget="_blank" href="/search/Secureboot/1.htm">Secureboot</a> <div>快速链接:.ARMv8/ARMv9架构入门到精通-[目录]付费专栏-付费课程【购买须知】:个人博客笔记导读目录(全部)TheArmv8.3architectureextensionTheArmv8.3architectureextensionisanextensiontoArmv8.2.Itaddsmandatoryandoptionalarchitecturalfeatures.Somefeat</div> </li> <li><a href="/article/1835449364223455232.htm" title="SpringCloudAlibaba—Sentinel(限流)" target="_blank">SpringCloudAlibaba—Sentinel(限流)</a> <span class="text-muted">菜鸟爪哇</span> <div>前言:自己在学习过程的记录,借鉴别人文章,记录自己实现的步骤。借鉴文章:https://blog.csdn.net/u014494148/article/details/105484410Sentinel介绍Sentinel诞生于阿里巴巴,其主要目标是流量控制和服务熔断。Sentinel是通过限制并发线程的数量(即信号隔离)来减少不稳定资源的影响,而不是使用线程池,省去了线程切换的性能开销。当资源</div> </li> <li><a href="/article/1835449249425354752.htm" title="Python算法L5:贪心算法" target="_blank">Python算法L5:贪心算法</a> <span class="text-muted">小熊同学哦</span> <a class="tag" taget="_blank" href="/search/Python%E7%AE%97%E6%B3%95/1.htm">Python算法</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95/1.htm">贪心算法</a> <div>Python贪心算法简介目录Python贪心算法简介贪心算法的基本步骤贪心算法的适用场景经典贪心算法问题1.**零钱兑换问题**2.**区间调度问题**3.**背包问题**贪心算法的优缺点优点:缺点:结语贪心算法(GreedyAlgorithm)是一种在每一步选择中都采取当前最优或最优解的算法。它的核心思想是,在保证每一步局部最优的情况下,希望通过贪心选择达到全局最优解。虽然贪心算法并不总能得到全</div> </li> <li><a href="/article/1835449123252301824.htm" title="Python入门之Lesson2:Python基础语法" target="_blank">Python入门之Lesson2:Python基础语法</a> <span class="text-muted">小熊同学哦</span> <a class="tag" taget="_blank" href="/search/Python%E5%85%A5%E9%97%A8%E8%AF%BE%E7%A8%8B/1.htm">Python入门课程</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1.htm">数据结构</a><a class="tag" taget="_blank" href="/search/%E9%9D%92%E5%B0%91%E5%B9%B4%E7%BC%96%E7%A8%8B/1.htm">青少年编程</a> <div>目录前言一.介绍1.变量和数据类型2.常见运算符3.输入输出4.条件语句5.循环结构二.练习三.总结前言欢迎来到《Python入门》系列博客的第二课。在上一课中,我们了解了Python的安装及运行环境的配置。在这一课中,我们将深入学习Python的基础语法,这是编写Python代码的根基。通过本节内容的学习,你将掌握变量、数据类型、运算符、输入输出、条件语句等Python编程的基础知识。一.介绍1</div> </li> <li><a href="/article/1835449106064044032.htm" title="2023-06-19【感恩日记】第246篇" target="_blank">2023-06-19【感恩日记】第246篇</a> <span class="text-muted">o泡沫o</span> <div>思想日记:坚持下去,相信自己一定可以的【感恩日记】第246篇1.我真是太幸福啦!感恩孩子早起阅读,放学到学生之家完成作业,平安度过美好的一天。感恩!感恩!感恩!❤️2.我真是太幸福啦!感恩自己早起给孩子煮早餐,完成计划的工作,晚上学习。感恩!感恩!感恩!❤️3.我真是太幸福啦!感恩为我设计效果图的老师。感恩!感恩!感恩!❤️4.我真是太幸福啦!感恩父母养育了我,有妈的孩子真幸福。感恩!感恩!感恩!</div> </li> <li><a href="/article/1835448462678781952.htm" title="请用幸福影响他人,请不要看不惯别人" target="_blank">请用幸福影响他人,请不要看不惯别人</a> <span class="text-muted">吕氏春秋驴驴</span> <div>这个世间包罗万象,这个世间丰富多彩,这个世间色彩缤纷。。。。。如果只一种模式,一种色彩,一种花朵,一样容颜,一种人,一个思想。。。。。多么无趣啊!不管怎样的思想和生活方式只要能够安慰自己的心灵,能克服自己的恐惧感受祥和,充满生命的活力。。。。就是正确的活法。读了金刚经你会感觉博大精深空灵之美,看见基督徒你会感知被爱,易经道德经你会定位人生不纠结,读了鲁米你会跟宇宙自然神灵做朋友,人生无意义会让你珍</div> </li> <li><a href="/article/1835448238103162880.htm" title="springboot+vue项目实战一-创建SpringBoot简单项目" target="_blank">springboot+vue项目实战一-创建SpringBoot简单项目</a> <span class="text-muted">苹果酱0567</span> <a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95%E9%A2%98%E6%B1%87%E6%80%BB%E4%B8%8E%E8%A7%A3%E6%9E%90/1.htm">面试题汇总与解析</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/boot/1.htm">boot</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E4%B8%AD%E9%97%B4%E4%BB%B6/1.htm">中间件</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a> <div>这段时间抽空给女朋友搭建一个个人博客,想着记录一下建站的过程,就当做笔记吧。虽然复制zjblog只要一个小时就可以搞定一个网站,或者用cms系统,三四个小时就可以做出一个前后台都有的网站,而且想做成啥样也都行。但是就是要从新做,自己做的意义不一样,更何况,俺就是专门干这个的,嘿嘿嘿要做一个网站,而且从零开始,首先呢就是技术选型了,经过一番思量决定选择-SpringBoot做后端,前端使用Vue做一</div> </li> <li><a href="/article/1835447700980592640.htm" title="摄影小白,怎么才能拍出高大上产品图片?" target="_blank">摄影小白,怎么才能拍出高大上产品图片?</a> <span class="text-muted">是波妞唉</span> <div>很多人以为文案只要会码字,会排版就OK了!说实话,没接触到这一行的时候,我的想法更简单,以为只要会写字就行!可是真做了文案才发现,码字只是入门级的基本功。一篇文章离不开排版、配图,说起来很简单!从头做到尾你就会发现,写文章用两个小时,找合适的配图居然要花掉半天的时间,甚至更久!图片能找到合适的就不怕,还有找不到的,比如产品图,只能亲自拍。拿着摆弄了半天,就是拍不出想要的效果,光线不好、搭出来丑破天</div> </li> <li><a href="/article/1835447606348705792.htm" title="C++ lambda闭包消除类成员变量" target="_blank">C++ lambda闭包消除类成员变量</a> <span class="text-muted">barbyQAQ</span> <a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a><a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a> <div>原文链接:https://blog.csdn.net/qq_51470638/article/details/142151502一、背景在面向对象编程时,常常要添加类成员变量。然而类成员一旦多了之后,也会带来干扰。拿到一个类,一看成员变量好几十个,就问你怕不怕?二、解决思路可以借助函数式编程思想,来消除一些不必要的类成员变量。三、实例举个例子:classClassA{public:...intfu</div> </li> <li><a href="/article/1835444959478640640.htm" title="2021 CCF 非专业级别软件能力认证第一轮(CSP-J1)入门级C++语言试题 (第三大题:完善程序 代码)" target="_blank">2021 CCF 非专业级别软件能力认证第一轮(CSP-J1)入门级C++语言试题 (第三大题:完善程序 代码)</a> <span class="text-muted">mmz1207</span> <a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a><a class="tag" taget="_blank" href="/search/csp/1.htm">csp</a> <div>最近有一段时间没更新了,在准备CSP考试,请大家见谅。(1)有n个人围成一个圈,依次标号0到n-1。从0号开始,依次0,1,0,1...交替报数,报到一的人离开,直至圈中剩最后一个人。求最后剩下的人的编号。#includeusingnamespacestd;intf[1000010];intmain(){intn;cin>>n;inti=0,cnt=0,p=0;while(cnt#includeu</div> </li> <li><a href="/article/1835443569528238080.htm" title="Vue( ElementUI入门、vue-cli安装)" target="_blank">Vue( ElementUI入门、vue-cli安装)</a> <span class="text-muted">m0_l5z</span> <a class="tag" taget="_blank" href="/search/elementui/1.htm">elementui</a><a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a> <div>一.ElementUI入门目录:1.ElementUI入门1.1ElementUI简介1.2Vue+ElementUI安装1.3开发示例2.搭建nodejs环境2.1nodejs介绍2.2npm是什么2.3nodejs环境搭建2.3.1下载2.3.2解压2.3.3配置环境变量2.3.4配置npm全局模块路径和cache默认安装位置2.3.5修改npm镜像提高下载速度2.3.6验证安装结果3.运行n</div> </li> <li><a href="/article/1835443569968640000.htm" title="Spring MVC 全面指南:从入门到精通的详细解析" target="_blank">Spring MVC 全面指南:从入门到精通的详细解析</a> <span class="text-muted">一杯梅子酱</span> <a class="tag" taget="_blank" href="/search/%E6%8A%80%E6%9C%AF%E6%A0%88%E5%AD%A6%E4%B9%A0/1.htm">技术栈学习</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/mvc/1.htm">mvc</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>引言:SpringMVC,作为Spring框架的一个重要模块,为构建Web应用提供了强大的功能和灵活性。无论是初学者还是有一定经验的开发者,掌握SpringMVC都将显著提升你的Web开发技能。本文旨在为初学者提供一个全面且易于理解的学习路径,通过详细的知识点分析和实际案例,帮助你快速上手SpringMVC,让学习过程既深刻又高效。一、SpringMVC简介1.1什么是SpringMVC?Spri</div> </li> <li><a href="/article/16.htm" title="深入浅出Java Annotation(元注解和自定义注解)" target="_blank">深入浅出Java Annotation(元注解和自定义注解)</a> <span class="text-muted">Josh_Persistence</span> <a class="tag" taget="_blank" href="/search/Java+Annotation/1.htm">Java Annotation</a><a class="tag" taget="_blank" href="/search/%E5%85%83%E6%B3%A8%E8%A7%A3/1.htm">元注解</a><a class="tag" taget="_blank" href="/search/%E8%87%AA%E5%AE%9A%E4%B9%89%E6%B3%A8%E8%A7%A3/1.htm">自定义注解</a> <div>一、基本概述        Annontation是Java5开始引入的新特征。中文名称一般叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。     更通俗的意思是为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且是供指定的工具或</div> </li> <li><a href="/article/143.htm" title="mysql优化特定类型的查询" target="_blank">mysql优化特定类型的查询</a> <span class="text-muted">annan211</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%B7%A5%E4%BD%9C/1.htm">工作</a><a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a> <div> 本节所介绍的查询优化的技巧都是和特定版本相关的,所以对于未来mysql的版本未必适用。 1 优化count查询 对于count这个函数的网上的大部分资料都是错误的或者是理解的都是一知半解的。在做优化之前我们先来看看 真正的count()函数的作用到底是什么。 count()是一个特殊的函数,有两种非常不同的作用,他可以统计某个列值的数量,也可以统计行数。 在统</div> </li> <li><a href="/article/270.htm" title="MAC下安装多版本JDK和切换几种方式" target="_blank">MAC下安装多版本JDK和切换几种方式</a> <span class="text-muted">棋子chessman</span> <a class="tag" taget="_blank" href="/search/jdk/1.htm">jdk</a> <div>环境: MAC AIR,OS X 10.10,64位   历史: 过去 Mac 上的 Java 都是由 Apple 自己提供,只支持到 Java 6,并且OS X 10.7 开始系统并不自带(而是可选安装)(原自带的是1.6)。 后来 Apple 加入 OpenJDK 继续支持 Java 6,而 Java 7 将由 Oracle 负责提供。   在终端中输入jav</div> </li> <li><a href="/article/397.htm" title="javaScript (1)" target="_blank">javaScript (1)</a> <span class="text-muted">Array_06</span> <a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E6%B5%8F%E8%A7%88%E5%99%A8/1.htm">浏览器</a> <div>JavaScript 1、运算符   运算符就是完成操作的一系列符号,它有七类:   赋值运算符(=,+=,-=,*=,/=,%=,<<=,>>=,|=,&=)、算术运算符(+,-,*,/,++,--,%)、比较运算符(>,<,<=,>=,==,===,!=,!==)、逻辑运算符(||,&&,!)、条件运算(?:)、位</div> </li> <li><a href="/article/524.htm" title="国内顶级代码分享网站" target="_blank">国内顶级代码分享网站</a> <span class="text-muted">袁潇含</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/jdk/1.htm">jdk</a><a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/.net/1.htm">.net</a><a class="tag" taget="_blank" href="/search/PHP/1.htm">PHP</a> <div>       现在国内很多开源网站感觉都是为了利益而做的                  当然利益是肯定的,否则谁也不会免费的去做网站      &</div> </li> <li><a href="/article/651.htm" title="Elasticsearch、MongoDB和Hadoop比较" target="_blank">Elasticsearch、MongoDB和Hadoop比较</a> <span class="text-muted">随意而生</span> <a class="tag" taget="_blank" href="/search/mongodb/1.htm">mongodb</a><a class="tag" taget="_blank" href="/search/hadoop/1.htm">hadoop</a><a class="tag" taget="_blank" href="/search/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/1.htm">搜索引擎</a> <div>  IT界在过去几年中出现了一个有趣的现象。很多新的技术出现并立即拥抱了“大数据”。稍微老一点的技术也会将大数据添进自己的特性,避免落大部队太远,我们看到了不同技术之间的边际的模糊化。假如你有诸如Elasticsearch或者Solr这样的搜索引擎,它们存储着JSON文档,MongoDB存着JSON文档,或者一堆JSON文档存放在一个Hadoop集群的HDFS中。你可以使用这三种配</div> </li> <li><a href="/article/778.htm" title="mac os 系统科研软件总结" target="_blank">mac os 系统科研软件总结</a> <span class="text-muted">张亚雄</span> <a class="tag" taget="_blank" href="/search/mac+os/1.htm">mac os</a> <div>1.1 Microsoft Office for Mac 2011      大客户版,自行搜索。      1.2 Latex (MacTex):      系统环境:https://tug.org/mactex/     &nb</div> </li> <li><a href="/article/905.htm" title="Maven实战(四)生命周期" target="_blank">Maven实战(四)生命周期</a> <span class="text-muted">AdyZhang</span> <a class="tag" taget="_blank" href="/search/maven/1.htm">maven</a> <div>1. 三套生命周期     Maven拥有三套相互独立的生命周期,它们分别为clean,default和site。 每个生命周期包含一些阶段,这些阶段是有顺序的,并且后面的阶段依赖于前面的阶段,用户和Maven最直接的交互方式就是调用这些生命周期阶段。 以clean生命周期为例,它包含的阶段有pre-clean, clean 和 post</div> </li> <li><a href="/article/1032.htm" title="Linux下Jenkins迁移" target="_blank">Linux下Jenkins迁移</a> <span class="text-muted">aijuans</span> <a class="tag" taget="_blank" href="/search/Jenkins/1.htm">Jenkins</a> <div>1. 将Jenkins程序目录copy过去      源程序在/export/data/tomcatRoot/ofctest-jenkins.jd.com下面            tar -cvzf jenkins.tar.gz ofctest-jenkins.jd.com &</div> </li> <li><a href="/article/1159.htm" title="request.getInputStream()只能获取一次的问题" target="_blank">request.getInputStream()只能获取一次的问题</a> <span class="text-muted">ayaoxinchao</span> <a class="tag" taget="_blank" href="/search/request/1.htm">request</a><a class="tag" taget="_blank" href="/search/Inputstream/1.htm">Inputstream</a> <div>问题:在使用HTTP协议实现应用间接口通信时,服务端读取客户端请求过来的数据,会用到request.getInputStream(),第一次读取的时候可以读取到数据,但是接下来的读取操作都读取不到数据   原因: 1. 一个InputStream对象在被读取完成后,将无法被再次读取,始终返回-1; 2. InputStream并没有实现reset方法(可以重</div> </li> <li><a href="/article/1286.htm" title="数据库SQL优化大总结之 百万级数据库优化方案" target="_blank">数据库SQL优化大总结之 百万级数据库优化方案</a> <span class="text-muted">BigBird2012</span> <a class="tag" taget="_blank" href="/search/SQL%E4%BC%98%E5%8C%96/1.htm">SQL优化</a> <div>网上关于SQL优化的教程很多,但是比较杂乱。近日有空整理了一下,写出来跟大家分享一下,其中有错误和不足的地方,还请大家纠正补充。 这篇文章我花费了大量的时间查找资料、修改、排版,希望大家阅读之后,感觉好的话推荐给更多的人,让更多的人看到、纠正以及补充。 1.对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。 2.应尽量避免在 where </div> </li> <li><a href="/article/1413.htm" title="jsonObject的使用" target="_blank">jsonObject的使用</a> <span class="text-muted">bijian1013</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/json/1.htm">json</a> <div>        在项目中难免会用java处理json格式的数据,因此封装了一个JSONUtil工具类。 JSONUtil.java package com.bijian.json.study; import java.util.ArrayList; import java.util.Date; import java.util.HashMap;</div> </li> <li><a href="/article/1540.htm" title="[Zookeeper学习笔记之六]Zookeeper源代码分析之Zookeeper.WatchRegistration" target="_blank">[Zookeeper学习笔记之六]Zookeeper源代码分析之Zookeeper.WatchRegistration</a> <span class="text-muted">bit1129</span> <a class="tag" taget="_blank" href="/search/zookeeper/1.htm">zookeeper</a> <div>Zookeeper类是Zookeeper提供给用户访问Zookeeper service的主要API,它包含了如下几个内部类     首先分析它的内部类,从WatchRegistration开始,为指定的znode path注册一个Watcher,   /** * Register a watcher for a particular p</div> </li> <li><a href="/article/1667.htm" title="【Scala十三】Scala核心七:部分应用函数" target="_blank">【Scala十三】Scala核心七:部分应用函数</a> <span class="text-muted">bit1129</span> <a class="tag" taget="_blank" href="/search/scala/1.htm">scala</a> <div>何为部分应用函数? Partially applied function: A function that’s used in an expression and that misses some of its arguments.For instance, if function f has type Int => Int => Int, then f and f(1) are p</div> </li> <li><a href="/article/1794.htm" title="Tomcat Error listenerStart 终极大法" target="_blank">Tomcat Error listenerStart 终极大法</a> <span class="text-muted">ronin47</span> <a class="tag" taget="_blank" href="/search/tomcat/1.htm">tomcat</a> <div>Tomcat报的错太含糊了,什么错都没报出来,只提示了Error listenerStart。为了调试,我们要获得更详细的日志。可以在WEB-INF/classes目录下新建一个文件叫logging.properties,内容如下 Java代码  handlers = org.apache.juli.FileHandler, java.util.logging.ConsoleHa</div> </li> <li><a href="/article/1921.htm" title="不用加减符号实现加减法" target="_blank">不用加减符号实现加减法</a> <span class="text-muted">BrokenDreams</span> <a class="tag" taget="_blank" href="/search/%E5%AE%9E%E7%8E%B0/1.htm">实现</a> <div>        今天有群友发了一个问题,要求不用加减符号(包括负号)来实现加减法。         分析一下,先看最简单的情况,假设1+1,按二进制算的话结果是10,可以看到从右往左的第一位变为0,第二位由于进位变为1。    </div> </li> <li><a href="/article/2048.htm" title="读《研磨设计模式》-代码笔记-状态模式-State" target="_blank">读《研磨设计模式》-代码笔记-状态模式-State</a> <span class="text-muted">bylijinnan</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/1.htm">设计模式</a> <div>声明: 本文只为方便我个人查阅和理解,详细的分析以及源代码请移步 原作者的博客http://chjavach.iteye.com/ /* 当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类 状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况 把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化 如果在</div> </li> <li><a href="/article/2175.htm" title="CUDA程序block和thread超出硬件允许值时的异常" target="_blank">CUDA程序block和thread超出硬件允许值时的异常</a> <span class="text-muted">cherishLC</span> <a class="tag" taget="_blank" href="/search/CUDA/1.htm">CUDA</a> <div>调用CUDA的核函数时指定block 和 thread大小,该大小可以是dim3类型的(三维数组),只用一维时可以是usigned int型的。 以下程序验证了当block或thread大小超出硬件允许值时会产生异常!!!GPU根本不会执行运算!!! 所以验证结果的正确性很重要!!! 在VS中创建CUDA项目会有一个模板,里面有更详细的状态验证。 以下程序在K5000GPU上跑的。</div> </li> <li><a href="/article/2302.htm" title="诡异的超长时间GC问题定位" target="_blank">诡异的超长时间GC问题定位</a> <span class="text-muted">chenchao051</span> <a class="tag" taget="_blank" href="/search/jvm/1.htm">jvm</a><a class="tag" taget="_blank" href="/search/cms/1.htm">cms</a><a class="tag" taget="_blank" href="/search/GC/1.htm">GC</a><a class="tag" taget="_blank" href="/search/hbase/1.htm">hbase</a><a class="tag" taget="_blank" href="/search/swap/1.htm">swap</a> <div>HBase的GC策略采用PawNew+CMS, 这是大众化的配置,ParNew经常会出现停顿时间特别长的情况,有时候甚至长到令人发指的地步,例如请看如下日志: 2012-10-17T05:54:54.293+0800: 739594.224: [GC 739606.508: [ParNew: 996800K->110720K(996800K), 178.8826900 secs] 3700</div> </li> <li><a href="/article/2429.htm" title="maven环境快速搭建" target="_blank">maven环境快速搭建</a> <span class="text-muted">daizj</span> <a class="tag" taget="_blank" href="/search/%E5%AE%89%E8%A3%85/1.htm">安装</a><a class="tag" taget="_blank" href="/search/mavne/1.htm">mavne</a><a class="tag" taget="_blank" href="/search/%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/1.htm">环境配置</a> <div>一 下载maven 安装maven之前,要先安装jdk及配置JAVA_HOME环境变量。这个安装和配置java环境不用多说。 maven下载地址:http://maven.apache.org/download.html,目前最新的是这个apache-maven-3.2.5-bin.zip,然后解压在任意位置,最好地址中不要带中文字符,这个做java 的都知道,地址中出现中文会出现很多</div> </li> <li><a href="/article/2556.htm" title="PHP网站安全,避免PHP网站受到攻击的方法" target="_blank">PHP网站安全,避免PHP网站受到攻击的方法</a> <span class="text-muted">dcj3sjt126com</span> <a class="tag" taget="_blank" href="/search/PHP/1.htm">PHP</a> <div> 对于PHP网站安全主要存在这样几种攻击方式:1、命令注入(Command Injection)2、eval注入(Eval Injection)3、客户端脚本攻击(Script Insertion)4、跨网站脚本攻击(Cross Site Scripting, XSS)5、SQL注入攻击(SQL injection)6、跨网站请求伪造攻击(Cross Site Request Forgerie</div> </li> <li><a href="/article/2683.htm" title="yii中给CGridView设置默认的排序根据时间倒序的方法" target="_blank">yii中给CGridView设置默认的排序根据时间倒序的方法</a> <span class="text-muted">dcj3sjt126com</span> <a class="tag" taget="_blank" href="/search/GridView/1.htm">GridView</a> <div>public function searchWithRelated() {         $criteria = new CDbCriteria;         $criteria->together = true; //without th</div> </li> <li><a href="/article/2810.htm" title="Java集合对象和数组对象的转换" target="_blank">Java集合对象和数组对象的转换</a> <span class="text-muted">dyy_gusi</span> <a class="tag" taget="_blank" href="/search/java%E9%9B%86%E5%90%88/1.htm">java集合</a> <div>    在开发中,我们经常需要将集合对象(List,Set)转换为数组对象,或者将数组对象转换为集合对象。Java提供了相互转换的工具,但是我们使用的时候需要注意,不能乱用滥用。 1、数组对象转换为集合对象     最暴力的方式是new一个集合对象,然后遍历数组,依次将数组中的元素放入到新的集合中,但是这样做显然过</div> </li> <li><a href="/article/2937.htm" title="nginx同一主机部署多个应用" target="_blank">nginx同一主机部署多个应用</a> <span class="text-muted">geeksun</span> <a class="tag" taget="_blank" href="/search/nginx/1.htm">nginx</a> <div>近日有一需求,需要在一台主机上用nginx部署2个php应用,分别是wordpress和wiki,探索了半天,终于部署好了,下面把过程记录下来。 1.   在nginx下创建vhosts目录,用以放置vhost文件。 mkdir vhosts   2.   修改nginx.conf的配置, 在http节点增加下面内容设置,用来包含vhosts里的配置文件 #</div> </li> <li><a href="/article/3064.htm" title="ubuntu添加admin权限的用户账号" target="_blank">ubuntu添加admin权限的用户账号</a> <span class="text-muted">hongtoushizi</span> <a class="tag" taget="_blank" href="/search/ubuntu/1.htm">ubuntu</a><a class="tag" taget="_blank" href="/search/useradd/1.htm">useradd</a> <div>ubuntu创建账号的方式通常用到两种:useradd 和adduser . 本人尝试了useradd方法,步骤如下: 1:useradd    使用useradd时,如果后面不加任何参数的话,如:sudo useradd sysadm 创建出来的用户将是默认的三无用户:无home directory ,无密码,无系统shell。 顾应该如下操作:   </div> </li> <li><a href="/article/3191.htm" title="第五章 常用Lua开发库2-JSON库、编码转换、字符串处理" target="_blank">第五章 常用Lua开发库2-JSON库、编码转换、字符串处理</a> <span class="text-muted">jinnianshilongnian</span> <a class="tag" taget="_blank" href="/search/nginx/1.htm">nginx</a><a class="tag" taget="_blank" href="/search/lua/1.htm">lua</a> <div>  JSON库   在进行数据传输时JSON格式目前应用广泛,因此从Lua对象与JSON字符串之间相互转换是一个非常常见的功能;目前Lua也有几个JSON库,本人用过cjson、dkjson。其中cjson的语法严格(比如unicode \u0020\u7eaf),要求符合规范否则会解析失败(如\u002),而dkjson相对宽松,当然也可以通过修改cjson的源码来完成</div> </li> <li><a href="/article/3318.htm" title="Spring定时器配置的两种实现方式OpenSymphony Quartz和java Timer详解" target="_blank">Spring定时器配置的两种实现方式OpenSymphony Quartz和java Timer详解</a> <span class="text-muted">yaerfeng1989</span> <a class="tag" taget="_blank" href="/search/timer/1.htm">timer</a><a class="tag" taget="_blank" href="/search/quartz/1.htm">quartz</a><a class="tag" taget="_blank" href="/search/%E5%AE%9A%E6%97%B6%E5%99%A8/1.htm">定时器</a> <div>原创整理不易,转载请注明出处:Spring定时器配置的两种实现方式OpenSymphony Quartz和java Timer详解 代码下载地址:http://www.zuidaima.com/share/1772648445103104.htm 有两种流行Spring定时器配置:Java的Timer类和OpenSymphony的Quartz。 1.Java Timer定时 首先继承jav</div> </li> <li><a href="/article/3445.htm" title="Linux下df与du两个命令的差别?" target="_blank">Linux下df与du两个命令的差别?</a> <span class="text-muted">pda158</span> <a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a> <div> 一、df显示文件系统的使用情况,与du比較,就是更全盘化。   最经常使用的就是 df -T,显示文件系统的使用情况并显示文件系统的类型。   举比例如以下:   [root@localhost ~]# df -T   Filesystem                   Type &n</div> </li> <li><a href="/article/3572.htm" title="[转]SQLite的工具类 ---- 通过反射把Cursor封装到VO对象" target="_blank">[转]SQLite的工具类 ---- 通过反射把Cursor封装到VO对象</a> <span class="text-muted">ctfzh</span> <a class="tag" taget="_blank" href="/search/VO/1.htm">VO</a><a class="tag" taget="_blank" href="/search/android/1.htm">android</a><a class="tag" taget="_blank" href="/search/sqlite/1.htm">sqlite</a><a class="tag" taget="_blank" href="/search/%E5%8F%8D%E5%B0%84/1.htm">反射</a><a class="tag" taget="_blank" href="/search/Cursor/1.htm">Cursor</a> <div>在写DAO层时,觉得从Cursor里一个一个的取出字段值再装到VO(值对象)里太麻烦了,就写了一个工具类,用到了反射,可以把查询记录的值装到对应的VO里,也可以生成该VO的List。   使用时需要注意: 考虑到Android的性能问题,VO没有使用Setter和Getter,而是直接用public的属性。 表中的字段名需要和VO的属性名一样,要是不一样就得在查询的SQL中</div> </li> <li><a href="/article/3699.htm" title="该学习笔记用到的Employee表" target="_blank">该学习笔记用到的Employee表</a> <span class="text-muted">vipbooks</span> <a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/sql/1.htm">sql</a><a class="tag" taget="_blank" href="/search/%E5%B7%A5%E4%BD%9C/1.htm">工作</a> <div>    这是我在学习Oracle是用到的Employee表,在该笔记中用到的就是这张表,大家可以用它来学习和练习。 drop table Employee; -- 员工信息表 create table Employee( -- 员工编号 EmpNo number(3) primary key, -- 姓</div> </li> </ul> </div> </div> </div> <div> <div class="container"> <div class="indexes"> <strong>按字母分类:</strong> <a href="/tags/A/1.htm" target="_blank">A</a><a href="/tags/B/1.htm" target="_blank">B</a><a href="/tags/C/1.htm" target="_blank">C</a><a href="/tags/D/1.htm" target="_blank">D</a><a href="/tags/E/1.htm" target="_blank">E</a><a href="/tags/F/1.htm" target="_blank">F</a><a href="/tags/G/1.htm" target="_blank">G</a><a href="/tags/H/1.htm" target="_blank">H</a><a href="/tags/I/1.htm" target="_blank">I</a><a href="/tags/J/1.htm" target="_blank">J</a><a href="/tags/K/1.htm" target="_blank">K</a><a href="/tags/L/1.htm" target="_blank">L</a><a href="/tags/M/1.htm" target="_blank">M</a><a href="/tags/N/1.htm" target="_blank">N</a><a href="/tags/O/1.htm" target="_blank">O</a><a href="/tags/P/1.htm" target="_blank">P</a><a href="/tags/Q/1.htm" target="_blank">Q</a><a href="/tags/R/1.htm" target="_blank">R</a><a href="/tags/S/1.htm" target="_blank">S</a><a href="/tags/T/1.htm" target="_blank">T</a><a href="/tags/U/1.htm" target="_blank">U</a><a href="/tags/V/1.htm" target="_blank">V</a><a href="/tags/W/1.htm" target="_blank">W</a><a href="/tags/X/1.htm" target="_blank">X</a><a href="/tags/Y/1.htm" target="_blank">Y</a><a href="/tags/Z/1.htm" target="_blank">Z</a><a href="/tags/0/1.htm" target="_blank">其他</a> </div> </div> </div> <footer id="footer" class="mb30 mt30"> <div class="container"> <div class="footBglm"> <a target="_blank" href="/">首页</a> - <a target="_blank" href="/custom/about.htm">关于我们</a> - <a target="_blank" href="/search/Java/1.htm">站内搜索</a> - <a target="_blank" href="/sitemap.txt">Sitemap</a> - <a target="_blank" href="/custom/delete.htm">侵权投诉</a> </div> <div class="copyright">版权所有 IT知识库 CopyRight © 2000-2050 E-COM-NET.COM , All Rights Reserved. <!-- <a href="https://beian.miit.gov.cn/" rel="nofollow" target="_blank">京ICP备09083238号</a><br>--> </div> </div> </footer> <!-- 代码高亮 --> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shCore.js"></script> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shLegacy.js"></script> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shAutoloader.js"></script> <link type="text/css" rel="stylesheet" href="/static/syntaxhighlighter/styles/shCoreDefault.css"/> <script type="text/javascript" src="/static/syntaxhighlighter/src/my_start_1.js"></script> </body> </html>