2021年2月11日 星期四

[SpringBoot 1.5] 引入靜態頁面相關問題及使用fragment

引入靜態頁面的時候

我們原本可能會使用

<link rel="stylesheet" href="../../static/css/me.css">

這個語法在模板templates(有引入thymeleaf)下, 並不能讀取到









因此~需要改為

<link rel="stylesheet" href="../../static/css/me.css" th:href="@{/css/me.css}">

用th:href="@{}"來引入資源

然後重新build再刷新頁面就沒問題了






---------------------------------------------------------------------------------------

使用fragment

1 首先產生一個html檔案命名為_fragments

2 然後我們將大部分頁面上會重複的程式碼複製到這頁面

ex

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>博客詳情</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
<link rel="stylesheet" href="../static/css/typo.css">
<link rel="stylesheet" href="../static/css/animate.css">
<link rel="stylesheet" href="../static/lib/prism/prism.css">
<link rel="stylesheet" href="../static/lib/tocbot/tocbot.css">
<link rel="stylesheet" href="../static/css/me.css">
</head>


3 將引入方式修改為th:href="@{}"

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
<link rel="stylesheet" href="../static/css/typo.css" th:href="@{css/typo.css}">
<link rel="stylesheet" href="../static/css/animate.css" th:href="@{css/animate.css}">
<link rel="stylesheet" href="../static/lib/prism/prism.css" th:href="@{/lib/prism/prism.css}">
<link rel="stylesheet" href="../static/lib/tocbot/tocbot.css" th:href="@{/lib/tocbot/tocbot.css}">
<link rel="stylesheet" href="../static/css/me.css" th:href="@{/css/me.css}">


4 將重複的片段掛上fragment

<head th:fragment="head(title)">


5 如果遇到每個頁面有共通元素, 但是內容不一樣,

例如 <title>博客詳情</title>

這時候我們就要使用傳參數的方式(注意取參數要用$)

<head th:fragment="head(title)">
<title th:replace="${title}">博客詳情</title>
</head>

這樣來做搭配


6 而在要引入的頁面(比方說index.html)

我們可以這樣做, 這樣就是將title裡面的參數傳遞過去

<head th:replace="_fragments :: head(~{::title})">


完整的程式碼:

_fragments.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head th:fragment="head(title)">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title th:replace="${title}">博客詳情</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
<link rel="stylesheet" href="../static/css/typo.css" th:href="@{css/typo.css}">
<link rel="stylesheet" href="../static/css/animate.css" th:href="@{css/animate.css}">
<link rel="stylesheet" href="../static/lib/prism/prism.css" th:href="@{/lib/prism/prism.css}">
<link rel="stylesheet" href="../static/lib/tocbot/tocbot.css" th:href="@{/lib/tocbot/tocbot.css}">
<link rel="stylesheet" href="../static/css/me.css" th:href="@{/css/me.css}">
</head>
<body>

</body>
</html>


index.html 的header

<head th:replace="_fragments :: head(~{::title})">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>博客</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
<link rel="stylesheet" href="../static/css/me.css" >
</head>


2021年2月2日 星期二

[SpringBoot 1.5] AOP概念及日誌範例

關於橫切面AOP的思維

個人的領會是, 在程式進行的流程為一個直向的流層, 但是, 如果要在其中幾個物件被使用的時候,都插入一段程式碼, 這種的思想就是橫切面設計, 

具體的範例, 是一種鑲嵌, 縫合程式碼的思維, 比方說在每一次從某些controller的時候都警示該功能已經被呼叫, 就是都額外run一段程式碼, 如果說要在每個controller上面加上這段, 就是一個麻煩的事, 如果我們在後面才設計出要被加入的這段程式碼, 那還不如能用縫合的, 將其呼叫的時機設定好, 然後不用每個controller都重新鑲入該程式碼, 那這下面會有些關鍵字的思維可以釐清


1 切面Aspect

切面是一個模塊關注點的模塊化, 他由切入點PointCut和通知Advice組成

@Aspect
@Component
public class LogAspect {
}


2 目標Target

將要被縫合的類別, 比方說, 我們就設計了一個controller, 等下測試縫合程式碼的執行順序

@Controller
public class IndexController {

@GetMapping("/{id}/{name}")
public String index(@PathVariable Integer id, @PathVariable String name){
System.out.println("~~~~~~~~~~~~index~~~~~~~~~~");
return "index";
}
}


3 連接點JoinPoint

被視為切入連接點, 是下列範例中的log()方法


@Before("log()")
public void doBefore(JoinPoint joinPoint){
 logger.info("~~~~~~~Before~~~~~~~");
}


4 切入點PointCut

而切入點則是下面括號中指定execution中的東西,* 是指說返回任何東西

 com.lrm.web.*.*(..)是AOP要切入的對象, web後面*表示任何類,第二個*表示任何方法

