1. 程式人生 > 其它 >springboot-3-web開發

springboot-3-web開發

一、檢視層技術thymeleaf

我們一般都是基於3.x版本

1、流程:

匯入依賴

<!--整合thymeleaf技術-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <!--不需要版本號-->
</dependency>

在yaml配置檔案中修改配置

# Thymeleaf
thymeleaf:
  # 是否開啟快取
  cache: true
  # 檢查模板是否存在
  check-template: true
  # 模板檔案編碼
  encoding: UTF-8
  # 檢查模板位置是否存在
  check-template-location: true
  # 模板檔案位置
  prefix: classpath:/templates/
  # content-type配置
  servlet:
    content-type: text/html
  # 檔案字尾名
  suffix: .html

在control中建立一個控制器,放入model資料

@GetMapping("/thymeleaf")
public String thymeleafTest(Model model){
    model.addAttribute("message","hello~~~!!!!");
    return "success";
}

在template下建立.html檔案

再加入名稱空間,xmlns:th="http://www.thymeleaf.org"

<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    
    
    
    <title>Document</title>
</head>
<body>

<h1 th:text="${message}"></h1>
</body>
</html>

2、常用名稱空間

xmlns:th=http://www.thymeleaf.org
xmlns:sec=http://www.thymeleaf.org/extras/spring-security
xmlns:shiro=http://www.pollix.at/thymeleaf/shiro

3、其他常用屬性

https://www.cnblogs.com/hjwublog/p/5051732.html

二、返回JSON:

springmvc中使用訊息轉換器HttpMessageConverter對Json資料的轉換提供了很好的支援,在springboot中更近一步,對相關配置做了進一步簡化

1、流程:

匯入web依賴即可,這個依賴中加入了jackson-databind作為JSON處理器

建立pojo類

這些額外的註解可以理解為就是在配置這個pojo類的轉換器

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
    private String name;
    private String author;
    @JsonIgnore
    private Float price;
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date publishedDate;
}

控制器

@ResponseBody
@GetMapping("/book")
public Book getBook(){
   return new Book("西遊記","吳承恩",30f,new Date());
}

測試:postman得到資料,價格被忽略了,日期也被格式化了

{
    "name": "西遊記",
    "author": "吳承恩",
    "publishedDate": "2021-07-12"
}

2、自定義轉換器:

springboot預設使用的就是jackson-databind,但還有其他json轉換器,Gson,fastjson,這裡講一講fastjson轉換器

fastjson轉換器是JSON轉換速度最快的開源框架

流程:

匯入依賴,注意需要先將web-starter中的jackson-databind去掉,然後再加上fastjson

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.47</version>
</dependency>

配置fastjson的HttpMessageConverter,主要兩個部分

  • MediaType
    • MediaType媒體型別:決定瀏覽器將以什麼形式、什麼編碼對資源進行解析
    • 也就是Content-Type:也屬於MediaType媒體型別,主要用於在請求頭中指定資源的MediaType
  • FastJsonConfig
    • 主要配置在傳輸給瀏覽器的json資料中的一些配置規則
@Configuration
public class MyFastJsonConfig {
    @Bean
    FastJsonHttpMessageConverter fastJsonHttpMessageConverter(){
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        com.alibaba.fastjson.support.config.FastJsonConfig config = new com.alibaba.fastjson.support.config.FastJsonConfig();
        config.setDateFormat("yyyy-MM-dd");
        config.setCharset(Charset.forName("UTF-8"));
        config.setSerializerFeatures(
                //輸出類名
                SerializerFeature.WriteClassName,
                //輸出value為null的map資料
                SerializerFeature.WriteMapNullValue,
                //輸出好看的json格式
                SerializerFeature.PrettyFormat,
                //當list為空時,輸出空list,而不是null
                SerializerFeature.WriteNullListAsEmpty,
                //當字串為空時,輸出空字串,而不是null
                SerializerFeature.WriteNullStringAsEmpty
        );
        converter.setFastJsonConfig(config);
        return converter;
    }
}

還需要再配置一下響應編碼,否則返回的json會亂碼

server.servlet.encoding.force-response=true

寫一個pojo類

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
    private String name;
    private String author;
    private Float price;
    private Date publishedDate;
}

