springBoot
- 配置如何编写 yaml
- 自动装配原理
- 集成web开发:业务的核心
- 集成数据库:Druid
- 分布式开发:Dubbo+zookeeper
- swagger:接口文档
- 任务调度
- SpringSecunity Shiro
1、微服务
微服务架构打破之前的all in one 的架构方式,把每个功能元素对出来。
好处:
- 节省 了调用资源
- 每个功能元素的服务都是一个可替代、可独立升级的软件代码
1.1 HelloWorld
可以从Spring Initializr上下载一个项目,耶可以从IDEA中创建springboot项目!
要在Application的同级目录下建包,不然不会生效!
在pom.xml里有一个web依赖:用来启动tomcat
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
springboot所有的依赖都是以spring-boot-starter开头!
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
<relativePath/>
</parent>
<groupId>com.liu</groupId>
<artifactId>helloWorld</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>helloWorld</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 如上所示:主要分为四部分
- 项目元数据信息:maven项目的基本元素:gav,name,description等
- parent:继承spring-boot-starter-parent的依赖管理、控制版本和打包等
- dependencies:项目具体依赖,这里包含了web依赖用于实现http接口(包含了springmvc)
- build:构建配置部分:默认使用spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把springboot应用打包成jar来运行。
1.2 打包成jar包
在maven的lifecycle里有一个package可以打包成jar包!
在target项目下可以得到生成的jar包!
1.3 IDEA创建springboot项目
第一步:新建项目
第二步:选择spring-web项目
1.4 更改项目的端口号
1.5 更改banner
在resources目录下新建banner.txt文件
放入你要生成的banner图示:
2、原理
2.1、自动配置
pom.xml
启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
主程序
@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:自动配置的核心文件
2.2 底层原理:
getAutoConfigurationEntry:获取自动配置的实体
getCandidateConfigurations:获取候选的配置
protected Class<?> getAnnotationClass() {
return EnableAutoConfiguration.class;
}
List<String> getCandidateConfigurations 所有的加载的配置
loadSpringFactories:项目资源:"META-INF/spring.factories"
系统资源
从这些资源中遍历所有的element并封装成properties供我们使用
2.3 结论:
- springboot所有的自动配置都在启动类中被扫描并加载:spring.factories所有的自动配置类都在这里,但是不一定生效,要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器,自动装配就生效,然后配置成功!
- springboot在启动的时候,从类路径下META-INF/spring.factories获取指定的值
- 将这些自动配置的类导入容器,自动配置就会生效,进行自动配置
- 整合javaEE,解决方案和自动配置的东西都在spring-boot-autoconfigure-2.2.0.RELEASE.jar包下
- 它会把所有需要导入的组件以类名的方式返回,这些组件就会被添加到容器中
- 容器中也会存在非常多的xxxAutoConfiguration的文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件并自动配置
2.4 Run
@SpringBootApplication
public class Springboot01HelloworldApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01HelloworldApplication.class, args);
}
}
2.5 springApplication
这个类主要做了以下几件事:
- 推断应用的类型是普通的项目还是web项目
- 查找并加载所有可用初始化器,设置到initializers属性中
- 找出所有的应用程序监听器,设置到listeners属性中,获取上下文,处理bean
- 推断并设置main方法的定义类,找到运行的主类
查看构造器
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();
3、springboot配置
3.1 配置文件
使用一个全局的配置文件,配置文件名称是固定的
- application.properties
- application.yml
- 配置文件的作用:修改springboot自动配置的默认值,因为springboot在底层给我们自动配置好了
3.2 yaml
1.格式
server:
port: 8080
2.标记语言
- 以前的配置文件大多数使用xml来配置,比如一个简单的端口配置
- xml配置:
<server>
<port>8081</port>
</server>
name: liuxiang
student:
name: liuxiang
age: 3
student1: {name: liuxiang,age: 3}
pets:
- cat
- dog
- pig
pets1: [cat,dog,pig]
3.3 给实体类赋值
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")
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;
}
- 松散绑定:比如yaml中写的last-name,和lastName是一样的,-后面跟着的字母默认是大写的
- JSR303数据校验,可以在字段增加一层过滤器验证,可以保证数据的合法性
- 复杂类型封装,yml中可以封装对象,使用@value就不支持
3.4 JSR303校验
@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格式
4、多环境配置及配置文件地址
配置文件存放地址:
- file:./config/ : 项目目录下的config下的优先级最高
- file:./ :优先级其次
- classpath:/config/:优先级第三
- classpath:/:优先级最低
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
5、自动配置再理解
一定要满足条件才会生效,导入相关依赖,找到对应的start启动器即可!
配置文件yaml和spring.factories的关系:
- 都是通过ConfigurationProperties(prefix = “xxx”)来实现
- xxxAutoConfiguration:有默认值,都有一个xxxProperties的文件和配置文件绑定,就可以使用自定义的配置了
- 这样配置文件就可以动态的修改spring的内容
5.1 自动装配的原理
- springboot启动会加载大量的自动配置类
- 看我们需要的功能有没有再springboot默认写好的自动配置类当中
- 再看这个自动配置类中到底配置了哪些组件(要用的在其中就不需要再配置了)
- 给容器中自动配置类添加组件时,会从properties类中获取某些属性,我们只需要在配置文件中指定这些属性的值即可
- xxxAutoConfiguration:自动配置类;给容器中添加组件
- xxxProperties:封装配置文件中相关属性
可以在yaml文件下通过debug=true来查看,哪些自动配置类生效了,一部分生效,一部分不生效,选择最好的
debug: true
6、springBoot Web开发
要解决的问题:
- 导入静态资源
- 首页
- jsp,模板引擎Thymeleaf
- 装配扩展springmvc
- 增删改查
- 拦截器
6.1、静态资源导入
这四个文件c夹下的所有的资源都可以被访问到,例如:
优先级:
resources>static>public
- 在springboot还可以通过webjars访问:localhost:8080/webjars/
6.2 thymeleaf模板引擎
引入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.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
结论:
需要使用thymeleaf,只需要将html放在templates下,在controller下设置跳转页面路径即可
-
用thymeleaf需要导入头文件约束 xmlns:th=“http://www.thymeleaf.org” -
超连接,文本等等需要放在th下 -
常用命名空间: -
<xmlns:th="http://www.thymeleaf.org">
<xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</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
文本操作:
数学运算:
其他操作:
6.3、装配扩展springMVC
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.
视图解析器的源码:
- 在springboot2.7.1版本中,将此部分改成了判断是否有下一个视图,有就添加
- 采用了迭代器而不是之前的全部遍历
- 获取候选的,再选择最好的视图
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;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Bean
public ViewResolver myViewResolver(){
return new 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文件中自己配置日期格式!
- springboot再自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果有用户自己配置的@bean),就用用户的,没有就用自动配置的,比如视图解析器,将用户配置的和自己默认的组合起来作为候选的,再选择最好的。
视图跳转
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/liu").setViewName("test");
}
}
@EnableWebMvc注解:自动配置的时候不能加这个注解
@EnableWebMvc
原因:在WebMvcAutoConfiguration这个类中有三个条件满足才生效,而在EnableWebMvc这个注解当中导入了这个类DelegatingWebMvcConfiguration,不满足自动装配的条件了
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
总结:
- 在springboot中,有许多的xxxConfiguration配置类帮助进行扩展配置,看见这个就是改变了原始的配置,要注意扩展了什么功能!
7、员工管理系统
spring.thymeleaf.cache=false
7.1 首页配置
- 所有的静态资源都需要用thymeleaf接管,链接用@{}
7.2 国际化(中英文切换)
要确保全部是UTF-8格式!
可视化配置:
页面的每个原始都需要这么配置:
在yml文件中配置真实的配置文件地址:
spring.messages.basename=i18n.login
国际化消息用:#{}取值
7.2.1 自定义国际化组件
前端的两个跳转链接:
<a class="btn btn-sm" th:href="@{index.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{index.html(l='en_US')}">English</a>
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) {
String language = request.getParameter("l");
Locale locale = Locale.getDefault();
if (!StringUtils.isEmpty(language)){
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();
}
}
步骤:
- 需要配置i18n文件
- 如果需要在项目进行按钮自动切换,需要自定义一个组件LocaleResolver
- 将组件配置到spring容器中@Bean
- #{}
7.3 登录功能
index.html
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
后端controller层:
- 接收前端name属性传过来的值,用@RequestParam保证路径不出错!
- main.html在myMvcConfig文件中设置了访问到dashboard.html页面!
@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.setAttribute("loginUser",username);
return "redirect:/main.html";
}else {
model.addAttribute("msg","用户名或者密码错误!");
return "index";
}
}
}
7.4 拦截器
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 {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object loginUser = request.getSession().getAttribute("loginUser");
if (loginUser==null){
request.setAttribute("msg","没有权限,请先登录");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}else {
return true;
}
}
}
- 到spring中注册Bean,也就是带@Configuration注解,继承了WebMvcConfigurer的类
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
}
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**").excludePathPatterns("/index.html","/","user/login");
}
}
7.5 前端侧边栏
实现代码的复用:用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>
7.6 提取公共页面
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页面的头部加上下面这个注释
7.7 展示数据库数据
<thead>
<tr>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>department</th>
<th>birth</th>
<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')}
7.8 添加员工
- 按钮提交
- 跳转到添加页面
- 添加员工成功
- 返回首页
问题报错:
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>LastName</label>
<input type="text" name="lastName" class="form-control" placeholder="name">
</div>
<div class="form-group">
<label>Email</label>
<input type="email" name="email" class="form-control" placeholder="email">
</div>
<div class="form-group">
<label>Gender</label>
<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>department</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>Birth</label>
<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";
}
7.9 修改员工信息
list.html页面
<td>
<a class="btn btn-sm btn-primary" th:href="@{'/emp/'+${emp.getId()}}">编辑</a>
</td>
- 因为有自增id,所以要隐藏,不然一修改就自动新增了!
<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>LastName</label>
<input th:value="${emp.getLastName()}" type="text" name="lastName" class="form-control" placeholder="name">
</div>
<div class="form-group">
<label>Email</label>
<input th:value="${emp.getEmail()}" type="email" name="email" class="form-control" placeholder="email">
</div>
<div class="form-group">
<label>Gender</label>
<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>department</label>
<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>Birth</label>
<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}")
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";
}
7.10 删除员工
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";
}
8、Data
对于数据访问层,无论是SQL还是NOsql,springboot底层都是采用springData的方式进行统一处理。
springData官网:https://spring.io/projects/spring-data
数据库相关的启动器:官方文档:Spring Boot Reference Guide
8.1、JDBC
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
配置文件:
- DataSourceProperties
- 对于的DataSourceAutoConfiguration
8.2 SpringBoot封装JDBC
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
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;
@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!";
}
}
8.3 数据源Druid
-
阿里巴巴平台的数据库连接池实现,结合C3P0,DBCP,PROXOOL等DB池的有点,同时加入日志监控。 -
天生针对监控而生的DB连接池 -
HikariDataSource是速度最快的数据源
依赖包:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</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
8.3.1 后台监控
- 因为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");
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请求时,可以看到监控统计:
9、整合mybatis
导入依赖包:
不是springboot官方的,不是以spring开头
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</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
<?xml version="1.0" encoding="UTF-8" ?>
<!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";
}
}
10、SpringSecurity(安全)
依赖包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
认证授权和权限分级
- 要用数据库连接,在认证部分需要用JDBC连接,而不是在内存中,用Autowired注解自动注入数据源
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;
@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();
}
@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>
thymeleaf和springsecurity的整合
导依赖包
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
导入命名空间:
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">"
11、Shiro
- Apache Shiro是Java的安全(权限)框架
- 下载地址:http://shiro.apache.org/
功能:
- Authentication:认证
- Authorization:授权
- session management:session管理
- cryptograhy:加密
- web support:web支持
- 缓存
11.1、HelloWorld
1.导入依赖
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</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;
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);
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Subject =>session! [" + value + "]");
}
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 (AuthenticationException ae) {
}
}
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
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.");
}
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!");
}
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
总结方法:
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
if (!currentUser.isAuthenticated()){UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");}
currentUser.getPrincipal()
if (currentUser.hasRole("schwartz")){}
if (currentUser.isPermitted("lightsaber:wield")) {}
currentUser.logout();
SpringBoot中集成Shiro
三大对象:(面试必问)
- subject:用户
- securityManager:管理所有用户
- realm:连接数据
导入依赖:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.9.0</version>
</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}">add</a>
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">update</a>
</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();
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal();
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);
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 {
@Bean(name = "filterShiroFilterRegistrationBean")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager getDefaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(getDefaultWebSecurityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
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;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
连接数据库,用druid数据源,整合mybatis,整合shiro
导入依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</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
<?xml version="1.0" encoding="UTF-8" ?>
<!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{
@Autowired
UserMapper userMapper;
@Override
public User queryUserByName(String name) {
return userMapper.queryUserByName(name);
}
}
12、Swagger
官网:API Documentation & Design Tools for Teams | Swagger
前后端分离:
api框架:
- RestFul API文档在线自动生成工具—api文档与api定义同步更新
- 直接运行,可以在线测试api接口
在项目中使用swagger需要springbox:
12.1、springBoot集成Swagger
依赖包
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
配置swagger:
@Configuration
@EnableSwagger2
public class SwaggerConfig {
}
产生的问题:Failed to start bean ‘documentationPluginsBootstrapper’; nested exception is java.lang.NullPointerException
因为版本不兼容
解决:
测试运行:Swagger UI
12.2、配置swagger
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo());
}
private ApiInfo apiInfo(){
Contact contact = new Contact("liuxiang", "http://localhost:8080/", "971223772@qq.com");
return new ApiInfo("刘想的swagger日记",
"牛牛的Java学习之旅",
"1.0",
"http://localhost:8080/",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList());
}
}
Swagger配置扫描接口
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.liu.swagger.controller"))
.paths(PathSelectors.ant("/liu/**"))
.build();
}
配置是否启动swagger
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(false)
.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){
Profiles profiles = Profiles.of("dev", "test");
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(flag)
.select()
.apis(RequestHandlerSelectors.basePackage("com.liu.swagger.controller"))
.build();
}
配置API文档分组
配置多个分组,多个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
@PostMapping(value = "/user")
public User user(){
return new User();
}
其他的注解
@ApiOperation("hello控制类")
@GetMapping(value = "/hello")
public String hello(@ApiParam("用户名") String username){
return "hello"+username;
}
结果:有中文了
13、任务
13.1 异步任务
在方法上加注解@Async
@Service
public class AsyncService {
@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);
}
}
就可以一瞬间响应,无需等待!
13.2 邮件任务
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
一个简单的邮件
1.先配置相关信息,先在qq邮件设置里将POP3/SMTP服务开启
spring.mail.username=971223772@qq.com
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("971223772@qq.com");
mailMessage.setFrom("971223772@qq.com");
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("<p style='color:red'>继续加油学习!冲鸭!</p>",true);
messageHelper.addAttachment("1.jpg",new File("C:\\Users\\liuxiang\\Desktop\\1.jpg"));
messageHelper.setTo("971223772@qq.com");
messageHelper.setFrom("971223772@qq.com");
mailSender.send(mimeMessage);
}
}
封装成工具类
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);
}
13.3 定时任务
- 在main线程开启定时功能的注解
@EnableAsync
@EnableScheduling
@SpringBootApplication
public class Springboot10TestApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot10TestApplication.class, args);
}
}
- 使用 @Scheduled(cron = “0 * * * * 0-7”)注解和cron表达式
@Service
public class ScheduleService {
@Scheduled(cron = "0 * * * * 0-7")
public void hello(){
System.out.println("hello");
}
}
文件上传和下载
springMVC中没有装配MultipartResolver,所以默认情况下不能处理文件上传工作,如果想要使用文件上传功能,需要在上下文中配置MultipartResolver。
前端表单要求:必须将表单的method设置为POST,并将enctype设置为multipart/form-data,只有在这种情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器。
- application/x-www=form-urlencoded:默认方式,只处理表单域中的value属性值,采用这种编码方式的表单会将表单域中的值处理成URL编码方式。
- multipart/form-data:这种编码方式会以二进制流的方式来处理表单数据,这种编码丰富会把文件域指定文件的内容也封装到请求参数中,不会对字符编码
- text/plain:除了把空格转换为"+"号外,其他字符不作编码处理,适用 直接通过表单发送右键
<form action="${pageContext.request.contextPath}/upload" enctype="multipart/form-data" method="post">
<input type="file" name="file">
<input type="submit">
</form>
后端导入文件上传的jar包:commons-fileupload
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</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("/upload")
public String fileUpLoad(@RequestParam("file")CommonsMultipartFile file, HttpServletRequest request) throws IOException {
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";
}
}
方法二:
@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);
file.transferTo(new File(realPath +"/" + file.getOriginalFilename()));
return "redirect:/index.jsp";
}
文件下载
- 设置response响应头
- 读取文件InputStream
- 写出文件OutputStream
- 执行操作
- 关闭流
@RequestMapping("/downLoad")
public String downLoads(HttpServletResponse response,HttpServletRequest request) throws IOException {
String path = request.getServletContext().getRealPath("/upload");
String fileName = "基础语法.jpg";
response.reset();
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);
InputStream is = new FileInputStream(file);
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;
}
<a href="${pageContext.request.contextPath}/downLoad">下载图片</a>
14、分布式Dubbo+Zookeeper+SpringBoot
分布式理论:
分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统
分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。
dubbo官方文档:文档 | Apache Dubbo
ORM单一应用架构:
当网站流量很小时,只需一个应用将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键
缺点:
垂直应用架构:
当访问量逐渐变大,单一应用增加机器带来的加速度越来越小,将应用拆分不相干的几个应用,以提升效率,MVC架构是关键
缺点:公用模块无法重复利用,开发性的浪费
分布式服务架构:
将核心业务提取出来作为独立的服务,组件形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求,RPC分布式服务框架是关键。
流动计算架构:
小服务资源的浪费等问题产生,需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。提高机器利用率的资源调度和治理中心(SOA)是关键
什么是RPC
RPC:Remote Procedure Call 远程过程调用
Provider:暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务
Consumer:o调用远程服务的服务消费方,服务消费者在启动时向注册中心订阅自己所需的服务
Registry:注册中心返回服务提供者地址列表给消费者
Monitor:服务消费者和提供者,在内存类级调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
Dubbo和zookeeper的安装
zookeeper下载地址:
http://archive.apache.org/dist/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz
管理员身份进入cmd命令:
可能遇到的问题:闪退
解决:
- 将conf文件夹下面的zoo_sample.cfg复制一份改名未zoo.cfg即可
- zookeeper的端口号:clientPort=2181
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.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>3.0.9</version>
</dependency>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.2</version>
</dependency>
需要排除日志,不然会起冲突,报错:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</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
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
public class UserService {
@Reference
TicketService ticketService;
public void buyTicket(){
String ticket = ticketService.getTicket();
System.out.println(ticket);
}
}
复利用,开发性的浪费
分布式服务架构:
将核心业务提取出来作为独立的服务,组件形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求,RPC分布式服务框架是关键。
流动计算架构:
小服务资源的浪费等问题产生,需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。提高机器利用率的资源调度和治理中心(SOA)是关键
什么是RPC
RPC:Remote Procedure Call 远程过程调用
Provider:暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务
Consumer:o调用远程服务的服务消费方,服务消费者在启动时向注册中心订阅自己所需的服务
Registry:注册中心返回服务提供者地址列表给消费者
Monitor:服务消费者和提供者,在内存类级调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
Dubbo和zookeeper的安装
zookeeper下载地址:
http://archive.apache.org/dist/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz
管理员身份进入cmd命令:
[外链图片转存中…(img-oR9kCsl1-1658244095119)]
可能遇到的问题:闪退
解决:
- 将conf文件夹下面的zoo_sample.cfg复制一份改名未zoo.cfg即可
- zookeeper的端口号:clientPort=2181
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.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>3.0.9</version>
</dependency>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.2</version>
</dependency>
需要排除日志,不然会起冲突,报错:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</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
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
public class UserService {
@Reference
TicketService ticketService;
public void buyTicket(){
String ticket = ticketService.getTicket();
System.out.println(ticket);
}
}
|