Chapter#01 : [Spring] IntelliJ를 사용한 Spring Project 생성 및 설정(Maven)
Chapter#02 : [Spring] Spring MVC 패턴 적용하기
Chapter#03 : [Spring] Component-Scan을 사용하는 Annotation 기반 설정
Chapter#04 : [Spring] MyBatis를 사용한 DataBase 연동
※ 해당포스팅의 예제는 [Spring] IntelliJ를 사용한 Spring Project 생성 및 설정 내용부터 이어집니다.
#1. Model1 아키텍처
90년대 말부터 2000년대 초반까지 자바 기반의 웹 어플리케이션 개발에 사용된 아키텍처는 Model1이다.
Model1 아키텍처는 JSP와 JavaBeans만 사용하여 웹을 개발하는 구조로 전체적인 구조와 흐름은 다음과 같다.
Model1 아키텍처를 구성하는 요소 중에서 가장 먼저 확인할 것은 Model 기능의 JavaBeans다.
자바에서 Bean이라는 용어는 객체를 의미하므로 JavaBean이라고 하면 결국
데이터베이스 연동에 사용되는 자바 객체를 의미한다.
Model1 아키텍처에서는 JSP 파일이 가장 중요한 역할을 수행하는데,
이는 JSP가 Controller와 View 기능을 모두 처리하기 때문이다.
Controller은 JSP 파일에 작성된 자바 코드를 의미하는데 JSP에 작성된 모든 자바 코드를 Controller라고 하지는 않는다.
일반적으로 Controller는 사용자의 처리와 관련된 자바 코드를 의미하며 대부분 다음과 같은 코드들로 구성된다.
기능 | 사용 예 |
사용자 입력 정보 추출 | String title = reques.getParameter( "title" ); |
DB 연동 처리 | SampleVO vo = new SampleVO( ); vo.setTitle( title ); SampleDao sampleDAO = new SampleDAOJDBC( ); SampleVO sample = sampleDAO.selectSample( vo ); |
화면 내비게이션 | response.sendRedirect( "selectSampleList.jsp" ); |
그리고 JSP에서는 Model을 사용하여 검색한 데이터를 사용자가 원하는 화면으로 제공하기 위해서
다양한 마크업( markup )을 사용한다.
이때 사용되는 대표적인 마크업이 바로 HTML과 CSS이다.
Model1은 JSP Controller 기능과 View 기능을 모두 처리하기 때문에 역할 구분이 명확하지 않고,
JSP 파일에 대한 디버깅과 유지보수에 많은 어려움이 생길 수밖에 없다.
따라서 Model1 구조는 적은 개발 인력으로 간단한 프로젝트를 수행하는 때라면 사용할 수 있지만,
Enterprise급의 복잡한 시스템에는 부적절한 모델이라 할 수 있다.
그래서 등장한 것이 Model2, 즉 MVC( Model View Controller ) 아키텍처다.
Model2를 이니셜만 이용하여 MVC라고 부르는데,
이는 Model, View, Controller 요소로 기능을 분리하여 개발하기 때문이다.
결국, MVC는 Model1 구조의 단점을 보완하기 위해 만들어진 구조라고 생각할 수 있다.
#2. MVC 아키텍처( Model2 아키텍처 )
Model1 아키텍처는 시스템 규모가 크고, 기능이 복잡한 Enterprise System을 개발한다면 Model1 아키텍처는 적합하지 않다.
Model1 아키텍처가 엔터프라이즈 시스템에 적합하지 않은 가장 큰 이유는
JSP 파일에 Java로직과 화면 디자인이 통합되어 유지보수가 어렵기 때문이다.
자바 개발자 입장에서 JSP 파일에 자바 로직과 화면 디자인이 통합되어 있으면,
우선 수정할 자바 로직을 찾기부터가 쉽지 않다. 그리고 디자이너가 디자인을 변경할 때도 복잡한 코드들 때문에 어려움을 느낄 수 밖에 없다.
이런 Model1 아키텍처의 문제를 해결하기 위해 고안된 웹 개발 모델이 Model2 아키텍처, 즉 MVC 아키텍처다.
Model2 아키텍처에서 가장 중요한 특징은 Controller의 등장이며, 이 Controller는 서블릿 클래스를 중심으로 구현한다.
Model2 아키텍처에서는 기존에 JSP가 담당당했던 Controller 로직이 서블릿으로 구현한 Controller로 이동한 것이다.
따라서 기존에 Model1 아키텍처로 개발한 프로그램에서 JSP 파일에 있는 컨트롤 자바 코드만 Controller로 이동하면 Model2 아키텍처가 된다.
결과적으로 Controller 로직이 사라진 JSP에는 View와 관련된 디자인만 남게 되어 디자이너는 JSP 파일을 관리하고,
자바 개발자는 Controller와 Model만 관리하면 된다.
다음은 MVC 아키텍처에서 각 요소의 기능과 개발 주제를 정리한 표다.
기능 | 구성 요소 | 개발 주체 |
Model | VO, DAO 클래스 | 자바 개발자 |
View | JSP 개발자 | 웹 디자이너 |
Controller | Servlet 클래스 | 자바 개발자 또는 MVC 프레임워크 |
MVC 아키텍처에서 가장 주용한 부분이 Controller다.
그리고 Struts나 Spring( MVC )같은 MVC프레임워크를 사용하는 이유는 바로 이런 효율적인 Controller를 제공해주기 때문이다.
우리가 적용할 스프링 MVC는 DispatcherServlet을 시작으로 다양한 객체들이 상호작용하면서 클라이언트의 요청을 처리한다.
#3. Spring MVC 패턴을 적용한 샘플 구현
1) EL과 JSTL을 이용한 화면 처리
Model1 아키텍처를 Model2, 즉 MVC로 변화했던 결정적인 이유는 JSP 파일에서 자바 코드를 제거하기 위해서이다.
사실 Controller 로직은 사용자 입력 정보 추출, DB연동 처리, 화면 내비게이션 같은 자바 코드만을 의미하기 때문에
현재 JSP파일에 남아있는 자바 코드는 정확하게는 Controller에 해당하는 로직은 아니다.
만일 이런 자바 코드 조차도 JSP 파일에서 제거하고 싶다면, JSP에서 제공하는
EL( Expression Language )과 JSTL( JSP Standard Tag Library )를 이용하면 된다.
지금부터 EL과 JSTL을 이용하여 JSP에서 자바 코드를 제거해보자.
EL( Expression Language )과 JSTL( JSP Standard Tag Library )
EL은 JSP 2.0에서 새로 추가된 스크립트 언어로서, 기존의 표현식( Expression )을 대체하는 표현 언어다. 예를 들어 세션에 저장되어 있는 사용자 이름을 JSP 화면에 출력할 때, 기존에는 <%= session.getAttribute("userName") %> 이렇게 표현했다면 EL을 이용하면 ${userName}으로 표현할 수 있다. 따라서 JSP에서 표현식을 제거할 수 있다.
JSTL이란, JSP 프로그램을 개발하다 보면 스크립트릿( scriptlet )에서 if, for, switch등과 같은 자바 코드를 사용해야 하는 때가 있다. JSTL은 JSP에서 사용해야 하는 이런 자바 코드들을 태그 형태로 사용할 수 있도록 지원한다. 따라서 JSTL을 이용하면 JSP ㅏㅍ일에서 자바코드를 제거할 수 있다.
EL과 JSP에 관하여 더 많은 정보와 학습을 원한다면 tutorialspoint의 JSP Tutorial을 참조하기 바란다.
pom.xml을 수정하여 Maven Repository에 servlet과, JSTL 라이브러리 추가한다.
pom.xml
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
servlet-api, jstl을 추가해주고 Maven을 재실행 해 준다.
이제 상단 메뉴에서 File → Project Stucture( Ctrl + Alt + Shift + S )를 선택하여 준다.
Project Stucture에서 Artifacts에 추가한 jstl 아티팩트를 library에 추가한다.
3) Model 생성
① VO파일 생성
먼저 service라는 Package를 생성한다.
다음으로 VO 클래스 파일을 생성해 주어야 한다.
위에서 생성한 service 패키지를 우클릭하여 ExampleVO 클래스 파일을 생성한다.
ExampleVO.java
package org.spring.example.service;
import java.util.Date;
public class ExampleVO {
private int exampleNumber;
private String exampleId;
private String exampleName;
private String exampleTitle;
private String exampleInfo;
private Date exampleDate;
}
② Getter and Setter 만들기
ExampleVO.java
package org.spring.example.service;
import java.util.Date;
public class ExampleVO {
private int exampleNumber;
private String exampleId;
private String exampleName;
private String exampleTitle;
private String exampleInfo;
private Date exampleDate;
public int getExampleNumber() {
return exampleNumber;
}
public void setExampleNumber(int exampleNumber) {
this.exampleNumber = exampleNumber;
}
public String getExampleId() {
return exampleId;
}
public void setExampleId(String exampleId) {
this.exampleId = exampleId;
}
public String getExampleName() {
return exampleName;
}
public void setExampleName(String exampleName) {
this.exampleName = exampleName;
}
public String getExampleTitle() {
return exampleTitle;
}
public void setExampleTitle(String exampleTitle) {
this.exampleTitle = exampleTitle;
}
public String getExampleInfo() {
return exampleInfo;
}
public void setExampleInfo(String exampleInfo) {
this.exampleInfo = exampleInfo;
}
public Date getExampleDate() {
return exampleDate;
}
public void setExampleDate(Date exampleDate) {
this.exampleDate = exampleDate;
}
}
③ 생성자( Constructor ) 만들기
ExampleVO.java
package org.spring.example.service;
import java.util.Date;
public class ExampleVO {
private int exampleNumber;
private String exampleId;
private String exampleName;
private String exampleTitle;
private String exampleInfo;
private Date exampleDate;
public int getExampleNumber() {
return exampleNumber;
}
public void setExampleNumber(int exampleNumber) {
this.exampleNumber = exampleNumber;
}
public String getExampleId() {
return exampleId;
}
public void setExampleId(String exampleId) {
this.exampleId = exampleId;
}
public String getExampleName() {
return exampleName;
}
public void setExampleName(String exampleName) {
this.exampleName = exampleName;
}
public String getExampleTitle() {
return exampleTitle;
}
public void setExampleTitle(String exampleTitle) {
this.exampleTitle = exampleTitle;
}
public String getExampleInfo() {
return exampleInfo;
}
public void setExampleInfo(String exampleInfo) {
this.exampleInfo = exampleInfo;
}
public Date getExampleDate() {
return exampleDate;
}
public void setExampleDate(Date exampleDate) {
this.exampleDate = exampleDate;
}
public ExampleVO(int exampleNumber, String exampleId, String exampleName, String exampleTitle, String exampleInfo, Date exampleDate) {
this.exampleNumber = exampleNumber;
this.exampleId = exampleId;
this.exampleName = exampleName;
this.exampleTitle = exampleTitle;
this.exampleInfo = exampleInfo;
this.exampleDate = exampleDate;
}
}
2) Controller 수정
이전 포스팅에서 생성한 ExampleController의 내용을 수정한다.
exampleInfo( ), exampleList( )라는 2개의 메소드를 추가할 것이다.
ExampleController.java
package org.spring.example.controller;
import org.spring.example.service.ExampleVO;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
@Controller
public class ExampleController {
@RequestMapping(value = "/example.do", method = RequestMethod.GET)
public String ExampleMain() {
return "example";
}
@RequestMapping(value = "/exampleList.do", method = RequestMethod.GET)
public String exampleList(Model model) throws Exception {
// 포맷터
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
List<ExampleVO> exampleList = new ArrayList<>();
ExampleVO exampleVO = null;
exampleVO = new ExampleVO(
1
, "captain"
, "캡틴 아메리카"
, "First Avenger"
, null
, formatter.parse("2011-07-28")
);
exampleList.add(exampleVO);
exampleVO = new ExampleVO(
2
, "ironman"
, "아이언 맨"
, "I Am Iron Man"
, null
, formatter.parse("2008-04-30")
);
exampleList.add(exampleVO);
exampleVO = new ExampleVO(
3
, "thor"
, "토르"
, "God of Thunder"
, null
, formatter.parse("2011-04-28")
);
exampleList.add(exampleVO);
model.addAttribute("exampleTitle", "영화 타이틀");
model.addAttribute("exampleList", exampleList);
return "exampleList";
}
@RequestMapping(value = "/exampleInfo.do", method = RequestMethod.GET)
public String exampleInfo(HttpServletRequest request, Model model) throws Exception {
if(request.getParameter("number").isEmpty() == false) {
if(request.getParameter("number").equals("1") == true) {
model.addAttribute("exampleId", "captain");
model.addAttribute("exampleName", "캡틴 아메리카");
model.addAttribute("exampleTitle", "First Avenger");
model.addAttribute("exampleInfo",
"캡틴 아메리카의 '캡틴'은 초창기 코믹스에서는 말 그대로 미국의 대장이라는 뉘앙스로 쓰였으나,"
+ "<br/>시간이 흘러 캡틴이라는 말에는 어벤져스의 넘버 원이라는 의미도 포함되었다."
);
model.addAttribute("exampleDate", "2011-07-28");
}
if(request.getParameter("number").equals("2") == true) {
model.addAttribute("exampleId", "ironman");
model.addAttribute("exampleName", "아이언 맨");
model.addAttribute("exampleTitle", "I Am Iron Man");
model.addAttribute("exampleInfo",
"억만장자 천재 발명가인 토니 스타크가 심장에 치명적인 상처를 입은 자신의 목숨을 지키며"
+ "<br/>동시에 세계를 지킬 강화 슈트를 제작하고 과학의 결정체로 만들어진 슈트를 입고"
+ "<br/>아이언맨이 되어 범죄와 싸워나간다."
);
model.addAttribute("exampleDate", "2008-04-30");
}
if(request.getParameter("number").equals("3") == true) {
model.addAttribute("exampleId", "thor");
model.addAttribute("exampleName", "토르");
model.addAttribute("exampleTitle", "God of Thunder");
model.addAttribute("exampleInfo",
"아스가르드의 주신 오딘은 아들 토르에게 인간성과 겸손함을 배우게 하기 위해 기억을 지우고"
+ "<br/>절름발이 의사 도널드 블레이크의 육신에 토르를 내려보냈다."
);
model.addAttribute("exampleDate", "2011-04-28");
}
}
return "exampleInfo";
}
}
생성한 exampleInfo( ), exampleList( ) 메소드의 return 타입을 보면 String을 사용하며
메소드의 마지막 return을 실행할때 View( exampleInfo.jsp, exampleList.jsp ) 이름을 문자열로 리턴한다.
Srping 컨테이너는 리턴된 문자열에 ViewResolver를 적용하여 적절한 JSP를 찾아 실행한다.
그리고 매개변수로 사용한 Model View에 리턴할 정보를 모두 저장한다.
이렇게 Model에 저장된 데이터는 JSP에서 EL을 이용하여 출력할 수 있다.
4) View 생성
① 게시판 - 포스팅 리스트 제작
WEB-INF → views 디렉토리를 선택하고 마우스 우클릭하여 exampleList.jsp 파일을 생성한다.
exampleList.jsp 파일이 생성되면 아래와같이 내용을 작성한다.
exampleList.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<html>
<head>
<title>영화 리스트</title>
</head>
<style>
table, thead, tbody, tfoot { border:1px solid #000000;border-collapse:collapse; }
tfoot { text-align:right; }
th, td { border:1px solid #000000;padding:10px; }
tbody > tr > td { cursor:pointer;cursor:hand; }
tbody > tr > td:first-child { text-align:center; }
button { cursor:pointer;cursor:hand; }
</style>
<body>
<h1>${headTitle}</h1>
<table>
<thead>
<tr>
<th>번호</th>
<th>제목</th>
<th>이름</th>
<th>개봉일</th>
</tr>
</thead>
<tbody>
<c:forEach var="example" items="${exampleList}">
<tr onClick="window.location.href='/SpringExample_war_exploded/exampleInfo.do?number=${example.exampleNumber}'">
<td>${example.exampleNumber}</td>
<td>${example.exampleTitle}</td>
<td>${example.exampleName}(${example.exampleId})</td>
<td><fmt:formatDate value="${example.exampleDate}" pattern="yyyy-MM-dd"/></td>
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>
② 게시판 - 포스팅 상세
WEB-INF → views 디렉토리를 선택하고 마우스 우클릭하여 exampleInfo.jsp 파일을 생성한다.
exampleInfo.jsp 파일이 생성되면 아래와같이 내용을 작성한다.
exampleInfo.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>${exampleTitle}</title>
</head>
<style>
table, thead, tbody { border: 1px solid #000000;border-collapse:collapse; }
th, td { border:1px solid #000000;padding:10px; }
tfoot { text-align:right; }
</style>
<body>
<h3>${exampleName} : ${exampleTitle}</h3>
<table>
<tbody>
</tbody>
<tr>
<th>제목</th>
<td>${exampleTitle}</td>
</tr>
<tr>
<th>이름</th>
<td>${exampleName}(${exampleId})</td>
</tr>
<tr>
<th>개봉일</th>
<td>${exampleDate}</td>
</tr>
<tr>
<th>내용</th>
<td>${exampleInfo}</td>
</tr>
</tbody>
</table>
</body>
</html>
5) 실행결과
Tomcat을 실행하여 작성한 내용을 확인해 보자.
Artifact is deployed successfully : 아티팩트가 성공적으로 배포되었습니다.
문구가 뜨고 브라우저가 실행되면 아래와 같은 결과를 얻을 수 있다.
① 게시판 - 포스팅 리스트 제작 URI : http://localhost:8181/SpringExample_war_exploded/exampleList.do
② 게시판 - 포스팅 상세 URI : http://localhost:8181/SpringExample_war_exploded/exampleInfo.do?number=1
2) 비즈니스 컴포넌트 의존성 주입하기
P.483 어노테이션 기반의 컨트롤러 구현
<ccontext:componet-scan> 추가하기
p.524 리스너를 등록하여 비즈니스 컴포넌트 호출
p.527 컨테이너들의 관계 정리
<!-- Page 525 ContextLoaderListenr -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/context-*.xml</param-value>
</context-param>
<!-- Page 525 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
----
3) 인코딩 설정
p.453 ~ p.454
web.xml 파일에 DispatcherServlet 클래스를 등록했으면 마지막으로 인코딩 설정돠 관련된 필터 클래스를 추가한다.
현재 상태에서 등록이나 수정 기능을 구현하면 한글을 처리하지 못하고 깨진다.
이는 web.xml 파일에 등록된 DispatcherServlet 클래스에 인코딩 관련된 처리가 없기 때문이다.
스프링에서는 인코딩 처리를 위해 CharacterEncodingFilter 클래스를 제공하며,
web.xml 파일에 CharactEncodingFilter를 등록하면 모든 클라이언트의 요청에 대해서 일괄적으로 인코딩을 처리할 수 있다.
web.xml 파일에 다음과 같이 CharacterEncodingFilter 클래스를 <welcome-file-list> 밑에 등록하자.
WEB-INF / web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/config/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<!-- 추가 -->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<filter>
<filter-name>characterEncoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncoding</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
<!-- 추가 -->
</web-app>
#03. 스프링 MVC 적용
샘플 만들기를 가장 먼저 위로 올리기
상세
02) HttpServletRequest에 값 저장하기( p459 )
리스트
JSTL은 추가하고 artifacts lib에 추가해 주어야함
#04. Dispatcher Servlet 설정
p.452
p.524 ~ 526- Listener를 등록하여 비즈니스 컴포너트 호출
p518 이미지
p.524
ContextLoaderListenr 적용
DispatcherServlet이 생성한 스프링 컨테이너는 Controller 객체들만 생성한다.
하지만 Controller 객체들이 생성되기 전에 누군가 먼저 src/main/resources 소스 폴더에 있는
context-XXX.xml 파일들을 읽어 비즈니스 컴포넌트들을 메모리에 생성해야 한다.
이때 사용하는 클래스가 스프링에서 제공하는 ContextLoaderListener다.
ContextLoaderListenr도 DispatcherServlet과 마찬가지로 스프링 컨테이너를 구동하는 기능이 구현되어 있다.
Lisener는 Servlet이나 Filter 클래스와 마찬가지로 web.xml 파일에 등록한다.
<listener> 태그 하위에 <listener-class> 태그를 이용하여 스프링에서 제공하는 ContextLoaderListenr 클래스를 등록하면 된다.
중요한 것은 ContextLoadListenr 클래스는 서블릿 컨테이너가 web.xml 파일을 읽어서 구동될 때,
자동으로 메모리에 생성된다. 즉 ContextLoaderListenr는 클라이언트의 요청이 없어도 컨테이너가 구동될 때 Pre-Loading되는 객체다.
web.xml 파일에 ContextLoaderListenr 클래스를 서블릿 설정위에 등록한다.
contextLoaderListenr가 Pre-Loading되어 스프링 컨테이너를 구동할 때,
src/main/resources 소스 폴더에 작성한 스프링 설정 파일들( 'context'로 시작하는 모든 XML )을 로딩하도록
<listenr> 엘리먼트위에 <context-param>을 추가했다.
이제 web.xml을 파일을 저장하고 서버를 재구동해보자.
그러면 ContextLoaderListenr 객체가 Pre-Loading되어 스프링 컨테이너를 먼저 구동하고 이때,
비즈니스 컴포넌트 객체들이 생성된느 것을 확인 할 수 있다.
'Spring Web > Spring Framework' 카테고리의 다른 글
[Spring] MyBatis를 사용한 DataBase 연동 - MySQL (0) | 2022.12.09 |
---|---|
[Spring] Component-Scan을 사용하는 Annotation 기반 설정 (0) | 2022.12.09 |
[Spring] IntelliJ를 사용한 Spring Project 생성 및 설정(Maven) (2) | 2022.11.29 |
[Spring] Android Studio GIT - Clone 기존 GIT 프로젝트와 연결 (0) | 2022.11.08 |
[Spring] MyBatis를 이용한 Oracle 데이터베이스 CRUD 기능 사용 (2) | 2022.07.06 |