初步了解:
SpringBoot就是一个JavaWeb框架
SpringBoot以约定大于配置的核心思想(maven、spring、springmvc、springboot…docker、k8s)
程序 = 数据结构 + 算法(集合框架map、list、数组);程序员
程序 = 面向对象 + 框架;码农
MVC 三层架构
MVVM 微服务架构
业务:service:userService:===> 模块!
springmvc,controller ==> 就负责提供接口!
http:rpc
无论是ERP、CRM或者是其他什么系统,你都把数据库访问,web访问,等等各个功能放到一个war包内。
打破之前 all in one的架构方式,把每个功能独立出来
Spring:the source for modern java
官方:提供了一个快速生成的网站!IDEA集成了这个网站
Application:程序的主入口
@SpringBootApplication
@SpringConfiguration
@Configuration
@Component
自动配置:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
starter
//SpringBootApplication : 标注这个类是一个springboot的应用
@SpringBootApplication
public class Springboot01HelloworldApplication {
public static void main(String[] args) {
//将springboot应用启动
SpringApplication.run(Springboot01HelloworldApplication.class, args);
}
}
@SpringBootConfiguration : springboot的配置
@Configuration : spring配置类
@Component : 说明这也是一个spring的组件
@EnableAutoConfiguration : 自动配置
@AutoConfigurationPackage : 自动配置包
@Import(AutoConfigurationPackages.Registrar.class) : 自动配置`包注册`
@Import(AutoConfigurationImportSelector.class) : 自动配置导入选择
//获取所有的配置
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
获取候选的位置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> 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;
}
META-INF/spring.factories : 自动配置的核心文件
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
所有的资源加载到配置类中!
结论:
Springboot所有的自动配置都在启动的时候被扫描并加载:
spring.factories所有的自动配置类都在这里,但是不一定生效,要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功!
关于SpringBoot,谈谈你的理解:
四步骤:
1. 推断这个应用的类是普通的项目还是Web项目
2. 查找并加载所有可用初始化器,设置到initializers属性中
3. 找出所有的应用程序监听器,设置到listeners属性中
4. 推断并设置main方法的定义类,找到运行的主类
全面接管SpringMVC的配置!
yaml可以直接给实体类赋值
yaml练习
# k=v
# 对空格的要求十分严格!
# 普通的key-value
# 注入到我们的配置类中!
# 对象
#student:
# 行内写法
student: {name: swaelee,age: 3}
# 数组
pets:
- cat
- dog
- pig
# pets: [cat,dog,pig]
yaml给属性赋值
此处属性与实体类上的@ConfigurationProperties(prefix = “guy1”)注解值相对应
guy1:
name: SwaeLee${random.uuid}
age: 12
happy: true
birth: 2021/04/19
maps: {k1: v1,k2: v2,k3: v3}
hello: lotie666
lists:
- code
- music
- game
dog:
name: ${guy1.hello:hilotie}_柯基666
age: 3
private String firstName;
yaml:
dog:
first-name: 阿黄
age: 2
结论:
在实体类上加@Validated注解
再对属性进行约束:
源码位置:Maven:validation:jakarta.validation-> validation->constraints
优先级问题:
server:
port: 8081
spring:
profiles:
active: dev
---
server:
port: 8082
spring:
profiles: dev
---
server:
port: 8083
spring:
profiles: test
@SpringBootApplication -> @EnableAutoConfiguration -> @Import(AutoConfigurationImportSelector.class)
表示这是一个配置类:
@Configuration
自动装配属性:
EnableConfigurationProperties(ServerProperties.class)-> @ConfigurationProperties(prefix = “server”)中的属性与application.properties中的属性一一对应
Spring的底层注解:根据不同的条件,来判断当前配置或者类是否生效
@ConditionalOnWebApplication
在我们这配置文件中能配置的东西,都存在一个固有的规律 xxxAutoConfiguration:默认值 xxxProperties 和 配置文件绑定,我们就可以使用自定义的配置了!
总结:
核心:
xxxAutoConfiguration:自动配置类;给容器中添加组件
xxxProperties:封装配置文件中相关属性;
我们随意拿到一个,它肯定有一个activemq.properties和activemqConfiguration
spring:
activemq:
in-memory:
jar :webapp
自动装配
思考:
springboot 到底帮我们配置了什么?
我们能不能进行修改?能修改那些东西?
要解决的问题:
静态资源
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
ServletContext servletContext = getServletContext();
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (servletContext != null) {
registration.addResourceLocations(new ServletContextResource(servletContext, SERVLET_LOCATION));
}
});
}
总结
public下放一些公共的访问资源:一些js
static静态资源:图片
resources:上传的文件
页面index.html通常放在templates,templates目录下的所有页面,只能通过controller来跳转
这个需要模板引擎的支持!
Thymeleaf官网文档:https://www.thymeleaf.org/
pom依赖
org.thymeleaf
thymeleaf-spring5
org.thymeleaf.extras
thymeleaf-extras-java8time
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
导入后,我们将html放在我们templates目录下即可
//在templates目录下的所有页面,只能通过controller来跳转
//这个需要模板引擎的支持!
@Controller
public class IndexController {
@RequestMapping("/TEST1")
public String test(){
return "index";
}
}
Thymeleaf语法:
html导入
<html lang="en" xmlns:th="http://www.thymeleaf.org">
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<p>
2017首次落败在节目
p>
<div th:text="${msg1}">div>
<div th:utext="${msg1}">div>
<hr>
<p th:each="user:${users}" th:text="${user}">p>
<p>
我在外面等A$en选拔结束
p>
body>
html>
@Controller
public class IndexController {
@RequestMapping("/TEST1")
public String test(Model model){
model.addAttribute("msg1","hello,springboot,01
");
model.addAttribute("users", Arrays.asList("SwaeLee","Jessie Lee"));
return "index";
}
}
@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
// ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
return resolver;
}
@Nullable // 注解说明:@Nullable 即参数可为null
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
if (requestedMediaTypes != null) {
// 获取候选的视图对象
List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
// 选择一个最适合的视图对象,然后把这个对象返回
View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
// .....
}
/*全面扩展 Springmvc 所有的请求都会经过dispatchservlet
1. 在类上加注解 @Configuration
2. 实现接口 WebMvcConfigurer
如果你想自定义DIY一些功能,只要写这个组件,然后将它交给springboot,springboot就会帮我们自动装配
*/
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//public interface ViewResolver 实现了视图解析器接口的类,我们就可以把它看错视图解析器
@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
//自定义了一个自己的视图解析器MyViewResolver
public static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String s, Locale locale) throws Exception {
return null;
}
}
}
自写start,写一个configuration和properties类,把两个类打到jar包里,放入spring-boot-autoconfigure,org,autoconfigure
//如果我们要扩展springmvc,官方建议我们这样去做
@Configuration
@EnableWebMvc //这就是,导入了一个类DelegatingWebMvcConfiguration作用:从容器中获取所有的webmvcconfig,如果我们要接管Springmvc,千万不能加这个注解,加了这个注解,就崩盘了
public class MyMvcConfig implements WebMvcConfigurer {
//视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/lee").setViewName("index");
}
}
在springboot中,有非常多的 xxx Configuration 帮助我们进行扩展配置,只要看见了这个东西,我们就要注意了!只要在源码中看见了这个类,立马关注它扩展了什么功能
首页实现
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
}
注意html处,需要改的位置
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<link th:href="@{/css/sigin.css}" rel="stylesheet">
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
首页配置:
页面国际化 (首先我们确保IDEA中 File Encodings中配置编码全为UTF-8)
<input type="text" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
<input type="password" class="form-control" th:placeholder="#{login.password}" required="">
<p class="mt-5 mb-3 text-muted">© 2017-2018p>
<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')}">Englisha>
form>
body>
html>
自定义国际化组件
package com.lee.config;
import org.apache.tomcat.jni.Local;
import org.springframework.web.servlet.LocaleResolver;
import org.thymeleaf.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
/**
* @author shkstart
* @date 21.5.7 - 10:46
*/
public class MyLocaleResolver implements LocaleResolver {
//解析请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取请求中的语言参数
String language = request.getParameter("l");
Locale locale = Locale.getDefault();//如果没有就使用默认的;
//如果请求的链接携带了国际化的参数
if (!StringUtils.isEmpty(language)){
//zh_CN
String[] split = language.split("_");
//国家,地区
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
在自定义MvcConfig中注册国际化组件
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
//自定义的国际化组件生效
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
loginController
@Controller
public class LoginController {
@RequestMapping("/user/login")
public String login(
@RequestParam("username") String username,
@RequestParam("password") String password,
Model model){
/*
具体的业务
判断
*/
if (!StringUtils.isEmpty(username) && "123456".equals(password)){
return "redirect:/main.html";
}else {
//给用户返回登录失败信息
model.addAttribute("errorLogin","用户名或密码错误");
return "index";
}
}
}
html注意处
<form class="form-signin" th:action="@{/user/login}">
<p style="color: darkblue;font-weight: bolder" th:text="${errorLogin}" th:if="${not #strings.isEmpty(errorLogin)}">p>
注意,在MyMvcConfig掩盖真实数据
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
}
我们思考,此时不需要登录直接输入main.html网址也可以进入网站,那我们需要用拦截器进行拦截
在LoginController加入
session.setAttribute("loginUser", username);
LoginHandlerInterceptor
package com.lee.config;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author shkstart
* @date 21.5.7 - 11:47
*/
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//登录成功后,应该有用户的session;
Object loginUser = request.getSession().getAttribute("loginUser");// 31
if (loginUser==null){//没有登录
request.setAttribute("errorLogin", "没有权限,请先登录");
request.getRequestDispatcher("/index.html").forward(request, response);
return false;
}else {
return true;
}
}
}
在dashboard中取出用户名返回
[[${session.loginUser}]]
员工列表展示
1. 提取公共部分
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="navbar">
<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">
<div th:replace="~{commons/commons::navbar}">div>
<div th:insert="~{commons/commons::sidebar}">div>
2. 如果要传递参数,可直接使用()传参,接收判断即可
<a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{/emps}">
<div th:insert="~{commons/commons::sidebar(active='list.html')}">div>
3. 列表循环展示
<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:${employees}">
<td th:text="${emp.getId()}">td>
<td th:text="${emp.getLastName()}">td>
<td th:text="${emp.getEmail()}">td>
<td th:text="${emp.getGender()}==0?'女':'男'">td>
<td th:text="${emp.department.getDepartmentName()}">td>
<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd')}">td>
<td>
<button class="btn btn-sm btn-primary">编辑button>
<button class="btn btn-sm btn-danger">删除button>
td>
tr>
tbody>
table>
添加员工
package com.lee.controller;
import com.lee.dao.DepartmentDao;
import com.lee.dao.EmployeeDao;
import com.lee.pojo.Department;
import com.lee.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpSession;
import java.util.Collection;
/**
* @author shkstart
* @date 21.5.8 - 9:48
*/
@Controller
public class EmployeeController {
//Controller层调Service层
@Autowired
EmployeeDao employeeDao;
@Autowired
DepartmentDao departmentDao;
@RequestMapping("/emps")
public String list(Model model){
Collection<Employee> employees = employeeDao.getAll();
model.addAttribute("employees",employees);
return "employee/list";
}
@RequestMapping("/emp")
public String toAddPage(Model model){
//查出所有部门的信息
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments",departments);
return "employee/add";
}
@PostMapping("/emp")
public String addEmp(Employee employee){
//添加的操作 forward
System.out.println("保存的信息=" + employee);
employeeDao.add(employee);//调用底层业务方法保存员工信息
return "redirect:/emps";
}
//到员工修改页面
@GetMapping("/emp/{id}") //href对应GetMapping
public String toUpdatePage(@PathVariable("id")Integer id,Model model){
//查出原来的数据
Employee employee = employeeDao.getEmployeeGetId(id);
model.addAttribute("emp",employee);
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments",departments);
return "employee/update";
}
@PostMapping("/updateEmp")
public String updateEmp(Employee employee){
employeeDao.add(employee);
return "redirect:/emps";
}
//删除员工
@RequestMapping("/delemp/{id}")
public String deleteEmp(@PathVariable("id")Integer id){
employeeDao.delete(id);
return "redirect:/emps";
}
//注销
@RequestMapping("/user/logout")
public String userLogout(HttpSession session){
session.removeAttribute("loginUser");
return "redirect:/index.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>Dashboard Template for Bootstraptitle>
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/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
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
style>
head>
<body>
<div th:replace="~{commons/commons::navbar}">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: 0px; top: 0px; right: 0px; bottom: 0px; 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">Dashboardh1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group mr-2">
<button class="btn btn-sm btn-outline-secondary">Sharebutton>
<button class="btn btn-sm btn-outline-secondary">Exportbutton>
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" src="asserts/js/jquery-3.2.1.slim.min.js" >script>
<script type="text/javascript" src="asserts/js/popper.min.js" >script>
<script type="text/javascript" src="asserts/js/bootstrap.min.js" >script>
<script type="text/javascript" src="asserts/js/feather.min.js" >script>
<script>
feather.replace()
script>
<script type="text/javascript" src="asserts/js/Chart.min.js" >script>
<script>
var ctx = document.getElementById("myChart");
var 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>Dashboard Template for Bootstraptitle>
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/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
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
style>
head>
<body>
<div th:insert="~{commons/commons::navbar}">div>
<div class="container-fluid">
<div class="row">
<div th:insert="~{commons/commons::sidebar(active='list.html')}">div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h2><a class="btn btn-sm btn-success" th:href="@{emp}">添加员工a> h2>
<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:${employees}">
<td th:text="${emp.getId()}">td>
<td th:text="${emp.getLastName()}">td>
<td th:text="${emp.getEmail()}">td>
<td th:text="${emp.getGender()}==0?'女':'男'">td>
<td th:text="${emp.department.getDepartmentName()}">td>
<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm')}">td>
<td>
<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑a>
<a class="btn btn-sm btn-danger" th:href="@{/delemp/}+${emp.id}">删除a>
td>
tr>
tbody>
table>
div>
main>
div>
div>
<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js">script>
<script type="text/javascript" src="asserts/js/popper.min.js">script>
<script type="text/javascript" src="asserts/js/bootstrap.min.js">script>
<script type="text/javascript" src="asserts/js/feather.min.js">script>
<script>
feather.replace()
script>
<script type="text/javascript" src="asserts/js/Chart.min.js">script>
<script>
var ctx = document.getElementById("myChart");
var 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>
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>Dashboard Template for Bootstraptitle>
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/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
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
style>
head>
<body>
<div th:insert="~{commons/commons::navbar}">div>
<div class="container-fluid">
<div class="row">
<div th:insert="~{commons/commons::sidebar(active='list.html')}">div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h2><a class="btn btn-sm btn-success" th:href="@{emp}">添加员工a> h2>
<form th:action="@{/emp}" method="post">
<div class="form-group">
<label>姓名label>
<input type="text" name="lastName" class="form-control" placeholder="swae">
div>
<div class="form-group">
<label>邮箱label>
<input type="email" name="email" class="form-control" placeholder="[email protected]">
div>
<div class="form-group">
<label>性别label><br>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1">
<label class="form-check-label">男label>
div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="0">
<label class="form-check-label">女label>
div>
div>
<div class="form-group">
<label>部门label>
<select class="form-control" name="department.id">
<option th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}">option>
select>
div>
<div class="form-group">
<label>生日label>
<input type="text" class="form-control"name="birth" placeholder="swaelee">
div>
<button type=submit class=btn btn-primary>添加button>
form>
main>
div>
div>
<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js">script>
<script type="text/javascript" src="asserts/js/popper.min.js">script>
<script type="text/javascript" src="asserts/js/bootstrap.min.js">script>
<script type="text/javascript" src="asserts/js/feather.min.js">script>
<script>
feather.replace()
script>
<script type="text/javascript" src="asserts/js/Chart.min.js">script>
<script>
var ctx = document.getElementById("myChart");
var 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>
<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 Bootstraptitle>
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/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
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
style>
head>
<body>
<div th:insert="~{commons/commons::navbar}">div>
<div class="container-fluid">
<div class="row">
<div th:insert="~{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 th:action="@{/updateEmp}" method="post">
<input type="hidden" name="id" th:value="${emp.getId()}">
<div class="form-group">
<label>姓名label>
<input th:value="${emp.getLastName()}" type="text" name="lastName" class="form-control" placeholder="swaelee">
div>
<div class="form-group">
<label>邮箱label>
<input th:value="${emp.getEmail()}" type="email" name="email" class="form-control" placeholder="[email protected]">
div>
<div class="form-group">
<label>性别label><br>
<div class="form-check form-check-inline">
<input th:checked="${emp.getGender()==1}" class="form-check-input" type="radio" name="gender" value="1">
<label class="form-check-label">男label>
div>
<div class="form-check form-check-inline">
<input th:checked="${emp.getGender()==0}" class="form-check-input" type="radio" name="gender" value="0">
<label class="form-check-label">女label>
div>
div>
<div class="form-group">
<label>部门label>
<select class="form-control" name="department.id">
<option th:selected="${dept.getId()==emp.getDepartment().getId()}" th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}">option>
select>
div>
<div class="form-group">
<label>生日label>
<input th:value="${emp.getBirth()}" type="text" class="form-control" name="birth">
div>
<button type=submit class=btn btn-primary>修改button>
form>
main>
div>
div>
<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js">script>
<script type="text/javascript" src="asserts/js/popper.min.js">script>
<script type="text/javascript" src="asserts/js/bootstrap.min.js">script>
<script type="text/javascript" src="asserts/js/feather.min.js">script>
<script>
feather.replace()
script>
<script type="text/javascript" src="asserts/js/Chart.min.js">script>
<script>
var ctx = document.getElementById("myChart");
var 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>
Data
application.yml
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/mybatisText?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
测试 (HikariDataSource 号称 Java WEB 当前速度最快的数据源)
package com.lee;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@SpringBootTest
class Springboot04DataApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
//查看一下默认的数据源
System.out.println(dataSource.getClass());//class com.zaxxer.hikari.HikariDataSource
//获得数据库链接
Connection connection = dataSource.getConnection();
System.out.println(connection);//HikariProxyConnection@2085952212 wrapping com.mysql.cj.jdbc.ConnectionImpl@7d5508e0
// xxx Template : SpringBoot已经配置好模板bean,拿来即用 CRUD
// jdbc
// redis
//关闭
connection.close();
}
}
controller
package com.lee.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
/**
* @author shkstart
* @date 21.5.9 - 10:18
*/
@RestController
public class JDBCController {
@Autowired
JdbcTemplate jdbcTemplate;
// 查询数据库的所有信息
// 没有实体类,数据库中的东西怎么获取? 万能Map
@GetMapping("/userList")
public List<Map<String,Object>> userList(){
String sql = "select * from user";
List<Map<String, Object>> list_maps = jdbcTemplate.queryForList(sql);
return list_maps;
}
@GetMapping("/addUser")
public String addUser(){
String sql = "insert into mybatisText.user(id,name,pwd) value(5,'菲董','999999')";
jdbcTemplate.update(sql);
return "insert-ok";
}
@GetMapping("/updateUser/{id}")
public String updateUser(@PathVariable("id") int id){
String sql = "update mybatisText.user set name=?,pwd=? where id="+id;
//封装
Object[] objects = new Object[2];
objects[0] = "菲董2";
objects[1] = "999998";
jdbcTemplate.update(sql,objects);
return "updateUser-okey";
}
@GetMapping("/deleteUser/{id}")
public String deleteUser(@PathVariable("id") int id){
String sql = "delete from mybatistext.user where id = ?";
jdbcTemplate.update(sql,id);
return "deleteUser-okey";
}
}
Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了C3P0、DBCP、PROXOOL等DB池的优点,同时加入了日志监控(纯天然)。
作用:Druid 可以很好的监控 DB 池链接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。
pom
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.24version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
yml
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/mybatisText?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourcesStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
测试
@Test
void contextLoads() throws SQLException {
//查看一下默认的数据源
System.out.println(dataSource.getClass());//class com.alibaba.druid.pool.DruidDataSource
//获得数据库链接
Connection connection = dataSource.getConnection();
System.out.println(connection);//com.mysql.cj.jdbc.ConnectionImpl@18b8d173
//关闭
connection.close();
}
DruidConfig
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")// 与yml内datasourcel连接
@Bean
public DataSource druidDataSource(){
return new DruidDataSource();
}
// 后台监控功能 : web.xml,ServletRegistrationBean
// 因为SpringBoot 内置了 servlet容器,所以没有web.xml,替代方法:ServletRegistrationBean
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
//后台需要有人登录,账号密码
HashMap<String, String> initParameters = new HashMap<>();
//增加配置 : 重点
initParameters.put("loginUsername", "admin");// 登录key 是固定的 loginUsername
initParameters.put("loginPassword", "123456");// loginPassword
//允许谁可以访问 : 重点
initParameters.put("allow", "localhost");
//禁止谁能访问
// initParameters.put("swaelee", "192.168.11.123");
bean.setInitParameters(initParameters);// 设置初始化参数 : 重点
return bean;
}
//filter
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
bean.setFilter(new WebStatFilter());
//可以过滤哪些请求?
HashMap<String , String> initParameters = new HashMap<>();
initParameters.put("exclusions", "*.js,*.css,/druid/*");
bean.setInitParameters(initParameters);
return bean;
}
}
整合包
mybatis-spring-boot-starter
pom
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.3version>
dependency>
在web开发中,安全第一位!过滤器,拦截器~
功能性需求:否
做网站:安全应该在什么时候考虑?设计之初!
shiro、SpringSecurity:很像~ 除了类不一样,名字不一样;
认证,授权(vip1,vip2,vip3)
authentication access-control framework
导入pom
thymeleaf
<dependency>
<groupId>org.thymeleafgroupId>
<artifactId>thymeleaf-spring5artifactId>
dependency>
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-java8timeartifactId>
dependency>
<dependency>
<groupId>org.thymeleafgroupId>
<artifactId>thymeleaf-spring5artifactId>
dependency>
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-java8timeartifactId>
dependency>
security
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
controller层
@Controller
public class RouterController {
@RequestMapping({"/","/index","/index.html"})
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;
}
}
SecurityConfig
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 链式编程
// 授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//首页所有人可以访问,功能页只有对应有权限的人才能访问
//请求授权的规则
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//没有权限默认会到登录页面,需要开启登录的页面
// /login
http.formLogin();
}
// 认证 Springboot 2.1.X 可以直接使用
// 密码编码 : PasswordEncoder
// 在Spring Security 5.0+ 新增了很多的加密方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 定制认证规则
// 这些数据正常应该从数据库中读
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("user").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1").and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2").and()
.withUser("swaelee").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3");
}
}
Thymeleaf 与 Spring Security整合
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-springsecurity4artifactId>
<version>3.0.4.RELEASEversion>
dependency>
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 链式编程
// 授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//首页所有人可以访问,功能页只有对应有权限的人才能访问
//请求授权的规则
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//没有权限默认会到登录页面,需要开启登录的页面
// /login
http.formLogin();
// 注销,跳到首页
// 网站防止攻击 : get , post
http.csrf().disable(); // 关闭csrf功能,logout失败可能存在的原因.
http.logout().logoutSuccessUrl("/");
}
// 认证 Springboot 2.1.X 可以直接使用
// 密码编码 : PasswordEncoder
// 在Spring Security 5.0+ 新增了很多的加密方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 定制认证规则
// 这些数据正常应该从数据库中读
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("user").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1").and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2").and()
.withUser("swaelee").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3");
}
}
index
<div class="ui container">
<div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
<div class="ui secondary menu">
<a class="item" th:href="@{/index}">首页a>
<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">
用户名: <spqn sec:authentication="name">spqn>
a>
div>
<div sec:authorize="isAuthenticated()">
<a class="item" th:href="@{/logout}">
<i class="sign-out icon">i> 注销
a>
div>
div>
div>
div>
<div class="ui segment" style="text-align: center">
<h3>Spring Security Study by SwaeLeeh3>
div>
<div>
<br>
<div class="ui three column stackable grid">
<div class="column" sec:authorize="hasRole('vip1')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 1h5>
<hr>
<div><a th:href="@{/level1/1}"><i class="bullhorn icon">i> Level-1-1a>div>
<div><a th:href="@{/level1/2}"><i class="bullhorn icon">i> Level-1-2a>div>
<div><a th:href="@{/level1/3}"><i class="bullhorn icon">i> Level-1-3a>div>
div>
div>
div>
div>
<div class="column" sec:authorize="hasRole('vip2')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 2h5>
<hr>
<div><a th:href="@{/level2/1}"><i class="bullhorn icon">i> Level-2-1a>div>
<div><a th:href="@{/level2/2}"><i class="bullhorn icon">i> Level-2-2a>div>
<div><a th:href="@{/level2/3}"><i class="bullhorn icon">i> Level-2-3a>div>
div>
div>
div>
div>
<div class="column" sec:authorize="hasRole('vip3')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 3h5>
<hr>
<div><a th:href="@{/level3/1}"><i class="bullhorn icon">i> Level-3-1a>div>
<div><a th:href="@{/level3/2}"><i class="bullhorn icon">i> Level-3-2a>div>
<div><a th:href="@{/level3/3}"><i class="bullhorn icon">i> Level-3-3a>div>
div>
div>
div>
div>
div>
div>
div>
开启记住我功能:
// 开启记住我功能 cookie,默认保存两周
http.rememberMe().rememberMeParameter("remember");
定制登录页
// 定制登录页
http.formLogin().loginPage("/toLogin");
// http.formLogin().loginPage("/toLogin").usernameParameter().passwordParameter().loginProcessingUrl();
导入pom
<dependencies>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-coreartifactId>
<version>1.6.0version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>jcl-over-slf4jartifactId>
<version>1.7.21version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.21version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
dependencies>
resources下log4j.properties
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
log4j.logger.org.apache=WARN
log4j.logger.org.springframework=WARN
log4j.logger.org.apache.shiro=INFO
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache,ehcache.EhCache=WARN
此处需导入ini、action tacker插件(Settings -> Plugins)
shiro.ini
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# =============================================================================
# Quickstart INI Realm configuration
#
# For those that might not understand the references in this file, the
# definitions are all based on the classic Mel Brooks' film "Spaceballs". ;)
# =============================================================================
# -----------------------------------------------------------------------------
# Users and their assigned roles
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setUserDefinitions JavaDoc
# -----------------------------------------------------------------------------
[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz
# -----------------------------------------------------------------------------
# Roles with assigned permissions
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5
Quickstart.java
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Simple Quickstart application showing how to use Shiro's API.
*
* @since 0.9 RC2
*/
public class Quickstart {
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
// The easiest way to create a Shiro SecurityManager with configured
// realms, users, roles and permissions is to use the simple INI config.
// We'll do that by using a factory that can ingest a .ini file and
// return a SecurityManager instance:
// Use the shiro.ini file at the root of the classpath
// (file: and url: prefixes load from files and urls respectively):
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
// for this simple example quickstart, make the SecurityManager
// accessible as a JVM singleton. Most applications wouldn't do this
// and instead rely on their container configuration or web.xml for
// webapps. That is outside the scope of this simple quickstart, so
// we'll just do the bare minimum so you can continue to get a feel
// for things.
SecurityUtils.setSecurityManager(securityManager);
// Now that a simple Shiro environment is set up, let's see what you can do:
// get the currently executing user:
Subject currentUser = SecurityUtils.getSubject();
// Do some stuff with a Session (no need for a web or EJB container!!!)
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Retrieved the correct value! [" + value + "]");
}
// let's login the current user so we can check against roles and permissions:
if (!currentUser.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
token.setRememberMe(true);
try {
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) {
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) {
//unexpected condition? error?
}
}
//say who they are:
//print their identifying principal (in this case, a username):
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
//test a role:
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
//test a typed permission (not instance-level)
if (currentUser.isPermitted("lightsaber:wield")) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
//a (very powerful) Instance Level permission:
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
//all done - log out!
currentUser.logout();
System.exit(0);
}
}
学习目标:
前后端分离
Vue + SpringBoot
后端时代:前端只用管理静态页面;html -> 后端。模板引擎 JSP -> 后端是主力
前后端分离式时代:
产生一个问题:
解决方案:
官网:https://swagger.io/
在项目中使用Swagger需要 springbox;
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.9.2version>
dependency>
@Configuration
@EnableSwagger2 // 开启Swagger2
public class SwaggerConfig {
}
Swagger的bean实例 Docket;
@Configuration
@EnableSwagger2 // 开启Swagger2
public class SwaggerConfig {
// 配置了Swagger的Docket的bean实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo());
}
// 配置Swagger信息=apiInfo
private ApiInfo apiInfo(){
// 作者信息
Contact DEFAULT_CONTACT = new Contact("SwaeLee", "https://blog.csdn.net/SwaeLeeUknow?spm=1000.2115.3001.5343", "[email protected]");
return new ApiInfo(
"SwaggerAPI日志 By SwaeLee",
"再小的帆也能远航",
"v1.0",
"https://blog.csdn.net/SwaeLeeUknow?spm=1000.2115.3001.5343", DEFAULT_CONTACT,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0", new ArrayList());
}
}
Docker.select
// 配置了Swagger的Docket的bean实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(false) // enable 是否启动swagger,如果为False,则swagger不能在浏览器中访问
.select()
// RequestHandlerSelectors,配置要扫描接口的方法
// basePackage:指定要扫描的包
// any(): 扫描全部
// none():不扫描
// withClassAnnotation(): 扫描类上的注解,参数是一个注解的反射对象
// withMethodAnnotation(): 扫描方法上的注解
.apis(RequestHandlerSelectors.basePackage("com.lee.swagger.controller"))
/* paths():过滤什么路径
.paths(PathSelectors.ant("/lee/**"))*/
.build(); // 工厂模式
}
// 配置Swagger信息=apiInfo
private ApiInfo apiInfo(){
// 作者信息
Contact DEFAULT_CONTACT = new Contact("SwaeLee", "https://blog.csdn.net/SwaeLeeUknow?spm=1000.2115.3001.5343", "[email protected]");
return new ApiInfo(
"SwaggerAPI日志 By SwaeLee",
"再小的帆也能远航",
"v1.0",
"https://blog.csdn.net/SwaeLeeUknow?spm=1000.2115.3001.5343", DEFAULT_CONTACT,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0", new ArrayList());
}
问题:我只希望我的Swagger在生产环境中使用,在发布的时候不使用?
// 配置了Swagger的Docket的bean实例
@Bean
public Docket docket(Environment environment){
// 设置要显示的Swagger环境
Profiles profiles = Profiles.of("dev","test");
//获取项目的环境:
//通过environment.acceptsProfiles判断是否处在自己设定的环境当中
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(flag) // enable 是否启动swagger,如果为False,则swagger不能在浏览器中访问
.select()
.apis(RequestHandlerSelectors.basePackage("com.lee.swagger.controller"))
// .paths(PathSelectors.ant("/lee/**"))
.build(); // 工厂模式
}
.groupName("SwaeLee")
问题:如何配置多个组?
@Bean
public Docket docket1(){
return new Docket(DocumentationType.SWAGGER_2).groupName("A");
}
@Bean
public Docket docket2(){
return new Docket(DocumentationType.SWAGGER_2).groupName("B");
}
@Bean
public Docket docket3(){
return new Docket(DocumentationType.SWAGGER_2).groupName("C");
}
实体类配置;
只需要两步
@ApiModel("用户类") // 给实体类加入文档注释
public class User {
@ApiModelProperty("用户名")
public String username;
@ApiModelProperty("密码")
public String password;
}
}
//只要我们的接口中,返回值中存在实体类,他就会被扫描到Swagger中
@PostMapping("/user")
public User user(){
return new User();
}
Api注解
@Api("Hello控制类")
@RestController
public class HelloController {
// /error
@GetMapping(value = "/hello")
public String hello(){
return "hello";
}
//只要我们的接口中,返回值中存在实体类,他就会被扫描到Swagger中
@PostMapping("/user")
public User user(){
return new User();
}
// Operation接口,放在方法上的
@ApiOperation("user控制类")
@GetMapping("/hello2")
public String hello(@ApiParam("用户名") String username){
return "hello"+username;
}
@ApiOperation("Post控制类")
@PostMapping("/postTest")
public User postTest(@ApiParam("用户名") User user){
int i = 5/0;
return user;
}
}
总结:
Swagger是一个优秀的工具
【注意点】在正式发布的时候,关闭Swagger!出于安全考虑,而且节省运行内存。
异步任务~
定时任务~ timer
邮件发送~
1. 异步:
service
@Service
public class AsyncService {
// 告诉Spring这是一个异步的方法
@Async
public void hello(){
try{
Thread.sleep(3000);
} catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("数据正在处理...");
}
}
controller
@RestController
public class AsyncController {
@Autowired
AsyncService asyncService;
@RequestMapping("/hello09")
public String hello(){
asyncService.hello(); // 停止三秒,转圈~
return "ok";
}
}
在主类上加入@EnableAsync注解 // 开启异步注解功能
2. 邮件发送
导入pom
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-mailartifactId>
dependency>
测试类
@Autowired
JavaMailSenderImpl mailSender;
@Test
void contextLoads() {
// 一个简单的邮件
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setSubject("123");
mailMessage.setText("123123");
mailMessage.setTo("[email protected]");
mailMessage.setFrom("[email protected]");
mailSender.send(mailMessage);
}
@Test
void contextLoads2() throws MessagingException {
// 一个复杂的邮件
MimeMessage mimeMessage = mailSender.createMimeMessage();
// 组装
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);
helper.setSubject("晚上好");
helper.setText("l came
" +
" l saw
",true);
// 附件
helper.addAttachment("skrt.jpg", new File("C:\\1.jpg"));
// helper.addAttachment("2.jpg", new File("C:\\1.jpg"));
helper.setTo("[email protected]");
helper.setFrom("[email protected]");
mailSender.send(mimeMessage);
}
/**
*
* @param html:文本
* @param subject:主题
* @param text:正文
* @throws MessagingException
*/
public void sendMail(boolean html,String subject,String text) throws MessagingException{
// 一个复杂的邮件
MimeMessage mimeMessage = mailSender.createMimeMessage();
// 组装
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,html);
helper.setSubject(subject);
helper.setText(text, true);
// 附件
helper.addAttachment("skrt.jpg", new File("C:\\1.jpg"));
helper.setTo("[email protected]");
helper.setFrom("[email protected]");
mailSender.send(mimeMessage);
}
3. 定时任务
TaskScheduler 任务调度者
TaskExecutor 任务执行者@ EnableScheduling //开启定时功能的注解
@ Scheduled //什么时候执行Cron表达式
@Service
public class ScheduledService {
// 在一个特定的时间执行这个方法 Timer
//cron 表达式
//秒 分 时 日 月 周几
/*
30 10 5 * * ? 每天5点10分30秒执行一次
0 10 5,8 * * ? 每天 5点和10点 执行一次
0 0/5 8 * * ? 每隔5分钟执行一次
0 15 10 ? * 1-6 每个月的,周一到周六,10.15分执行
*/
@Scheduled(cron = "30 08 20 * * ?")
public void hello(){
System.out.println("hello,你被执行了~");
}
}
标题 ‘7’ 处
|阿里云|腾讯云|华为云
分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现时为了用廉价、普通的机器完成单个计算机无法完成的而计算、存储任务。其目的是利用更多的机器,处理更多的数据。
单一应用架构:
适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。
缺点:
分布式架构:
提高业务复用及整合的分布式服务框架(RPC)是关键。
流动计算架构:用于提高机器利用率的资源调度和治理中心(SOA)[Service Oriented Architecture]是关键。
通信两种协议 : HTTP RPC
RPC(远程过程调用),是一种进程通信方式,是一种技术的思想,而不是规范。
它允许程序员调用另一个地址空间(通常共享网络的另一台机器上)的过程或函数,而不用程序显式编码这个远程调用的细节。
程序员无论调用本地的还是远程的函数,本质上编写的调用代码基本相同。
也就是说两台服务器A,B,一个应用部署再A服务器上,想要调用B服务器上应用提供的函数、方法,由于不再一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。
为什么要用RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方法完成的需求,比如不同的系统件的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要再多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调用远程函数;
RPC两个核心模块:通讯,序列化。
底层式Netty
Dubbo(18年重启):高可用的RPC框架,专注于RPC。Dubbo 3.x Error Exception
HTTP SpringCloud(生态)
专业的事,交给专业的人来做。
Apache Dubbo 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
快速启动
官方文档:dubbo官方文档
Dubbo 采用全 Spring 配置方式,透明化接入应用,对应用没有任何 API 侵入,只需要 Spring 加载 Dubbo 的配置即可,Dubbo 基于 Spring 的 Schema 扩展进行加载。
前台 中台(整合) 后台
服务提供者(Provider)
服务消费者(Consumer)
注册中心(Registry)
监控中心(Monitor)
zookeeper:hadoop hive
window下载安装aookeeper
注意在3.5以后,带bin的包才是我们要下载的包
window下载安装dubbo-admin
官网地址:https://github.com/apache/dubbo-admin/tree/master
mvn clean package -Dmaven.test.skip=true
java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
访问localhost:7001
默认密码root-root
zookeeper:注册中心
dubbo-admin:是一个监控管理后台~查看我们注册了哪些服务,哪些服务被消费了。
Dubbo:jar包
测试:
public interface TicketService {
public String getTicket();
}
serviceImpl
//zookeeper:服务注册与发现
@Service //可以被扫描到,项目一启动就注册到Registry中心
@Component // 这里使用Dubbo后,尽量不用Service注解
public class TicketServiceImpl implements TicketService {
@Override
public String getTicket() {
return "Swalee On Da Track";
}
}
org.apache.dubbo
dubbo-spring-boot-starter
2.7.3
com.github.sgroschupf
zkclient
0.1
org.apache.curator
curator-framework
2.12.0
org.apache.curator
curator-recipes
2.12.0
org.apache.zookeeper
zookeeper
3.4.14
org.slf4j
slf4j-log4j12
server.port=8001
# 服务应用名字
dubbo.application.name=provider-server
# 注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
# 哪些服务要被注册
dubbo.scan.base-packages=com.lee.service
consumer-application.properties
server.port=8002
# 消费者去哪里拿服务暴漏自己的名字
dubbo.application.name=consumer-sever
# Registry中心的地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
@Service // 放到容器中
public class UserService {
//想拿到provider-sever提供的票,要去注册中心拿到服务
@Reference //引用,Pom坐标,可以定义路径相同的接口
TicketService ticketService;
public void buyTicket(){
String ticket = ticketService.getTicket();
System.err.println("在注册中心拿到的=>" + ticket);
}
}
测试类:
@SpringBootTest
class ConsumerSeverApplicationTests {
@Autowired
UserService userService;
@Test
void contextLoads() {
userService.buyTicket();
}
}
步骤:
前提:zookeeper服务已开启!
回顾以前,架构
三层架构 + MVC
架构 ---> 解耦
开发框架
Spring
IOC AOPIOC:控制反转
(容器)原来我们都是自己一步一步操作,现在交给容器,我们需要什么就去拿就可以了
AOP:切面(本质,动态代理)
为了解决什么问题?
不影响业务本来的情况下,实现动态增加功能,大量应用在日志,事务…等等方面Spring是一个轻量级的Java开发框架
目的:解决企业开发的复杂性问题
Spring是春天,配置文件十分复杂。
SpringBoot
新一代JavaEE的开发标准
SpringBoot并不是新东西,就是Spring的升级版!
开箱即用! -> 拿过来就可以用!
它帮助我们自动配置了非常多的东西,我们拿来即用!特性:约定大于配置!
随着公司体系越来越大,用户越来越多。
微服务架构 —> 新架构
模块化,功能化
用户,支付,签到,娱乐…
人数过于庞大:一台服务器解决不了;再增加一台服务器。(横向解决问题)
假设A服务器占用98%资源,B服务器只占用10%–负载均衡
将原来的整体项目,分成模块化,用户就是一个单独的项目,项目和项目之间需要通信,如何通信?
用户非常多,而签到十分少。给用户多一点服务器,给签到少一点服务器。
微服务架构问题?
分布式架构会遇到的四个核心问题?
1. 这么多服务,客户端该如何去访问?
2. 这么多服务,服务之间如何进行通信?
3. 这么多服务,如何统一管理/治理?
4. 服务挂了,怎么办?
解决方案:
SpringCloud,是一套生态,就是来解决以上分布式架构的4个问题。
想使用SpringCloud,必须要掌握SpringBoot,因为SpringCloud是基于SpringBoot;
1. Spring Cloud NetFlix,出来了一套解决方案!一站式解决方案。我们都可以直接去这里拿
Api网关,zuul组件
Feign --> HttpClient --> HTTP的通信方式,同步并阻塞
服务注册与发现,Eureka
熔断机制,Hystrix
2018年年底,NetFlix宣布无限期停止维护。生态不再维护,就会脱节。
2. Apache Dubbo zookeeper,第二套解决系统
API:没有,要么找第三方组件,要么自己实现。
Dubbo是一个高性能的基于Java实现的 RPC通信框架。2.6.x
服务注册与发现,zookeeper:Hadoop,Hiv
熔断机制:没有,借助了Hystrix
不完善,Dubbo 当前 Dubbo 3.0 将提供具备当代特征(如响应性编程)的相关支持,同时汲取阿里内部 HSF 的设计长处来shi
3. SpringCloud Alibaba 一站式解决方案!
目前,又提出了一种方案:
服务网格::下一代微服务标准,Server Mesh
代表解决方案:istio(未来可能需要掌握)
解决的问题,万变不离其宗,一通百通!
1. API网关,服务路由
2. HTTP,RPC框架,异步调用
3. 服务注册与发现,高可用
4. 熔断机制,服务降级基于这四个问题,开发一套解决方案,也叫SpringCloud微服务解决方案。
为什么要解决这个问题?本质:网络是不可靠的!
不要停下学习的脚步!
笔记参照-B站up主:狂神说