控制器

@ResponseBody
@GetMapping("/book")
public Book getBook(){
   return new Book("西遊記","吳承恩",30f,new Date());
}

測試:

在fastjson中怎麼忽略欄位:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
    private String name;
    private String author;
    @JSONField(serialize=false)
    private Float price;
    private Date publishedDate;
}

三、靜態資源訪問

1、訪問本地資源:

流程:

配置webmvc

@Configuration
@EnableWebMvc
public class MyWebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/");
    }
}

也可以用引數配置代替

# 攔截規則
spring.mvc.static-path-pattern=/static/**
# 靜態資源放置位置
spring.web.resources.static-locations= classpath:/static/

把1.png放在classpath:/static/下,即:resource下的static目錄下

測試:

如果沒成功,clean一下,親證

2、用依賴載入jquery

流程:

在https://www.webjars.org/上查詢到相應的依賴,再匯入依賴

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.6.0</version>
</dependency>

重啟專案,再訪問:http://localhost:9090/webjars/jquery/3.6.0/jquery.js就發現能訪問到jquery.js檔案了

為什麼是webjars/jquery/3.6.0/jquery.js訪問地址

在檔案中的位置:

因為再automvc類中的配置中有了對映,所以只要匯入了依賴就可以直接訪問到這個靜態檔案

3、上傳檔案

涉及到兩個元件:CommonsMultipartResolverStandardServletMultipartResolver,前者使用commons-fileupload來處理multipart請求,後者基於servlet3.0來處理multipart請求(在tomcat7.0就開始支援,不需要新增額外的jar包)

springboot中的檔案上傳自動化配置類MultipartAutoConfiguration,預設使用了StandardServletMultipartResolver

流程:

匯入web-starter依賴

配置引數

# 檔案上傳
#支援檔案上傳
spring.servlet.multipart.enabled=true
# 檔案些入磁碟的閾值,預設為0
spring.servlet.multipart.file-size-threshold=0
# 檔案上傳的臨時儲存位置
spring.servlet.multipart.location=D://temp
# 單檔案上傳的最大大小
spring.servlet.multipart.max-file-size=1MB
# 多檔案上傳的最大總大小
spring.servlet.multipart.max-request-size=10MB
# 表示是否進行延遲解析
spring.servlet.multipart.resolve-lazily=false

在classpath:/static/目錄下建立一個upload.html檔案

<!DOCTYPE html>
<html lang="en">
<head>
    
    <title>檔案上傳</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="uploadFile" value="請選擇檔案"><br/>
    <input type="submit" value="提交">
</form>
</body>
</html>

建立檔案上傳介面(控制器)

這裡建議把字串的操作看一看:https://www.cnblogs.com/huxiuqian/p/10167415.html

最好還要會議正則表示式:https://www.runoob.com/java/java-regular-expressions.html

@RestController
public class UploadControl {
    SimpleDateFormat sdf = new SimpleDateFormat("/yyyy/MM/dd");

    @PostMapping(value = "upload")
    public String upload(MultipartFile uploadFile, HttpServletRequest request) {
        //獲取"/uploadFile"檔案的全路徑
        String realPath = request.getSession().getServletContext().getRealPath("/uploadFile");
        //獲取當前的時間,並格式化
        String time = sdf.format(new Date());
        //合併得到的檔案路徑和時間格式得到新檔名
        File folder = new File(realPath + time);
        System.out.println("檔案放置--->>>>" + folder);
        //放入檔案
        if (!folder.isDirectory()) {
            folder.mkdirs();
        }
        String oldName = uploadFile.getOriginalFilename();
        String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf('.'));
        System.out.println("新名字--->>>"+newName);
        try {
            uploadFile.transferTo(new File(folder, newName));
            String filepath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + "/uploadFile" + time +"/"+ newName;
            return filepath;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "上傳失敗";
    }
}

form中的enctype屬性

enctype 屬性規定在傳送到伺服器之前應該如何對錶單資料進行編碼。

預設地,表單資料會編碼為 "application/x-www-form-urlencoded"。就是說,在傳送到伺服器之前,所有字元都會進行編碼(空格轉換為 "+" 加號,特殊符號轉換為 ASCII HEX 值)。

application/x-www-form-urlencoded 在傳送前編碼所有字元(預設)
multipart/form-data 不對字元編碼。在使用包含檔案上傳控制元件的表單時,必須使用該值。
text/plain 空格轉換為 "+" 加號,但不對特殊字元編碼。

多檔案上傳

upload.html

<form action="/uploads" method="post" enctype="multipart/form-data">
    <input type="file" name="uploadFiles" value="請選擇多檔案上傳" multiple>
    <input type="submit" value="提交">
</form>

control(就是多了一個遍歷器)

@PostMapping(value = "uploads")
public List upload(MultipartFile[] uploadFiles,HttpServletRequest request){
    //獲取"/uploadFile"檔案的全路徑
    String realPath = request.getSession().getServletContext().getRealPath("/uploadFile");
    //獲取當前的時間,並格式化
    String time = sdf.format(new Date());
    //合併得到的檔案路徑和時間格式得到新檔名
    File folder = new File(realPath + time);
    System.out.println("檔案放置--->>>>" + folder);
    //放入檔案
    if (!folder.isDirectory()) {
        folder.mkdirs();
    }
    List<String> newFiles = new ArrayList<>();
    for (MultipartFile uploadFile : uploadFiles){
        String oldName = uploadFile.getOriginalFilename();
        String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf('.'));
        System.out.println("新名字--->>>"+newName);
        try {
            uploadFile.transferTo(new File(folder, newName));
            String filepath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + "/uploadFile" + time +"/"+ newName;
            newFiles.add(filepath);
        } catch (IOException e) {
            e.printStackTrace();
            newFiles.add("上傳已失敗");
        }
    }
    return newFiles;
}

4、整合阿里雲oss上傳檔案

流程:

匯入依賴:

<!--oss-->
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>2.8.2</version>
</dependency>

在註冊好的oss中找到下面的訊息

public static String ENDPOINT = "http://oss-cn-beijing.aliyuncs.com";
public static String ACCESSKEYID = "LTAI9rV1x0TmtGjq";
public static String ACCESSKEYSECRET = "3QCJw4MhlyZC8zQUATKaLxWZpk4bFY";
public static String BUCKETNAME = "songsiraliyun";
public static String KEY = "springbootTest/";

html介面

<form action="/uploadOss" method="post" enctype="multipart/form-data">
    <input type="file" name="uploadFile" value="請選擇檔案"><br/>
    <input type="submit" value="提交">
</form>

圖片接收介面

@PostMapping("/uploadOss")
public String uploadOss(MultipartFile uploadFile, HttpServletRequest request) {
    try{
        String fileName = uploadFile.getOriginalFilename();
        InputStream input = uploadFile.getInputStream();
        //建立OSSClient例項
        OSSClient ossClient = new OSSClient(ENDPOINT, ACCESSKEYID, ACCESSKEYSECRET);
        ossClient.putObject(BUCKETNAME,KEY+fileName,input);
        ossClient.shutdown();
    }catch (IOException e){
        e.printStackTrace();
    }
    return "hello";
}

5、配置CORS

Cross-Orgin Resource Sharing是w3c制定得一種跨域共享技術標準,主要用來解決前端得跨域問題,在javaEE中,最常見得前端跨域請求解決方案是Jsonp,但是Jsonp只支援GET請求,這是一個巨大得缺陷,而CORS可以支援多種HTTP請求方法

流程:

匯入了web-starter依賴即可

配置跨域CORS

@Configuration
@EnableWebMvc
public class MyMvcWebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:63343")
                .allowedMethods("*")
                .allowedHeaders("*")
                .maxAge(1800);
    }
}

四、註冊攔截器

springmvc 提供了AOP風格的攔截器,擁有更加精細化的攔截處理能力,springboot的攔截器更加方便

複習攔截器工作流程:

perhandle-->control-->postcontrol-->aftercontrol

流程:

通過MvcWebConfig去註冊攔截器,需要我們自己重寫一個攔截器類

注意這個類上不需要加註解@Configuration

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor-->preHandle");
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor-->postHandle");

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor-->afterCompletion");

    }
}

再把這個攔截器配置到MvcWebConfig中

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new MyInterceptor())
            .addPathPatterns("/**")
            .excludePathPatterns("/dologin");
}

五、啟動系統任務

有一些特殊的任務再系統啟動的時候執行,例如檔案配置、資料庫初始化等操作,沒有用ssm階段這些問題在Listener中可以解決,springboot對此提供了兩種解決方案:CommandLineRunner、ApplicationRunner,這連個方法基本一致,主要差別主要體現在引數上

1、CommandLineRunner

springboot專案在啟動時會遍歷所有CommandLineRunner的實現類並呼叫其中的run方法,如果整個系統中含有多個CommandLineRunner的實現類,那麼可以使用@Order註解對這些實現類的呼叫順序進行排序

流程:

@Component
@Order(1)
public class MyCommandLineRunner1 implements CommandLineRunner {
    /*run方法呼叫的核心邏輯
    引數是系統啟動時傳入的引數,即入口類中main方法的引數,即springApplication.run方法的引數*/
    @Override
    public void run(String... args) throws Exception {
        System.out.println("runner1>>>"+ Arrays.toString(args));
    }
}
@Component
@Order(2)
public class MyCommandLineRunner2 implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("runner2>>>"+ Arrays.toString(args));
    }
}

