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>
  
访问效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/591615.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第7课 利用FFmpeg将摄像头画面与麦克风数据合成后推送到rtmp服务器

上节课我们已经拿到了摄像头数据和麦克风数据&#xff0c;这节课我们来看一下如何将二者合并起来推送到rtmp服务器。推送音视频合成流到rtmp服务器地址的流程如下&#xff1a; 1.创建输出流 //初始化输出流上下文 avformat_alloc_output_context2(&outFormatCtx, NULL, &…

杜笙MB-115up抛光树脂(出水18兆欧)

TULSIONMB-115UP是一种高阶核子级抛光树脂&#xff0c;由核子级强酸型阳离子TulsimerMB115和强碱阴离子交换树脂A33按一定比例混合而成。这种树脂具有独特的结构和性能&#xff0c;能够有效地去除材料表面的污渍和杂质&#xff0c;提高材料的表面质量和光泽度。 首先&#xff0…

【Spring实战】15 Logback

文章目录 1. 依赖2. 配置3. 打印日志4. 启动程序5. 验证6. 调整日志级别7. 代码详细总结 Spring 作为一个现代化的 Java 开发框架&#xff0c;提供了很多便利的功能&#xff0c;其中包括灵活而强大的日志记录。本文将介绍如何结合 Spring 和 Logback 配置和使用日志&#xff0c…

基于mediapipe的人体姿态估计模型——没有GPU依然速度飞起

关于人体姿态检测模型,我们前期也介绍过了很多相关的模型,比如基于Yolo-NAS的姿态检测以及基于YOLOv8的人体姿态检测,而人体姿态估计一直是计算机视觉任务中比较重要的一个模型。但是基于YOLO系列的人体姿态检测模型需要较大的算力,且很难在CPU模型上快速的运行。 基于medi…

HTML5 和 CSS3 新特性(常用)

HTML5 的新特性 HTML5 的新增特性主要是针对于以前的不足&#xff0c;增加了一些新的标签、新的表单和新的表单属性等。 这些新特性都有兼容性问题&#xff0c;基本是 IE9 以上版本的浏览器才支持&#xff0c;如果不考虑兼容性问题&#xff0c;可以大量使用这 些新特性。 HTML…

【二叉树的顺序结构及实现一-堆】

文章目录 一、二叉树的顺序结构二、堆的概念及结构三、堆的实现(以小堆为例)1、堆的结构体2、堆的初始化->void HeapInit(HP* hp);3、堆的销毁->void HeapDestroy(HP* hp);4、堆的判空->bool HeapEmpty(HP* hp);5、取堆顶的数据->HPDataType HeapTop(HP* hp);6、堆…

抖店新手该如何运营?

我是电商珠珠 在抖店开好之后&#xff0c;大部分新手都不知道怎么去运营&#xff0c;今天&#xff0c;我就来给大家详细的讲一下。 第一步&#xff0c;店铺基础设置 我一直跟我的学生讲&#xff0c;一定要懂基本流程&#xff0c;只有前期将流程跑通了后期才可以毫无压力。 …

Java基础综合练习(飞机票,打印素数,验证码,复制数组,评委打分,数字加密,数字解密,抽奖,双色球)

练习一&#xff1a;飞机票 需求: ​ 机票价格按照淡季旺季、头等舱和经济舱收费、输入机票原价、月份和头等舱或经济舱。 ​ 按照如下规则计算机票价格&#xff1a;旺季&#xff08;5-10月&#xff09;头等舱9折&#xff0c;经济舱8.5折&#xff0c;淡季&#xff08;11月到来…

【algorithm】自动驾驶常见常考的几个模型和推导,顺便总结自己遇到的考题经验不断更新之———控制版

写在前面 本来快达成目标了&#xff0c;没想到公司遭受了问题&#xff0c;公司和同事我感觉还是挺好的&#xff0c;有国企的正规也有小企业的灵活&#xff0c;大家都很有学习欲望。 作为本次再次复习回忆如下&#xff1a; 把之前面试准备的 机器学习&#xff08;基本搬运到CSD…

JVM篇:JVM的简介

