微服务架构打破之前的all in one 的架构方式,把每个功能元素对出来。
好处:
可以从Spring Initializr上下载一个项目,耶可以从IDEA中创建springboot项目!
要在Application的同级目录下建包,不然不会生效!
在pom.xml里有一个web依赖:用来启动tomcat
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
springboot所有的依赖都是以spring-boot-starter开头!
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.7.1version>
<relativePath/>
parent>
<groupId>com.liugroupId>
<artifactId>helloWorldartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>helloWorldname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
在maven的lifecycle里有一个package可以打包成jar包!
第一步:新建项目
第二步:选择spring-web项目
在resources目录下新建banner.txt文件
放入你要生成的banner图示:
pom.xml
spring-boot-dependencies:核心依赖在父工程中
依赖不需要配置版本号是因为有版本仓库
启动器
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
启动器:就是springBoot的启动场景
要使用什么功能就找到对应的启动器就可以了start
主程序
@SpringBootApplication : 标注这个类是一个springboot的应用
@SpringBootConfiguration:springboot的配置类,启动类下的所有资源被导入
@Configuration:配置
@Component:组件
@EnableAutoConfiguration:自动配置
@AutoConfigurationPackage:自动配置包
@Import({Registrar.class}):导入`包注册`
@Import({AutoConfigurationImportSelector.class}) :自动导入选择器
//获取所有的配置
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//获取候选的配置的方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
META-INF/spring.factories:自动配置的核心文件
getAutoConfigurationEntry:获取自动配置的实体
getCandidateConfigurations:获取候选的配置
protected Class> getAnnotationClass() {
return EnableAutoConfiguration.class;
}
List getCandidateConfigurations 所有的加载的配置
loadSpringFactories:项目资源:"META-INF/spring.factories"
系统资源
从这些资源中遍历所有的element并封装成properties供我们使用
@SpringBootApplication
public class Springboot01HelloworldApplication {
//该方法返回一个configurableApplicationContext对象
//参数一:应用入口的类 参数类:命令行传参
public static void main(String[] args) {
SpringApplication.run(Springboot01HelloworldApplication.class, args);
}
}
这个类主要做了以下几件事:
查看构造器
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = Collections.emptySet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
this.applicationStartup = ApplicationStartup.DEFAULT;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
使用一个全局的配置文件,配置文件名称是固定的
1.格式
server:
port: 8080
2.标记语言
<server>
<port>8081port>
server>
# 普通的key-value
name: liuxiang
# 对象
student:
name: liuxiang
age: 3
#行内写法
student1: {name: liuxiang,age: 3}
#数组
pets:
- cat
- dog
- pig
pets1: [cat,dog,pig]
1.原生的赋值:需要给每一个属性赋值,麻烦
@Value("旺财")
private String name;
@Value("3")
private Integer age;
2.用yaml赋值
person:
name: liuxiang${random.uuid}
age: ${random.int}
happy: true
birth: 1997/12/12
maps: {k1: v1,k2: v2}
list:
- code
- music
- boy
dog:
name: ${person.hello:hello}_旺财 #前面值如果不存在就选择后面的值
age: 3
springboot测试:
package com.liu;
import com.liu.pojo.Dog;
import com.liu.pojo.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Springboot02ConfigApplicationTests {
@Autowired
private Person person;
@Test
void contextLoads() {
System.out.println(person);
}
}
结果:注入成功!
Person{name='liuxiang', age=18, happy=true, birth=Fri Dec 12 00:00:00 CST 1997, maps={k1=v1, k2=v2}, list=[code, music, boy], dog=Dog{name='旺财', age=3}}
实体类:
package com.liu.pojo;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Component
@ConfigurationProperties(prefix = "person")
/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值映射到这个组件中
告诉springboot将本类中的所有属性和配置文件中相关的配置进行绑定
参数prefix = "person":将配置文件中的person下面的所有属性一一对应
只有这个组件是容器中的组件才能用@ConfigurationProperties
*/
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> list;
private Dog dog;
public Person() {
}
public Person(String name, Integer age, Boolean happy, Date birth, Map<String, Object> maps, List<Object> list, Dog dog) {
this.name = name;
this.age = age;
this.happy = happy;
this.birth = birth;
this.maps = maps;
this.list = list;
this.dog = dog;
}
@Validated //数据校验
参考配置:
@Null 限制只能为null
@NotNull 限制必须不为null
@AssertFalse 限制必须为false
@AssertTrue 限制必须为true
@DecimalMax(value) 限制必须为一个不大于指定值的数字
@DecimalMin(value) 限制必须为一个不小于指定值的数字
@Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future 限制必须是一个将来的日期
@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@Past 限制必须是一个过去的日期
@Pattern(value) 限制必须符合指定的正则表达式
@Size(max,min) 限制字符长度必须在min到max之间
@Past 验证注解的元素值(日期类型)比当前时间早
@NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
配置文件存放地址:
application.properties文件:
# springboot的多环境配置:可以选择激活哪一个配置文件
spring.profiles.active=dev
application-dev.properties 线下环境:
server.port=8082
application-test.properties 测试环境:
server.port=8081
yaml配置实现:(推荐)
server:
port: 8081
spring:
profiles:
active: dev
---
server:
port: 8082
spring:
profiles:dev
---
server:
port: 8083
spring:
profiles:dev
一定要满足条件才会生效,导入相关依赖,找到对应的start启动器即可!
配置文件yaml和spring.factories的关系:
可以在yaml文件下通过debug=true来查看,哪些自动配置类生效了,一部分生效,一部分不生效,选择最好的
debug: true
要解决的问题:
这四个文件c夹下的所有的资源都可以被访问到,例如:
优先级:
resources>static>public
jsp就是一个模板引擎
前端页面变量报红添加注释
引入thymeleaf:
Thymeleaf
GitHub - thymeleaf/thymeleaf: Thymeleaf is a modern server-side Java template engine for both web and standalone environments.
Spring Boot Reference Guide spring导入start文档
依赖jar包:
<dependency>
<groupId>org.thymeleafgroupId>
<artifactId>thymeleaf-spring5artifactId>
dependency>
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-java8timeartifactId>
dependency>
结论:
需要使用thymeleaf,只需要将html放在templates下,在controller下设置跳转页面路径即可
用thymeleaf需要导入头文件约束 xmlns:th=“http://www.thymeleaf.org”
超连接,文本等等需要放在th下
常用命名空间:
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<div th:text="${msg}">div>
<div th:utext="${msg}">div>
<hr>
<h3 th:each="user:${users}" th:text="${user}">h3>
body>
html>
变量表达式:
变量:${}:与EL表达式一样
消息:#{}
URL:@{}
Fragment:~{}:片段表达式
文本:‘’
数字:1,2
布尔值:true
Null:null
文本操作:
数学运算:
其他操作:
等于操作:==,!=
比较:>,<,<=,>=
三元运算符:(if) ? (then) ? (else)
29. Developing Web Applications (spring.io)
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.
视图解析器的源码:
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered, InitializingBean{}
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes) throws Exception {
List<View> candidateViews = new ArrayList();
if (this.viewResolvers != null) {
Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
Iterator var5 = this.viewResolvers.iterator();
while(var5.hasNext()) {
ViewResolver viewResolver = (ViewResolver)var5.next();
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
candidateViews.add(view);
}
Iterator var8 = requestedMediaTypes.iterator();
自定义视图解析器:继承视图解析器的接口并注册到bean中会自动装配
package com.liu.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Locale;
//全面扩展mvc
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//ViewResolver 实现了视图解析器接口的类,可以把它看作视图解析器
@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
//自定义一个自己的视图解析器MyViewResolver
public static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
}
格式Formatter
public String getDateFormat() {
return this.format.getDate();
}
可以再yml文件中自己配置日期格式!
视图跳转
//扩展springMVC,官方这样操作
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/liu").setViewName("test");
}
}
@EnableWebMvc注解:自动配置的时候不能加这个注解
@EnableWebMvc //导入了这个类DelegatingWebMvcConfiguration 作用:从容器中获取所有的webmvc config
原因:在WebMvcAutoConfiguration这个类中有三个条件满足才生效,而在EnableWebMvc这个注解当中导入了这个类DelegatingWebMvcConfiguration,不满足自动装配的条件了
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
总结:
#关闭模板引擎的缓存
spring.thymeleaf.cache=false
要确保全部是UTF-8格式!
可视化配置:
页面的每个原始都需要这么配置:
在yml文件中配置真实的配置文件地址:
#真实配置文件
spring.messages.basename=i18n.login
国际化消息用:#{}取值
前端的两个跳转链接:
<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>
package com.liu.config;
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;
public class MyLocaleResolver implements LocaleResolver {
//解析请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取请求中的语言参数 带l都走这个请求
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 request, HttpServletResponse response, Locale locale) {
}
}
要将这个写的类注册到spring中:@Bean
package com.liu.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.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");
}
//自定义的国际化组件
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
步骤:
index.html
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}">p>
判断条件格式:
${#strings.isEmpty(name)}
${#strings.arrayIsEmpty(nameArr)}
${# strings.listIsEmpty(nameList)}
${# strings.setIsEmpty(nameSet)}
后端controller层:
@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();
}
}
@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
session.setAttribute("loginUser",username);
return "redirect:/main.html";
}else {
model.addAttribute("msg","用户名或者密码错误!");
return "index";
}
}
}
package com.liu.config;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginHandlerInterceptor implements HandlerInterceptor {
//return true 放行 反之不放行
@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;
}
}
}
@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");
}
}
实现代码的复用:用thymeleaf模板进行片段的插入
将公共部分的代码提取到common文件下:
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">
dashboard.html:侧边栏
<div th:replace="~{/common/commons::sidebar}">div>
list.html:在同样的位置进行插入片段!用~{}方式
<div th:replace="~{/common/commons::sidebar}">div>
dashboard.html:导航栏
<div th:replace="~{/common/commons::topbar}">div>
list.html:在同样的位置进行插入片段!用~{}方式
<div th:replace="~{/common/commons::topbar}">div>
commons.html
<a th:class="${active=='main.html'?'nav-link active':'nav-link'}" th:href="@{/index.html}">
<a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{/emps}">
dashboard.html
<div th:replace="~{/common/commons::sidebar(active='main.html')}">div>
list.html
<div th:replace="~{/common/commons::sidebar(active='list.html')}">div>
thymeleaf前端变量报红的解决办法:在html页面的头部加上下面这个注释
<thead>
<tr>
<th>idth>
<th>lastNameth>
<th>emailth>
<th>genderth>
<th>departmentth>
<th>birthth>
<th>操作th>
tr>
thead>
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.getId()}">td>
<td>[[${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(epm.getBirth(),'yyyy-MM-dd HH:mm:ss')}">td>
<td>
<a class="btn btn-sm btn-primary" th:href="@{'/emp/'+${emp.getId()}}">编辑a>
<a class="btn btn-sm btn-danger">删除a>
td>
tr>
tbody>
日期转换:去参考thymeleaf官方文档
地址:Tutorial: Using Thymeleaf
${#dates.format(date, 'dd/MMM/yyyy HH:mm')}
${#dates.arrayFormat(datesArray, 'dd/MMM/yyyy HH:mm')}
${#dates.listFormat(datesList, 'dd/MMM/yyyy HH:mm')}
${#dates.setFormat(datesSet, 'dd/MMM/yyyy HH:mm')}
问题报错:
Failed to convert property value of type ‘java.lang.String’ to required type ‘java.util.Date’ for property ‘birth’;
解决:
在pojo实体类的属性上添加注解:
@DateTimeFormat(pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
或者在配置文件中修改:
spring.mvc.date-format=yyyy-MM-dd
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form th:action="@{emp}" method="post">
<div class="form-group">
<label>LastNamelabel>
<input type="text" name="lastName" class="form-control" placeholder="name">
div>
<div class="form-group">
<label>Emaillabel>
<input type="email" name="email" class="form-control" placeholder="email">
div>
<div class="form-group">
<label>Genderlabel>
<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>departmentlabel>
<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>Birthlabel>
<input type="text" name="birth" class="form-control" placeholder="2022-07-13">
div>
<button type="submit" class="btn btn-default btn-success">添加button>
form>
main>
@GetMapping("/emp")
public String toAddPage(Model model){
//查出所有部门的信息
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments",departments);
return "emp/add";
}
@PostMapping("/emp")
public String addEmp(Employee employee){
//添加的操作
System.out.println("add=>"+employee);
employeeDao.add(employee);//保存员工信息
return "redirect:/emps";
}
list.html页面
<td>
<a class="btn btn-sm btn-primary" th:href="@{'/emp/'+${emp.getId()}}">编辑a>
td>
<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>LastNamelabel>
<input th:value="${emp.getLastName()}" type="text" name="lastName" class="form-control" placeholder="name">
div>
<div class="form-group">
<label>Emaillabel>
<input th:value="${emp.getEmail()}" type="email" name="email" class="form-control" placeholder="email">
div>
<div class="form-group">
<label>Genderlabel>
<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>departmentlabel>
<select class="form-control" name="department.id">
<option th:selected="${dept.getId()==emp.department.getId()}"
th:each="dept:${departments}" th:text="${dept.getDepartmentName()}"
th:value="${dept.getId()}">option>
select>
div>
<div class="form-group">
<label>Birthlabel>
<input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd')}" type="text" name="birth" class="form-control" placeholder="2022-07-13">
div>
<button type="submit" class="btn btn-default btn-success">修改button>
form>
main>
//跳转到修改页面
@GetMapping("/emp/{id}") //restful风格
public String tuUpdateEmp(@PathVariable("id")Integer id,Model model){
//查出原来的数据
Employee employeeById = employeeDao.getEmployeeById(id);
model.addAttribute("emp",employeeById);
//查询部门所有信息
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments",departments);
return "emp/update";
}
@PostMapping("/updateEmp")
public String updateEmp(Employee employee){
employeeDao.add(employee);//修改员工信息
return "redirect:/emps";
}
index.html
<a class="btn btn-sm btn-danger" th:href="@{'/delete/'+${emp.getId()}}">删除a>
//删除员工信息
@RequestMapping("/delete/{id}")
public String deleteById(@PathVariable("id")Integer id){
employeeDao.deleteEmployeeById(id);
return "redirect:/emps";
}
对于数据访问层,无论是SQL还是NOsql,springboot底层都是采用springData的方式进行统一处理。
springData官网:https://spring.io/projects/spring-data
数据库相关的启动器:官方文档:Spring Boot Reference Guide
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jdbcartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jdbcartifactId>
dependency>
Could not autowire. No beans of ‘DataSource’ type found问题解决办法:
@Autowired(required = false)
配置文件:
spring:
datasource:
username: root
password: 123456789
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver #数据源HikariDataSource
package com.liu.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;
@RestController
public class JdbcController {
@Autowired(required = false)
JdbcTemplate jdbcTemplate;
//查询数据库的所有信息
//无实体类时用万能的map查询数据
@GetMapping("/userList")
public List<Map<String,Object>> userList(){
String sql = "select * from mybatis.user";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return maps;
}
@GetMapping("/addUser")
public String addUser(){
String sql = "insert into mybatis.user(id,name,pwd) values (5,'小明','12345678')";
int i = jdbcTemplate.update(sql);
return "addOK!";
}
@GetMapping("/updateUser/{id}")
public String updateUser(@PathVariable("id")int id){
String sql = "update mybatis.user set name=?,pwd=? where id="+id;
//封装数据
Object[] objects = new Object[2];
objects[0] = "小华"; //修改的名字
objects[1] = "zxczxc"; //密码
jdbcTemplate.update(sql,objects);
return "updateOK";
}
@GetMapping("/deleteUser/{id}")
public String deleteUser(@PathVariable("id")int id){
String sql = "delete from mybatis.user where id=?";
jdbcTemplate.update(sql,id);
return "deleteOK!";
}
}
阿里巴巴平台的数据库连接池实现,结合C3P0,DBCP,PROXOOL等DB池的有点,同时加入日志监控。
天生针对监控而生的DB连接池
HikariDataSource是速度最快的数据源
依赖包:
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.21version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
yaml配置中修改数据源的使用:只用加type即可
spring:
datasource:
username: root
password: 123456789
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
filters: stat,wall,log4j
max-pool-prepared-statement-per-connection-size: 20
use-global-data-source-stat: true
connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
测试数据源:
@SpringBootTest
class Springboot04DataApplicationTests {
@Autowired(required = false)
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
System.out.println(dataSource.getClass());
//获得数据库连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
}
结果:
强大之处:自动配置
建立一个自动配置的文件
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource(){
return new DruidDataSource();
}
}
与yaml配置文件绑定:只需要再前缀处加入数据源的名称和@Bean注解
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
//后台监控
//因为springboot内置了servlet容器,所以没有web.xml,替代方法:ServletRegistrationBean
@Bean
public ServletRegistrationBean StatViewServlet(){
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
//后台需要登录,账号密码配置
Map<String,String> initParameters = new HashMap<>();
//增加配置
initParameters.put("loginUsername","admin");//登录的key是固定的loginUsername loginPassword
initParameters.put("loginPassword","123456");
//允许谁可以访问
initParameters.put("allow","");
//静止谁访问
initParameters.put("liuxiang","192.168.11.123");
bean.setInitParameters(initParameters);//设置初始参数
return bean;
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
Map<String, String> map = new HashMap<>();
map.put("exclusions", "*.js*,*.css,/druid/*");
bean.setInitParameters(map);
return bean;
}
}
访问druid monitor自动跳转到login,html页面
输入用户名和密码进入后台监控:
出现filter下的数据即可监控统计sql了
当发起后台sql请求时,可以看到监控统计:
导入依赖包:
不是springboot官方的,不是以spring开头
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.1version>
dependency>
以前是写一个mapper和一个对应的mapper.xml,现在统一放在resource目录下
配置文件下:
#整合mybatis
#别名
mybatis.type-aliases-package=com.liu.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
UserMapper
@org.apache.ibatis.annotations.Mapper
@Repository
public interface Mapper {
List<User> queryUserList();
User queryUserById(int id);
int updateUser(User user);
int addUser(User user);
int deleteUser(int id);
}
UserMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.liu.mapper.Mapper">
<select id="queryUserList" resultType="User">
select *
from mybatis.user;
select>
<select id="queryUserById" resultType="User">
select *
from mybatis.user
where user.id=#{id};
select>
<update id="updateUser" parameterType="User">
update mybatis.user
set name = #{name},pwd=#{pwd}
where id=#{id};
update>
<delete id="deleteUser" parameterType="int">
delete
from mybatis.user
where id=#{id};
delete>
<insert id="addUser" parameterType="User">
insert into mybatis.user(id, name, pwd)
values (#{id},#{name},#{pwd});
insert>
mapper>
controller层
@RestController
public class UserController {
@Autowired(required = false)
private UserMapper userMapper;
@GetMapping("/queryUserList")
public List<User> queryUserList(){
List<User> userList = userMapper.queryUserList();
return userList;
}
@GetMapping("/queryUserById/{id}")
public User queryUserById(@PathVariable("id") int id){
User user = userMapper.queryUserById(id);
return user;
}
@GetMapping("/updateUser")
public String updateUser(){
userMapper.updateUser(new User(2,"hyt","3423213"));
return "OK";
}
@GetMapping("/addUser")
public String addUser(){
userMapper.addUser(new User(4,"dsajda","esdfsdf"));
return "OK";
}
@GetMapping("/deleteUser")
public String deleteUser(){
userMapper.deleteUser(4);
return "ok";
}
}
依赖包
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
package com.liu.config;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
//AOP思想
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//首页所有人可以访问,功能页只有对应有权限的人才能访问
//授权
http.authorizeHttpRequests().antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//没有权限默认会登录页面
http.formLogin();
//注销
http.logout();
}
//认证
//密码编码:passwordEncoder
//在spring Security 5.0+ 新增很多加密方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("liuxiang")
.password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
}
}
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;
}
}
更改前端页面的图标icon
推荐一个网站:https://semantic-ui.com/elements/icon.html
如下所示前端代码即可更改样式:
<a class="item" th:href="@{/toLogin}">
<i class="hand point right icon">i> 登录
a>
<a class="item" th:href="@{/logout}">
<i class="share square icon">i> 注销
a>
导依赖包
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-springsecurity4artifactId>
<version>3.0.4.RELEASEversion>
dependency>
导入命名空间:
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">"
功能:
1.导入依赖
<dependencies>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-coreartifactId>
<version>1.4.1version>
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>
2.ini配置
[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
3.Quickstart
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) {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
// get the currently executing user:
//获取当前用户对象subject
Subject currentUser = SecurityUtils.getSubject();
// 通过当前用户拿到session
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Subject =>session! [" + 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);
}
}
4.log4j.properties
#输出日志文件到console和file目的地
log4j.rootLogger=DEBUG,console,file
# 控制台(console)
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.Target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
# 文件输出的相关设置(file)
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.File=D:/logs/log.log4j
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p] [%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
总结方法:
//1.获取当前用户对象subject
Subject currentUser = SecurityUtils.getSubject();
// 2.通过当前用户拿到session
Session session = currentUser.getSession();
// 3.判断当前的用户是否被认证
if (!currentUser.isAuthenticated()){UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");}
// 4.获得当前用户的认证
currentUser.getPrincipal()
// 5.拥有什么角色
if (currentUser.hasRole("schwartz")){}
// 6.获取什么权限
if (currentUser.isPermitted("lightsaber:wield")) {}
//7.注销
currentUser.logout();
三大对象:(面试必问)
导入依赖:
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-spring-boot-web-starterartifactId>
<version>1.9.0version>
dependency>
index.html
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>首页title>
head>
<body>
<h1>首页h1>
<div th:if="${session.loginUser==null}">
<a th:href="@{toLogin}">登录a>
div>
<p th:text="${msg}">p>
<hr>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">adda>
div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">updatea>
div>
body>
html>
连接数据部分:授权和认证UserRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=>授权doGetAuthorizationInfo");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// info.addStringPermission("user:add"); 所有人都有权限
//拿到当前登录的这个对象
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal(); //拿到user对象
//设置当前用户的权限
info.addStringPermission(currentUser.getPerms());
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了=>认证doGetAuthenticationInfo");
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
//用户名,密码:连接数据库
User user = userService.queryUserByName(userToken.getUsername());
if (user==null){
return null;//抛去用户名不存在的异常
}
Subject currentSubject = SecurityUtils.getSubject();
Session session = currentSubject.getSession();
session.setAttribute("loginUser",user);
//密码认证:加密,MD5
return new SimpleAuthenticationInfo(user,user.getPwd(),"");
}
}
Controller层
@Controller
public class MyController {
@RequestMapping({"/","/index"})
public String toIndex(Model model){
model.addAttribute("msg","hello,Shiro!");
return "index";
}
@RequestMapping("/user/add")
public String add(){
return "user/add";
}
@RequestMapping("/user/update")
public String update(){
return "user/update";
}
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
@RequestMapping("/login")
public String login(String username,String password,Model model){
//获取当前用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//登录
try {
subject.login(token);
return "index";
} catch (UnknownAccountException e){
model.addAttribute("msg","用户名错误");
return "login";
} catch (IncorrectCredentialsException e){
model.addAttribute("msg","密码错误");
return "login";
}
}
@RequestMapping("/unauth")
@ResponseBody
public String unAuthor(){
return "未经授权不予登录!";
}
}
shiroConfig
@Configuration
public class ShiroConfig {
//3.ShiroFilterFactoryBean
@Bean(name = "filterShiroFilterRegistrationBean")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager getDefaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(getDefaultWebSecurityManager);
//添加shiro的内置过滤器
/*
anno:无需认证就可以访问
authc:必须认证才能访问
user:必须拥有 记住我 功能才能用
perms:拥有对某个资源的权限才能访问
roles:拥有某个角色权限才能访问
*/
//拦截
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// filterChainDefinitionMap.put("/user/add","authc");
// filterChainDefinitionMap.put("/user/update","authc");
//授权
filterChainDefinitionMap.put("/user/add","perms[user:add]");
filterChainDefinitionMap.put("/user/update","perms[user:update]");
//拦截请求
filterChainDefinitionMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
//设置登录的请求
bean.setLoginUrl("/toLogin");
//设置未被认证的请求
bean.setUnauthorizedUrl("/unauth");
return bean;
}
//2.DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联Realm
securityManager.setRealm(userRealm);
return securityManager;
}
//1.创建Realm对象,需要自定义
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
//整合shiroDialect:用来整合shiro和thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
导入依赖
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.8version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.2.0version>
dependency>
<dependency>
<groupId>com.github.theborakompanionigroupId>
<artifactId>thymeleaf-extras-shiroartifactId>
<version>2.0.0version>
dependency>
编写配置文件:yaml
spring:
datasource:
username: root
password: 123456789
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
filters: stat,wall,log4j
max-pool-prepared-statement-per-connection-size: 20
use-global-data-source-stat: true
connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mybatis:
type-aliases-package: com.liu.pojo
mapper-locations: classpath:mapper/*.xml
User
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String pwd;
private String name;
private String perms;//权限表
}
UserMapper接口
@Mapper
@Repository
public interface UserMapper {
public User queryUserByName(String name);
}
UserMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.liu.mapper.UserMapper">
<select id="queryUserByName" resultType="User">
select *
from mybatis.user
where user.name=#{name};
select>
mapper>
service接口
public interface UserService {
public User queryUserByName(String name);
}
service实现类
@Service
public class UserServiceImpl implements UserService{
//调mapper层
@Autowired
UserMapper userMapper;
@Override
public User queryUserByName(String name) {
return userMapper.queryUserByName(name);
}
}
官网:API Documentation & Design Tools for Teams | Swagger
前后端分离:
api框架:
在项目中使用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>
配置swagger:
@Configuration
@EnableSwagger2 //开启swagger2
public class SwaggerConfig {
}
产生的问题:Failed to start bean ‘documentationPluginsBootstrapper’; nested exception is java.lang.NullPointerException
因为版本不兼容
解决:
在springboot的配置文件中配置如下:因为高版本后的路径匹配修改了
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
降低springboot版本到2.5.6
测试运行:Swagger UI
@Configuration
@EnableSwagger2 //开启swagger2
public class SwaggerConfig {
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo());
}
//配置swagger信息=apiInfo
private ApiInfo apiInfo(){
//作者信息
Contact contact = new Contact("liuxiang", "http://localhost:8080/", "[email protected]");
return new ApiInfo("刘想的swagger日记",
"牛牛的Java学习之旅",
"1.0",
"http://localhost:8080/",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList());
}
}
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
//basePackage:指定要扫描的包
//any():扫描全部
//none():不扫描
//withClassAnnotation():扫描类上的注解
//withMethodAnnotation():扫描方法上的注解
.apis(RequestHandlerSelectors.basePackage("com.liu.swagger.controller"))
//paths():过滤路径
.paths(PathSelectors.ant("/liu/**"))
.build();
}
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(false) //是否启动swagger
.select()
.apis(RequestHandlerSelectors.basePackage("com.liu.swagger.controller"))
.build();
}
如果想要在生产环境下开启swagger,在测试环境下不开启
方法:
通过设置两套模式application-dev.properties和application-prod.properties,分别配置不同的端口号,在application.properties中选择开启哪套环境
spring.profiles.active=dev
在swaggerConfig中配置:
@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)
.select()
.apis(RequestHandlerSelectors.basePackage("com.liu.swagger.controller"))
.build();
}
配置多个分组,多个Docket即可!
@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;
}
controller
//只要接口中的返回值存在实体类,就会被扫描到swagger中
@PostMapping(value = "/user")
public User user(){
return new User();
}
其他的注解
@ApiOperation("hello控制类") //用在方法上
@GetMapping(value = "/hello")
public String hello(@ApiParam("用户名") String username){
return "hello"+username;
}
结果:有中文了
在方法上加注解@Async
@Service
public class AsyncService {
//告诉spring这是一个异步的方法
@Async
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据正在处理中");
}
}
在main方法上加注解@EnableAsync开启异步功能
@EnableAsync
@SpringBootApplication
public class Springboot10TestApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot10TestApplication.class, args);
}
}
就可以一瞬间响应,无需等待!
导入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
一个简单的邮件
1.先配置相关信息,先在qq邮件设置里将POP3/SMTP服务开启
[email protected]
spring.mail.password=zyvegqhkctuobeif
spring.mail.host=smtp.qq.com
#开启加密验证
spring.mail.properties.mail.smtp,ssl.enable=true
2.测试
@SpringBootTest
class Springboot10TestApplicationTests {
@Autowired(required = false)
JavaMailSenderImpl mailSender;
@Test
void contextLoads() {
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setSubject("牛牛的Java学习之旅!");
mailMessage.setText("加油继续努力!");
mailMessage.setTo("[email protected]");
mailMessage.setFrom("[email protected]");
mailSender.send(mailMessage);
}
}
一个复杂的邮件发送
@SpringBootTest
class Springboot10TestApplicationTests {
@Autowired(required = false)
JavaMailSenderImpl mailSender;
@Test
void contextLoads() throws MessagingException {
MimeMessage mimeMessage = mailSender.createMimeMessage();
//组件
MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage,true);
//设置主题
messageHelper.setSubject("牛牛的Java的学习");
messageHelper.setText("继续加油学习!冲鸭!
",true);
//附件 绝对地址
messageHelper.addAttachment("1.jpg",new File("C:\\Users\\liuxiang\\Desktop\\1.jpg"));
messageHelper.setTo("[email protected]");
messageHelper.setFrom("[email protected]");
mailSender.send(mimeMessage);
}
}
封装成工具类
//封装成工具类
/**
*
* @param html
* @param subject
* @param text
* @param fileName
* @param fileUrl
* @param ReceiveAddress
* @param sendAddress
* @throws MessagingException
* @Author liuxiang
*/
public void sendMail(Boolean html,String subject,String text,String fileName,String fileUrl,String ReceiveAddress,String sendAddress) throws MessagingException{
MimeMessage mimeMessage = mailSender.createMimeMessage();
//组件
MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage,html);
//设置主题
messageHelper.setSubject(subject);
messageHelper.setText(text,html);
//附件 绝对地址
messageHelper.addAttachment(fileName,new File(fileUrl));
messageHelper.setTo(ReceiveAddress);
messageHelper.setFrom(sendAddress);
mailSender.send(mimeMessage);
}
@EnableAsync
@EnableScheduling //开始定时功能的注解
@SpringBootApplication
public class Springboot10TestApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot10TestApplication.class, args);
}
}
@Service
public class ScheduleService {
//在一个特定的事件执行这个方法
//cron表达式 任务调度
//秒 分 时 日 月 周几
@Scheduled(cron = "0 * * * * 0-7")
public void hello(){
System.out.println("hello");
}
}
springMVC中没有装配MultipartResolver,所以默认情况下不能处理文件上传工作,如果想要使用文件上传功能,需要在上下文中配置MultipartResolver。
前端表单要求:必须将表单的method设置为POST,并将enctype设置为multipart/form-data,只有在这种情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器。
后端导入文件上传的jar包:commons-fileupload
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.3.3version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>4.0.1version>
dependency>
在resources目录下的springmvc-servlet.xml配置:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8"/>
<property name="maxUploadSize" value="10485760"/>
<property name="maxInMemorySize" value="40960"/>
bean>
controller层
package com.liu.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@RestController
public class FileController {
//@RequestMapping("file") 将name=file控件得到的文件封装成CommonsMultipartFile对象
//批量上传CommonsMultipartFile则为数组即可
@RequestMapping("/upload")
public String fileUpLoad(@RequestParam("file")CommonsMultipartFile file, HttpServletRequest request) throws IOException {
//获取文件名:file.getOriginalFilename();
String uploadFileName = file.getOriginalFilename();
//如果文件名为空,直接回到首页
if ("".equals(uploadFileName)){
return "redirect:/index.jsp";
}
System.out.println("上传文件名:"+uploadFileName);
//上传路径保存设置
String path = request.getServletContext().getRealPath("/upload");
//如果路径不存在,创建一个
File realPath = new File(path);
if (!realPath.exists()){
realPath.mkdir();
}
System.out.println("上传文件保存地址:"+realPath);
InputStream is = file.getInputStream();//文件输入流
FileOutputStream os = new FileOutputStream(new File(realPath, uploadFileName));//输出流
//读取写出
int len = 0;
byte[] buffer = new byte[1024];
while ((len=is.read(buffer))!=-1){
os.write(buffer,0,len);
os.flush();
}
os.close();
is.close();
return "redirect:/index.jsp";
}
}
方法二:
//采用file.TransferTo来保存上传的文件
@RequestMapping("/upload2")
public String fileUpLoad2(@RequestParam("file")CommonsMultipartFile file, HttpServletRequest request) throws IOException {
//上传路径保存设置
String path = request.getServletContext().getRealPath("/upload");
//如果路径不存在,创建一个
File realPath = new File(path);
if (!realPath.exists()) {
realPath.mkdir();
}
System.out.println("上传文件保存地址:"+realPath);
//通过CommonsMultipartFile的方法直接写文件
file.transferTo(new File(realPath +"/" + file.getOriginalFilename()));
return "redirect:/index.jsp";
}
文件下载
//下载图片方法
@RequestMapping("/downLoad")
public String downLoads(HttpServletResponse response,HttpServletRequest request) throws IOException {
//要下载的图片
String path = request.getServletContext().getRealPath("/upload");
String fileName = "基础语法.jpg";
//1.设置response响应头
response.reset();//设置页面不缓存,清空buffer
response.setCharacterEncoding("utf-8");
response.setContentType("multipart/form-data");//二进制传输数据
//设置响应头
response.setHeader("Content-Disposition",
"attachment;fileName="+ URLEncoder.encode(fileName,"UTF-8"));
File file = new File(path, fileName);
//2.读取文件--输入流
InputStream is = new FileInputStream(file);
//3.写出文件-输出流
OutputStream fos = response.getOutputStream();
byte[] buffer = new byte[1024];
int index = 0;
while ((index = is.read(buffer))!=-1){
fos.write(buffer,0,index);
fos.flush();
}
fos.close();
is.close();
return null;
}
下载图片
分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统
分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。
dubbo官方文档:文档 | Apache Dubbo
当网站流量很小时,只需一个应用将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键
缺点:
当访问量逐渐变大,单一应用增加机器带来的加速度越来越小,将应用拆分不相干的几个应用,以提升效率,MVC架构是关键
缺点:公用模块无法重复利用,开发性的浪费
将核心业务提取出来作为独立的服务,组件形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求,RPC分布式服务框架是关键。
小服务资源的浪费等问题产生,需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。提高机器利用率的资源调度和治理中心(SOA)是关键
RPC:Remote Procedure Call 远程过程调用
允许程序调用另外一个地址空间的过程或函数,而不用程序员显示编码这个远程调用的细节。
核心:通讯、序列化
Provider:暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务
Consumer:o调用远程服务的服务消费方,服务消费者在启动时向注册中心订阅自己所需的服务
Registry:注册中心返回服务提供者地址列表给消费者
Monitor:服务消费者和提供者,在内存类级调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
zookeeper下载地址:
http://archive.apache.org/dist/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz
管理员身份进入cmd命令:
可能遇到的问题:闪退
解决:
dubbo-admin下载
地址:https://github.com/apache/dubbo-admin/tree/master
端口号:7001
在项目目录下打包dubbo-admin
D:\Environment\dubbo-admin-master>mvn clean package -Dmaven.test.skip=true
出现build success即可
cmd命令下执行jar包:
java -jar dubbo-admin-server-0.3.0.jar
账户密码是 root-root
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>3.0.9version>
dependency>
<dependency>
<groupId>com.101tecgroupId>
<artifactId>zkclientartifactId>
<version>0.2version>
dependency>
需要排除日志,不然会起冲突,报错:
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>2.12.0version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-recipesartifactId>
<version>2.12.0version>
dependency>
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.4.14version>
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
exclusion>
exclusions>
dependency>
注册中心:
server.port=8001
#服务应用名字
dubbo.application.name=privider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#哪些服务要被注册
dubbo.scan.base-packages=com.liu.service
service层:
@DubboService
@Component //使用了dubbo后不要使用service
public class TicketServiceImpl implements TicketService{
@Override
public String getTicket() {
return "llx";
}
}
消费者配置:
server.port=8002
#消费者应用名字
dubbo.application.name=consumer-server
#注册中心的地址
dubbo.registry.address=zookeeper://127.0.0.1.2181
service
@Service //是spring的注解service
public class UserService {
//想拿到provider-service的票,去注册中心拿到服务
@Reference //定义路径相同的的接口名 引用 从远程注入服务
TicketService ticketService;
public void buyTicket(){
String ticket = ticketService.getTicket();
System.out.println(ticket);
}
}
复利用,开发性的浪费
将核心业务提取出来作为独立的服务,组件形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求,RPC分布式服务框架是关键。
小服务资源的浪费等问题产生,需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。提高机器利用率的资源调度和治理中心(SOA)是关键
RPC:Remote Procedure Call 远程过程调用
允许程序调用另外一个地址空间的过程或函数,而不用程序员显示编码这个远程调用的细节。
核心:通讯、序列化
Provider:暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务
Consumer:o调用远程服务的服务消费方,服务消费者在启动时向注册中心订阅自己所需的服务
Registry:注册中心返回服务提供者地址列表给消费者
Monitor:服务消费者和提供者,在内存类级调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
zookeeper下载地址:
http://archive.apache.org/dist/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz
管理员身份进入cmd命令:
[外链图片转存中…(img-oR9kCsl1-1658244095119)]
可能遇到的问题:闪退
解决:
dubbo-admin下载
地址:https://github.com/apache/dubbo-admin/tree/master
端口号:7001
在项目目录下打包dubbo-admin
D:\Environment\dubbo-admin-master>mvn clean package -Dmaven.test.skip=true
出现build success即可
[外链图片转存中…(img-Qmxxx9gy-1658244095119)]
cmd命令下执行jar包:
java -jar dubbo-admin-server-0.3.0.jar
账户密码是 root-root
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>3.0.9version>
dependency>
<dependency>
<groupId>com.101tecgroupId>
<artifactId>zkclientartifactId>
<version>0.2version>
dependency>
需要排除日志,不然会起冲突,报错:
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>2.12.0version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-recipesartifactId>
<version>2.12.0version>
dependency>
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.4.14version>
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
exclusion>
exclusions>
dependency>
注册中心:
server.port=8001
#服务应用名字
dubbo.application.name=privider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#哪些服务要被注册
dubbo.scan.base-packages=com.liu.service
service层:
@DubboService
@Component //使用了dubbo后不要使用service
public class TicketServiceImpl implements TicketService{
@Override
public String getTicket() {
return "llx";
}
}
消费者配置:
server.port=8002
#消费者应用名字
dubbo.application.name=consumer-server
#注册中心的地址
dubbo.registry.address=zookeeper://127.0.0.1.2181
service
@Service //是spring的注解service
public class UserService {
//想拿到provider-service的票,去注册中心拿到服务
@Reference //定义路径相同的的接口名 引用 从远程注入服务
TicketService ticketService;
public void buyTicket(){
String ticket = ticketService.getTicket();
System.out.println(ticket);
}
}