然後需要我們配置傳入的引數

先開啟配置專案

再操作

然後啟動專案即可

2、ApplicationRunner

與CommandLineRunner的差別主要體現在run方法的引數上

流程:

@Component
@Order(2)
public class MyApplicationRunner1 implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        /*
        * args是一個ApplicationArgument物件
        *getNonOptionArgs獲取到專案啟動引數
        *getOptionNames獲取到命令列啟動專案的引數map中的name
        * getOptionValues(optionName)獲取到命令列啟動專案的引數map中的optionName對應的value
        * */
        List<String> nonOptionArgs = args.getNonOptionArgs();
        System.out.println("2-nonOptionArgs>>>"+ nonOptionArgs);
        Set<String> optionNames = args.getOptionNames();
        for (String optionName:optionNames){
            System.out.println("2-key:"+optionName+";value:"+args.getOptionValues(optionName));
        }
    }
}
@Component
@Order(1)
public class MyApplicationRunner2 implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        List<String> nonOptionArgs = args.getNonOptionArgs();
        System.out.println("1-nonOptionArgs:"+nonOptionArgs);
        Set<String> optionNames = args.getOptionNames();
        for (String optionName:optionNames){
            System.out.println("1-key:"+optionName+";value:"+args.getOptionValues(optionName));
        }
    }
}