JVM简介 JVM全称为Java Virtual Machine&#xff0c;翻译过来就是java虚拟机&#xff0c;Java程序&#xff08;Java二进制字节码&#xff09;的运行环境 JVM的优点&#xff1a; Java最大的一个优点是&#xff0c;一次编写&#xff0c;到处运行。之所以能够实现这个功能就是依…

电脑突然不能使用win+x后的快捷键的解决方法

在一次使用电脑后我习惯性的winxuh进行休眠&#xff0c;但是失败了&#xff0c;我发现winx后并没有出现曾经常用的快捷键方式。 左边图片显示的是正常情况。我遇到的情况是图片右边快捷键位没有了&#xff0c;并且也不能进行快捷操作。 国内的网站我都搜索过了&#xff0c;甚至…

outlook邮箱群发邮件方法?邮箱如何群发?

outlook邮箱群发邮件如何使用&#xff1f;QQ邮箱设置群发的步骤&#xff1f; Outlook邮箱群发邮件&#xff1a;必要性 Outlook邮箱作为全球广泛使用的邮件服务之一&#xff0c;不仅提供了便捷的邮件收发功能&#xff0c;还支持多种附件、日历提醒及强大的联系人管理。Outlook…

Python 实现给 pdf 文件自动识别标题并增添大纲

一、背景&#xff1a; 客户方提供过来一个开放平台的pdf文档&#xff0c;文档里有几十个接口&#xff0c;没有大纲和目录可以定位到具体内容&#xff0c;了解整体的API功能&#xff0c;观看体验极度差劲&#xff0c;所以想使用Python代码自动解析pdf文档&#xff0c;给文档增添…

某人寿保险公司基础架构云化与小机数仓下移实践

随着数据中心 IT 基础架构的不断演进&#xff0c;云计算、大数据、移动互联的需求日益高涨&#xff0c;快速敏捷、易于维护以及扩展性&#xff0c;逐渐成为金融机构在升级数据中心时重点考虑的方面。 某人寿保险公司&#xff08;以下简称“客户”&#xff09;过往采用传统三层架…

PS插件一键生成超治愈向日葵花海

金黄色的向日葵总能给人带来治愈的感觉&#xff0c;仿佛在这里能够疗愈心灵所有的伤口。今天我们通过START AI来生成一片美丽的向日葵花海~ 这是小编使用的关键词&#xff0c;负面词需要填写你不想要拥有的&#xff0c;能够让生成的结果更贴合你的想法 最后的生成效果就如下图…

IC工程师级别与薪资是怎样的?资深工程师一文带你了解清楚

入行IC之后&#xff0c;想必大家更关心的就是工程师薪资和级别&#xff0c;因为入行的大多数也是工程师。 国际的一流企业基本上工程师分为以下几个级别&#xff1a;普通工程师&#xff0c;资深工程师&#xff0c;主管工程师&#xff0c;资深主管&#xff0c;总工, 资深总工&am…

电子电器架构(E/E)演化 —— 车载以太网

电子电器架构&#xff08;E/E&#xff09;演化 —— 车载以太网 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 本文13000字。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一…

笔记1:基于锚框(先验框)的目标检测

一、边缘框&#xff08;bounding box&#xff09; 1.1 定义 边缘框&#xff1a;真实标注的物体位置 2.1 表示方式 1、&#xff08;x1,y1)和(x2,y2) 2、&#xff08;x1,y1)和w,h 二、锚框(anchor box)/先验框&#xff08;prior bounding box&#xff09; 2.1 定义 对边缘…

Django 学习教程- Django模板(Template)

系列 Django 学习教程-介绍与安装-CSDN博客 Django 学习教程- Hello world入门案例-CSDN博客 前言 在上一章节中我们使用django.http.HttpResponse() 来输出 "Hello World&#xff01;"。该方式将数据与视图混合在一起&#xff0c;不符合 Django 的 MTV 思想。 本…

Linux network — 网络层收发包流程及 Netfilter 框架浅析

Linux network — 网络层收发包流程及 Netfilter 框架浅析 1. 前言2. 基础网络知识2.1 网络分层模型2.2 数据包协议分层2.3 sk_buff 结构2.4 收发包整体框架 3. 网络层&#xff08;IPv4&#xff09;收发包流程4. Netfilter 框架4.1 IPv4 网络层的 Netfilter Hook 点4.2 iptable…