当前位置: 首页 > news >正文

Java经典框架之SpringBoot

SpringBoot

Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机,Java 仍是企业和开发人员的首选开发平台。
  

课程内容的介绍

1. SpringBoot基础
2. SpringBoot高级 
  

一、SpringBoot基础

1. SpringBoot概念
官网:https://spring.io/
Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。
SpringBoot是由Pivotal团队在2013年开始研发、2014年4月发布第一个版本的全新开源的轻量级框架。它基于Spring4.0设计,不仅继承了Spring框架原有的优秀特性,而且还通过简化配置来进一步简化了Spring应用的整个搭建和开发过程。另外SpringBoot通过集成大量的框架使得依赖包的版本冲突,以及引用的不稳定性等问题得到了很好的解决。
  
SpringBoot所具备的特征有:
(1)可以创建独立的Spring应用程序,并且基于其Maven或Gradle插件,可以创建可执行的JARs和WARs;
(2)内嵌Tomcat或Jetty等Servlet容器;
(3)提供自动配置的“starter”项目对象模型(POMS)以简化Maven配置;
(4)尽可能自动配置Spring容器;
(5)提供准备好的特性,如指标、健康检查和外部化配置;
(6)绝对没有代码生成,不需要XML配置。
  
SpringBoot框架中还有两个非常重要的策略:开箱即用和约定优于配置。开箱即用,Outofbox,是指在开发过程中,通过在MAVEN项目的pom文件中添加相关依赖包,然后使用对应注解来代替繁琐的XML配置文件以管理对象的生命周期。这个特点使得开发人员摆脱了复杂的配置工作以及依赖的管理工作,更加专注于业务逻辑。约定优于配置,Convention over configuration,是一种由SpringBoot本身来配置目标结构,由开发者在结构中添加信息的软件设计范式。这一特点虽降低了部分灵活性,增加了BUG定位的复杂性,但减少了开发人员需要做出决定的数量,同时减少了大量的XML配置,并且可以将代码编译、测试和打包等工作自动化。
SpringBoot应用系统开发模板的基本架构设计从前端到后台进行说明:前端常使用模板引擎,主要有FreeMarker和Thymeleaf,它们都是用Java语言编写的,渲染模板并输出相应文本,使得界面的设计与应用的逻辑分离,同时前端开发还会使用到Bootstrap、AngularJS、JQuery等;在浏览器的数据传输格式上采用Json,非xml,同时提供RESTfulAPI;SpringMVC框架用于数据到达服务器后处理请求;到数据访问层主要有Hibernate、MyBatis、JPA等持久层框架;数据库常用MySQL;开发工具推荐IntelliJIDEA。
  
2. SpringBoot项目构建
SpringBoot项目的构建方式本身是非常简单的,实现的方式也有多种。
  
2.1 手动创建
我们通过基本的Maven项目来手动配置一个SpringBoot。
    
2.1.1 创建MAVEN项目
创建一个普通的Maven项目即可。

    
2.1.2 添加依赖
创建一个SpringBoot的Web项目,我们需要添加对应的依赖。
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.bobo</groupId><artifactId>SpringBootDemo01</artifactId><version>1.0-SNAPSHOT</version><!-- 1.添加SpringBoot的依赖 --><parent><artifactId>spring-boot-starter-parent</artifactId><groupId>org.springframework.boot</groupId><version>2.3.8.RELEASE</version></parent><dependencies><!-- 2. 添加SpringMVC的支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies></project>
    
2.1.3 创建启动类
我们要启动当前项目需要创建一个Java启动类。
package com.bobo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** SpringBoot项目的启动类*/
@SpringBootApplication
public class AppStart {/*** 程序启动的入口* @param args*/public static void main(String[] args) {SpringApplication.run(AppStart.class,args);}
}
  
2.1.4 启动程序
执行我们的主方法即可。

访问出现404,说明服务启动成功,只是请求访问的资源不存在。

   
2.2 在线构建
我们也可以通过SpringBoot提供的在线地址创建我们的SpringBoot项:https://start.spring.io/

  
在线生成我们的SpringBoot项目,解压后可以直接导入。
 
2.3 IDEA直接创建
IDEA工具可以直接通过在线创建的工具来直接生成,帮助我们简化了下载解压缩的步骤。

到此创建完成。
  