命令列操作

mvn package
java -jar webtest-0.0.1-SNAPSHOT.jar --name=wang --age=99 三國  水滸

結果:

六、整合Servlet、Filter、Listener

一般情況下,spring,springmvc這些框架之後,基本就告別了Servlet,filter,Listener了,但是有時在整合一些第三方框架時,可能還是不得不使用Servlet,比如在整合某報表外掛時時就需要使用Servlet。springboot對於這些web元件也提供了很好的支援

流程:

建一個servlet的包,包下再放這些類,因為需要springboot去掃描

Servlet,Filter,Listener剛好對應三個註解@WebServlet("/my")、@WebFilter("/")、@WebListener

Servlet

@WebServlet("/my")
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("MyServlet>>>"+req.getParameter("name"));
    }
}

Filter

@WebFilter("/")
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("MyFilter>>>init");
    }


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("MyFilter>>>doFilter");
        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {
        System.out.println("MyFilter>>>destroy");
    }
}

Listener

//這裡只舉了ServletRequestListener的例子,還可以是HttpSessionListener、ServletContextListener
@WebListener
public class MyListener implements ServletRequestListener {
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("MyListener>>>requestDestroyed");
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("MyListener>>>requestInitialized");
    }
}

在專案入口上新增@ServletComponentScan註解表示對Servlet的元件進行掃描

@ServletComponentScan
@SpringBootApplication
public class WebtestApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebtestApplication.class, args);
    }

}

測試:

執行,然後瀏覽器輸入:http://localhost:8080/my?name=wang

七、路徑對映

流程:

