微服务以前也在用,但是没有深入去学习,今年面试时候很多公司都在问会不会使用微服务架构,微服务架构面试问题回答的不好,所以现在记录下springboot框架基础学习。
一、基础学习
1,首先建立一个springboot项目,使用intellij idea 来快速创建一个springboot项目
2、点击下一步
3、在下一步,这里使用的是springboot 2.1.4版本。点击next就创建好一个springboot项目了。
4、我这里创建后项目结构如下,并添加了多个子模块。
先看看父类pom.xml文件配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.learn</groupId>
<artifactId>learn</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>learn</name>
<modules>
<module>learn_spring_boot_demo</module>
<module>learn_spring_boot_drools</module>
<module>learn_spring_boot_mybatis_demo</module>
<module>learn_spring_boot_mybatis_datasource</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jdk.version>1.8</jdk.version>
<spring.version>5.1.6.RELEASE</spring.version>
<mysql.version>8.0.11</mysql.version>
<thymeleaf.layout.version>2.4.1</thymeleaf.layout.version>
<lombok.version>1.18.2</lombok.version>
<drools.version>7.20.0.Final</drools.version>
<kie.version>7.20.0.Final</kie.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--核心模块,包括自动配置支持、日志和YAML-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--Web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--测试模块,包括JUnit、Hamcrest、Mockito-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--thymeleaf 框架-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--springBoot 2.0 将布局单独提取出来,需要单独引入依赖-->
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
<version>${thymeleaf.layout.version}</version>
</dependency>
<!--修改html后自动发布-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!--自动配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<version>${kie.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!--在添加了该插件之后,当运行“mvn package”进行打包时,
会打包成一个可以直接运行的 JAR 文件,使用“java -jar”命令就可以直接运行。
这在很大程度上简化了应用的部署,只需要安装了 JRE 就可以运行。-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<!-- 指定maven编译的jdk版本,如果不指定,maven3默认用jdk 1.5 maven2默认用jdk1.3 -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<!-- 一般而言,target与source是保持一致的,但是,
有时候为了让程序能在其他版本的jdk中运行(对于低版本目标jdk,
源代码中不能使用低版本jdk中不支持的语法),会存在target不同于source的情况 -->
<source>1.8</source> <!-- 源代码使用的JDK版本 -->
<target>1.8</target> <!-- 需要生成的目标class文件的编译版本 -->
<encoding>UTF-8</encoding> <!-- 字符集编码 -->
</configuration>
</plugin>
</plugins>
</build>
</project>
5、第一个子模块leran_spring_boot_demo启动类
该模块的pom.xml 配置如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.learn</groupId>
<artifactId>learn</artifactId>
<version>1.0-SNAPSHOT</version>
<!--这里是指向父类pom文件-->
<relativePath> <!-- lookup parent from repository -->
../pom.xml
</relativePath>
</parent>
<artifactId>learn_spring_boot_demo</artifactId>
<dependencies>
<!--核心模块,包括自动配置支持、日志和YAML-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--Web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--测试模块,包括JUnit、Hamcrest、Mockito-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--自动配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<!--Maven通过Maven Surefire Plugin插件执行单元测试。
(通过Maven Failsafe Plugin插件执行集成测试)
在pom.xml中配置JUnit,TestNG测试框架的依赖,即可自动识别和运行src/test目录下利用该框架编写的测试用例。
surefire也能识别和执行符合一定命名约定的普通类中的测试方法(POJO测试)。
生命周期中test阶段默认绑定的插件目标就是surefire中的test目标,无需额外配置,直接运行mvn test就可以。-->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</project>
6、resources目录下结构
7、application.properties配置文件
server.port=8081
spring.banner.charset=UTF-8
server.tomcat.uri-encoding=UTF-8
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
spring.messages.encoding=UTF-8
spring.jpa.show-sql=true
logging.level.org.springframework.data=debug
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/lin?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=true
spring.datasource.username=root
spring.datasource.password=123
# 这个必须依赖了mysql 才行
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.thymeleaf.cache=false
# 定位模板的目录
spring.mvc.view.prefix=classpath:/templates/
# 给返回的页面添加后缀名
spring.mvc.view.suffix=.html
org.learn.boot.demo.name="林"
org.learn.boot.demo.wish="你要加油呀"
8、运行后,控制台打印的日志,端口为80819、在浏览器中访问这个http://localhost:8081/地址,便可以看到以下页面(Spring Boot默认错误页面)
10、现在通过子模块learn_spring_boot_demo来做一下简单增删改查。
创建两个实体类
11、UserRepository 接口类,这里通过实现JpaRepository类来进行数据增、删、改、查操作
package org.learn.boot.demo.model;
import org.learn.boot.demo.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
/**
* ClassName: UserDao
* Description: 数据访问层
* Date: 2019/3/20 15:25
* History:
* <version> 1.0
* @author lin
*/
public interface UserRepository extends JpaRepository<User,Long> {
/**
* @Description 根据ID查询
* @param id
* @return com.boot.demo.entity.User
* @author lin
* @Date 15:42 2019/3/20
**/
@Query(value = "select u from User u where u.id=:id")
User findById(@Param("id") int id);
/**
* @Description 根据姓名查询
* @param name
* @return java.util.List<com.boot.demo.entity.User>
* @author lin
* @Date 15:43 2019/3/20
**/
@Query(value ="select u from User u where u.name=:name")
List<User> findByName(@Param(value = "name") String name);
/**
* @Description 添加用户
* @param name
* @param age
*
* @return com.boot.demo.entity.User
* @author lin
* @Date 16:40 2019/3/20
**/
@Modifying
@Query(value = "insert into c_user(name,age) values(?1,?2)",nativeQuery = true)
int saveUser(String name, Integer age);
/**
* @Description 修改
* @param id
* @param name
* @param age
* @return com.boot.demo.entity.User
* @exception
* @author lin
* @Date 11:43 2019/3/21
**/
@Modifying
@Query(value = "update User u set u.name=?2 , u.age=?3 where u.id=?1")
int update(Integer id, String name, Integer age);
/**
* @Description 删除(使用原生sql)
* @param id
* @return void
* @exception
* @author lin
* @Date 11:50 2019/3/21
**/
@Modifying
@Query(value = "delete from c_user WHERE id=?1",nativeQuery = true)
void deleteById(Integer id);
}
12、UserService接口类
package org.learn.boot.demo.service;
import org.learn.boot.demo.entity.User;
import java.util.List;
/**
* ClassName: UserService
* Description: 接口类
* Author: lin
* Date: 2019/3/20 16:23
* History:
* <version> 1.0
*/
public interface UserService {
/**
* @Description 根据ID查询
* @param id
* @return com.boot.demo.entity.User
* @author lin
* @Date 15:42 2019/3/20
**/
User findById(int id);
/**
* @Description 根据姓名查询
* @param name
* @return java.util.List<com.boot.demo.entity.User>
* @author lin
* @Date 15:43 2019/3/20
**/
List<User> findByName(String name);
/**
* @Description 添加用户
* @param name
* @param age
*
* @return int
* @author lin
* @Date 16:40 2019/3/20
**/
int saveUser(String name, Integer age);
/**
* @Description 查询全部
* @return java.util.List<com.boot.demo.entity.User>
* @author lin
* @Date 18:55 2019/3/20
**/
List<User> finAll();
/**
* @Description 更新
* @param id
* @param name
* @param age
* @return com.boot.demo.entity.User
* @exception
* @author lin
* @Date 11:38 2019/3/21
**/
int update(Integer id, String name, Integer age);
/**
* @Description 删除
* @param id
* @return void
* @exception
* @author lin
* @Date 11:51 2019/3/21
**/
void delete(Integer id);
/**
* @Description 添加用户信息
* @param user
* @return User
* @exception
* @author lin
* @Date 12:50 2019/3/21
**/
User addUser(User user);
}
13、UserServiceImpl实现类
package org.learn.boot.demo.service.impl;
import org.learn.boot.demo.entity.User;
import org.learn.boot.demo.model.UserRepository;
import org.learn.boot.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* ClassName: UserServiceImpl
* Description: 实现类
* Date: 2019/3/20 16:41
* History:
* <version> 1.0
* @author lin
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public User findById(int id) {
return userRepository.findById(id);
}
@Override
public List<User> findByName(String name) {
return userRepository.findByName(name);
}
@Override
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = {RuntimeException.class})
public int saveUser(String name, Integer age) {
return userRepository.saveUser(name, age);
}
@Override
public List<User> finAll() {
return userRepository.findAll();
}
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = {RuntimeException.class})
@Override
public int update(Integer id, String name, Integer age) {
return userRepository.update(id,name,age);
}
/**
* @param id
* @return void
* @throws
* @Description 删除
* @author lin
* @Date 11:51 2019/3/21
**/
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = {RuntimeException.class})
@Override
public void delete(Integer id) {
userRepository.deleteById(id);
}
/**
* @param user
* @return int
* @throws
* @Description 添加用户信息
* @author lin
* @Date 12:50 2019/3/21
**/
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = {RuntimeException.class})
@Override
public User addUser(User user) {
return userRepository.save(user);
}
}
14、UserRedirectController 类
package org.learn.boot.demo.controller;
import org.learn.boot.demo.entity.User;
import org.learn.boot.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
/**
* ClassName: UserRedirectController
* Description: 重定向
* Date: 2019/3/21 0:18
* History:
* <version> 1.0
* @author lin
*/
@Controller
public class UserRedirectController {
@Autowired
private UserService userService;
@RequestMapping("/indexRedirect")
public String index() {
return "redirect:/list";
}
@RequestMapping("/list")
public String list(Model model){
List<User> users = userService.finAll();
model.addAttribute("users",users);
return "user/list";
}
//return "user/userEdit"; 代表会直接去 resources 目录下找相关的文件。
//return "redirect:/list"; 代表转发到对应的 Controller,这个示例就相当于删除内容之后自动调整到 list 请求,然后再输出到页面。
@RequestMapping("/toAdd")
public String toAdd(){
return "user/userAdd";
}
@RequestMapping("/add")
public String add(User user) {
userService.addUser(user);
return "redirect:/list";
}
@RequestMapping("/toEdit")
public String toEdit(Model model, Integer id) {
User user=userService.findById(id);
model.addAttribute("user", user);
return "user/userEdit";
}
@RequestMapping("/edit")
public String edit(Integer id, String userName, Integer age) {
userService.update(id, userName, age);
return "redirect:/list";
}
@RequestMapping("/delete")
public String delete(Integer id) {
userService.delete(id);
return "redirect:/list";
}
}
15、访问http://localhost:8081/indexRedirect 地址会重新定向到http://localhost:8081/list 地址,然后就是list.html页面,在这里就可以进行简单的增删查改操作。
二、springboot 源码分析
1、在程序的入口类中有个注解@SpringBootApplication ,该注解的作用是什么呢?
2、进入springBootApplication注解类中,可以看到有@springBootConfiguration 注解,@EnableAutoConfiguration注解。
@springBootConfiguration:表示的是spring Boot的配置类。进入该类就可以知道这个类是有注解@Configuration 注解,这就是我们在使用spring 的一个 配置时的一种方式(spring 配置方式 有 xml方式、基于注解的方式、基于java 的配置方式)。
https://blog.youkuaiyun.com/icarus_wang/article/details/51649635(spring Bean配置的三种方式)
3、在进入配置类里面,我们可以看到@Component 注解,这就表示这个类也是容器中的一个组件。
4、@EnableAutoConfiguration注解
该注解就是开启自动装配功能,在springBoot 应用中我们没有做任何配置,那springmvc 也启动起来了整个应用也能用了,包扫面也扫进去了。这些功能是怎么做的呢? 就是通过@EnableAutoConfiguration注解 来实现。
以前spring 需要配置的东西,springBoot帮我们自动配置,@EnableAutoConfiguration 告诉springBoot 开启自动配置功能。这样自动配置功能才能生效。
在进入 @EnableAutoConfiguration 注解类中,我们可以看到@AutoConfigurationPakage 注解 ,表示自动配置包
5、进入@AutoConfigurationPakage 注解类中,它是通过@Import 注解来实现,这个注解是属于spring 的底层注解。它的作用就是给容器导入一个组件;导入的组件由 AutoConfigurationPakages.Registras.class 这个class类
6、我们进入registar 这个内部类。这个类有个方法,就是注册一些bean定义信息,这是给容器导组件
7、组件主要是 New PackageImport(metadata).getPackageName(); 这个metadata 就是这个注解标注的原信息。然后同getPackageName() 去拿去包名。通过debug的方式可以拿到 这个包名。所以这个@AutoConfigurationPakage 注解 将主配置类(@springbootApplication 标注的类)的所在包及下面所有子包里面的所有组件扫描到spring 容器; 所有我们能扫面到controller ,因为他是在主配置类的子包下。
8、按照上面的想法,一旦换包了或者在目录java 下再建立一个包并创建一个类用来验证。
9、这个时候再启动,并访问 localhost:8080/index 就会报404。
所以在创建项目包的时候要将包和有@SpringBootApplication注解的类放在 的子包下或者同一级下。
10、在@EnableAutoConfiguration注解类中 还有 @Import(AutoConfigurationImportSelector.class) 注解,这个注解是给容器导入注解,该import导入的是AutoConfigurationImportSelector组件。进入该组件类中 一个方法是selectImports
11、selectImports 方法 中会去调用getAutoConfigurationEntry()方法,进入该方法
autoConfigurationMetadata主要是自动配置的元数据,
annotationMetadata 是注解的元数据。
12、在 方法中 list configurations 这个list 中包含很多的自动配置类(xxxAutoConfiguration). 就是给容器导入这个场景所需要的所有组件,并配置好这些组件。
如果要Aop的功能,那么呢Aop的自动配置类就帮我配置好了,如果我们要做批处理功能,那么批处理Batch 就会帮我们配置好。如果要做Mongodb功能 ,mongodb配置类就会帮我配置好。
13、有了自动配置类,免去了我们手动编写配置注入功能组件等的工作。通过自动配置类帮我处理。
那么自动配置类怎么扫描到这些呢,从哪儿得到的呢。 它是通过 getCandidateConfigurations 候选配置文件。主要调用了SpringFactoriesLoader.loadFactoryNames()。他有两个参数一个是 EnableAutoConfiguration.class, 另一个是getBeanClassLoader() 类加载器机制。
14、在loadFactoryNames方法中调用loadSpringFactories方法, 并通过该方法中参数classloader 类加载器来获取资源,获取资源后会把这个资源当成一个 properties配置文件,从这个配置文件中获取这个工厂的name。
15、获取的地方就是这个META-INF/spring.factories中获取 EnableAutoConfiguration指定的值
在autoConfigure中 spring.factories中 有EnableAutoConfiguration的信息,这里配置信息就是我们导入自动配置类。
总结下:springboot启动时候会去类路径下的META-INF/spring.factories中获取 EnableAutoConfiguration指定的值,并将这些值作为自动配置类导入到容器中去。这些自动配置类就生效了 就能帮我们进行自动配置工作。 相比我们在使用spring时需要进行xml的配置或者注解的配置。springboot中它把spring需要进行xml形式的配置简化了。所有的配置都由springboot来帮我们完成。