3. SpringBoot基本使用
3.1 自定义控制器
我们创建的是一个基于SpringBoot的WEB项目,那么怎么处理客户端提交的请求呢?这时我们可以直接在启动器所在的子目录下创建对应的Controller即可。
package com.bobo.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;/*@Controller
@ResponseBody*/
@RestController
public class HelloController {@RequestMapping("/hello")public String hello(){System.out.println("hello ...);return "Hello ...";}
}
    
访问即可

       
为什么能够扫描到这个@Controller注解,根本原因是在我们的启动器中的那个@SpringBootApplication注解,这个注解本身是一个组合注解。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 以上四个是JAVA中提供的元注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}
), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class}
)}
)
  

   
3.2 静态资源
在SpringBoot项目中默认的静态资源【html,css,js,图片....】是放置在resource/static目录中。

    
效果

  
3.3 定制Banner
如果我们想要修改服务启动时的那个banner图标,我们只需要在resource目录下创建一个banner.txt文件,然后将我们要显示的信息写入即可http://patorjk.com/software/taag/#p=display&f=Graffiti&t=HELLO
  

  

    
当然我们也可以不显示Banner信息。
package com.bobo;import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;/*** @SpringBootApplication 组合注解* @ComponentScan 可以直接扫描路径*                如果没有指定要扫描的特定的路径,*                那么默认的是会把当前注解所在的类的包及其子包作为扫描路径*/
@SpringBootApplication
public class SpringBootDemo03Application {public static void main(String[] args) {SpringApplication app = new SpringApplication(SpringBootDemo03Application.class);app.setBannerMode(Banner.Mode.OFF); // 关闭掉Bannerapp.run(args);}}
  
3.4 属性文件
在resource目录下的application.properties文件。
   
3.4.1 默认设置
我们可以通过application.properties文件来修改系统默认的属性,比如修改Tomcat相关配置信息。
server.port=8082
server.servlet.context-path=/springboot
   
3.4.2 自定义属性
我们可以在application.properties文件中自定义属性,供我们在代码中使用。
   
自定义属性
# 默认属性修改
server.port=8082
server.servlet.context-path=/springboot
# 自定义属性
user.userName=admin
user.realName=波波
user.address=湖南长沙
  
获取自定义属性
package com.bobo.controller;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;/*@Controller
@ResponseBody*/
@RestController
public class HelloController {@Value(value = "${user.userName}")private String userName;@Value(value = "${user.realName}")private String realName;@Value(value = "${user.address}")private String address;@RequestMapping("/hello")public String hello(){System.out.println("hello ..."+ userName + "  " + realName + " " + address);return "Hello ...";}
}
     
出现乱码的情况

  
解决方法

    
修改了乱码后的效果

    
3.4.3 yml文件
yml是我们在配置系统属性或者自定义属性的另外一种方式。
user.hello.username=a
user.hello.password=123
user.hello.address=cs
user.hello.age=1
  
改成yml
user:hello:username:apassword:123address:csage:1
  
3.5 日志
SpringBoot中支持 JavaUtil Logging, Log4J, Log4J2和Logback作为日志框架,而在SpringBoot中默认支持的是Logback作为日志框架。
    
简单配置
# 日志配置
#logging.file.path=d:/tools/log
logging.file.name=d:/tools/log/log.log
logging.level.org.springframework.web=DEBUG
  

     
引入日志文件的扩展配置文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false"><!--日志文件主目录:这里${user.home}为当前服务器用户主目录--><property name="LOG_HOME" value="${user.home}/log"/><!--日志文件名称:这里spring.application.name表示工程名称--><springProperty scope="context" name="APP_NAME" source="spring.application.name"/><!--默认配置--><include resource="org/springframework/boot/logging/logback/defaults.xml"/><!--配置控制台(Console)--><include resource="org/springframework/boot/logging/logback/console-appender.xml"/><!--配置日志文件(File)--><appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><!--设置策略--><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!--日志文件路径:这里%d{yyyyMMdd}表示按天分类日志--><FileNamePattern>${LOG_HOME}/%d{yyyyMMdd}/${APP_NAME}.log</FileNamePattern><!--日志保留天数--><MaxHistory>15</MaxHistory></rollingPolicy><!--设置格式--><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><!-- 或者使用默认配置 --><!--<pattern>${FILE_LOG_PATTERN}</pattern>--><charset>utf8</charset></encoder><!--日志文件最大的大小--><triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"><MaxFileSize>100MB</MaxFileSize></triggeringPolicy></appender><!-- 多环境配置 按照active profile选择分支 --><springProfile name="dev"><!--root节点 全局日志级别,用来指定最基础的日志输出级别--><root level="DEBUG"><appender-ref ref="FILE"/><appender-ref ref="CONSOLE"/></root><!-- 子节点向上级传递 局部日志级别--><logger level="WARN" name="org.springframework"/><logger level="WARN" name="com.netflix"/><logger level="DEBUG" name="org.hibernate.SQL"/></springProfile><springProfile name="prod"><!--root节点 全局日志级别,用来指定最基础的日志输出级别--><root level="INFO"><appender-ref ref="FILE"/><appender-ref ref="CONSOLE"/></root></springProfile>
</configuration>
    
引入

   
3.6 Profile
项目开发中会出现开发环境的切换,为了更好的处理我们可以通过Profile来实现,之前在Spring的阶段就已经给大家介绍过了Profile,但是实现相对复杂了点,在SpringBoot中也提供了对Profile的支持,而且更加简化,创建对应的属性文件。
  
开发环境
user.host=192.168.100.120
   
生产环境
user.host=192.168.111.123
  
文件名称的命名规则是 application-环境.properties
  

  
要让哪个文件生效,我们只需要在application.properties文件中指定即可。
  

  
测试效果
  

  
3.7 静态资源文件
在SpringBoot项目中默认的存放路径是在static目录下,但是实际开发的时候有可能我们需要调整资源的目录。
   
在main目录下创建一个webapp目录 设置类型为 ResourceRoot。

   
可以直接访问
  

  
自定义目录
有些情况下我们需要将特定的目录作为我们存放静态资源文件的目录。
## 设置自定义的路径
spring.mvc.static-path-pattern=/**
## 覆盖掉默认的配置录
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,class path:/sfile/
   

  

   
3.8 Servlet操作
我们在项目开发过程中可以要碰到直接操作Servlet的情况,这时我们应该怎么去实现。
  
3.8.1 Servlet
第一种方式
定义Servlet
package com.bobo.servlet;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;@WebServlet(name="firstServlet",urlPatterns = "/first")
public class FirstServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("--firstServlet -- doGet 方法");PrintWriter writer = resp.getWriter();writer.write("success");writer.flush();writer.close();}
}
     
在启动类中添加扫描的注解。
@SpringBootApplication
// 在SpringBoot项目启动的时候会扫描 @WebServlet注解
@ServletComponentScan
public class SpringbootDemo06Application {public static void main(String[] args) {SpringApplication.run(SpringbootDemo06Application.class, args);}
}
   
测试访问

      
第二种方式
创建Servlet,不用添加WebServlet注解。
package com.bobo.servlet;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;public class SecondServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("--secondServlet -- doGet 方法");PrintWriter writer = resp.getWriter();writer.write("success");writer.flush();writer.close();}
}
     
在启动类中注入ServletRegistrationBean对象。
@SpringBootApplication
// 在SpringBoot项目启动的时候会扫描 @WebServlet注解
@ServletComponentScan
public class SpringbootDemo06Application {public static void main(String[] args) {SpringApplication.run(SpringbootDemo06Application.class, args);}@Beanpublic ServletRegistrationBean servletRegistrationBean(){ServletRegistrationBean bean = new ServletRegistrationBean(new SecondServlet());bean.addUrlMappings("/second");return bean;}
}
  
测试

    
3.8.2 Filter
第一种方式
创建过滤器
@WebFilter(urlPatterns = "/first")
public class FirstFilter implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("FirstFilter before");filterChain.doFilter(servletRequest,servletResponse);System.out.println("FirstFilter end");}
}
   
在启动器中添加注解。

  
测试

  
第二种方式
在该过滤器中我们不用添加对应的注解。
package com.bobo.filter;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;public class SecondFilter implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("SecondFilter before");filterChain.doFilter(servletRequest,servletResponse);System.out.println("SecondFilter end");}
}
   
启动器中注入 注册器。
    @Beanpublic FilterRegistrationBean filterRegistrationBean(){FilterRegistrationBean bean = new FilterRegistrationBean(new SecondFilter());bean.addUrlPatterns("/second");return bean;}
  
测试

  
3.8.3 Listener
第一种方式
package com.bobo.listener;import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;@WebListener
public class FirstListener implements ServletContextListener {@Overridepublic void contextInitialized(ServletContextEvent sce) {System.out.println("FirstListener ... 初始化");}@Overridepublic void contextDestroyed(ServletContextEvent sce) {System.out.println("FirstListener ... 销毁");}
}
  
启动器中注解

  
测试效果

  
第二种方式
package com.bobo.listener;import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;public class SecondListener implements ServletContextListener {@Overridepublic void contextInitialized(ServletContextEvent sce) {System.out.println("SecondListener ... 初始化");}@Overridepublic void contextDestroyed(ServletContextEvent sce) {System.out.println("SecondListener ... 销毁");}
}
    @Beanpublic ServletListenerRegistrationBean servletListenerRegistrationBean(){return new ServletListenerRegistrationBean(new SecondListener());}
  
测试

 
3.9 文件上传
3.9.1 表单页面
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>用户管理</title>
</head>
<body><h1>文件上传案例:</h1><form action="/user/upload" method="post" enctype="multipart/form-data" ><label>账号:</label><input type="text" name="username"><br><label>头像:</label><input type="file" name="upload"><br><input type="submit" value="提交"></form>
</body>
</html>
  
3.9.2 控制器
package com.bobo.controller;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.IOException;@RestController
@RequestMapping("/user")
public class UserContoller {@RequestMapping("/upload")public String fileUpload(String username, MultipartFile upload) throws IOException {System.out.println(username + " " + upload.getOriginalFilename());upload.transferTo(new File("d:/tools/",upload.getOriginalFilename()));return "success";}}
  
3.9.3 属性文件设置
server.port=8082spring.servlet.multipart.enabled=true
# 设置单个文件上传的大小
spring.servlet.multipart.max-file-size=20MB
# 设置一次请求上传文件的总的大小
spring.servlet.multipart.max-request-size=200MB
  
3.9.4 测试

  

 
上传成功的文件

       
4.SpringBoot基本应用
4.1 Freemaker
FreeMarker是一款模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页、电子邮件、配置文件、源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。
   
http://freemarker.foofun.cn/
  
JavaEE中的两种开发方式
前后端不分离
        要求程序员要掌握js,为了简化页面开发,引入页面模板,页面模板整体上来说又可以分为两大类。
   
前端模板
前端模板就是后缀为html的模板,代表就是Thymeleaf,这种模板有一个好处就是不需要服务端解析就能直接在浏览器中打开。
   
后端模板
必须经过服务端解析才能被浏览器展示出来的模板。
        JSP
        Freemarker
        velocity
  

前后端分离
前后端分离的时候,后端纯粹只是接口,没有任何页面。所有的页面由前端完成,前端会使用相关的模板。
        Vue
        AngularJS
        React
  
4.2 整合Freemaker
4.2.1 添加依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
  
4.2.2 配置
我们在属性文件中设置视图解析器的前后缀。
spring.freemarker.suffix=.ftl
   
4.2.3 创建Freemaker文件
然后我们在系统的模板文件中创建Freemaker文件,注意该文件为一个后缀为 .ftl 的文件。
<html><head><title>Freemaker</title><meta charset="UTF-8"></head><body><h1>Hello Freemark ...</h1></body>
</html>
  
4.2.4 控制器
因为在 template 目录下的文件是没法直接访问的,而且我们也需要先在服务端获取数据绑定数据后再在页面模板文件中呈现,所以请求先到控制器,然后通过模板引擎解析模板文件生成具体的HTML页面响应客户。
@Controller
@RequestMapping("/user")
public class UserController {@RequestMapping("/query")public String query(){System.out.println("query ....");return "user";}
}
   
4.2.5 测试
直接启动服务,访问看效果。

  

  
这就表示整合成功了。
   

4.3 Freemaker的基本应用
接下来介绍下如果在Freemaker中绑定数据。
  
4.3.1 绑定单个数据
我们在Model中绑定的是单个数据,怎么在ftl文件中绑定呢?
    /*** 基本数据类型* 自定义数据类型* 数据容器* @param model* @return*/@RequestMapping("/query")public String query(Model model){System.out.println("query ....");model.addAttribute("userName","波波老师");model.addAttribute("age",18);model.addAttribute("address","湖南长沙");model.addAttribute("flag",true);model.addAttribute("birth",new Date());return "user";}
     
字符和数字类型我们可以通过EL表达式直接取出来。
<html><head><title>Freemaker</title><meta charset="UTF-8"></head><body><h1>Hello Freemark ...</h1>${userName}<br>${age}<br></body>
</html>
   
boolean不能直接转换为string类型。

  
这时我们要通过内部的转换函数来处理。
<html><head><title>Freemaker</title><meta charset="UTF-8"></head><body><h1>Hello Freemark ...</h1>${userName}<br>${age}<br>${address}<br>${flag?string("真","假")}<br></body>
</html>
      

      
<html><head><title>Freemaker</title><meta charset="UTF-8"></head><body><h1>Hello Freemark ...</h1>${userName}<br>${age}<br>${address}<br>${flag?string("真","假")}<br>${birth}<br></body>
</html>
      
时间类型也需要转换。

   
<html><head><title>Freemaker</title><meta charset="UTF-8"></head><body><h1>Hello Freemark ...</h1>${userName}<br>${age}<br>${address}<br>${flag?string("真","假")}<br>${birth?string("yyyy-MM-dd")}<br></body>
</html>
     

     
4.3.2 单个数据处理
我们服务端绑定的单个数据,比如字符串或者数字,我们可能需要对这些数据做出调整,比如数字要四舍五入,字符串我们需要截取等操作。
<html><head><title>Freemaker</title><meta charset="UTF-8"></head><body><h1>Hello Freemark ...</h1>${userName}<br>${age}<br>${address}<br>${flag?string("真","假")}<br>${birth?string("yyyy-MM-dd")}<br><hr><#-- 注释符 --><#assign x=3.1415><#assign y=6><!--mN:小数部分最小N位MN:小数部分最大N位-->x=${x}<br>y=${y}<br>#{x;M2}<br><!-- 3.14 -->#{x;m2}<br><!-- 3.14 -->#{y;M2}<br><!-- 6 -->#{y;m2}<br><!-- 6.00 --><body>
</html>
     

  
字符串拼接处理
<#assign hello="hello freemarker" >
<#-- 字符串拼接 -->
HELLO-${hello}<br>
<#-- EL表达式中的常量表示 -->
${'HELLO|'+hello}<br>
<#-- 常量中使用数据 -->
${'HELLO*${hello}'}<br>
${userName}----${hello}<br>
${userName+'-->' + hello}<br>
   

  
字符串截取
${hello}<br>
${hello[1]}<br>
${hello[4]}<br>
${hello[1..6]}<br>
${hello[3..]}<br>
  

     
4.3.3 自定义对象
@RequestMapping("/query1")
public String query1(Model model){User user = new User(666,"admin","123456");model.addAttribute("user",user);return "user1";
}
<html><head><title>Freemaker</title><meta charset="UTF-8"></head><body><#-- 自定义对象 -->${user.id}<br>--> <#--${user[id]}<br>-->${user.userName}<br>-->${user['userName']}<br>${user.password}<br></body>
</html>
   

      
4.3.4 集合对象
    @RequestMapping("/query1")public String query1(Model model){User user = new User(666,"admin","123456");model.addAttribute("user",user);Map<String,Object> map = new HashMap<>();map.put("user",user);List list = Arrays.asList("张三","李四","王五");List list1 = Arrays.asList("1111","2222","3333");model.addAttribute("list",list);model.addAttribute("list1",list1);model.addAttribute("map",map);return "user1";}
   

    
4.3.5 算数运算
算数运算包含基本的四则运算和求模运算,运算符有:
加法: +
减法: -
乘法: *
除法: /
求模 (求余): %
<html><head><title>Freemaker</title><meta charset="UTF-8"></head><body><h1>算术运算符:</h1><br>${99+100*30}<br>${99/7}<br>${(99/7)?int}<br>${55%3}<br></body>
</html>
  

     
4.3.6 比较运算符

  
4.3.7 逻辑操作
常用的逻辑操作符:
逻辑 或: ||
逻辑 与: &&
逻辑 非: !
    
逻辑操作符仅仅在布尔值之间有效,若用在其他类型将会产生错误导致模板执行中止。
  
4.3.8 内置函数
内建函数就像FreeMarker在对象中添加的方法一样。 要防止和实际方法和其它子变量的命名冲突,则不能使用点 (.),这里使用问号 (?)来和父对象分隔开。 比如,想要保证 path 有起始的 / ,那么可以这么来写: path?ensure_starts_with(’/’)。 path 后的Java对象(通常就是 String) 并没有这样的方法,这是FreeMarker添加的。为了简洁,如果方法没有参数, 那么就可以忽略 (),比如想要获取 path 的长度,就可以写作:path?length, 而不是 path?length()。

     
更多内置函数见此:http://freemarker.foofun.cn/ref_builtins.html
   
<html><head><title>Freemaker</title><meta charset="UTF-8"></head><body><h1>算术运算符:</h1><br>${99+100*30}<br>${99/7}<br>${(99/7)?int}<br>${55%3}<br><h1>内建函数:</h1><#assign hello="Hello FreeMarker"><#assign page="<span style='color:red'>HELLO</span>">${hello}<br>${page}<br>${page?html}<br>${hello?upper_case}<br>${hello?lower_case}<br>${now?date}<br>${now?datetime}<br>${now?time}<br></body>
</html>
  

     
4.3.9 分支和循环
if语句,switch语句已经循环语句,基本的语法格式和我们在Java中使用的是一样的。我们只需要注意下在具体的使用格式上。
<html><head><title>Freemaker</title><meta charset="UTF-8"></head><body><h1>算术运算符:</h1><br>${99+100*30}<br>${99/7}<br>${(99/7)?int}<br>${55%3}<br><h1>内建函数:</h1><#assign hello="Hello FreeMarker"><#assign page="<span style='color:red'>HELLO</span>">${hello}<br>${page}<br>${page?html}<br>${hello?upper_case}<br>${hello?lower_case}<br>${now?date}<br>${now?datetime}<br>${now?time}<br><hr><#assign age = 18 ><#if age == 18>等于18<#elseif age gt 18 >大于18<#else >小于18</#if>null的判断:<br><#assign  mypage="a"><#-- ?? 检测值是否存在 --><#if mypage??>mypage存在<#else >mypage不存在</#if><br><#assign i=3><#switch i><#case 1>ok<#break ><#case 2>ok2<#break ><#case 3>ok3<#break ><#default >ok4</#switch><#list list as obj><#if obj=='李四'><#break ></#if>${obj}<br></#list><#assign  aaa=555><#-- !的使用--><br>${aaa!"666"}<!-- 如果aaa存在就显示aaa本来的值,如果aaa不存在就显示666 --><br></body>
</html>
   

   
5.综合案例
前面介绍了Freemaker,这节介绍SpringBoot整合MyBatis,同时结合Freemaker展现数据。
   
5.1 项目创建
添加相关的依赖
<?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.3.9.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.bobo</groupId><artifactId>springboot-demo09</artifactId><version>0.0.1-SNAPSHOT</version><name>springboot-demo09</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-freemarker</artifactId></dependency><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><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.2</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.0.14</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
     
添加相关的配置文件
server.port=8082# 配置JDBC的相关信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/logistics?
characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456# 配置连接池
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource# 配置MyBatis的package 设置别名
mybatis.type-aliases-package=com.bobo.pojo
     
创建实体对象
package com.bobo.pojo;public class User {private String user_id    ;private String user_name  ;private String real_name  ;private String password   ;private String email      ;private String phone      ;private String u1         ;private String u2         ;public String getUser_id() {return user_id;}public void setUser_id(String user_id) {this.user_id = user_id;}public String getUser_name() {return user_name;}public void setUser_name(String user_name) {this.user_name = user_name;}public String getReal_name() {return real_name;}public void setReal_name(String real_name) {this.real_name = real_name;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}public String getU1() {return u1;}public void setU1(String u1) {this.u1 = u1;}public String getU2() {return u2;}public void setU2(String u2) {this.u2 = u2;}
}
   
5.2 查询用户信息
创建接口。
package com.bobo.mapper;import com.bobo.pojo.User;import java.util.List;public interface UserMapper {List<User> query();}
      
创建映射文件。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bobo.mapper.UserMapper"><select id="query" resultType="user">select * from t_user</select>
</mapper>
   
属性文件中添加Mapper映射文件的路径。

  
创建Service
package com.bobo.service;import com.bobo.pojo.User;import java.util.List;public interface IUserService {List<User> query();}
package com.bobo.service.impl;import com.bobo.mapper.UserMapper;
import com.bobo.pojo.User;
import com.bobo.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class UserServiceImpl implements IUserService {@Autowiredprivate UserMapper mapper;@Overridepublic List<User> query() {return mapper.query();}}
   
创建控制器
package com.bobo.controller;import com.bobo.pojo.User;
import com.bobo.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;import java.util.List;@Controller
@RequestMapping("/user")
public class UserController {@Autowiredprivate IUserService service;@RequestMapping("/query")public String query(Model model){List<User> list = service.query();model.addAttribute("list",list);return "/user";}}
  
属性文件中配置Freemaker的后缀。

  
创建Freemaker模板文件,并且展示数据。
<html><head><title>用户管理</title><meta charset="UTF-8"></head><body><h1>用户管理</h1><table><tr><th>编号</th><th>账号</th><th>姓名</th><th>邮箱</th><th>电话</th><th>操作</th></tr><#list list as user><tr><td>${user.user_id}</td><td>${user.user_name}</td><td>${user.real_name!""}</td><td>${user.email!""}</td><td>${user.phone!""}</td><td>...</td></tr></#list></table></body>
</html>
   
启动操作之前我们需要添加 MyBatis接口的扫描路径。

  
访问测试

   
5.3 添加用户
<html><head><title>用户管理</title><meta charset="UTF-8"></head><body><h1>用户管理</h1><form action="/user/userUpdate" method="post" ><input type="hidden" name="user_id" value="${user.user_id}"><label>账号</label><input type="text" name="user_name" value="${user.user_name}"><br><label>姓名</label><input type="text" name="real_name" value="${user.real_name!""}"><br><label>邮箱</label><input type="text" name="email" value="${user.email!""}"><br><label>电话</label><input type="text" name="phone" value="${user.phone!""}"><br><input type="submit" value="提交"></form></body>
</html>
    
5.4 更新用户
<html><head><title>用户管理</title><meta charset="UTF-8"></head><body><h1>用户管理</h1><form action="/user/userUpdate" method="post" ><input type="hidden" name="user_id" value="${user.user_id}"><label>账号</label><input type="text" name="user_name"value="${user.user_name}"><br><label>姓名</label><input type="text" name="real_name"value="${user.real_name!""}"><br><label>邮箱</label><input type="text" name="email"value="${user.email!""}"><br><label>电话</label><input type="text" name="phone"value="${user.phone!""}"><br><input type="submit" value="提交"></form></body>
</html>
     
5.5 删除用户
<html><head><title>用户管理</title><meta charset="UTF-8"></head><body><h1>用户管理</h1><h2><a href="/user/dispatchUpdate">添加用户</a></h2><table><tr><th>编号</th><th>账号</th><th>姓名</th><th>邮箱</th><th>电话</th><th>操作</th></tr><#list list as user><tr><td>${user.user_id}</td><td>${user.user_name}</td><td>${user.real_name!""}</td><td>${user.email!""}</td><td>${user.phone!""}</td><td><a href="/user/dispatchUpdate?id=${user.user_id}">更新</a><a href="/user/deleteUser?id=${user.user_id}">删除</a></td></tr></#list></table></body>
</html>
   
6. Thymeleaf
Thymeleaf是SpringBoot中推荐使用的前端模板框架。所以比较重要。
  
6.1 SpringBoot整合
创建一个SpringBoot项目,然后添加对应的依赖。
<?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.3.9.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.bobo</groupId><artifactId>springboot-demo10</artifactId><version>0.0.1-SNAPSHOT</version><name>springboot-demo10</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-thymeleaf</artifactId></dependency><!-- 使用Thymeleaf需要添加的依赖 --><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><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
     
创建Thymeleaf文件,Thymeleaf的后缀就是html,我在template目录下直接创建一个html页面即可,但是为了能够使用Thymeleaf中的标签提示,我们添加对应的xmlns即可。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Thymeleaf介绍</title>
</head>
<body><h1>Hello Thymeleaf</h1></body>
</html>
  
添加跳转的控制器。
@Controller
public class UserController {@RequestMapping("/hello")public String hello(){System.out.println("hello ....");return "/user";}
}
   
启动服务测试

  
访问成功,说明整合搞定。
  
6.2 Thymeleaf基本使用
Thymeleaf表达式只能放置在Thymeleaf的自定义属性中(html标签中)。
  
6.2.1 变量输出
控制器中绑定数据。
@RequestMapping("/hello")
public String hello(Model model){System.out.println("hello ....");model.addAttribute("hello","Hello Thymeleaf");model.addAttribute("msg","hahaha");model.addAttribute("now",new Date());model.addAttribute("flag",true);model.addAttribute("age",18);return "/user";
}
  
模板文件
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Thymeleaf介绍</title>
</head>
<body><h1>Hello Thymeleaf</h1><label th:text="hello"></label><br><label th:text="${hello}"></label><br><label th:text="${now}"></label><br><label th:text="${flag}"></label><br><label th:text="${age}"></label><br><h2>th:value的使用</h2><input type="text" value="aaa"><br><input type="text" th:value="${msg}"><br>
</body>
</html>
   
显示效果

  
6.2.2 内置函数
我们通过上面的案例发现显示Model中的数据很方便,但是显示的数据的格式可能不满足我们的需求,这时我们需要调整就需要借助内置的函数来帮助我们实现,我们主要介绍字符串和时间相关的函数。
  
注意点
1. 调用内置函数对象一定要使用#
2. 大部分的内置函数都以 s 结尾, 比如 strings numbers dates
  
字符串的处理
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Thymeleaf介绍</title>
</head>
<body><h1>string类型介绍</h1>hello:<span th:text="${hello}"></span><br>hello是否为空:<span th:text="${#strings.isEmpty(hello)}"></span><br>hello字符串是否包含"th":<span th:text="${#strings.contains(hello,'th')}">
</span><br>hello字符串是否包含"Th":<span th:text="${#strings.contains(hello,'Th')}">
</span><br>hello以H开头:<span th:text="${#strings.startsWith(hello,'H')}"></span><br>hello以a开头:<span th:text="${#strings.startsWith(hello,'a')}"></span><br>hello以H结尾:<span th:text="${#strings.endsWith(hello,'H')}"></span><br>hello以a结尾:<span th:text="${#strings.endsWith(hello,'a')}"></span><br>hello的长度:<span th:text="${#strings.length(hello)}"></span><br>hello都大写:<span th:text="${#strings.toUpperCase(hello)}"></span><br>hello都小写:<span th:text="${#strings.toLowerCase(hello)}"></span><br>
</body>
</html>
   

  
日期时间类型的处理
<h1>日期时间处理</h1>
时间:<span th:text="${now}"></span><br>
时间:<span th:text="${#dates.format(now)}"></span><br>
时间:<span th:text="${#dates.format(now,'yy/MM/dd')}"></span><br>
时间:<span th:text="${#dates.format(now,'yy/MM/dd hh:ss:mm')}"></span><br>
时间:<span th:text="${#dates.format(now,'yy/MM/dd HH:ss:mm')}"></span><br>
年份:<span th:text="${#dates.year(now)}"></span><br>
月份:<span th:text="${#dates.month(now)}"></span><br>
日期:<span th:text="${#dates.day(now)}"></span><br>
本周的第几天:<span th:text="${#dates.dayOfWeek(now)}"></span><br>
小时:<span th:text="${#dates.hour(now)}"></span><br>
  

  
6.2.3 条件判断
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Thymeleaf介绍</title>
</head>
<body><h1>条件判断</h1><h2>if语句</h2><span th:if="${sex} == '男'">男</span><span th:unless="${sex} =='男'">女</span><br><!-- and or not --><span th:if="${flag or false}">or的使用11</span><span th:unless="${flag or false}">or的使用12</span><br><span th:if="${flag and false}">and的使用21</span><span th:unless="${flag and false}">and的使用22</span><br><span th:if="${not flag}">not的使用11</span><span th:unless="${not flag}">not的使用22</span><br><!-- 三木运算符 --><span th:text="true?'A':'B'"></span><br><!-- switch语句 --><hr><div th:switch="${age}"><div th:case="17">17岁</div><div th:case="18">18岁</div><div th:case="19">19岁</div><div th:case="*">其他...</div></div>
</body>
</html>
    

  
6.2.4 循环语句
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Thymeleaf介绍</title>
</head>
<body><h1>循环判断</h1><div th:each="c : ${list1}"><span th:text="${c}"></span><br></div><hr><div th:each="user : ${list2}"><span th:text="${user.id}"></span>&nbsp;&nbsp;<span th:text="${user.userName}"></span>&nbsp;&nbsp;<span th:text="${user.address}"></span><br></div><hr><div th:each="m : ${map}"><!-- 每次循环获取的是一个KV对 --><span th:text="${m.getKey() + ':' + m.getValue().getId()}"></span><span th:text="${m.getKey() + ':' + m.getValue().getUserName()}"></span><span th:text="${m.getKey() + ':' + m.getValue().getAddress()}"></span></div><hr><div th:each="user,iter : ${list2}"><span th:text="${iter.count}"></span>&nbsp;&nbsp;<span th:text="${iter.index}"></span>&nbsp;&nbsp;<span th:text="${user.id}"></span>&nbsp;&nbsp;<span th:text="${user.userName}"></span>&nbsp;&nbsp;<span th:text="${user.address}"></span><br></div>
</body>
</html>
   

  
6.2.5 域对象的操作
也就是我们怎么在Thymeleaf中获取三大作用域中绑定的数据。
@RequestMapping("/hello4")
public String hello4(HttpServletRequest request){request.setAttribute("req","request msg ...");request.getSession().setAttribute("sess","session msg ....");request.getServletContext().setAttribute("app","application msg ....");return "/user4";
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Thymeleaf介绍</title>
</head>
<body><h1>域对象使用</h1><h2>request:</h2><span th:text="${#httpServletRequest.getAttribute('req')}"></span><br><span th:text="${#request.getAttribute('req')}"></span><br><span th:text="${req}"></span><br><h2>session:</h2><span th:text="${#httpSession.getAttribute('sess')}"></span><br><span th:text="${#session.getAttribute('sess')}"></span><br><h2>servletContext:</h2><span th:text="${#servletContext.getAttribute('app')}"></span><br>
</body>
</html>
  
效果

  
6.2.6 URL表达式
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Thymeleaf介绍</title>
</head>
<body><h1>URL使用</h1><a href="http://www.baidu.com">百度</a><br><a th:href="@{http://www.baidu.com}">百度</a><br><hr><a th:href="@{/show}">相对路径</a><br><a th:href="@{~/project2/app1}">相对于服务器的根</a><br><a th:href="@{/show(id=1,name=aaa)}">相对路径--参数传递</a><br><a th:href="@{/path/{id}/show(id=66,name=123)}">RestFul支持</a>
</body>
</html>
  

  
6.2.7 整合案例改造
我们可以将前面介绍的SpringBoot+MyBatis+Freemaker的案例改为SpringBoot+MyBatis+Thymeleaf的案例,涉及到的页面代码如下
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h1>用户管理</h1>
<h2><a th:href="@{/user/dispatchUpdate}">添加用户</a>
</h2><table><tr><th>编号</th><th>账号</th><th>姓名</th><th>邮箱</th><th>电话</th><th>操作</th></tr><tr th:each="user : ${list}"><td th:text="${user.user_id}"></td><td th:text="${user.user_name}"></td><td th:text="${user.real_name}"></td><td th:text="${user.email}"></td><td th:text="${user.phone}"></td><td><a th:href="@{/user/dispatchUpdate(id=${user.user_id})}">更新</a><a th:href="@{/user/deleteUser(id=${user.user_id})}">删除</a></td></tr></table>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><form th:action="@{/user/userUpdate}" method="post" ><span th:if="${user}"><input type="hidden" name="user_id" th:value="${user.user_id}"></span><label>账号</label><input type="text" name="user_name" th:value="${ user==null ?'':user.user_name}"><br><label>姓名</label><input type="text" name="real_name" th:value="${user==null ?'':user.real_name}"><br><label>邮箱</label><input type="text" name="email" th:value="${user==null ?'':user.email}"><br><label>电话</label><input type="text" name="phone" th:value="${user==null ?'':user.phone}"><br><input type="submit" value="提交"></form>
</body>
</html>
   

二、SpringBoot高级

1.热部署
为了提高我们的开发效率,我们可以放开IDEA中的SpringBoot项目的热部署操作。
  
1.1 放开配置
在IDEA中默认是没有放开热部署操作的,我们需要手动的放开设置。

  
1.2 注册
Control+shift+Alt+/ 会出现一个弹出界面。

  
然后选择Registry

  
1.3 添加devtools
<!--devtools 热部署的支持 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId>
</dependency>
  
2. 异常处理
2.1 自定义错误页面
SpringBoot默认的处理异常的机制:一旦程序出现了异常SpringBoot会想 /error 的url发送请求,在SpringBoot中提供了一个 BasicExceptionController来处理 /error 请求,然后跳转到默认显示异常的页面来展示异常信息。

  
如果我们需要将所有的异常统一跳转到我们自定义的错误页面,需要在src/main/resources/template 目录下创建一个 error.html页面,注意名称必须是 error.html。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><h1>系统出错,请联系管理员....</h1><span th:text="${exception}"></span>
</body>
</html>
  

  

  
2.2 @ExceptionHandle注解
package com.bobo.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;@Controller
public class UserController {@RequestMapping("/show1")public String showInfo1(){String name = null;// 模拟 空指针异常name.length();return "index";}@RequestMapping("/show2")public String showInfo2(){int a = 1/0; // 默认算术异常return "index";}@ExceptionHandler(value = {NullPointerException.class})public ModelAndView nullPointerExceptionHandler(Exception e){ModelAndView mm = new ModelAndView();mm.addObject("error",e.toString());mm.setViewName("error1");return mm;}@ExceptionHandler(value = {ArithmeticException.class})public ModelAndView arithmeticException(Exception e){ModelAndView mm = new ModelAndView();mm.addObject("error",e.toString());mm.setViewName("error2");return mm;}
}
  
error1.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><h1>系统出错,请联系管理员....nullPointerExceptionHandler</h1><span th:text="${error}"></span>
</body>
</html>
   
error2.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><h1>系统出错,请联系管理员....arithmeticException</h1><span th:text="${error}"></span>
</body>
</html>
  
效果

  

  
2.3 @ControllerAdvice注解
上面的实现将控制器和异常处理的方法写在了一块,显然不太合理,这时我们可以通过@ControllerAdvice注解来实现解耦。
     
专门的异常处理类
package com.bobo.exception;import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;// @ControllerAdvice
public class GlobalException {//@ExceptionHandler(value = {NullPointerException.class})public ModelAndView nullPointerExceptionHandler(Exception e){ModelAndView mm = new ModelAndView();mm.addObject("error",e.toString());mm.setViewName("error1");return mm;}//@ExceptionHandler(value = {ArithmeticException.class})public ModelAndView arithmeticException(Exception e){ModelAndView mm = new ModelAndView();mm.addObject("error",e.toString());mm.setViewName("error2");return mm;}
}
  
控制器代码
package com.bobo.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;@Controller
public class UserController {@RequestMapping("/show1")public String showInfo1(){String name = null;// 模拟 空指针异常name.length();return "index";}@RequestMapping("/show2")public String showInfo2(){int a = 1/0; // 默认算术异常return "index";}
}
   

   
2.4 SimpleMappingExceptionResolver
我们还可以通过SimpleMappingExceptionResolver来简化我们的异常处理。
package com.bobo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;import java.util.Properties;@SpringBootApplication
public class SpringbootDemo11Application {public static void main(String[] args) {SpringApplication.run(SpringbootDemo11Application.class, args);}/*** 通过SimpleMappingExceptionResolver 设置 特定异常和 处理器的映射关系* @return*/// @Beanpublic SimpleMappingExceptionResolver getSimpleMappingExceptionResolver(){SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();Properties properties = new Properties();properties.put("java.lang.NullPointerException","error1");properties.put("java.lang.ArithmeticException","error2");resolver.setExceptionMappings(properties);return  resolver;}
}
   
2.5 HandleExceptionResolver处理
我们上面讲的SimpleMappingExceptionResolver本质上就是实现HandleExceptionResolver的。

  
所以我们也可以自己来实现HandleExceptionResolver接口。
package com.bobo.exception;import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class MyHandleExceptionResolver implements HandlerExceptionResolver {@Overridepublic ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {ModelAndView mm = new ModelAndView();if(e instanceof NullPointerException){mm.setViewName("error1");}else if(e instanceof  ArithmeticException){mm.setViewName("error2");}else{mm.setViewName("error");}return mm;}
}
   
3. 单元测试
为了提高在开发过程中的效率,我们可以通过SpringBoot中提供的单元测试来快速测试service和dao的业务逻辑。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions>
</dependency>
   
业务逻辑
package com.bobo.service.impl;import com.bobo.service.IUserService;
import org.springframework.stereotype.Service;import java.util.Arrays;
import java.util.List;@Service
public class UserServiceImpl implements IUserService {@Overridepublic List<String> query() {return Arrays.asList("张三","李四","王五");}
}
     
单元测试
package com.bobo;import com.bobo.service.IUserService;
import net.bytebuddy.asm.Advice;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class SpringbootDemo12ApplicationTests {@Autowiredprivate IUserService service;@Testvoid contextLoads() {System.out.println("---->" + service.query());}@BeforeEachvoid before(){System.out.println("before ...");}@AfterEachvoid after(){System.out.println("after ...");}}
  

    
4. 整合Shiro
4.1 项目准备
创建一个SpringBoot项目整合MyBatis,Thymeleaf,SpringMVC等并创建相关的配置文件和Service逻辑。
    <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><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><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.2</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.8</version></dependency><!--devtools 热部署的支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId></dependency><dependencies>
  
属性配置文件
server.port=8082# 配置JDBC的相关信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/logistics?
characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456# 配置连接池
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource# 配置MyBatis的package 设置别名
mybatis.type-aliases-package=com.bobo.pojo# 指定映射文件的位置
mybatis.mapper-locations=classpath:mapper/*.xml
   
通过MyBatis Generator自动生成持久层的相关的代码(t_user表)或者从之前的货运系统中拷贝对应的代码。

  
Service的逻辑实现
package com.bobo.service;import com.bobo.pojo.User;import java.util.List;public interface IUserService {public User login(String userName);public List<User> query(User user);
}
package com.bobo.service.impl;import com.bobo.mapper.UserMapper;
import com.bobo.pojo.User;
import com.bobo.pojo.UserExample;
import com.bobo.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class UserServiceImpl implements IUserService {@Autowiredprivate UserMapper mapper;@Overridepublic User login(String userName) {User user =  new User();user.setUserName(userName);List<User> list = this.query(user);if(list != null && list.size() == 1){return list.get(0);}return null;}@Overridepublic List<User> query(User user) {UserExample example = new UserExample();UserExample.Criteria criteria = example.createCriteria();if(user != null){if(!"".equals(user.getUserName()) && user.getUserName() != null){criteria.andUserNameEqualTo(user.getUserName());}}return mapper.selectByExample(example);}
}
  
到此准备完成。
  
4.2 Shiro整合
4.2.1 Shiro的依赖
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.3.2</version>
</dependency>
   
4.2.2 自定义Realm
package com.bobo.realm;import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;public class MyRealm extends AuthorizingRealm {@Autowiredprivate IUserService service;/*** 认证* @param authenticationToken* @return* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {return null;}/*** 授权* @param principalCollection* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {return null;}
}
  
4.2.3 Shiro的配置
package com.bobo.config;import com.bobo.realm.MyRealm;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.crypto.hash.Hash;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import org.apache.shiro.mgt.SecurityManager;import java.util.HashMap;
import java.util.Map;@Configuration
public class ShiroConfig {/*** 配置凭证匹配器* @return*/@Beanpublic HashedCredentialsMatcher hashedCredentialsMatcher(){HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();matcher.setHashAlgorithmName("md5");matcher.setHashIterations(1024);return matcher;}/*** 注册自定义的Realm* @param hashedCredentialsMatcher* @return*/@Beanpublic MyRealm myRealm(CredentialsMatcher hashedCredentialsMatcher){MyRealm realm = new MyRealm();realm.setCredentialsMatcher(hashedCredentialsMatcher);return realm;}/*** 注册SecurityManager对象* @return*/@Beanpublic SecurityManager securityManager(Realm myRealm){DefaultWebSecurityManager manager = new DefaultWebSecurityManager();manager.setRealm(myRealm);return manager;}/*** 注册ShiroFilterFactoryBean* @return*/@Bean(name = "shiroFilter")public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager manager){ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();filter.setSecurityManager(manager);filter.setLoginUrl("/login.do");filter.setSuccessUrl("/success.html");filter.setUnauthorizedUrl("/refuse.html");// 设置过滤器Map<String,String> map = new HashMap<>();map.put("/css/**","anon");map.put("/img/**","anon");map.put("/js/**","anon");map.put("/login","anon");map.put("/login.do","authc");map.put("/**","authc");filter.setFilterChainDefinitionMap(map);return filter;}
}
  
4.2.4 测试
添加对应的测试文件

   
package com.bobo.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;@Controller
public class LoginController {@RequestMapping("/login")public String goLoginPage(){return "login";}
}
package com.bobo.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;@Controller
@RequestMapping("/user")
public class UserController {@RequestMapping("/query")public String query(){System.out.println("----user query----");return "user";}
}
  
4.3 认证实现
4.3.1 自定义Realm
在自定义Realm中完成认证逻辑。
package com.bobo.realm;import com.bobo.pojo.User;
import com.bobo.service.IUserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.SimpleByteSource;
import org.springframework.beans.factory.annotation.Autowired;public class MyRealm extends AuthorizingRealm {@Autowiredprivate IUserService service;/*** 认证* @param authenticationToken* @return* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;String userName = token.getUsername();User user = new User();user.setUserName(userName);// 账号验证user = service.login(userName);if(user == null){return null;}return new SimpleAuthenticationInfo(user,user.getPassword(),new SimpleByteSource(user.getU1()) ,"myRealm");}/*** 授权* @param principalCollection* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {return null;}
}
    
4.3.2 控制器
在控制器中完成认证失败的处理。
package com.bobo.controller;import org.apache.shiro.SecurityUtils;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;import javax.servlet.http.HttpServletRequest;@Controller
public class LoginController {@RequestMapping("/login")public String goLoginPage(){return "login";}@RequestMapping("/login.do")public String login(HttpServletRequest request){Object obj = request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);System.out.println("认证错误的信息:" + obj);return "/login";}@RequestMapping("/logout")public String logout(){SecurityUtils.getSubject().logout();return "/login";}
}
   
4.3.3 登录页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><h1>登录页面</h1><form th:action="@{/login.do}" method="post">账号:<input type="text" name="username" th:value="'admin1'"><br>密码:<input type="password" name="password" th:value="'123'"><br><input type="submit" value="提交"></form>
</body>
</html>
   
5. 授权操作
5.1 注解的使用
我们需要开启SpringMVC对注解的支持。
/**
* 开启对Shiro授权注解的支持
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();advisor.setSecurityManager(securityManager);return advisor;
}
   
在自定义Realm中添加权限。
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {User user = (User) principalCollection.getPrimaryPrincipal();System.out.println("获取授权的账号:" + user.getUserName());SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.addRole("role1");return info;
}
     

   
<dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId>
</dependency>
   
启动访问

  

  

  
5.2 标签的使用
添加相关的依赖。
<dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.0.0</version>
</dependency>
  
注入对象
@Bean
public ShiroDialect shiroDialect(){return new ShiroDialect();
}
   
在页面中实现处理
<!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</title>
</head>
<body><h1>用户管理</h1><shiro:authenticated>已登录:<shiro:principal property="userName"></shiro:principal></shiro:authenticated><a href="#" shiro:hasRole="role1">用户查询</a><a href="#" shiro:hasRole="role1">用户添加</a><a href="#" shiro:hasRole="role2">用户修改</a><a href="#" shiro:hasRole="role2">用户删除</a>
</body>
</html>
  
访问效果

相关文章:

Java经典框架之SpringBoot

SpringBoot Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机&#xff0c;Java 仍是企业和开发人员的首选开发平台。 课程内容的介绍 1. SpringBoot基础 2. Spring…...

LeetCode75| 二叉搜索树

目录 700 二叉搜索树中的搜索 迭代 递归 450 删除二叉搜索树中的节点 700 二叉搜索树中的搜索 注意二叉搜索树的性质即可 迭代 class Solution { public:TreeNode* searchBST(TreeNode* root, int val) {while(root ! NULL){if(root->val < val)root root->r…...

博物馆3d虚拟场景复原制作有助于传承和弘扬中华民族优秀传统文化

古建筑3D虚拟复原是一种利用现代科技手段对古代建筑进行数字化保护和展示的方法。它通过高精度的三维扫描技术&#xff0c;将古建筑的形态、结构、材料等信息转化为数字化数据&#xff0c;再通过计算机图形学技术将这些数据重建为虚拟的三维模型。这种技术在古建筑保护、研究、…...

二维码地址门牌系统:便捷报修服务引领社区新篇章

文章目录 前言一、二维码门牌系统介绍二、便捷报修服务三、多功能应用四、技术发展与应用前景 前言 科技的快速发展引领了社区生活的新变革&#xff0c;其中二维码门牌系统技术在社区管理方面带来了极大的便利和智能化服务。本文将深入了解这项创新技术及其优势。 一、二维码门…...

c++基础(对c的扩展)

文章目录 命令空间引用基本本质引用作为参数引用的使用场景 内联函数引出基本概念 函数补充默认参数函数重载c中函数重载定义条件函数重载的原理 命令空间 定义 namespace是单独的作用域 两者不会相互干涉 namespace 名字 { //变量 函数 等等 }eg namespace nameA {int num;v…...

RS485数据采集模块,如何一次采集多个modbus设备数据?

在工业数据采集中&#xff0c;RS485是一种常见的数据通信协议&#xff0c;而Modbus则是其上的常用设备协议。那么&#xff0c;如何用一个模块高效采集多个Modbus设备的数据呢&#xff1f;这就是我们今天要探讨的话题&#xff01; 什么是RS485数据采集模块&#xff1f; 首先&a…...

面试 Vue 框架八股文十问十答第一期

面试 Vue 框架八股文十问十答第一期 作者&#xff1a;程序员小白条&#xff0c;个人博客 相信看了本文后&#xff0c;对你的面试是有一定帮助的&#xff01;关注专栏后就能收到持续更新&#xff01; ⭐点赞⭐收藏⭐不迷路&#xff01;⭐ 1&#xff09;MVVM 的理解 MVVM (Mod…...

【积微成著】性能测试调优实战与探索(存储模型优化+调用链路分析)| 京东物流技术团队

一、前言 性能测试之于软件系统&#xff0c;是保障其业务承载能力及稳定性的关键措施。以软件系统的能力建设为主线&#xff0c;系统能力设计工作与性能测试工作&#xff0c;既有先后之顺序&#xff0c;亦有相互之影响。以上&#xff0c;在性能测试的场景决策&#xff0c;架构…...

建立分位制,用标准去量化优化效果 - 启动优化为例

Android开发的四年多时间中&#xff0c;逐渐将自己的工作重心从业务移动到小型项目的架构设计&#xff0c;在此过程中代码的书写有了更高的标准和要求&#xff0c;性能优化从此伴随着工作脚步&#xff0c; 为什么要进行性能优化呢&#xff1f; 页面访问时长从1s增加到3s&#…...

Modbus 通信协议 二

Modbus 常用缩写 通用Modbus帧结构 -应用数据单元&#xff08;ADU&#xff09; Modbus数据模型 Modbus ADU 和 PDU 的长度 Modbus PDU结构 串行链路上的 Modbus 帧结构 Modbus 地址规则 ASCLL 模式 和 RTU 模式的比较 RTU 模式 RTU 模式位序列 帧格式 帧的标识与鉴别 CRC 循环冗…...

关于系统设计的一些思考

0.前言 当我们站在系统设计的起点&#xff0c;面对一个新的需求&#xff0c;我们该如何开始呢&#xff1f;这是许多处于系统分析与设计领域的新手常常思考的问题。有些人可能会误以为&#xff0c;只要掌握了诸如面向对象、统一建模语言、设计模式、微服务、Serverless、Servic…...

Java 第19章 IO流 课堂练习+本章作业

文章目录 Buffered流拷贝二进制文件创建文件写入文本读取文本文件存读Properties文件 Buffered流拷贝二进制文件 package com.hspedu.chapter19.outputStream;import java.io.*;public class BufferedCopy02 {public static void main(String[] args) {String srcFilePath &q…...

一键制作电子样册,提升企业品牌形象

​电子样册作为一种新型的宣传方式&#xff0c;具有许多优势。首先&#xff0c;它打破了传统纸质宣传册的局限性&#xff0c;可以随时随地展示企业的产品和服务。其次&#xff0c;电子样册可以通过多媒体形式展示企业的品牌形象&#xff0c;包括图片、视频、文字等多种形式&…...

Linux 的引导与服务控制

一 开机启动过程 bios加电自检-->mbr-->grub-->加载内核文件-->启动进程 1 bios家电自检 检测硬件是否正常&#xff0c;然后根据bios中的启动项设置&#xff0c;去找内核文件 2 mbr 因为grup太大,第一个扇区存不下所有的grub程序&#xff0c;所以分为2部分指…...

多输入多输出 | MATLAB实现SSA-CNN麻雀算法优化卷积神经网络多输入多输出预测

多输入多输出 | MATLAB实现SSA-CNN麻雀算法优化卷积神经网络多输入多输出预测 目录 多输入多输出 | MATLAB实现SSA-CNN麻雀算法优化卷积神经网络多输入多输出预测预测效果基本介绍模型背景程序设计参考资料 预测效果 基本介绍 MATLAB实现SSA-CNN麻雀算法优化卷积神经网络多输入…...

高端电流检测方案

随着过去传统的“开环”系统被智能和高效率“闭环”设计所取代&#xff0c;准确的电流检测在多种应用中变得越来越重要。常见的电流检测方法&#xff0c;需要将检流电阻串联进被测电流通路&#xff0c;再用放大电路放大检流电阻上的压降。这个放大电路常被称之为电流检测放大器…...

IP地址、子网掩码与网络地址

一、IP地址 在 TCP/IP 体系中&#xff0c;IP 地址是一个最基本的概念。IP地址的作用&#xff1a;实现和网上的其他设备进行通信 IP地址的表示方法 互联网上的每台主机&#xff08;或路由器&#xff09;的每个接口都分配一个全世界唯一的IP地址。该IP地址由ICANN分配。 IP地址…...

python 深度学习 记录遇到的报错问题10

本篇继python 深度学习 解决遇到的报错问题9_module d2l.torch has no attribute train_ch3-CSDN博客 一、CUDA error: no kernel image is available for execution on the device CUDA kernel errors might be asynchronously reported at some other API call,so the stackt…...

linux下docker搭建Prometheus +SNMP Exporter +Grafana进行核心路由器交换机监控

一、安装 Docker 和 Docker Compose https://docs.docker.com/get-docker/ # 安装 Docker sudo apt-get update sudo apt-get install -y docker.io# 安装 Docker Compose sudo apt-get install -y docker-compose二、创建配置文件及测试平台是否正常 1、选个文件夹作为自建…...

Github 2023-12-31 开源项目日报 Top10

根据Github Trendings的统计&#xff0c;今日(2023-12-31统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量TypeScript项目3Swift项目1Java项目1HTML项目1Astro项目1Python项目1C项目1Dart项目1Jupyter Notebook项目1C项…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

vue3 字体颜色设置的多种方式

在Vue 3中设置字体颜色可以通过多种方式实现&#xff0c;这取决于你是想在组件内部直接设置&#xff0c;还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法&#xff1a; 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP

编辑-虚拟网络编辑器-更改设置 选择桥接模式&#xff0c;然后找到相应的网卡&#xff08;可以查看自己本机的网络连接&#xff09; windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置&#xff0c;选择刚才配置的桥接模式 静态ip设置&#xff1a; 我用的ubuntu24桌…...

嵌入式常见 CPU 架构

架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集&#xff0c;单周期执行&#xff1b;低功耗、CIP 独立外设&#xff1b;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel&#xff08;原始…...

tomcat指定使用的jdk版本

说明 有时候需要对tomcat配置指定的jdk版本号&#xff0c;此时&#xff0c;我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...