直接重寫WebMvcConfigurer中的addViewControllers(ViewControllerRegistry registry)方法即可

@Configuration
@EnableWebMvc
public class MyWebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
        registry.addViewController("/index").setViewName("index");
    }
}

效果類似於

@Controller
public class LoginControl {
    
    @GetMapping("/login")
    public String getLogin(){
        return "login";
    }
    
    @GetMapping("/index")
    public String getIndex(){
        return "index";
    }
}

八、配置AOP

複習AOP概念:

AOP,即面向切片程式設計(Aspect-Oriented Programming),有時我們的系統在執行的時候我們發現了bug,或者需要進行其他在系統執行的時候動態新增程式碼的方式,被稱為AOP操作,spring對AOP提供了很好的支援,還有一些常見的概念,

  • JoinPoint(連線點):類裡面可以加強的方法被稱為連線點,例如:想修改哪個方法,該方法就是一個JoinPoint。
  • PointCut(切入點):對連線點進行攔截的定義即為切入點,例如:攔截所有以insert開始的方法,這個定義即為切入點。
  • Advice(通知):攔截到JoinPoint之後的事情就是通知,例如:上文說到了列印日誌就是通知操作,通知分為:
    • 前置通知
    • 後置通知
    • 異常通知
    • 最終通知
    • 環繞通知
  • Aspect(切面):PointCut和Advice的結合
  • Target(目標物件):要增強的類稱為Target

流程:

匯入依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

在service包下建立UserService

@Service
public class UserService {
    
    public String getUserById(Integer id){
        System.out.println("get>>>"+id);
        return "user";
    }
    
    public int deleteUserById(Integer id){
        System.out.println("delete>>>"+id);
        return 1;
    }
}

建立切面:

@Component
@Aspect
public class LogAspect {

    //切入點定義
    @Pointcut("execution(* com.wang.service.*.*(..))")
    public void pc1(){};

    @Before(value = "pc1()")
    public void before(JoinPoint joinPoint){
        //joinPoint物件可以獲得目標方法的方法名、修飾符等資訊
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法開始執行.....");
    }

    @After(value = "pc1()")
    public void after(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法開始執行.....");
    }


    /*表明這是一個最終通知,可以獲取到目標方法的放回值
    * 註解中,returning引數對應的是目標方法的放回值的變數名,對應方法的引數
    * 而在方法中,result可以是任意型別,不同型別代表處理不同類定的放回值
    * Object表明處理任何型別放回值
    * Long表明只處理Long型別放回值
    * */
    @AfterReturning(value = "pc1()",returning = "result")
    public void afterReturn(JoinPoint joinPoint,Object result){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法放回值為:"+result);
    }

    //方法的引數為Exception e表明所有的異常都會進入這個通知
    @AfterThrowing(value = "pc1()",throwing = "e")
    public void afterThrowing(JoinPoint joinPoint,Exception e){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法丟擲異常,異常是:"+e.getMessage());
    }

    /*
    * 環繞通知是功能最強大的一個通知
    * 可以實現前置通知,後置通知,異常通知,返回通知的功能
    * 目標方法進入環繞通知之後,通過過返回proceedingJoinPoint.proceed()來讓方法繼續執行
    * 可以在這裡修改方法的執行引數,返回值,異常等。
    * */
    @Around(value = "pc1()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
       return proceedingJoinPoint.proceed();
    }

}

九、其他

1、自定義歡迎頁

想要靜態的歡迎頁只需要在resource/static下放上index.html

想要動態的歡迎頁只需要在resource/template下建立對應的thymeleaf頁面

2、自定義favicon

使用線上網站:https://jinaconvert.com/cn/convert-to-ico.php或

http://www.favicon-icon-generator.com/favicon對圖片進行轉換

然後把這個檔案改名為favicon.icon,放到resource/static下即可

一時載入不出來可能是因為瀏覽器快取的問題

3、除去某個自動配置類

例如:除去ErrorMvcAutoConfiguration自動配置

//@SpringBootApplication

@SpringBootConfiguration
@EnableAutoConfiguration(exclude = {ErrorMvcAutoConfiguration.class})
@ComponentScan()
public class WebtestApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebtestApplication.class, args);
    }

}