学习网站:https://www.bilibili.com/video/BV1PE411i7CV
学习笔记源码:https://gitee.com/zy-hao/StudySpringBoot
SpringBoot基本概念:
- 简化配置:Springboot是对Spring的进一步封装,基于注解开发,舍弃了笨重的xml配置,使用yml或者properties配置
- 产品级独立运行:每一个工程都可以打包成一个jar包,内置了Tomcat和Servlet容器,可以独立运行
- 强大的场景启动器:每一个特定场景下的需求都封装成了一个starter,只要导入了这个starter就有了这个场景所有的一切
一、Hello World
1、环境
- JDK
- Maven
- MySQL
- SpringBoot
2、Idea中创建
选择spring initalizr,初学勾选Web即可
3、启动
- 必须和主启动类在同一级目录下创建包和方法才能生效,这里创建包controller并创建一个JavaController类
@RestController
public class HelloWordController {
/*
springBoot的contextPath默认是""空字符串,不用想SSM配置Tomcat时候配置工程名,简化了配置
访问地址:http://localhost:8080/hello
*/
@RequestMapping("/hello")
public String hello() {
return "hello world";
}
}
- 来到主启动类右击鼠标点击运行后来到浏览器中地址栏输入http://localhost:8080/hello即可访问。
4、打包
- 在idea右边的工具栏中点击Maven 选择要打包的微服务点击LifeCycle选择package打包发布(target中就会出现xxx.jar包,显示打包成功)
二、原理
1、依赖
- 主要是是依赖一个父项目,是管理项目的资源过滤及插件!
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/>
</parent>
- 点spring-boot-starter-parent进去看,就会看到官方配置好的依赖和资源版本号
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
- 这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;
2、启动器
- SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ;我们未来也可以自己自定义 starter;
springboot-boot-starter-xxx:就是spring-boot的场景启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
3、主启动类
//@SpringBootApplication 来标注一个主程序类
//说明这是一个Spring Boot应用
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
//以为是启动了一个方法,没想到启动了一个服务
SpringApplication.run(SpringbootApplication.class, args);
}
}
3.1、@SpringBootApplication
- 作用:标注在某个类上说明这个类是SpringBoot的主配置类 , SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;
我们点进去看一下
3.2、@ComponentScan
- 作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
3.3、@SpringBootConfiguration
- 作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;
我们继续点进去看一下
这里的 @Configuration,说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件;
3.3、@EnableAutoConfiguration
作用:开启自动配置功能
以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;
点进注解接续查看:
@AutoConfigurationPackage :自动配置包
@import :Spring底层注解@import , 给容器中导入一个组件
Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 ;
这个分析完了,退到上一步,继续看
@Import({AutoConfigurationImportSelector.class}) :给容器导入组件 ;
我们继续点进去看一下AutoConfigurationImportSelector,有如下的一个类
上面的这个方法又调用了 SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法
我们继续点击查看 loadSpringFactories 方法
发现一个多次出现的文件:spring.factories,全局搜索它
spring.factories
我们在上面的自动配置类随便找一个打开看看,比如 :WebMvcAutoConfiguration
可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean,可以找一些自己认识的类,看着熟悉一下!
所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。
结论:
-
SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
-
将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
-
整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
-
它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
-
有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;
三、yml配置注入
1、对比传统xml语法是以标记语言为中心
<server>
<port>8081<port>
</server>
2、yml配置以数据为中心
server:
prot: 8080
servlet:
# 原先的Tomcat工程路径在这里修改
context-path: /laosong
3、yml语法
- 空格不能省略,键值对中间必须要有一个空格
- 以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
- 属性和值的大小写都是十分敏感的。
4、yml配置bean属性,导入依赖
- 依赖
<!-- 1 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
-
创建一个application.yml
-
@ConfigurationProperties(prefix = “person”)默认是从全局配置获取,文件名只能是application.yml
-
测试
// 3 编写测试类
@SpringBootTest
class DemoApplicationTests {
@Autowired
Person person;
@Test
void personTest() {
System.out.println(person);
}
}
5、yml可以安装的位置
- file:./config和file:./config
- file:config/appplication.yml
- file:application.yml
- resources:/config/application.yml
- resources:application.yml
执行的优先级依次递减:
四、Web开发
1、静态资源的位置
优先级:resources > static(默认) > public
2、Thymeleaf
template/**下的任何页面都需要Controller来跳转才能访问,不能直接访问
3、导入依赖
<!--thymeleaf模版引擎在,都是基于3.x版本开发-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
4、Thymeleaf常用语法
手册网址:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.pdf
5、实现过程
- 5.1、静态资源目录下创建index.html
<!DOCTYPE html>
<!--thymeleaf约束-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>thymeleaf</title>
</head>
<body>
<!--所有的html元素都可以被Th接管:th:xx元素名-->
<!--test=默认是转义文本-->
<h1 th:text="${msg}"></h1>
<!--utest=默认是不转义文本-->
<h1 th:utext="${msg}"></h1>
<h1>
<!--遍历往前写item-->
遍历一 推荐这么使用:
<h2 th:each="user:${users}" th:text="${user}"></h2><br>
遍历二:
<h2 th:each="user:${users}" >[[${user}]]</h2>
</h1>
</body>
</html>
- 5.2、编写controller
@Controller
public class ThymeleafController {
@RequestMapping("/thymeleaf")
public String thy(Model model){
model.addAttribute("msg","<p>hello Thymeleaf</p>");
model.addAttribute("users", Arrays.asList("张三","李四","王五"));
// 后缀默认是 .html
return "thymeleafTest";
}
}
5.3、启动
五、员工管理系统
基础项目结构
1、首页及静态页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Signin Template for Bootstrap</title>
<link th:href="@{/asserts/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/asserts/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<form class="form-signin" th:action="@{/user/login}" method="post">
<img class="mb-4" th:src="@{/asserts/img/bootstrap-solid.svg}" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<!--如果msg的值为空则不显示消息-->
<p style="color: #ff0000" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
<input type="text" name="username" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
<input type="password" name="password" class="form-control" th:placeholder="#{login.password}" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{login.remember}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">[[#{login.btn}]]</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
</form>
</body>
</html>
dashboard.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Template for Bootstrap</title>
<link th:href="@{/asserts/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
<style>
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
</style>
</head>
<body>
<div th:insert="~{commons/commons::topbar}"></div>
<div class="container-fluid">
<div class="row">
<!--侧边栏-->
<!--传递参数给组件-->
<div th:replace="~{commons/commons::sidebar(active='main.html')}"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<div class="chartjs-size-monitor"
style="position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden; pointer-events: none; visibility: hidden; z-index: -1;">
<div class="chartjs-size-monitor-expand"
style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
<div style="position:absolute;width:1000000px;height:1000000px;left:0;top:0"></div>
</div>
<div class="chartjs-size-monitor-shrink"
style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
<div style="position:absolute;width:200%;height:200%;left:0; top:0"></div>
</div>
</div>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
<h1 class="h2">Dashboard</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group mr-2">
<button class="btn btn-sm btn-outline-secondary">Share</button>
<button class="btn btn-sm btn-outline-secondary">Export</button>
</div>
<button class="btn btn-sm btn-outline-secondary dropdown-toggle">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="feather feather-calendar">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
<line x1="16" y1="2" x2="16" y2="6"></line>
<line x1="8" y1="2" x2="8" y2="6"></line>
<line x1="3" y1="10" x2="21" y2="10"></line>
</svg>
This week
</button>
</div>
</div>
<canvas class="my-4 chartjs-render-monitor" id="myChart" width="1076" height="454"
style="display: block; width: 1076px; height: 454px;"></canvas>
</main>
</div>
</div>
<script type="text/javascript" th:src="@{asserts/js/jquery-3.2.1.slim.min.js}"></script>
<script type="text/javascript" th:src="@{asserts/js/popper.min.js}"></script>
<script type="text/javascript" th:src="@{asserts/js/bootstrap.min.js}"></script>
<script type="text/javascript" th:src="@{asserts/js/feather.min.js}"></script>
<script>
import {
feather} from "../static/asserts/js/feather.min";
feather.replace()
</script>
<script type="text/javascript" th:src="@{asserts/js/Chart.min.js}"></script>
<script>
let ctx = document.getElementById("myChart");
let myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
</script>
</body>
</html>
commons.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!--头部导航栏-->
<!--顶部导航栏-->
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="#">[[${session.loginUser}]]</a>
<input class="form-control form-control-dark w-100" type="text" placeholder="搜索" aria-label="搜索">
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" th:href="@{/user/loginOut}">注销</a>
</li>
</ul>
</nav>
<!--侧边栏-->
<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<a th:class="${active=='main.html'?'nav-link active':'nav-link'}" th:href="@{/main.html}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="feather feather-home">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
<polyline points="9 22 9 12 15 12 15 22"></polyline>
</svg>
首页 <span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item">
<a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{/emps}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="feather feather-users">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
</svg>
员工管理
</a>
</li>
</ul>
</div>
</nav>
</html>
add.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Template for Bootstrap</title>
<link th:href="@{/asserts/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
<style>
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
</style>
</head>
<body>
<div th:replace="~{commons/commons::topbar}"></div>
<div class="container-fluid">
<div class="row">
<div th:replace="~{commons/commons::sidebar(active='list.html')}"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form class="form-horizontal" th:action="@{/emp}" method="post">
<div class="form-group">
<label class="col-sm-2 control-label">名字</label>
<div class="col-sm-10">
<input type="text" class="form-control" placeholder="张三" name="ename" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">邮件</label>
<div class="col-sm-10">
<input type="email" class="form-control" placeholder="[email protected]" name="email" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">性别</label>
<div class="col-sm-offset-2 col-sm-10">
<label>
<input type="radio" name="gender" checked value="1"> 男
</label>
<label>
<input type="radio" name="gender" value="0"> 女
</label>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">部门</label>
<div class="col-sm-10">
<select class="form-control" name="did">
<option th:each="dept:${departments}" th:text="${dept.getDname()}"
th:value="${dept.getId()}"></option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">生日</label>
<div class="col-sm-10">
<input type="text" class="form-control" placeholder="2000-11-11" name="birthday" required>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-sm btn-success" type="submit">添加</button>
</div>
</div>
</form>
</main>
</div>
</div>
<script type="text/javascript" th:src="@{asserts/js/jquery-3.2.1.slim.min.js}"></script>
<script type="text/javascript" th:src="@{asserts/js/popper.min.js}"></script>
<script type="text/javascript" th:src="@{asserts/js/bootstrap.min.js}"></script>
<script type="text/javascript" th:src="@{asserts/js/feather.min.js}"></script>
<script>
import {
feather} from "../../static/asserts/js/feather.min";
feather.replace()
</script>
<script type="text/javascript" th:src="@{asserts/js/Chart.min.js}"></script>
<script>
let ctx = document.getElementById("myChart");
let myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
</script>
</body>
</html>
list.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Template for Bootstrap</title>
<link th:href="@{/asserts/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
<style>
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
</style>
</head>
<body>
<div th:replace="~{commons/commons::topbar}"></div>
<div class="container-fluid">
<div class="row">
<div th:replace="~{commons/commons::sidebar(active='list.html')}"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form class="form-inline" style="float: left;">
<h2>员工列表</h2>
<p style="width: 650px;"></p>
<a class="btn btn-sm btn-success" th:href="@{/emp}">添加</a>
</form>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>编号</th>
<th>姓名</th>
<th>邮箱</th>
<th>性别</th>
<th>部门</th>
<th>生日</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.getId()}"></td>
<td th:text="${emp.getEname()}"></td>
<td th:text="${emp.getEmail()}"></td>
<td th:text="${emp.getGender()==0?'女':'男'}"></td>
<td th:text="${emp.getDname()}"></td>
<td th:text="${emp.getBirthday()}"></td>
<td>
<a class="btn btn-sm btn-primary" th:href="@{/upemp/}+${emp.getId()}">编辑</a>
<a class="btn btn-sm btn-danger" th:href="@{/deleteEmp/}+${emp.getId()}">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
</main>
</div>
</div>
<script type="text/javascript" th:src="@{asserts/js/jquery-3.2.1.slim.min.js}"></script>
<script type="text/javascript" th:src="@{asserts/js/popper.min.js}"></script>
<script type="text/javascript" th:src="@{asserts/js/bootstrap.min.js}"></script>
<script type="text/javascript" th:src="@{asserts/js/feather.min.js}"></script>
<script>
import {
feather} from "../../static/asserts/js/feather.min";
feather.replace()
</script>
<script type="text/javascript" th:src="@{asserts/js/Chart.min.js}"></script>
<script>
let ctx = document.getElementById("myChart");
let myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
</script>
</body>
</html>
update.html
<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/asserts/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
</style>
</head>
<body>
<div th:replace="~{commons/commons::topbar}"></div>
<div class="container-fluid">
<div class="row">
<div th:replace="~{commons/commons::sidebar(active='list.html')}"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form class="form-horizontal" th:action="@{/updateEmp}" method="post">
<input type="hidden" name="id" th:value="${emp.getId()}">
<div class="form-group">
<label class="col-sm-2 control-label">名字</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="ename" th:value="${emp.getEname()}" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">邮件</label>
<div class="col-sm-10">
<input type="email" class="form-control" th:value="${emp.getEmail()}" name="email" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">性别</label>
<div class="col-sm-offset-2 col-sm-10">
<label>
<input th:checked="${emp.getGender()==1}" type="radio" name="gender" checked value="1"> 男
</label>
<label>
<input th:checked="${emp.getGender()==0}" type="radio" name="gender" value="0"> 女
</label>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">部门</label>
<div class="col-sm-10">
<select class="form-control" name="did">
<option th:selected="${emp.getDid()==dept.getId()}"
th:each="dept:${departments}" th:text="${dept.getDname()}"
th:value="${dept.getId()}"></option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">生日</label>
<div class="col-sm-10">
<input th:value="${emp.getBirthday()}" type="text"
class="form-control"
name="birthday">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-sm btn-success" type="submit">修改</button>
</div>
</div>
</form>
</main>
</div>
</div>
<script type="text/javascript" th:src="@{asserts/js/jquery-3.2.1.slim.min.js}"></script>
<script type="text/javascript" th:src="@{asserts/js/popper.min.js}"></script>
<script type="text/javascript" th:src="@{asserts/js/bootstrap.min.js}"></script>
<script type="text/javascript" th:src="@{asserts/js/feather.min.js}"></script>
<script>
import {
feather} from "../../static/asserts/js/feather.min";
feather.replace()
</script>
<script type="text/javascript" th:src="@{asserts/js/Chart.min.js}"></script>
<script>
let ctx = document.getElementById("myChart");
let myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
</script>
</body>
</html>
2、国际化
- Idea中setting中“file-encoding”修改项目编码、配置文件编码均为“utf-8”,否则配置文件中文会显示乱码
- resources下配置i18n文件,index.html前端页面使用th:text="#{login.btn}"等接收配置文件里的参数
login.properties
login.btn=登录
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名
在启动类的同级目录下创建config软件包
编写MyLocalResolver
package com.zhu.config;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
//国际化
public class MyLocaleResolver implements LocaleResolver {
//解析请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取请求中的语言参数
String lan = request.getParameter("l");
Locale locale = Locale.getDefault(); //如果没有就使用默认的
//如果请求的链接携带了国际化的参数
if (!StringUtils.isEmpty(lan)) {
String[] split = lan.split("_");
//国家,地区
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
MyMvcConfig
package com.zhu.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
}
//自定义的国际化组件就生效了
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**").excludePathPatterns("/index.html", "/", "/user/login", "/asserts/**");
}
}
3、拦截器
LoginHandlerInterceptor
package com.zhu.config;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 拦截器
*/
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//登录成功之后,应该有用户的session
Object loginUser = request.getSession().getAttribute("loginUser");
if (loginUser == null) {
//没有登录,而是直接进入的首页,肯定是不让进的
request.setAttribute("msg", "没有权限,请先登录");
request.getRequestDispatcher("/index.html").forward(request, response);
return false;
} else {
return true;
}
}
}
4、CRUD
- 这里我链接的数据库和狂神原版视频不太一样,请参考一下文章内容
- 创建数据库
create table department
(
id int not null
primary key,
dname varchar(20) not null
);
create table employee
(
id int auto_increment
primary key,
ename varchar(50) not null,
email varchar(50) null,
gender int null,
birthday datetime null,
did int null
);
- 配置yml’
spring:
thymeleaf:
cache: false
messages:
basename: i18n.login
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
url: jdbc:mysql://127.0.0.1:3306/springbootweb?useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=UTC
mybatis:
type-aliases-package: com.zhu.pojo
mapper-locations: classpath:com/zhu/mapper/*.xml
导入依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
实体类
Department
package com.zhu.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
//部门表
public class Department {
private Integer id;
private String dname;
}
Employee
package com.zhu.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
//员工表
public class Employee {
private Integer id;
private String ename;
private String email;
private Integer gender;
private int did;
private String birthday;
private String dname;
}
实现类接口
DepartmentMapper
package com.zhu.mapper;
import com.zhu.pojo.Department;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
@Mapper
public interface DepartmentMapper {
//获得所有部门信息
List<Department> getAll();
//通过ID的到部门
int getIdByDname(@Param("dname") String dname);
}
EmployeeMapper
package com.zhu.mapper;
import com.zhu.pojo.Employee;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
@Mapper
public interface EmployeeMapper {
//查询全部的员工信息
List<Employee> getAll();
//通过ID查询员工
Employee getEmployeeById(@Param("id") int id);
//通过ID删除员工
int delete(@Param("id") int id);
//增加一个员工
int save(Employee employee);
//修改员工
int updateEmpById(Employee employee);
}
接口实现类
DepartmentService
package com.zhu.service;
import com.zhu.pojo.Department;
import java.util.List;
public interface DepartmentService {
List<Department> getAll();
}
EmployeeService
package com.zhu.service;
import com.zhu.pojo.Employee;
import java.util.List;
public interface EmployeeService {
List<Employee> getAll();
//通过ID查询员工
Employee getEmployeeById(int id);
//通过ID删除员工
int delete(int id);
//增加一个员工
int save(Employee employee);
int updateEmpById(Employee employee);
}
DepartmentServiceImpl
package com.zhu.service;
import com.zhu.mapper.DepartmentMapper;
import com.zhu.pojo.Department;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DepartmentServiceImpl implements DepartmentService {
@Autowired
private DepartmentMapper departmentMapper;
@Override
public List<Department> getAll() {
return departmentMapper.getAll();
}
}
EmployeeServiceImpl
package com.zhu.service;
import com.zhu.mapper.EmployeeMapper;
import com.zhu.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
private EmployeeMapper employeeMapper;
@Override
public List<Employee> getAll() {
return employeeMapper.getAll();
}
@Override
public Employee getEmployeeById(int id) {
return employeeMapper.getEmployeeById(id);
}
@Override
public int delete(int id) {
return employeeMapper.delete(id);
}
@Override
public int save(Employee employee) {
return employeeMapper.save(employee);
}
@Override
public int updateEmpById(Employee employee) {
return employeeMapper.updateEmpById(employee);
}
}
sql 的xml
DepartmentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhu.mapper.DepartmentMapper">
<select id="getAll" resultType="Department">
select *
from springbootweb.department
</select>
</mapper>
EmployeeMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhu.mapper.EmployeeMapper">
<select id="getAll" resultType="Employee">
select e.id, e.ename, e.email, e.gender, e.birthday, e.did, dname
from springbootweb.employee e
left join springbootweb.department d on d.id = e.did
</select>
<select id="getEmployeeById" resultType="Employee">
select e.id, e.ename, e.email, e.gender, e.birthday, e.did, dname
from springbootweb.employee e
left join springbootweb.department d on d.id = e.did
where e.id = #{id}
</select>
<delete id="delete">
delete
from springbootweb.employee
where id = #{id}
</delete>
<insert id="save" parameterType="Employee">
insert into springbootweb.employee (ename, email, gender, birthday, did)
VALUES (#{ename}, #{email}, #{gender}, #{birthday}, #{did})
</insert>
<update id="updateEmpById" parameterType="Employee">
update springbootweb.employee
set ename=#{ename},
email = #{email},
gender = #{gender},
did = #{did},
birthday = #{birthday}
where id = #{id}
</update>
</mapper>
控制器
EmployeeController
package com.zhu.controller;
import com.zhu.pojo.Department;
import com.zhu.pojo.Employee;
import com.zhu.service.DepartmentServiceImpl;
import com.zhu.service.EmployeeServiceImpl;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@Controller
public class EmployeeController {
private final DepartmentServiceImpl departmentService;
final
EmployeeServiceImpl employeeService;
public EmployeeController(DepartmentServiceImpl departmentService, EmployeeServiceImpl employeeService) {
this.departmentService = departmentService;
this.employeeService = employeeService;
}
@RequestMapping("/emps")
public String list(Model model) {
List<Employee> employees = employeeService.getAll();
model.addAttribute("emps", employees);
return "emp/list";
}
@GetMapping("/emp")
public String toAdd(Model model) {
//查出部门的所有信息
List<Department> departments = departmentService.getAll();
model.addAttribute("departments", departments);
return "emp/add";
}
@PostMapping("/emp")
public String add(Employee employee) {
//添加的操作
employeeService.save(employee);
return "redirect:/emps";
}
//去员工的修改页面
@GetMapping("/upemp/{id}")
public String toUpdateEmp(@PathVariable("id") int id, Model model) {
//查出原来的数据
Employee employee = employeeService.getEmployeeById(id);
model.addAttribute("emp", employee);
List<Department> departments = departmentService.getAll();
model.addAttribute("departments", departments);
return "emp/update";
}
@PostMapping("/updateEmp")
public String UpdateEmp(Employee employee) {
employeeService.updateEmpById(employee);
return "redirect:/emps";
}
//删除员工
@GetMapping("/deleteEmp/{id}")
public String deleteEmp(@PathVariable("id") int id) {
employeeService.delete(id);
return "redirect:/emps";
}
}
LoginController
package com.zhu.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpSession;
@Controller
public class LoginController {
@RequestMapping("/user/login")
public String login(
@RequestParam("username") String username,
@RequestParam("password") String password,
Model model,
HttpSession session
) {
//具体的业务
if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
session.setAttribute("loginUser", username);
return "redirect:/main.html";
} else {
//告诉用户你登录失败了
model.addAttribute("msg", "用户名或者密码错误");
return "index";
}
}
@RequestMapping("/user/loginOut")
public String loginOut(HttpSession session) {
session.removeAttribute("loginUser");
return "redirect:/index.html";
}
}
六、整合JDBC
-
对于数据访问层,无论是SQL还是NOSQL,SpringBoot都是采用Spring Data方式统一处理
-
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
- application.yml
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/mybatis?userUnicode=true&chacacterEncoding=utf-8&serverTimezone=UTC
# Springboot使用 com.mysql.cj.jdbc.Driver 针对Mysql8以上,5可能会有bug
driver-class-name: com.mysql.cj.jdbc.Driver
- 测试查看默认数据源:hikari,据说是目前最快的数据源连接
@SpringBootTest
class JdbcApplicationTests {
/**
* SpringBoot只要配置了数据源,就自动将数据源封装进IOC容器,用户无需配置数据源组件,直接取出
*/
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
// 查看使用的数据源:目前最快的数据源:hikari.HikariDataSource
// System.out.println(dataSource.getClass());
// 获取数据库连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
// 关闭数据源
connection.close();
}
}
- controller
@Controller
public class JdbcController {
@Autowired
JdbcTemplate jdbcTemplate;
// 查询出user表中的所有信息,没有实体类,可以使用万能的map来存
@GetMapping("/userList")
@ResponseBody
public List<Map<String, Object>> userList() {
String sql = "select * from user";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return maps;
}
}
七、整合Druid
- Druid是阿里开源(现在是阿帕奇)的数据源,自动整合了日志监控
1、依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.22</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2、DruidConfig
Druid内置了一个监控工页面,可以通过配置监控后台进行访问:http://localhost:8080/druid/index.html
@Configuration
public class DruidConfig {
//将自定义的Druid配置进IOC容器
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource getDataSource() {
return new DruidDataSource();
}
//配置 Druid 监控管理后台的Servlet;
//内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式
@Bean
public ServletRegistrationBean statViewServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
// 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet
// 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
Map<String, String> initParams = new HashMap<>();
initParams.put("loginUsername", "admin"); //后台管理界面的登录账号
initParams.put("loginPassword", "123456"); //后台管理界面的登录密码
//后台允许谁可以访问
//initParams.put("allow", "localhost"):表示只有本机可以访问
//initParams.put("allow", ""):为空或者为null时,表示允许所有访问
initParams.put("allow", "");
//deny:Druid 后台拒绝谁访问
//initParams.put("kuangshen", "192.168.1.20");表示禁止此ip访问
//设置初始化参数
bean.setInitParameters(initParams);
return bean;
}
//配置 Druid 监控 之 web 监控的 filter
//WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
//exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
Map<String, String> initParams = new HashMap<>();
initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
bean.setInitParameters(initParams);
//"/*" 表示过滤所有请求
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
八、整合Mybatis
1、依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2、application.yml
# 整合数据源
spring:
datasource:
username: root
password: admin
url: jdbc:mysql://localhost:3306/mybatis?userUnicode=true&chacacterEncoding=utf-8&serverTimezone=UTC
# Springboot使用 com.mysql.cj.jdbc.Driver 针对Mysql8以上,5可能会有bug
driver-class-name: com.mysql.cj.jdbc.Driver
# 整合mybatis
mybatis:
type-aliases-package: com.ssl.bean
# 解决绑定异常:mapper.xml最好和接口的包名路径一致
mapper-locations: classpath:com.ssl.mapper/*.xml
3、接口和xml,bean
// 这个注解表示了这是一个mapper的注解类
@Mapper
@Repository
public interface UserMapper {
List<User> getAllUser();
User getUserById(@Param("id") int id);
void addUser(User user);
void deleteUser(@Param("id")int id);
User updateUser(User user);
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ssl.mapper.UserMapper">
<select id="getAllUser" resultType="User">
SELECT * FROM USER;
</select>
<select id="getUserById" parameterType="int" resultType="User">
select * from user where id=#{id};
</select>
<insert id="addUser" parameterType="User">
insert into user(id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
<update id="updateUser" parameterType="User">
update user set name=#{name},pwd=#{pwd} where id = #{id}
</update>
<delete id="deleteUser" parameterType="int">
delete from user where id = #{id}
</delete>
</mapper>
4、controller
@Controller
public class UserController {
@Autowired
private UserMapper userMapper;
@RequestMapping("/users")
@ResponseBody
public List<User> getUsers() {
return userMapper.getAllUser();
}
}
九、整合SpringSecurity
- 功能权限、访问权限、菜单权限…,我们使用过滤器,拦截器需要写大量的原生代码,这样很不方便
- 所以在网址设计之初,就应该考虑到权限验证的安全问题,其中Shiro、SpringSecurity使用很多
SpringSecurity是Springboot底层安全模块默认的技术选型,它可以实现强大的Web安全机制,只需要少数的spring-boot–spring-security依赖,进行少量的配置,就可以实现
1、依赖
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2、controller
@Controller
public class RouterController {
@RequestMapping({
"/", "/index"})
public String index() {
return "index";
}
@RequestMapping("/toLogin")
public String toLogin() {
return "views/login";
}
@RequestMapping("/level1/{id}")
public String level1(@PathVariable("id") int id) {
return "views/level1/" + id;
}
@RequestMapping("/level2/{id}")
public String level2(@PathVariable("id") int id) {
return "views/level2/" + id;
}
@RequestMapping("/level3/{id}")
public String level3(@PathVariable("id") int id) {
return "views/level3/" + id;
}
}
3、MySecurityConfig
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
// url授权: HttpSecurity
@Override
protected void configure(HttpSecurity security) throws Exception {
// 首页所有人可以访问,但是功能也只有对有权限的人可以访问
security
.authorizeRequests() // 认证请求
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3")
;
// 自带登录页面,http://localhost:8080/login
// 定制登录页,loginPage("/toLogin")
// 指定表单提交url:loginProcessingUrl("/user/login")
security.formLogin().loginPage("/toLogin")
.usernameParameter("username").passwordParameter("password")
.loginProcessingUrl("/user/login");
// 开启注销功能,源码http://localhost:8080/logout,并且注销成功后跳转到/的Controller
security.logout().logoutSuccessUrl("/");
// 版本不同问题,可能会出现注销失败,关闭csrf
// security.csrf().disable();
// 开启记住我功能:本质就是记住一个cookies,默认保存2周
security.rememberMe().rememberMeParameter("remember");
}
// 用户认证:AuthenticationManagerBuilder
// SpringSecurity5 以后默认需要新郑密码密码加密方式
@Override
public void configure(AuthenticationManagerBuilder builder) throws Exception {
// 内存中测试数据
builder.inMemoryAuthentication() // SpringSecurity5 以后默认需要新郑密码密码加密方式
.passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1")
.and()
.withUser("admin1").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2", "vip3")
;
}
}
4、登录注销和记住我
// 自带登录页面,http://localhost:8080/login
// 定制登录页,loginPage("/toLogin")
// 指定表单提交url:loginProcessingUrl("/user/login")
security.formLogin().loginPage("/toLogin")
.usernameParameter("username").passwordParameter("password")
.loginProcessingUrl("/user/login");
// 开启注销功能,源码http://localhost:8080/logout,并且注销成功后跳转到/的Controller
security.logout().logoutSuccessUrl("/");
// 开启记住我功能:本质就是记住一个cookies,默认保存2周
security.rememberMe().rememberMeParameter("remember");
5、前端权限验证
<!--security整合thymeleaf,便于前端整合
Springboot2.1.X以上需要springSecurity5的版本
但是xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"后缀还是4依然可以使用
-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
6、前端
<!--登录注销-->
<div class="right menu">
<!--如果未登录,就消失登录按钮-->
<div sec:authorize="!isAuthenticated()">
<a class="item" th:href="@{/toLogin}">
<i class="address card icon"></i> 登录
</a>
</div>
<!--如果登录,就显示用户名和注销-->
<div sec:authorize="isAuthenticated()">
<a class="item" th:href="@{/logout}">
<!--从授权那里获取name-->
用户名:<span sec:authentication="name"></span>
<!-- 有bug不能使用,角色:<span sec:authentication="principal.getAuthorities()"></span>-->
</a>
<a class="item" th:href="@{/logout}">
<i class="sign-out card icon"></i> 注销
</a>
</div>
</div>
十、整合Shiro
shiro官网:http://shiro.apache.org/
核心三大对象:用户Subject, 管理用户SecurityManager, 连接数据Realms
1、依赖
<!--前端交互整合-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!--整合Mybatis-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.22</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- shiro-spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.3</version>
</dependency>
<!--thymeleaf-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
2、Controller
@Controller
public class MyController {
@RequestMapping({
"/", "/index"})
public String toIndex(Model model) {
model.addAttribute("msg", "首页");
return "index";
}
@RequestMapping("/user/add")
public String addUser() {
return "user/add";
}
@RequestMapping("/user/update")
public String updateUser() {
return "user/update";
}
@RequestMapping("/toLogin")
public String toLogin() {
return "login";
}
@RequestMapping("/login")
public String login(String username, String password, Model model) {
// 获取用户
Subject user = SecurityUtils.getSubject();
// 封装参数,获取token
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 验证登录
try {
// 执行登录操作,跨类调用
user.login(token);
model.addAttribute("msg", "成功登录");
return "index";
} catch (UnknownAccountException uae) {
model.addAttribute("msg", "用户名错误");
return "login";
} catch (IncorrectCredentialsException ice) {
model.addAttribute("msg", "用户密码错误");
return "login";
}
}
@RequestMapping("/noAuth")
@ResponseBody
public String noAuth() {
return "未授权,无法访问";
}
}
3、ShiroConfig
@Configuration
public class ShiroConfig {
// 3 获取ShiroBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(
@Qualifier("getDefaultWebSecurityManager") WebSecurityManager securityManager
) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置安全管理器
bean.setSecurityManager(securityManager);
// 添加shiro的内置过滤器
/*
anon: 无需认证就可以登录
authc:必须认证才能登录
user: 必须拥有“记住我”这个功能
perms:拥有对某个资源的权限才能访问
role:拥有某个角色才能访问
*/
LinkedHashMap<String, String> map = new LinkedHashMap<>();
// 权限授权,访问url需要权限,支持通配符
map.put("/user/add", "perms[user:add]");
map.put("/user/update", "perms[user:update]");
bean.setFilterChainDefinitionMap(map);
// 设置登录url映射
bean.setLoginUrl("/toLogin");
// 设置未授权的请求
bean.setUnauthorizedUrl("/noAuth");
return bean;
}
// 2 获取安全管理器
@Bean(name = "getDefaultWebSecurityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(
@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 直接userRealm()传参也可以,这里演示Spring指定自动注入
securityManager.setRealm(userRealm);
return securityManager;
}
// 1 创建realm对象,需要自定义另一个类
@Bean(name = "userRealm")
public UserRealm userRealm() {
return new UserRealm();
}
// 结合Mybatis,整合ShiroDialect进组件
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
4、userRelam
// 自定义realms,继承AuthorizingRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserServiceImpl userService;
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 进入被拦截的url,就会进这个info
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 授权应该从数据库查出权限字段
// info.addStringPermission("user:add");
// 从 new SimpleAuthenticationInfo(queryUser, queryUser.getPwd(), "");传递过来第一个参数user最为subject
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal();
// 从数据库中获取验证权限
info.addStringPermission(currentUser.getPerms());
return info;
}
// 用户认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 模拟数据库中查出用户名、密码
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
User currentUser = userService.queryUserByName(userToken.getUsername());
// 验证用户名
if (currentUser == null) {
// 用户名不正确,就抛出UnknownAccountException
return null;
}
// 密码验证,shiro完成,不需要用户判断.直接返回
return new SimpleAuthenticationInfo(currentUser, currentUser.getPwd(), "");
}
}
十一、整合Swagger
1、依赖
<!-- springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
2、Controller
@RestController
public class HelloController {
@RequestMapping(value = "/hello")
public String hello() {
return "hello";
}
}
3、Config
@Configuration
@EnableSwagger2 // 开启Swagger2
public class MySwagger {
// 配置SwaggerBean实例
@Bean
public Docket getDocker(Environment environment) {
// 判断是否是生产环境的配置文件
Profiles isDevPro = Profiles.of("dev");
boolean flag = environment.acceptsProfiles(isDevPro);
return new Docket(DocumentationType.SWAGGER_2)
// 是否启用swagger,则浏览器不能访问
// .enable(false)
// 这里判断是dev生产环境才开启Swagger
.enable(flag)
.apiInfo(changeInfo())
.select()
// 扫描接口:,点进去看源码有很多种方式指定扫描方式
.apis(RequestHandlerSelectors.basePackage("com.ssl.controller"))
// 扫描路劲:过滤路径
.paths(PathSelectors.ant("/hello"))
.build();
}
// 更改默认info
private ApiInfo changeInfo() {
Contact author_contact = new Contact("laoSong", "http://123", "[email protected]");
return new ApiInfo(
"swagger文档学习",
"学习swagger",
"v1.0",
"http://123",
author_contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList());
}
}
测试运行:访问:http://localhost:8080/swagger-ui.html
4、指定swagger在生产环境中使用
方法:
- 判断是不是生产环境的yml
- 注入enable(flag)
server:
port: 8080
# 选择使用哪些配置
spring:
profiles:
active: dev # 激活dev配置
---
server:
port: 8081
spring:
profiles: test
---
server:
port: 8082
spring:
profiles: dev
5、设置docket分组
配置多个不同名注入bean,供多人开发使用
@Bean
public Docket docket2() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("AA");
}
十二、异步任务,定时任务,邮件任务
1、异步
异步处理还是非常常用的,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。
- 编写方法,假装正在处理数据,使用线程设置一些延时,模拟同步等待的情况
- 创建一个service包
- 创建一个类AsyncService
@Service
public class AsyncService {
//告诉Spring这是一个异步的方法
@Async
public void hello() {
try{
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在处理异步请求");
}
}
- 编写controller包
- 编写AsyncController类
我们去写一个Controller测试一下
@Controller
public class AsyncController {
@Autowired
private AsyncService asyncService;
@RequestMapping("/hello")
@ResponseBody
public String hello() {
return "hello";
}
// 开启异步后,先返回结果,在等待后台处理请求
@RequestMapping("/sleep")
@ResponseBody
public String sleep() {
// hello开启异步后,会创建另一个线程进行该操作
asyncService.hello();
return "hello";
}
}
// 启动类开启异步注解功能
@EnableAsync
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
访问http://localhost:8080/hello进行测试,3秒后出现success,这是同步等待的情况。
2、定时任务
项目开发中经常需要执行一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息,Spring为我们提供了异步执行任务调度的方式,提供了两个接口。
- TaskExecutor接口
- TaskScheduler接口
两个注解:
- @EnableScheduling
- @Scheduled
创建一个ScheduledService
@Service
public class ScheduledService {
//秒 分 时 日 月 周几
//0 * * * * MON-FRI
//注意cron表达式的用法;
@Scheduled(cron = "0 * * * * 0-7")
public void hello(){
System.out.println("hello.....");
}
}
这里写完定时任务之后,我们需要在主程序上增加@EnableScheduling 开启定时任务功能
@EnableAsync //开启异步注解功能
@EnableScheduling //开启基于注解的定时任务
@SpringBootApplication
public class SpringbootTaskApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootTaskApplication.class, args);
}
}
我们来详细了解下cron表达式;
http://www.bejson.com/othertools/cron/
3、邮件任务
- 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
<version>1.6.4</version>
<scope>compile</scope>
</dependency>
配置文件:
[email protected]
spring.mail.password=你的qq授权码
spring.mail.host=smtp.qq.com
# qq需要配置ssl
spring.mail.properties.mail.smtp.ssl.enable=true
获取授权码:在QQ邮箱中的设置->账户->开启pop3和smtp服务
测试
@Autowired
JavaMailSenderImpl mailSender;
@Test
public void contextLoads() {
//邮件设置1:一个简单的邮件
SimpleMailMessage message = new SimpleMailMessage();
message.setSubject("通知-明天来狂神这听课");
message.setText("今晚7:30开会");
message.setTo("[email protected]");
message.setFrom("[email protected]");
mailSender.send(message);
}
@Test
public void contextLoads2() throws MessagingException {
//邮件设置2:一个复杂的邮件
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setSubject("通知-明天来狂神这听课");
helper.setText("<b style='color:red'>今天 7:30来开会</b>",true);
//发送附件
helper.addAttachment("1.jpg",new File(""));
helper.addAttachment("2.jpg",new File(""));
helper.setTo("[email protected]");
helper.setFrom("[email protected]");
mailSender.send(mimeMessage);
}
十三、整合Dubbo+Zookeeper
1、RPC
两个核心模块:通讯,序列化。
2、Dubbo
官网:https://dubbo.apache.org/zh/
服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
调用关系说明
l 服务容器负责启动,加载,运行服务提供者。
l 服务提供者在启动时,向注册中心注册自己提供的服务。
l 服务消费者在启动时,向注册中心订阅自己所需的服务。
l 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
l 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
l 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
3、zookeeper
下载地址:http://archive.apache.org/dist/zookeeper/
- 运行/bin/zkServer.cmd ,初次运行会报错,没有zoo.cfg配置文件,可能遇到问题:闪退 !
解决方案:将conf文件夹下面的zoo_sample.cfg复制一份改名为zoo.cfg即可。编辑zkServer.cmd文件末尾添加pause 。这样运行出错就不会退出,会提示错误信息,方便找到原因。
-
使用zkCli.cmd测试
-
ls /:列出zookeeper根下保存的所有节点
-
create –e /zhu123:创建一个kuangshen节点,值为123
-
get /zhu:获取/zhu节点的值
4、window下安装dubbo-admin
地址 :https://github.com/apache/dubbo-admin/tree/master
dubbo本身并不是一个服务软件。它其实就是一个jar包,能够帮你的java程序连接到zookeeper,并利用zookeeper消费、提供服务。
但是为了让用户更好的管理监控众多的dubbo服务,官方提供了一个可视化的监控程序dubbo-admin,不过这个监控即使不装也不影响使用。
在项目目录下打包dubbo-admin
执行 dubbo-admin\target
下的dubbo-admin-0.0.1-SNAPSHOT.jar
java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
http://localhost:7001/ 用户名密码为:root-root;
5、SpringBoot + Dubbo + zookeeper
- 依赖
<!-- Dubbo Spring Boot Starter -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<!-- 引入zookeeper -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<!--排除这个slf4j-log4j12-->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
-
启动zookeeper !
-
IDEA创建一个空项目;
-
创建一个模块,实现服务提供者:provider-server , 选择web依赖即可
-
项目创建完毕,我们写一个服务,比如卖票的服务;
编写接口
package com.kuang.provider.service;
public interface TicketService {
public String getTicket();
}
编写实现类
package com.kuang.provider.service;
public class TicketServiceImpl implements TicketService {
@Override
public String getTicket() {
return "《狂神说Java》";
}
}
- 创建一个模块,实现服务消费者:consumer-server , 选择web依赖即可
- 项目创建完毕,我们写一个服务,比如用户的服务;
编写service
package com.zhu.consumer.service;
public class UserService {
//我们需要去拿去注册中心的服务
}
服务提供者
- 将服务提供者注册到注册中心,
- 在springboot配置文件中配置dubbo相关属性!
#当前应用名字
dubbo.application.name=provider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#扫描指定包下服务
dubbo.scan.base-packages=com.zhu.provider.service
- 在service的实现类中配置服务注解,发布服务!注意导包问题
import org.apache.dubbo.config.annotation.Service;
import org.springframework.stereotype.Component;
@Service //将服务发布出去
@Component //放在容器中
public class TicketServiceImpl implements TicketService {
@Override
public String getTicket() {
return "《狂神说Java》";
}
}
逻辑理解 :应用启动起来,dubbo就会扫描指定的包下带有@component注解的服务,将它发布在指定的注册中心中!
服务消费者
- 导入依赖,和之前的依赖一样;
配置参数
#当前应用名字
dubbo.application.name=consumer-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
- 本来正常步骤是需要将服务提供者的接口打包,然后用pom文件导入,我们这里使用简单的方式,直接将服务的接口拿过来,路径必须保证正确,即和服务提供者相同;
完善消费者的服务类
package com.kuang.consumer.service;
import com.kuang.provider.service.TicketService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;
@Service //注入到容器中
public class UserService {
@Reference //远程引用指定的服务,他会按照全类名进行匹配,看谁给注册中心注册了这个全类名
TicketService ticketService;
public void bugTicket(){
String ticket = ticketService.getTicket();
System.out.println("在注册中心买到"+ticket);
}
}
- 测试类编写;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ConsumerServerApplicationTests {
@Autowired
UserService userService;
@Test
public void contextLoads() {
userService.bugTicket();
}
}
启动测试
-
开启zookeeper
-
打开dubbo-admin实现监控
-
开启服务者
-
消费者消费测试,