而(..)表示任何參數, 當然也可以使用web.IndexController 這樣就只有這個Controller會被

橫切插入程式

@Pointcut("execution(* com.lrm.web.*.*(..))")
public void log(){
}


5 通知Advice

有不同被通知的種類, 比方說Before, After, 

@Before("log()")
public void doBefore(JoinPoint joinPoint){
 logger.info("~~~~~~~Before~~~~~~~");
}

@After("log()")
public void doAfter(){
logger.info("~~~~~~~After~~~~~~~");

}


6 織入Weaving

將切面Aspect與對象Target連接起來, 織入可以在類別加載的的時候完成, 也可以在編譯, 或是運行的時候完成...如果是編譯時完成就是靜態代理, 如果在運行時完成就是動態代理

--------------------------------------------------------------------------------------

以下是一個完整的程式碼

會設計一個內部類別, 然後將其Url, ip, 目標的物件名稱及參數都印出來

package com.lrm.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

@Aspect
@Component
public class LogAspect {

private final Logger logger= LoggerFactory.getLogger(this.getClass());
@Pointcut("execution(* com.lrm.web.*.*(..))")
public void log(){

}

@Before("log()")
public void doBefore(JoinPoint joinPoint){
logger.info("~~~~~~~before~~~~~~~");
ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request=attributes.getRequest();
String url =request.getRequestURL().toString();
String ip=request.getRemoteAddr();
String classMethod=joinPoint.getSignature().getDeclaringTypeName() + "." +joinPoint.getSignature().getName();
Object[] args= joinPoint.getArgs();
RequestLog requestLog= new RequestLog(url,ip,classMethod,args);
logger.info("Request : {}",requestLog);
}

@After("log()")
public void doAfter(){
logger.info("~~~~~~~After~~~~~~~");

}

@AfterReturning(returning = "result" , pointcut = "log()")
public void doAfterReturn(Object result){
logger.info("Result: {}",result);
}

private class RequestLog{
private String url;
private String ip;
private String classMethod;
private Object[] args;

public RequestLog(String url, String ip, String classMethod, Object[] args) {
this.url = url;
this.ip = ip;
this.classMethod = classMethod;
this.args = args;
}

@Override
public String toString() {
return "RequestLog{" +
"url='" + url + '\'' +
", ip='" + ip + '\'' +
", classMethod='" + classMethod + '\'' +
", args=" + Arrays.toString(args) +
'}';
}
}
}

再搭配上前面的IndexController, 開啟然後輸入 http://localhost:8081/1/hat

後面兩個參數是先隨意給的, 以下是結果的截圖




參考網址:

https://zhuanlan.zhihu.com/p/60842627

https://matthung0807.blogspot.com/2018/02/spring-aop.html

2021年1月31日 星期日

[SpringBoot 1.5] 錯誤處理 @ControllerAdvice, @ExceptionHandler, @ReponseStatus

 SpringBoot有預設的錯誤頁面處理

要實現這個我們首先可以在resource>templates>error的資料夾下

加入兩個頁面500.html , 404.html

並在resource>templates下面放index.html

然後造一個controller

@Controller
public class IndexController {

@GetMapping("/")
public String index(){
int i = 9/0;
return "index";
}
}

由於9/0會報錯

畫面就會自動導到命名為500的頁面

將報錯的那一行註解掉

我們在Build中點Recompile

然後刷新頁面

畫面就會來到首頁了

而當我們的URL打一個沒有資源的

ex http://127.0.0.1:8081/123456

他就會導到404的頁面

------------------------------------------------

而~如果我們要使用錯誤頁面, 並用其來顯示錯誤訊息, 可以以下搭配

1 先在error資料夾下產生一個error.xml檔案如下

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<html lang="en">
<head>
<meta charset="UTF-8">
<title>錯誤</title>
</head>
<body>
<h1>錯誤</h1>

<div>
<div th:utext="'&lt;!--'" th:remove="tag"></div>
<div th:utext="'Failed Request URL : ' + ${url}" th:remove="tag"></div>
<div th:utext="'Exception message : ' + ${exception.message}" th:remove="tag"></div>
<ul th:remove="tag">
<li th:each="st : ${exception.stackTrace}" th:remove="tag"><span th:utext="${st}" th:remove="tag"></span></li>
</ul>
<div th:utext="'--&gt;'" th:remove="tag"></div>
</div>

</body>
</html>

2產生一個掛@ControllerAdvice的類別

他主要用法是一個搭配ExceptionHandler攔截Exception的一個功能

下列程式碼, 基本上攔截後, 如果他有掛ResponseStatus類別的話, 他會繼續throw e

而如果沒有掛ResponseStatus就會回傳一個ModelAndView物件並導到error頁面

