1.springboot入门
简介
•Spring Boot是基于Spring框架开发的全新框架,其设计目的是简化新Spring应用的初始化搭建和开发过程。
•Spring Boot整合了许多框架和第三方库配置,几乎可以达到“开箱即用”。
入门程序
-
@SpringBootApplication 标记该类为主程序启动类
-
**SpringApplication.run()**方法启动主程序类
-
@RestController :该注解为组合注解,等同于Spring中**@Controller**+@ResponseBody注解
注意:使用此注解只能返回数据 使用@Controller 可返回数据也可返回模板页面
-
@GetMapping :等同于Spring框架中**@RequestMapping**(RequestMethod.GET)注解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D8fzQJ10-1593454373934)(D:\掌握月\大三(下)\j2ee\笔记\images\1587801398348.png)]
注意: 在Spring Boot处选择稳定版本(SNAPSHOT表示开发中的不稳定版本)
单元测试与热部署
单元测试
1.在pom文件中添加spring-boot-starter-test测试启动器
2.编写单元测试类
3.编写单元测试方法
4.运行结果
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
- @RunWith(SpringRunner.class) :加载Spring Boot测试注解
- @SpringBootTest :加载项目的ApplicationContext上下文环境
-
除了@Test还有哪些注解
注解 功能说明 @Before 表示在任意使用@Test注解标注的public void方法执行之前执行 @After 表示在任意使用@Test注解标注的public void方法执行之后执行 @Order 定义Spring IOC容器中Bean的执行顺序的优先级
热部署
1.在pom文件中添加****spring-boot-devtools热部署依赖
2.IDEA中热部署设置
3.热部署****测试
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SuRVHYqY-1593454373944)(D:\掌握月\大三(下)\j2ee\笔记\images\1587801581010.png)]
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
Spring Boot 原理分析
<!-- Spring Boot父项目依赖管理 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/>
</parent>
spring-boot-starter-parent是通过标签对一些常用技术框架的依赖文件进行了统一版本号管理。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency>
spring-boot-starter-web依赖启动器的主要作用是提供Web开发场景所需的底层所有依赖文件,它对Web开发场景所需的依赖文件进行了统一管理。
•Spring Boot应用的启动入口是**@SpringBootApplication**注解标注类中的main()方法;
•@SpringBootApplication能够扫描Spring组件并自动配置Spring Boot。
•@SpringBootApplication注解是一个组合注解,包含**@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan**三个核心注解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wD8wLrJ9-1593454373954)(D:\掌握月\大三(下)\j2ee\笔记\images\1587801934474.png)]
2.springboot核心配置
@Component
@ConfigurationProperties注入属性
@Value
@PropertySource
@ImportResource
@Configuration
@Profile
Application.yaml
•YAML文件格式是Spring Boot支持的一种JSON超集文件格式。
•相较于传统的Properties配置文件,YAML文件以数据为核心,是一种更为直观且容易被电脑识别的数据序列化格式。
application.yaml文件的和一样
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8cGh32t0-1593454373960)(D:\掌握月\大三(下)\j2ee\笔记\images\1587802179098.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KiRM69ew-1593454373966)(D:\掌握月\大三(下)\j2ee\笔记\images\1587802197066.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GAjKq6Vf-1593454373968)(D:\掌握月\大三(下)\j2ee\笔记\images\1587802210605.png)]
## @ConfigurationProperties
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E8M4UeVY-1593454373970)(D:\掌握月\大三(下)\j2ee\笔记\images\1587802315628.png)]
使用@ConfigurationProperties注解批量注入属性值时,要保证配置文件中的属性与对应实体类的属性一致,否则无法正确获取并注入属性值。
@Value
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DpyHR4ci-1593454373974)(D:\掌握月\大三(下)\j2ee\笔记\images\1587802333643.png)]
使用@Value注解对每一个属性注入设置,免去了属性setXX()方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gfRD0s7U-1593454373977)(D:\掌握月\大三(下)\j2ee\笔记\images\1587802354917.png)]
@ PropertySource / @ Configuration
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1pzokd6q-1593454373980)(D:\掌握月\大三(下)\j2ee\笔记\images\1587802413217.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XD0fn90p-1593454373982)(D:\掌握月\大三(下)\j2ee\笔记\images\1587802488909.png)]
@Configuration
public class MyConfig {
@Bean
public MyService myService(){
return new MyService();
}
}
@Value与@PropertySource的比较
可以看到,@Value注解提供了细粒度的操作,使我们可以自主选择要注入的属性。而当存在大量的属性需要批量注入时,使用@ConfigurationProperties注解可以减少不必要的代码。
PropertySource注解不支持yml文件加载
当配置文件中存在中文时,为了防止乱码需要在PropertySource注解中加入encoding参数。
@ ImportResource**
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QDm53N3e-1593454373984)(D:\掌握月\大三(下)\j2ee\笔记\images\1587802459124.png)]
Profile****多环境配置
略
3.springboot整合jsp
1.引入jsp依赖
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!--jsp页面使用jstl标签-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
2.编写配置文件
server.port=8088
spring.mvc.view.prefix=/WEB-INF/view/
spring.mvc.view.suffix=.jsp
spring.devtools.restart.enabled=true
3.在WEB-INF编写jsp页面
4.注意事项
(1)@GetMapping("/view")表示处理Get请求
(2)@RequestParam Long id 表示获取前端参数id,并转化为Long类型
(3)return "redirect:/ticket"表示重定向到路径/ticket
(4) return "/ticket/list"表示跳转到/ticket/list.jsp视图
4.springboot整合thymeleaf
1.引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2.编写配置文件
server.port=8088
spring.devtools.restart.enabled=true
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.mode=HTML5
spring.thymeleaf.suffix=.html
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.cache=false
#spring.thymeleaf.servlet.content-type=text/html
3.在templates下编写html5页面
5.springboot视图技术
1.Thymeleaf的国际化
1.编写国际化配置类
@Configuration
public class MyLocalResovel implements LocaleResolver {
//自定义区域解析方式
@Override
public Locale resolveLocale(HttpServletRequest httpServletRequest) {
//获取页面手动切换传递的语言参数1
String l = httpServletRequest.getParameter("l");
//获取请求头自动传递的语言参数Acceot-Language
String header = httpServletRequest.getHeader("Accept-Language");
Locale locale = null;
//如果手动切换参数不为空,就根据手动参数进行语言切换,否则默认根据请求头信息切换
if (!StringUtils.isEmpty(l)) {
String[] split = l.split("_");
locale = new Locale(split[0], split[1]);
} else {
//Accept-Language:en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
String[] splits = header
.split(",");
String[] split = splits[0].split("-");
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
@Bean
public LocaleResolver localeResolver() {
return new MyLocalResovel();
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" media="all"
href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
<title>Title</title>
</head>
<body>
<p th:text="#{hello}">欢迎进入Thymeleaf的学习</p>
</body>
</html>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-11SszSDp-1593454373986)(images\1587803019778.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QJ34mEmx-1593454373988)(images\1587803032261.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xai5hb9M-1593454373991)(D:\掌握月\大三(下)\j2ee\笔记\images\1587803120290.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0hfNK8YQ-1593454373992)(D:\掌握月\大三(下)\j2ee\笔记\images\1587803143705.png)]
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
spring.thymeleaf.cache = true
spring.thymeleaf.encoding = UTF-8
spring.thymeleaf.mode = HTML5
spring.thymeleaf.prefix = classpath:/templates/
spring.thymeleaf.suffix = .html
Øclasspath:/META-INF/resources/:项目类路径下的META-INF文件夹下 的resources文件夹下的所有文件。
Øclasspath:/resources/:项目类路径下的resources文件夹下的所有文件。
Øclasspath:/static/:项目类路径下的static文件夹下的所有文件
Øclasspath:/public/:项目类路径下的public文件夹下的所有文件。
@Controller
public class LoginController {
@GetMapping("/toLoginPage")
public String toLoginPage(Model model){
model.addAttribute("currentYear", Calendar.getInstance().get(Calendar.YEAR));
return "login";
}
}
这里不能用@RestController
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1,
shrink-to-fit=no">
<title>用户登录界面</title>
<link th:href="@{/login/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/login/css/signin.css}" rel="stylesheet">
</head>
2.常用写法
-
th:replace="fragments/layout::head"
-
th:href="@{/ticket/list}"
-
th:object="${ticket}"
-
th:field="*{id}"
-
th:fragment="header"
-
th:src="@{/bootstrap4/js/jquery.min.js}"
-
th:action="@{/ticket/create}"
-
th:name="body"
-
th:text="${status.count}"
-
th:each="ticket, status : ${ticketList}"
6.springboot整合三大组件
1.拦截器
注册自定义拦截器MyInterceptor**,实现****HandlerInterceptor** 接口,在该类中编写如下方法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
String uri = request.getRequestURI();
Object loginUser = request.getSession().getAttribute("loginUser");
if (uri.startsWith("/admin") && null == loginUser) {
response.sendRedirect("/toLoginPage");
return false;
}
return true;}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, @Nullable ModelAndView modelAndView) throws Exception {
request.setAttribute("currentYear", Calendar.getInstance().get(Calendar.YEAR));
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse
response, Object handler, @Nullable Exception ex) throws Exception {
}
**在自定义配置类MyMVCconfig中,重写addInterceptors()**方法注册自定义的拦截器
其中MyMVCConfig实现WebMvcConfigurer****接口 的配置类
@Autowired
private MyInterceptor myInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login.html");
}
2.Servlet
创建一个自定义Servlet类MyServlet,使用@Component注解将MyServlet类作为组件注入Spring容器。MyServlet类继承自HttpServlet**,通过HttpServletResponse对象向页面输出“hello MyServlet”。**
@Configuration
public class ServletConfig {
@Bean
public ServletRegistrationBean getServlet(MyServlet myServlet){
ServletRegistrationBean registrationBean =
new ServletRegistrationBean(myServlet,"/myServlet");
return registrationBean;
}
}
3.Filter
创建一个自定义Servlet类MyFilter,使用@Component注解将当前MyFilter类作为组件注入到Spring容器中。MyFilter类实现Filter****接口,
@Bean
public FilterRegistrationBean getFilter(MyFilter filter){
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setUrlPatterns(Arrays.asList("/toLoginPage","/myFilter"));
return registrationBean;
}
4.Listener
@Component
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("contextInitialized ..."); }
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("contextDestroyed ..."); }
}
@Bean
public ServletListenerRegistrationBean getServletListener(MyListener myListener){
ServletListenerRegistrationBean registrationBean =
new ServletListenerRegistrationBean(myListener);
return registrationBean;
}
**5.使用路径扫描的方式整合Servlet、Filter、**Listener
**@**WebServlet **@**WebListener
@SpringBootApplication
@ServletComponentScan // 开启基于注解方式的Servlet组件扫描支持
public class Chapter05Application {
public static void main(String[] args) {
SpringApplication.run(Chapter05Application.class, args);
}
}
7.springboot与文件上传
1.创建上传文件的upload.html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>动态添加文件上传列表</title>
<link th:href="@{/login/css/bootstrap.min.css}" rel="stylesheet">
<script th:src="@{/login/js/jquery.min.js}"></script>
</head>
<div th:if="${uploadStatus}" style="color: red" th:text="${uploadStatus}">
上传成功</div>
<form th:action="@{/uploadFile}" method="post" enctype="multipart/form-data">
上传文件:
<input type="button" value="添加文件" onclick="add()"/>
<div id="file" style="margin-top: 10px;" th:value="文件上传区域"> </div>
<input id="submit" type="submit" value="上传"
style="display: none;margin-top: 10px;"/>
</form>
function add(){
var innerdiv = "<div>";
innerdiv += "<input type='file' name='fileUpload' required='required'>" +
"<input type='button' value='删除' οnclick='remove(this)'>";
innerdiv +="</div>";
$("#file").append(innerdiv);
$("#submit").css("display","block");
}
function remove(obj) {
$(obj).parent().remove();
if($("#file div").length ==0){
$("#submit").css("display","none");
}
}
2.② 在全局配置文件中添加文件上传的相关配置
# thymeleaf页面缓存设置(默认为true)
spring.thymeleaf.cache=false
# 配置国际化文件基础名
spring.messages.basename=i18n.login
# 单个上传文件大小限制(默认1MB)
spring.servlet.multipart.max-file-size=10MB
# 总上传文件大小限制(默认10MB)
spring.servlet.multipart.max-request-size=50MB
3. 进行文件上传处理实现文件上传功能
创建一个管理文件上传下载的控制类****FileController
@GetMapping("/toUpload")
public String toUpload(){
return "upload";
}
进行文件上传处理实现文件上传功能
@PostMapping("/uploadFile")
public String uploadFile(MultipartFile[] fileUpload, Model model) {
model.addAttribute("uploadStatus", "上传成功!");
for (MultipartFile file : fileUpload) {
String fileName = file.getOriginalFilename();
fileName = UUID.randomUUID()+"_"+fileName;
String dirPath = "F:/file/";File filePath = new File(dirPath);
if(!filePath.exists()){filePath.mkdirs();}
try {file.transferTo(new File(dirPath+fileName));
} catch (Exception e) {e.printStackTrace();
model.addAttribute("uploadStatus","上传失败: "+e.getMessage());}
}return "upload";}
8.springboot 与文件下载
1. 添加文件下载依赖
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
2. 定制文件下载页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>文件下载</title></head><body>
<div style="margin-bottom: 10px">文件下载列表:</div>
<table><tr> <td>bloglogo.jpg</td>
<td><a th:href="@{/download(filename='bloglogo.jpg')}">下载文件</a></td></tr>
<tr> <td>Spring Boot应用级开发教程.pdf</td>
<td><a th:href="@{/download(filename='Spring Boot')}">
下载文件</a></td></tr> </table></body></html>
3. 编写文件下载处理办法
@GetMapping("/toDownload")
public String toDownload(){
return "download";
}
@GetMapping("/download")
public ResponseEntity<byte[]> fileDownload(String filename){
String dirPath = "F:/file/";
File file = new File(dirPath + File.separator + filename);
HttpHeaders headers = new HttpHeaders();
headers.setContentDispositionFormData("attachment",filename);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
try {return new ResponseEntity<>(FileUtils.readFileToByteArray(file),
headers, HttpStatus.OK);}
catch (Exception e) {e.printStackTrace();
return new ResponseEntity<byte[]>(e.getMessage().getBytes(),
HttpStatus.EXPECTATION_FAILED);}}
4.⑤ 中文名文件下载改进
**在FileController类的fileDownload()方法中添加中文的编码处理代码,getFilename(**HttpServletRequest request,String **filename)**方法用来根据不同浏览器对下载的中文名进行转码。
@GetMapping("/download")
public ResponseEntity<byte[]> fileDownload(HttpServletRequest request,
String filename) throws Exception{
String dirPath = "F:/file/";
File file = new File(dirPath + File.separator + filename);
HttpHeaders headers = new HttpHeaders();
filename=getFilename(request,filename);here
headers.setContentDispositionFormData("attachment",filename);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
try {return new ResponseEntity<>(FileUtils.readFileToByteArray(file),
headers, HttpStatus.OK);} catch (Exception e) {e.printStackTrace();
return new ResponseEntity<byte[]>(e.getMessage().getBytes(),
HttpStatus.EXPECTATION_FAILED);}}
9.springboot项目打包
1.jar
项目打包为jar包
执行命令 mvn clean pakcage
使用idea的maven插件
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
Jar****包方式部署
java -jar xxx.jar
2.war
项目打包为war包
-
修改pom.xml文件,将项目的默认打包方式改为war包
<packaging>war</packaging>
-
声明使用外部tomcat服务器
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <!-- 表明该包只在编译和测试的时候用 --> <scope>provided</scope> </dependency>
-
为Spring Boot提供启动的Servlet初始化器
@SpringBootApplication public class TicketSysApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(TicketSysApplication.class); } public static void main(String[] args) { SpringApplication.run(TicketSysApplication.class, args); } }
-
执行打包命令
War包形式的打包方式与Jar包的打包方式完全一样
-
将生成的war包放入tomcat的webapps文件夹中
- 将打包好的War包拷贝到Tomcat安装目录下的webapps目录中,执行Tomcat安装目录下bin目录中的startup.bat命令启动War包项目
- 使用外部Tomcat部署的项目进行访问时,必须加上项目名称(打成war包后的项目全名)
-
启动tomcat
10.springboot整合mybatis
1.编写配置文件
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
#配置MyBatis的xml配置文件路径
mybatis.mapper-locations=classpath:mapper/*.xml
#配置XML映射文件中指定的实体类别名路径
mybatis.type-aliases-package=com.itheima.domain
#开启实体与数据库的字段映射 骆驼峰写法
mybatis.configuration.map-underscore-to-casel-case=true
#配置Mybatis的相关属性
mybatis:
#指定mapper XML文件的位置
mapper-locations: classpath:com.zsc.ticketsys.mapper/*.xml
#指定实体类的别名的映射路径
type-aliases-package: com.zsc.ticketsys.domain
configuration:
#打印输出SQL语句
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 启动驼峰式转换
map-underscore-to-camel-case: true
#开启自增组件
use-generated-keys: true
#启动懒加载
lazy-loading-enabled: true
#禁用立即加载
aggressive-lazy-loading: false
2. 设置数据源类型配置
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
如果在开发过程中,需要对这些第三方Druid的运行参数进行重新设置,必须在application.properties配置文件中进行默认参数覆盖。
spring.datasource.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.initialSize=20
spring.datasource.minIdle=10
spring.datasource.maxActive=100
3.创建Mapper接口文件:@Mapper
@Select("SELECT * FROM t_comment WHERE id =#{id}")
public Comment findById(Integer id);
@Insert("INSERT INTO t_comment(content,author,a_id) " +
"values (#{content},#{author},#{aId})")
public int insertComment(Comment comment);
@Update("UPDATE t_comment SET content=#{content} WHERE id=#{id}")
public int updateComment(Comment comment);
@Delete("DELETE FROM t_comment WHERE id=#{id}")
public int deleteComment(Integer id);}
4.创建XML映射文件:编写对应的SQL语句
<mapper namespace="com.itheima.mapper.ArticleMapper">
<select id="selectArticle" resultMap="articleWithComment">
SELECT a.*,c.id c_id,c.content c_content,c.author
FROM t_article a,t_comment c
WHERE a.id=c.a_id AND a.id = #{id}
</select>
<update id="updateArticle" parameterType="Article" >
UPDATE t_article
<set>
<if test="title !=null and title !=''">
title=#{title},
</if>
<if test="content !=null and content !=''">
content=#{content}
</if>
</set>
WHERE id=#{id}
</update>
<resultMap id="articleWithComment" type="Article">
<id property="id" column="id" />
<result property="title" column="title" />
<result property="content" column="content" />
<collection property="commentList" ofType="Comment">
<id property="id" column="c_id" />
<result property="content" column="c_content" />
<result property="author" column="author" />
</collection>
</resultMap>
11.springboot整合jpa
Spring Data JPA是Spring基于ORM框架、JPA规范的基础上封装的一套JPA应用框架,它提供了增删改查等常用功能,使开发者可以用较少的代码实现数据操作,同时还易于扩展。
1.编写ORM实体类:实体类与数据表进行映射,并且配置好映射关系。
2.编写Repository接口:针对不同的表数据操作编写各自对应的Repositor接口,并根据需要编写对应的数据操作方法。
1.编写ORM实体类
@Entity(name = "t_comment")
public class Discuss {
@Id //主键id
@GeneratedValue(strategy =GenerationType.IDENTITY)//主键生成策略
private Integer id;
@Column(name = "a_id")//字段映射
private Integer aId;
// 省略getXX()和setXX()方法
}
2. 编写Repository接口
//查询author非空的Discuss评论信息
public List<Discuss> findByAuthorNotNull();
//通过文章id分页查询出Discuss评论信息。
@Query("SELECT c FROM t_comment c WHERE c.aId = ?1")
public List<Discuss> getDiscussPaged(Integer aid,Pageable pageable);
//通过文章id分页查询出Discuss评论信息。
@Query(value = "SELECT * FROM t_comment WHERE a_Id = ?1",nativeQuery = true)
public List<Discuss> getDiscussPaged2(Integer aid,Pageable pageable);
注:
与getDiscussPaged()方法的参数和作用完全一样。
区别是该方法上方的@Query注解将nativeQuery属性设置为了true,用来编写原生SQL语句。
//对数据进行更新和删除操作
@Transactional
@Modifying
@Query("UPDATE t_comment c SET c.author = ?1 WHERE c.id = ?2")
public int updateDiscuss(String author,Integer id);
@Transactional
@Modifying
@Query("DELETE t_comment c WHERE c.id = ?1")
public int deleteDiscuss(Integer id);
3.Repository****继承关系
使用Spring Data JPA自定义Repository接口,必须继承XXRepository******接口。**
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q8Mtxarq-1593454373995)(images\1589526146968.png)]
•在自定义的Repository接口中,针对数据的变更操作(修改、删除),无论是否使用了@Query注解,都必须在方法上方添加@Transactional注解进行事务管理,否则程序执行就会出现InvalidDataAccessApiUsageException异常。
•如果在调用Repository接口方法的业务层Service类上已经添加了@Transactional注解进行事务管理,那么Repository接口文件中就可以省略@Transactional注解。
如果自定义接口继承了JpaRepository接口,则默认包含了一些常用的CRUD方法。
自定义Repository接口中,可以使用@Query注解配合SQL语句进行数据的查、改、删操作。
自定义Repository接口中,可以直接使用方法名关键字进行查询操作。
变更操作,要配合使用**@Query与Modify****注解**
•在自定义的Repository接口中,使用@Query注解方式执行数据变更操作(修改、删除),除了要使用@Query注解,还必须添加@Modifying注解表示数据变更。
4. 在pom文件中添加Spring Data JPA****依赖启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
5.编写配置文件
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
12.springboot整合redis
1.存取速度快:Redis速度非常快,每秒可执行大约110000次的设值操作,或者执行81000次的读取操作。
2.支持丰富的数据类型:Redis支持开发人员常用的大多数数据类型,例如列表、集合、排序集和散列等。
3.操作具有原子性:所有Redis操作都是原子操作,这确保如果两个客户端并发访问,Redis服务器能接收更新后的值。
4.提供多种功能:Redis提供了多种功能特性,可用作非关系型数据库、缓存中间件、消息中间件等。
1. 在pom文件中添加Spring Data Redis****依赖启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. 编写实体类****Person
@RedisHash("persons")
public class Person {
@Id
private String id;
@Indexed //二级索引
private String firstname;
@Indexed
private String lastname;
private Address address;
private List<Family> familyList;}
3. 编写Repository接口****PersonRepository
public interface PersonRepository extends CrudRepository<Person, String> {
List<Person> findByLastname(String lastname);
Page<Person> findPersonByLastname(String lastname, Pageable page);
List<Person> findByFirstnameAndLastname(String firstname, String lastname);
List<Person> findByAddress_City(String city);
List<Person> findByFamilyList_Username(String username);
}
4. Redis****数据库连接配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
13.springboot实现缓存管理
1.springboot默认缓存管理
1.使用@EnableCaching注解开启基于注解的缓存支持
@EnableCaching是由Spring框架提供的,Spring Boot框架对该注解进行了继承,该注解需要配置在类上(在Spring Boot中,通常配置在项目启动类上),用于开启基于注解的缓存支持。
@EnableCaching
@SpringBootApplication
public class Chapter06Application {
public static void main(String[] args) {
SpringApplication.run(Chapter06Application.class, args);
}
}
2.使用@Cacheable注解对数据操作方法进行缓存管理
@Cacheable注解也是由Spring框架提供的,可以作用于类或方法(通常用在数据查询方法上),用于对方法结果进行缓存存储。
@Cacheable注解的执行顺序是,先进行缓存查询,如果为空则进行方法查询,并将结果进行缓存;如果缓存中有数据,不进行方法查询,而是直接使用缓存数据。
@Cacheable(cacheNames = "comment")
public Comment findById(int comment_id){
Optional<Comment> optional = commentRepository.findById(comment_id);
if(optional.isPresent()){
return optional.get();
}
return null;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YnEnfNlj-1593454373997)(D:\掌握月\大三(下)\j2ee\笔记\images\1590229093745.png)]
3.@CachePut
@CachePut注解是由Spring框架提供的,可以作用于类或方法(通常用在数据更新方法上),该注解的作用是更新缓存数据。@CachePut注解的执行顺序是,先进行方法调用,然后将方法结果更新到缓存中。
@CachePut注解也提供了多个属性,这些属性与@Cacheable注解的属性完全相同。
4.@CacheEvict
@CacheEvict注解是由Spring框架提供的,可以作用于类或方法(通常用在数据删除方法上),该注解的作用是删除缓存数据。@CacheEvict注解的默认执行顺序是,先进行方法调用,然后将缓存进行清除。
@CacheEvict注解也提供了多个属性,这些属性与@Cacheable注解的属性基本相同,除此之外,还额外提供了两个特殊属性allEntries和beforeInvocation。
(1)allEntries属性
allEntries属性表示是否清除指定缓存空间中的所有缓存数据,默认值为false(即默认只删除指定key对应的缓存数据)。
(2)beforeInvocation属性
beforeInvocation属性表示是否在方法执行之前进行缓存清除,默认值为false(即默认在执行方法后再进行缓存清除)。
4**.@Caching**
@Caching注解用于针对复杂规则的数据缓存管理,可以作用于类或方法,在@Caching注解内部包含有Cacheable、put和evict三个属性,分别对应于**@Cacheable、@CachePut和@CacheEvict三个注解**。
5**.@CacheConfig**
@CacheConfig注解使用在类上,主要用于统筹管理类中所有使用@Cacheable、@CachePut和@CacheEvict注解标注方法中的公共属性,这些公共属性包括有cacheNames、keyGenerator、cacheManager和cacheResolver。
2.基于注解的Redis缓存实现
1.添加****Spring Data Redis 依赖启动器
2.Redis****服务连接配置
3.使用**@Cacheable****、@CachePut、@CacheEvict注解定制缓存管理**
4.基于注解的Redis查询缓存测试
5.将缓存对象实现序列化
前4个步骤和默认缓存基本一致,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qJ3DmJ8G-1593454373999)(D:\掌握月\大三(下)\j2ee\笔记\images\1590229995125.png)]
3.基于API的Redis缓存实现
1.基于API的Redis缓存实现不需要@EnableCaching注解开启基于注解的缓存支持。
2.基于API的Redis缓存实现需要在Spring Boot项目的pom.xml文件中引入Redis依赖启动器,并在配置文件中进行Redis服务连接配置,同时将进行数据存储的Comment实体类实现序列化接口。
4.自定义RedisTemplate(为了替换默认的redisAPI序列化机制)
1.使用RedisTemplate进行Redis数据缓存操作时,内部默认使用的是JdkSerializationRedisSerializer序列化方式,所以进行数据缓存的实体类必须实现JDK自带的序列化接口(例如Serializable);
2.使用RedisTemplate进行Redis数据缓存操作时,如果自定义了缓存序列化方式defaultSerializer,那么将使用自定义的序列化方式。
在项目中创建创建一个Redis自定义配置类RedisConfig,通过@Configuration注解定义了一个RedisConfig配置类,并使用@Bean注解注入了一个默认名称为方法名的redisTemplate组件(注意,该Bean组件名称必须是redisTemplate)。在定义的Bean组件中,自定义了一个RedisTemplate,使用自定义的Jackson2JsonRedisSerializer数据序列化方式;在定制序列化方式中,定义了一个ObjectMapper用于进行数据转换设置。
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
//使用jackson序列化
// 使用JSON格式序列化对象,对缓存数据key和value进行转换
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询时候的转换异常问题
ObjectMapper objectMapper = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 此项必须配置,否则会报java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 设置RedisTemplate模板API的序列化方式为JSON
template.setDefaultSerializer(jackson2JsonRedisSerializer);
return template;
}
5.自定义RedisCacheManager(为了替换默认的redis注解序列化机制)
在RedisConfig类中添加方法cacheManager**,该方法主要由三部分组成**
RedisSerializer<String> strSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jacksonSeial =
new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(strSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(jacksonSeial))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager
.builder(redisConnectionFactory).cacheDefaults(config).build();
return cacheManager;
分成函数写:
// 定义cacheManager,统一redis的属性配置
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
// 配置序列化(解决乱码的问题)
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
// 缓存有效期
.entryTtl(timeToLive)
// 使用StringRedisSerializer来序列化和反序列化redis的key值
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2Serializer()))
// 禁用空值
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build();
return cacheManager;
}
/**
* 配置Jackson2JsonRedisSerializer序列化策略
*/
private Jackson2JsonRedisSerializer<Object> jackson2Serializer() {
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 此项必须配置,否则会报java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
return jackson2JsonRedisSerializer;
}
14 .springboot整合消息服务
1.引入pom依赖
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
2.配置
# 配置RabbitMQ消息中间件连接配置
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
#配置RabbitMQ虚拟主机路径/,默认可以省略
spring.rabbitmq.virtual-host=/
3.配置类配置
@Configuration
public class RabbitConfig {
@Value("code-queue")
private String code_queue;
@Value("notice-queue")
private String notice_queue;
/**
* json对象解析配置bean
*
* @return
*/
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
/**
* 获取验证码队列bean
*
* @return
*/
@Bean
public Queue getCodeQueue() {
return new Queue(code_queue);
}
/**
* 获取通知队列bean
*
* @return
*/
@Bean
public Queue getNoticeQueue() {
return new Queue(notice_queue);
}
/**
* 获取验证码消息交换器bean
*
* @return
*/
@Bean
public Exchange getCodeExchange() {
return ExchangeBuilder.fanoutExchange("code_exchange").build();
}
/**
* 获取通知消息交换器bean
*
* @return
*/
@Bean
public Exchange getNoticeExchange() {
return ExchangeBuilder.fanoutExchange("notice_exchange").build();
}
/**
* 验证码交换器绑定验证码队列
*
* @return
*/
@Bean
public Binding bindingCode() {
return BindingBuilder.bind(getCodeQueue()).to(getCodeExchange()).with("").noargs();
}
/**
* 通知交换器绑定通知队列
*
* @return
*/
@Bean
public Binding bindingNotice() {
return BindingBuilder.bind(getNoticeQueue()).to(getNoticeExchange()).with("").noargs();
}
}
4.生产者发送消息
package com.zsc.shixun.utils;
import com.zsc.shixun.model.unauthorize.MessageVO;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* <p>
* 生产者发送
* </p>
*
* @author ZWYZY
* @since 2020/6/11
*/
@Component
public class ProducerUtils {
@Resource
public RabbitTemplate rabbitTemplate;
/**
* 发送验证码消息
*
* @param mailto 邮箱
* @param title 标题
* @param cotent 内容
*/
public void sendCode(String mailto, String title, String cotent) {
MessageVO messageVO = new MessageVO();
messageVO.setMailto(mailto);
messageVO.setTitle(title);
messageVO.setContent(cotent);
rabbitTemplate.convertAndSend("code_exchange", "", messageVO);
}
/**
* 发送通知消息
*
* @param mailto 邮箱
* @param title 标题
* @param cotent 内容
*/
public void sendNotice(String mailto, String title, String cotent) {
MessageVO messageVO = new MessageVO();
messageVO.setMailto(mailto);
messageVO.setTitle(title);
messageVO.setContent(cotent);
rabbitTemplate.convertAndSend("notice_exchange", "", messageVO);
}
}
5.消费者接收消息
package com.zsc.shixun.utils;
import com.zsc.shixun.model.unauthorize.MessageVO;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* <p>
* 消费者接收
* </p>
*
* @author ZWYZY
* @since 2020/6/11
*/
@Component
public class ConsumerUtils {
@Autowired
public MailUtils mailUtils;
@RabbitListener(bindings = @QueueBinding(value = @Queue("code-queue"),
exchange = @Exchange(value = "code_exchange", type = "fanout")))
public void psubConsumerCodeAno(MessageVO messageVO) {
mailUtils.sendCodeEmail(messageVO.getMailto(), messageVO.getTitle(), messageVO.getContent());
System.out.println("验证码业务接收消息: " + messageVO);
}
@RabbitListener(bindings = @QueueBinding(value = @Queue("notice-queue"),
exchange = @Exchange(value = "notice_exchange", type = "fanout")))
public void psubConsumerSmsAno(MessageVO messageVO) {
mailUtils.sendSimpleEmail(messageVO.getMailto(), messageVO.getTitle(), messageVO.getContent());
System.out.println("通知注册业务接收消息: " + messageVO);
}
}
15.springboot任务管理
1.异步任务
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
1.无返回值异步调用
-
@Async
-
使用**@EnableAsync****开启基于注解的异步方法支持**
@EnableAsync @SpringBootApplication public class Chapter09Application { public static void main(String[] args) { SpringApplication.run(Chapter09Application.class, args); } }
-
编写测试
@Async public void sendSMS() throws Exception { System.out.println("调用短信验证码业务方法..."); Long startTime = System.currentTimeMillis(); Thread.sleep(5000); Long endTime = System.currentTimeMillis(); System.out.println("短信业务执行完成耗时:" + (endTime - startTime)); }
2.有返回值异步调用
-
编写测试方法
@Async public Future<Integer> processB() throws Exception { System.out.println("开始分析并统计业务B数据..."); Long startTime = System.currentTimeMillis(); Thread.sleep(5000); int count=654321; Long endTime = System.currentTimeMillis(); System.out.println("业务B数据统计耗时:" + (endTime - startTime)); return new AsyncResult<Integer>(count); }
-
调用方法
@GetMapping("/statistics")
public String statistics() throws Exception {
Long startTime = System.currentTimeMillis();
Future<Integer> futureA = myService.processA();
Future<Integer> futureB = myService.processB();
int total = futureA.get() + futureB.get();
System.out.println("异步任务数据统计汇总结果: "+total);
Long endTime = System.currentTimeMillis();
System.out.println("主流程耗时: "+(endTime-startTime));
return "success";}
2.定时任务
- 编写测试方法
private static final SimpleDateFormat dateFormat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private Integer count1 = 1;
private Integer count2 = 1;
private Integer count3 = 1;
@Scheduled(fixedRate = 60000)
public void scheduledTaskImmediately() {
System.out.println(String.format("fixedRate第%s次执行,当前时间为:%s",
count1++, dateFormat.format(new Date())));
}
@Scheduled(fixedDelay = 60000)
public void scheduledTaskAfterSleep() throws InterruptedException {
System.out.println(String.format("fixedDelay第%s次执行,当前时间为:%s",
count2++, dateFormat.format(new Date())));
Thread.sleep(10000);
}
@Scheduled(cron = "0 * * * * *")
public void scheduledTaskCron(){
System.out.println(String.format("cron第%s次执行,当前时间为:%s",
count3++, dateFormat.format(new Date())));
}
-
注解开启
@EnableScheduling @EnableAsync @SpringBootApplication public class Chapter09Application { public static void main(String[] args) { SpringApplication.run(Chapter09Application.class, args); } }
3.邮件任务
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
注:
当添加上述依赖后,Spring Boot自动配置的邮件服务会生效,在邮件发送任务时,可以直接使用Spring框架提供的JavaMailSender接口或者它的实现类JavaMailSenderImpl邮件发送。
-
添加配置
#126免费邮邮件发送服务配置 spring.mail.host=smtp.126.com #spring.mail.port=587 # 配置126免费邮账户和密码(密码是加密后的授权码) spring.mail.username=zhang1417279498@126.com spring.mail.password=EUHGPBNEVJIAIOCU
-
编写发送邮件任务工具类
package com.zsc.shixun.utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Service; import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; /** * @Classname MailUtils * @Description 邮件发送工具类 * @Date 2019-3-14 16:25 * @Created by CrazyStone */ @Service public class MailUtils { private static final Logger logger = LoggerFactory.getLogger(MailUtils.class); @Autowired private JavaMailSenderImpl mailSender; @Value("${spring.mail.username}") private String mailfrom; //Thymeleaf提供的模板引擎解析器 @Autowired TemplateEngine templateEngine; /** * 发送简单邮件 * * @param mailto * @param title * @param content */ public void sendSimpleEmail(String mailto, String title, String content) { // 定制邮件发送内容 SimpleMailMessage message = new SimpleMailMessage(); message.setFrom(mailfrom); message.setTo(mailto); message.setSubject(title); message.setText(content); try { // 发送邮件 mailSender.send(message); logger.info("发送成功"); } catch (Exception e) { logger.error("简单邮件发送失败"); } } /** * 发送验证码邮件 * * @param mailto * @param title * @param code */ public void sendCodeEmail(String mailto, String title, String code) { // 定制邮件发送内容 MimeMessage message = mailSender.createMimeMessage(); Context context = new Context(); context.setVariable("code", code); String emailContent = templateEngine.process("emailCodeTemplate", context); try { //在定制Html模板邮件信息时,、使用了MimeMessageHelper类对邮件信息进行封装处理。 MimeMessageHelper helper = new MimeMessageHelper(message, true); helper.setFrom(mailfrom); helper.setTo(mailto); helper.setSubject(title); helper.setText(emailContent, true); mailSender.send(message); System.out.println("模板邮件发送成功"); } catch (MessagingException e) { System.out.println("模板邮件发送失败 " + e.getMessage()); e.printStackTrace(); } } }
-
验证码随机生成补充
<!--RandomString类,用于生成指定长度的随机字符串。需要以下依赖--> <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> </dependency>
//RandomString.make(6);
-
16.springboot安全管理(前后不分离)
1.初始
MVC Security是Spring Boot整合Spring MVC框架搭建的Web应用的安全管理。
WebFlux Security是Spring Boot整合Spring WebFlux框架搭建的Web应用的安全管理。
OAuth2是大型项目的安全管理框架,可以实现第三方认证、单点登录等功能。
Actuator Security用于对项目的一些运行环境提供安全监控,例如Health健康信息、Info运行信息等,它主要作为系统指标供运维人员查看管理系统的运行情况。
•Spring Security的安全管理有两个重要概念,分别是Authentication(认证)和Authorization(授权)。
2.pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
3.自定义WebSecurityConfigurerAdapter配置类
@EnableWebSecurity // 1.开启MVC security安全支持
@EnableGlobalMethodSecurity(securedEnabled = true) // 2.开启控制权限注解
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
注:
@EnableWebSecurity注解是一个组合注解,主要包括@Configuration注解、@Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class})注解和@EnableGlobalAuthentication注解
4.认证配置
- 认证配置
@Autowired
private UserDetailsServiceImpl userDetailsService;
/** * 重写configure(AuthenticationManagerBuilder auth)方法,进行自定义用户认证 * * @param auth * @throws Exception */@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception { // 密码需要设置编码器 BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); //UserDetailsService认证处理 auth.userDetailsService(userDetailsService).passwordEncoder(encoder);}
- 用户认证信息封装
package com.zsc.shixun.service.function;
import com.zsc.shixun.common.ResultCode;
import com.zsc.shixun.config.UserSecurity;
import com.zsc.shixun.entity.User;
import com.zsc.shixun.exception.ApiException;
import com.zsc.shixun.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Classname UserDetailsServiceImpl
* @Description 自定义一个UserDetailsService接口实现类进行用户认证信息封装
* @Date 2019-3-5 16:08
* @Created by CrazyStone
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private IUserService userService;
@Override
public UserDetails loadUserByUsername(String s) {
// 1.通过业务方法获取用户及权限信息
User user = userService.getUser(s);
List<String> authorities = userService.getUserAuthority(s);
// 2.对用户权限进行封装
List<SimpleGrantedAuthority> list = authorities.stream().map(authority -> new SimpleGrantedAuthority(authority.toString())).collect(Collectors.toList());
// 3.返回封装的UserDetails用户详情类
if (user != null) {
UserDetails userDetails = new UserSecurity(user, list);//下面的第6点有详细解释
return userDetails;
} else {
4.// 如果查询的用户不存在(用户名不存在),必须抛出此异常
throw new ApiException(ResultCode.USER_NOT_FOUND);
}
}
}
- 业务方法获取权限及用户信息
/**
* 获取用户权限
*
* @param s
* @return
*/
public List<String> getUserAuthority(String s) {
List<String> list = new ArrayList<>(10);
//取缓存
Object o = redisTemplate.opsForValue().get(Contant.AUTHORITY_KEY + s);
if (o != null) {
list = (List<String>) o;
} else {
List<AuthorityVO> authorities = userMapper.getAuthority(s);
for (AuthorityVO a : authorities
) {
if (a.crudauth.length() != 0) {
list.addAll(Arrays.asList(a.getCrudauth().split(",")));
}
}
if (authorities.size() > 0) {
//存缓存
redisTemplate.opsForValue().set(Contant.AUTHORITY_KEY + s, list);
}
}
return list;
}
/**
* 获取用户实体
*
* @param s
* @return
*/
@Override
public User getUser(String s) {
User user = null;
//取缓存
Object o = redisTemplate.opsForValue().get(Contant.MESSAGE_KEY + s);
if (o != null) {
user = (User) o;
} else {
user = this.getOne(new QueryWrapper<User>().eq("email", s));
if (user != null) {
//存缓存
redisTemplate.opsForValue().set(Contant.MESSAGE_KEY + s, user);
}
}
return user;
}
5.授权配置
/**
* 重写configure(HttpSecurity http)方法,进行用户授权管理
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// // 1.定制Remember-me记住我功能
http.rememberMe()
.rememberMeParameter("rememberme")
.tokenValiditySeconds(COOKIE_VALIDITY)
// 2.对cookie信息进行持久化管理
.tokenRepository(tokenRepository());
//3.关闭csrf防护
http.csrf().disable();
// 4.自定义用户访问控制
http.authorizeRequests()
//5. .permitAll()表示无需授权通过
//6.。anMatchers()表示捕获指定的请求路径
.antMatchers("/", "/unAuthorize/**", "/test/**", "/user/**").permitAll()
.antMatchers("/dist/**", "/docs/**", "/pages/**", "/plugins/**", "/admin/**", "/unauthorize/**", "/deny/**", "/upload/**").permitAll()
//7.。hasRole()表示指定需要什么权限/角色才可以通过
// .antMatchers("/admin/**").hasRole("admin")
//8.表示其余请求需要有权限才可以通过
.anyRequest().authenticated();
// 9、自定义用户登录控制
http.formLogin()
//10.自定义登录请求页面
.loginPage("/unAuthorize/login")
//11.指定用户名及密码
.usernameParameter("email").passwordParameter("password")
//12.登录成功之后执行的handler 可自定义fuilter,登录时的逻辑处理及页面跳转
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
String url = httpServletRequest.getParameter("url");
System.out.println("之前得url:"+url);
// 获取被拦截的原始访问路径
RequestCache requestCache = new HttpSessionRequestCache();
SavedRequest savedRequest = requestCache.getRequest(httpServletRequest, httpServletResponse);
if (savedRequest != null) {
// 如果存在原始拦截路径,登录成功后重定向到原始访问路径
httpServletResponse.sendRedirect(savedRequest.getRedirectUrl());
} else if (url != null && !url.equals("")) {
// 跳转到之前所在页面
URL fullURL = new URL(url);
System.out.println("执行");
System.out.println(fullURL.getQuery());
System.out.println(fullURL.getPath());
if(fullURL.getQuery()!=null)
{
httpServletResponse.sendRedirect(fullURL.getPath()+"?"+fullURL.getQuery());
}else{
httpServletResponse.sendRedirect(fullURL.getPath());
}
} else {
// 直接登录的用户,根据用户角色分别重定向到后台首页和前台首页
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
boolean isAdmin = authorities.contains(new SimpleGrantedAuthority("ROLE_admin"));
if (isAdmin) {
httpServletResponse.sendRedirect("/home/index");
} else {
httpServletResponse.sendRedirect("/");
}
}
}
})
//13.登录失败之后的执行的handler 可自定义fuilter
.failureUrl("/unAuthorize/login?error");
// 14.自定义用户退出控制
http.logout().logoutUrl("/logout").logoutSuccessUrl("/unAuthorize/index");
// 15.针对访问无权限页面出现的403页面进行定制处理
http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
// 如果是权限访问异常,则进行拦截到指定错误页面
//返回json形式的错误信息
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json");
//阿里巴巴fastjson json解析库
httpServletResponse.getWriter().write(JSON.toJSONString(new CommonResult(ResultCode.AUTHORITY_HAS_EXIST), SerializerFeature.WriteMapNullValue));
httpServletResponse.getWriter().flush();
}
});
}
6登录信息子类扩展实现登录成功除了能获取用户名、密码、权限外,还可以获取用户其他信息
- 定义子类继承org.springframework.security.core.userdetails.User;
package com.zsc.shixun.config;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.List;
/**
* <p>
* 登录用户封装
* </p>
*
* @author ZWYZY
* @since 2020/6/14
*/
public class UserSecurity extends User {
public com.zsc.shixun.entity.User loginUser;
public UserSecurity(com.zsc.shixun.entity.User user, List<SimpleGrantedAuthority> authorities) {
super(user.getUsername(), user.getPassword(), true, true, true, true, authorities);
this.loginUser = user;
}
public com.zsc.shixun.entity.User getLoginUser() {
return loginUser;
}
public void setLoginUser(com.zsc.shixun.entity.User loginUser) {
this.loginUser = loginUser;
}
}
- 用户信息工具类----------自定义获取登录信息类及方法供我们调用
package com.zsc.shixun.utils;
import com.zsc.shixun.config.UserSecurity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
/**
* <p>
*
* </p>
*
* @author ZWYZY
* @since 2020/6/15
*/
@Service
public class LoginMessageUtils {
/**
* 根据登录后获取登录信息
*
* @return
*/
public UserSecurity getMessage() {
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
UserSecurity principal = (UserSecurity) authentication.getPrincipal();
return principal;
}
/**
* 动态更新登录信息
*
* @param userDetails
*/
public void setMessage(UserDetails userDetails) {
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()));
}
}
7.控制器安全权限注解
具体配置见第2点
控制器添加**@Secured**
@Secured(value = "ROLE_article_select")
8.security控制前端页面(以thymeleaf为例)
- pom文件引入依赖
<!-- thymeleaf模板整合security控制页面安全访问依赖 -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
-
thymeleaf引入标签
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
-
用法如下
-
sec:authorize="isAuthenticated()"
表示用户认证通过才可以显示的html元素
-
sec:authorize="hasRole('ROLE_admin')"
表示用户必须具有指定权限才可以显示
-
sec:authorize="isAnonymous()"
表示用户未认证前显示的,认证通过后不显示
-
th:src="@{${#authentication.principal.loginUser.img}}"
-
th:text="${#authentication.principal.username}"
结合thymeleaf来显示用户登录成功后的其他信息
-