@ControllerAdvice
public class ControllerExceptionHandler {

private final Logger logger = LoggerFactory.getLogger(this.getClass());
@ExceptionHandler(Exception.class)
public ModelAndView exceptionHandler(HttpServletRequest request, Exception e) throws Exception {
logger.error("Request URL : {}, Exception : {}",request.getRequestURL(),e);

if(AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class)!=null){
throw e;
}

ModelAndView mv= new ModelAndView();
mv.addObject("url",request.getRequestURL());
mv.addObject("exception",e);
mv.setViewName("error/error");
return mv;
}
}


而, 如果我們想要做出對應404的Exception

1 產生一個個性化的Exception類別並掛上ResponseStatus

@ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException{
public NotFoundException() {
}

public NotFoundException(String message) {
super(message);
}

public NotFoundException(String message, Throwable cause) {
super(message, cause);
}
}


2在indexController中加入以下這段

String blog=null;
if(blog==null){
throw new NotFoundException("博客不存在");
}

因為剛剛已經在ControllerExceptionHandler中設置要繼續拋出e了

他就會跳轉到404的頁面



2021年1月30日 星期六

MySQL安裝問題

 一開始在安裝的時候, 如果一直點下一步的話

可能會遇到沒有安裝server的問題

事實上比較主要的部份包括介面, 還有server







所以, 如果裝完之後, 發現少了什麼, 要回來installer檢查一下

2021年1月26日 星期二

20210126月會 工作啟發

因為我們每兩周都有(同仁)分享報告

而主管也有要求我們要回饋分享的報告內容

主管也會統計這些回饋分配

由這樣的回饋我觀察有以下幫助

1 訓練對於簡報的報告品質(因為在回饋的同時也會注意到自己對報告的要求)

2 因為有一次一次的回饋, 同仁可以透過別人的回饋而改善下一次的報告品質

3 幫助同仁有互相幫助和欣賞的機會

4 由於差不多一兩個月就需要分享一次, 需要平時就累積工作上的職能(個人職能提升)

5 對於在場的聽者, 可以聽到不同與職能相關項目, 有助於開拓視野


2021年1月22日 星期五

寫完20篇心情和前輩交流紀錄

從12月至今, 已經完成了20篇文章,
這段時間, 考了兩次多益, 結果同分, 閱讀還略退步了點,
托福講師說, 還是從閱讀中來學單字, 會比單獨背單字會有效一點
這些也是題外話, 這是寫到一半, 大約斷了兩周的原因, 
然後~之後因為會準備考托福的緣故, 幾乎整周的時間表都確定
4天補英文, 3天+1天(不固定)會舉行英文讀書會, 在民權的路易莎

這段期間小有收穫, 幾次下來讀書會總成員已經有5位, 1女4男
有特別挑過是真心想讀書(把愛拉賽的都挑掉), 都可以坐上兩個小時,
能一起讀書, 的確比之前自己拚的感覺好, 一個人的進修是真的很煎熬,
特別取了名字Elite Programmer Taipei, 這是我對於自己的期許, 
我希望自己能成為Programmer中的菁英

來記錄一下想寫網誌的心情,
原本一開始, 是因為在工作上覺得有些東西, 受了前輩很大的幫助,
如果紀錄下來, 我覺得這些對於我在工作上會很好,
後來有一天, 我看到保哥的網誌, 我注意到了他年月出的文章,
剛開始的兩年, 幾乎每個月能出32篇文章,
他是.net領域的神級人物, 如果說, 這是他的變強途徑,
那麼我能不能複製呢?
想到這裡, 我就開始複製同樣的想法, 我也要每個月寫個30篇

關於內容上, 目前是以看Utube教學, 邊做, 邊寫筆記,
這一部分還是比較屬於開專案所要使用到的基本技能,
重點來了~~~~
---------------------------------------------------------------------------
今天讀書會的前輩跟我講說
其實這些spring的各項目IOC,AOP, JPA, SpringCloud
都是一個一個主題, 
一開始可以粗略地去懂怎麼使用就好,
但是更進階的, 可以去了解它的原碼,
懂得其運作的緣由, 進一步就可以對其設定進行操作,
有些JVM的調教, 提升效率的方式, 還是要對其原碼懂得操作,
才能夠設計得到位,
除此之外, 可以在每個新版本推出來的項目上,
比較其和舊版本中改善的項目, 或是新出現的項目,
那可能解決的問題, 就是目前該領域中最Hot最前衛的技術
研究其技術, 就自然會在該領域中作為技術的領導角色

聽完這些話真是價值連城阿,
一直在想, 為什麼公司裡屌不拉基的前輩為什麼可以這麼厲害,
在讀書會中, 聽到讀書會的前輩分享這些他關注的事情,
讓我體會出, 到底要如何才能更往高的階段去
---------------------------------------------------------------------------
目前一切都覺得進行不錯,
有穩定的英文和程式時間, 
擁有英文讀書會和程式讀書會各一, 都為主招集人,
幾乎每天寫技術文章, 有時一天還出兩篇
運動時間兩天, 相親時間一晚 也慢慢排入,
而最後大概就一天晚上玩遊戲,
我想, 成為大神的路慢慢的, 不會是夢想
現在比較主要的, 累積一些後, 跳到更厲害的地方去,
這樣就能跟更厲害的人切磋學習,

透過觀察大神所走過的路徑, 我相信我能更快的完成夢想


[SpringBoot 1.5] SpringBoot 新增範例

我們說到新增功能

他應該要包含兩個步驟

1 點進去進入到一個可以填寫空白資訊的頁面(employee>Get)

2 輸入完新增的資訊然後按下儲存鍵, 完成新增項目的儲存(employee>Post)

從畫面過來, 一開始是點擊這個標籤

<a href="emp" th:href="@{/emp}" class="btn btn-sm btn-success">員工添加</a>

然後會進到controller, 因為裡面會要將所有可以選取的Department顯示出來,

所以要先getDepartments再放到model中

@GetMapping("/emp")
public String toAddPage(Model model){
// 來到添加頁面, 查出所有的部門, 在頁面顯示
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("depts",departments);
return "emp/add";
}

這裡的Department類別

public class Department {

private Integer id;
private String departmentName;
}

這是Dao

@Repository
public class DepartmentDao {

private static Map<Integer, Department> departments = null;

static{
departments = new HashMap<Integer, Department>();

departments.put(101, new Department(101, "D-AA"));
departments.put(102, new Department(102, "D-BB"));
departments.put(103, new Department(103, "D-CC"));
departments.put(104, new Department(104, "D-DD"));
departments.put(105, new Department(105, "D-EE"));
}

public Collection<Department> getDepartments(){
return departments.values();
}

public Department getDepartment(Integer id){
return departments.get(id);
}

}

然後畫面上的form是這樣

<form th:action="@{/emp}" method="post">

<input type="hidden" name="_method" value="put" th:if="${emp != null}">
<
input type="hidden" name="id" th:value="${emp != null} ? ${emp.id}">
<
div class="form-group">
<
label>LastName</label>
<
input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp != null} ? ${emp.lastName}">
</
div>
<
div class="form-group">
<
label>Email</label>
<
input name="email" type="email" class="form-control" placeholder="zhangsan@atguigu.com" th:value="${emp != null} ? ${emp.email}">
</
div>
<
div class="form-group">
<
label>Gender</label><br/>
<
div class="form-check form-check-inline">
<
input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp != null} ? ${emp.gender == 1}">
<
label class="form-check-label"></label>
</
div>
<
div class="form-check form-check-inline">
<
input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp != null} ? ${emp.gender == 0}">
<
label class="form-check-label"></label>
</
div>
</
div>
<
div class="form-group">
<
label>department</label>
<
select class="form-control" name="department.id">
<option th:value="${dept.id}" th:each="dept : ${depts}" th:text="${dept.departmentName}" th:selected="${emp != null} ? ${dept.id == emp.department.id}"></option>
</
select>
</
div>
<
div class="form-group">
<
label>Birth</label>
<
input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp != null} ? ${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm:ss')}">
</
div>
<
button type="submit" class="btn btn-primary" th:text="${emp != null} ? '修改' : '添加'"></button>
</
form>

其中我們可以看到拿出depts塞到dept中, 他會需要將id塞到value中, 要特別注意一下













那麼他送出, 就是一個Employee的model

這樣畫面上是使用name來做對應屬性

而department則要用department.id來對應

這個form發出的是Post請求

@PostMapping("/emp")
public String addEmp(Employee employee){
System.out.println("保存的員工信息: "+employee);
employeeDao.save(employee);
return "redirect:/emps";
}

但是記得進入的時候要重新導向redirect

而以下是dao的 save功能

private static Integer initId = 1006;
public void save(Employee employee){
if(employee.getId() == null){
employee.setId(initId++);
}

employee.setDepartment(departmentDao.getDepartment(employee.getDepartment().getId()));
employees.put(employee.getId(), employee);
}

還有一個日期要做補充

通常預設日期會為20xx/xx/xx 

如果要格式化日期, 是可以在application.properties中做控制的, 如以下

spring.mvc.date-format=yyyy-mm-dd


量身訂做建議(37 歲,6 年 Java 後端工程師)from chatgpt

🎯 量身訂做建議(37 歲,6 年 Java 後端工程師) 1️⃣ 先看你的條件 年齡 37 屬於「中高年資」工程師,履歷上的 深度 / 系統設計能力 會比「語言多寡」更重要。 6 年 Java 後端 代表你在 Spring Boot、資料庫、API 設計...