코딩 79 일 / 2022.10.17 / Spring MVC 웹 소켓 프로그램, Spring Boot 환경 설정, Lombok 기능
웹 소켓 기능
- 접속해 있는 유저들끼리 채팅 가능한 기능
- Spirng 외 다른 언어로도 웹 소켓 구현 가능하다
웹 소켓 기능 쓰기 위해 추가해야할 환경설정 파일의 코드
1. pom.xml : 웹 소켓 라이브러리
- spring-websocket
2. servlet.xml : Handler 매핑 잡기
웹 소켓 예제 : 프로젝트 webSock
실습 준비
파일들 살펴보기 : pom.xml
- pom.xml 부분
<!-- WebSocket -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${org.springframework-version}</version>
</dependency>
- 웹소켓 라이브러리가 추가되어야 웹 소켓으로 통신 가능
파일들 살펴보기 : web.xml
- *.do 로 매핑을 잡아뒀다
- 그 외는 기존 내용들과 같다
파일들 살펴보기 : servlet-context.xml
- 다른 내용들이 들어가있다
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<context:component-scan base-package="com.ch.webSock" />
<default-servlet-handler/>
<websocket:handlers>
<websocket:mapping handler="chatHandler" path="chat-ws.do"/>
</websocket:handlers>
<beans:bean id="chatHandler" class="com.ch.webSock.WebChatHandler"/>
</beans:beans>
- base-package 와 View 파일 저장 위치인 ViewResolver 는 기존과 같다
- base-package 로 지정된 패키지 하위에 HomeController.java, WebChatHandler.java 가 있다
- ViewResolver 로 지정된 패키지 하위에 chat.jsp, header.jsp 가 있다
Handler 매핑 잡기
- 어떤 요청으로 들어올때 웹 소켓을 연결시킬지 매핑을 잡아야한다
- 웹 소켓 기능 처리 위해 만든 클래스인 WebChatHandler 가 웹 소켓 연결, 처리 역할을 한다
- 이 클래스는 필요한 클래스를 상속받아 내가 만든 클래스이다
- 이 클래스로 찾아가기 위해 매핑을 잡아준다, 현재는 "chat-ws.do" 로 요청할때만 chatHandler 라는 id 값과 매핑되어 이 클래스가 동작
com.ch.webSock.WebChatHandler 클래스
- WebChatHandler.java
package com.ch.webSock;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
public class WebChatHandler extends TextWebSocketHandler {
Map<String, WebSocketSession> users = new HashMap<String, WebSocketSession>();
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
users.put(session.getId(), session);
}
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
users.remove(session.getId());
}
protected void handleTextMessage(WebSocketSession session,
TextMessage message) throws Exception {
String msg = message.getPayload();
TextMessage tMsg = new TextMessage(msg.substring(4));
Collection<WebSocketSession> list = users.values();
for (WebSocketSession wss : list) {
wss.sendMessage(tMsg);
}
}
}
- 내가 만든 클래스이다
- TextWebSocketHandler 클래스를 상속받아야한다
- afterConnectionEstablished(), afterConnectionClosed(), handleTextMessage() 메소드를 오버라이딩 해야한다
- afterConnectionEstablished() 메소드 : 연결이 된 후 어떤 일을 수행할지 작성
- afterConnectionClosed() 메소드 : 연결이 끊긴 후, 주로 연결을 사겢함
- handleTextMessage() 메소드 : 특정 유저가 전송한 메세지를 다른 유저에게 전송해주는 역할
코드 흐름
- HomeController 에서 요청을 받고, chat.jsp 에서 메세지 관련, 소켓 관련 처리를 한다
파일들 살펴보기 : root-context.xml
- DB연동을 하지 않으므로 root-context.xml 이 비어있다
흐름 설명
- index.jsp 를 실행
- 브라우저를 바꿔서 다시 실행해서 두개의 브라우저를 열기
- 서로 메세지를 주고 받을 수 있음
+ views/chat.jsp 파일에서 80 포트로 맞춰뒀으므로 80포트를 써야함
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<script type="text/javascript">
location.href="chat.do";
</script>
</body>
</html>
- Dispatcher Servlet -> HomeController 클래스
- Controller 클래스 HoemController.java
package com.ch.webSock;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HomeController {
@RequestMapping("chat.do")
public String home() {
return "chat";
}
}
- chat.jsp 로 이동
- 채팅하기 위한 페이지인 chat.jsp 로 이동
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ include file="header.jsp"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
var websock;
$(function() {
$('#message').keypress(function(event) {
var keycode = event.keyCode ? event.keyCode : event.which;
if (keycode == 13)
send();
event.stopPropagation();
});
$('#enterBtn').click(function() {
connect();
});
$('#exitBtn').click(function() {
disconnect();
});
$('#sendBtn').click(function() {
send();
});
});
function send() {
var nickName = $('#nickName').val();
var msg = $('#message').val();
websocket.send('msg:' + nickName + ' => ' + msg);
$('#message').val('');
}
function connect() {
websock = new WebSocket("ws://localhost:80/webSock/chat-ws.do");
websock.onopen = onOpen;
// websock.onclose = onClose;
websock.onmessage = onMessage;
}
function disconnect() {
websock.close();
}
function send() {
var nickname = $('#nickName').val();
var message = $('#message').val();
websock.send('msg:' + nickname + ' : ' + message);
$('#message').val('');
}
function onOpen(event) {
appendMessage("연결되었습니다.");
}
function onClose(event) {
appendMessage("연결이 종료되었니다.");
}
function onMessage(event) {
var data = event.data;
appendMessage(data);
}
function appendMessage(msg) {
$('#chatMessageArea').append(msg + "<br>");
var chatAreaheight = $('#chatArea').height();
var maxscroll = $('#chatMessageArea').height() - chatAreaheight;
$('#chatArea').scrollTop(maxscroll);
}
</script>
</head>
<body>
<div class="container">
별명 : <input type="text" id="nickName">
<input type="button" value="입장" id="enterBtn" class="btn btn-success">
<input type="button" value="퇴장" id="exitBtn" class="btn btn-danger">
<h2 class="text-primary">대화영역</h2>
<input type="text" id="message" required="required">
<input type="button" value="전송" id="sendBtn" class="btn btn-info">
<div id="chatArea">
<div id="chatMessageArea"></div>
</div>
</div>
</body>
</html>
- 가장 위에서 jQuery 함수로 이벤트 처리를 하고 있다
- send(), connect(), disconnect(), send(), onOpen(), onClose(), onMessage(), appendMessage() 메소드들
- 별명을 입력하기 위한 양식 폼이 아래에 있다
+ header.jsp 파일에서 각종 라이브러리를 불러오고 있다
- 별명 입력 양식과 대화영역 입력 양식, 버튼들의 id 값들을 자세히 보기
- id 가 "chatMessageArea" 인 div 영역에 대화내용들이 나올 것
위 코드 나눠서 캡처 설명
- 가장 위에서 jQuery 함수로 이벤트 처리를 하고 있다
- message 는 메세지를 입력하기 위한 양식, 엔터키를 누르면 아래 내용이 실행됨
+ keypress : 키를 눌렀을때 발생하는 이벤트
- keypress 이벤트 발생시 사용자가 누른 키 코드 keycode 를 가져온다, 13 번은 아스키코드로 "ENTER" 키이다.
- 즉 ENTER 키를 눌렀을때 send() 메소드를 호출해서 메소드를 전송시켜라는 의미
- 즉 메세지 입력 후 ENTER 키를 눌러도 send() 에 의해 메세지가 전송되고, "전송" 버튼을 눌러도 send() 에 의해 메세지가 전송된다
- enterBtn 은 "입장" 버튼, exitBtn 은 "퇴장" 버튼, sendBtn 은 "전송" 버튼, 각 버튼을 불러와서 클릭시 아래의 메소드를 실행
- chat.jsp 에서 connect() 메소드 부분
function connect() {
websock = new WebSocket("ws://localhost:80/webSock/chat-ws.do");
websock.onopen = onOpen;
// websock.onclose = onClose;
websock.onmessage = onMessage;
}
- 웹 소켓을 연결하고 있다
- WebSocket 객체를 생성하고 전역변수 websock 로 객체를 받느다
- WebSocket 객체를 생성할때 ip 주소와 포트번호까지 작성, 현재는 localhost 이고 포트번호는 80 포트로 접속
- WebSocket 객체를 생성할때 "chat-ws.do" 로 요청, 이 Handler 매핑은 servlet-context.xml 에서 잡아뒀다
- "chat-ws.do" 로 요청시 servlet-context.xml 에서 매핑이 잡혀서 WebChatHanlder 클래스가 동작한다
+ WebChatHandler 클래스에선 회원의 id 값과 WebSocketSession 세션을 put() 으로 추가해서 맵 users 에 저장함
- 아래에 정의된 onOpen() 메소드를 호출해서 appendMessage() 로 "연결되었습니다" 메세지를 출력
function onOpen(event) {
appendMessage("연결되었습니다.");
}
+ WebChatHandler.java 부분
- chat.jsp 에서 send() 메소드 부분만
function send() {
var nickName = $('#nickName').val();
var msg = $('#message').val();
websocket.send('msg:' + nickName + ' => ' + msg);
$('#message').val('');
}
- 사용자가 별명 입력창에 작성한 닉네임과, 대화영역에서 사용자가 입력한 대화내용을 가져옴
- 구해온 WebSocket 객체 websocket 으로 send() 메소드를 사용
- 그럼 WebChatHanlder.java 에서 handleTextMessage() 메소드가 호출되어, 목록을 가진 사용자들에게 메세지를 전송
- WebChatHanlder.java 에서 handleTextMessage() 부분만
- 메세지를 가져오고, 모든 유저를 가져와서 리스트에 저장한 후, for 문을 통해 모든 유저에게 메세지를 뿌림
- chat.jsp 에서 disconnect() 메소드 부분만
function disconnect() {
websock.close();
}
- websocket.close() 로 연결을 끊는다
- close() 가 실행되면 WebChatHanlder 의 afterConenctionClosed() 가 호출된다
- WebChatHandler.java 에서 afterConnectionClosed() 부분만
- 세션을 지움
- 퇴장 버튼을 눌렀던 유저만 users 에서 빠지는 것
결과
- 글을 입력하고 "전송" 을 누르면 브로드캐스팅 하듯이 접속되어있는 모든 사람에게 메세지를 전송한다
- 유저가 여러명이어도 가능
Spring Boot
Spring Boot 특징
- 독립 실행이 가능한 스프링 애플리케이션 개발 가능(Tomcat, Jetty 내장)
- Tomcat 을 설치하지 않아도 자동으로 Tomcat 이 내장되어있으므로 서비스 가능
- 통합 Starter를 이용하여 프로젝트를 만든다
- 통합 Starter 를 이용하여 Maven/Gradle 로 라이브러리 관리
- 통합 Starter를 통한 자동화된 스프링 설정 제공
- 번거로운 XML 설정을 요구하지 않음, XML 파일들이 많이 빠진다
ex) web.xml, servlet-context.xml, root-context.xml 파일 이 없다
- 새로운 환경설정 파일이 하나 만들어지고, 거기에 직접 필요한 환경을 구축해야함
- Spring Actuator 제공 (애플리케이션의 모니터링과 관리를 위해서 사용)
Spring Boot 라이브러리 관련 환경설정 파일
- 통합 Starter 를 이용하여 Maven/Gradle 로 라이브러리 관리
- Maven 으로 환경설정시 pom.xml 파일이 생성됨, 체크해서 라이브러리 설치 가능
- Gradle 으로 환경설정시 Gradle 환경설정 파일이 생성됨
Spring Boot 환경 구축 방법
1. Eclipse 에 STS 3.x plug-in 추가 또는 STS 를 사용해야한다
- 현재는 STS 3 점대를 설치된 상태이므로 Spring, Spring Boot 프로젝트 모두 생성 가능
+ STS 4 점대는 Spring 프로젝트를 만들 수 있는 메뉴가 없다, Spring Boot 로 넘어가는 추세
2. [File] - New - Project
- Spring Starter Project 선택
- Type 에서 라이브러리 관리 방법 선택, Gradle / Maven 중 현재는 "Maven" 선택
+ Gradle 선택시 Gradle 로 라이브러리 관리하는 패키지 생성
- Packaging 에서는 압축 포맷 선택, War / Jar 중 현재는 "War" 선택
- Java Version 은 현재 사용하는 JAVA 버전인 8 을 선택
- Language 는 JAVA 선택
- Package 가 com.example.demo 로 되어있다, 이게 Spring 의 top-level 패키지와 같은 역할
- 이 패키지 com.example.demo 가 java 폴더 하위에 생성된다
- 이 패키지 하위에 Controller 등의 자바 클래스들이 오게 된다
+ demo 는 현재 프로젝트명
- Maven 을 선택했으면, Maven 환경설정 파일 (pom.xml) 안에 여기서 선택한 의존 라이브러리들이 추가됨
- Gradle 을 선택했으면, Gradle 환경설정 파일안에 여기서 선택한 의존 라이브러리들이 추가됨
- 기본적으로 선택해야할 라이브러리 : Spring Web 을 선택해야 Spring MVC 패턴으로 만들어짐
+ 다른 라이브러리들
- Lombok 라이브러리 : DTO 클래스 안에 정해진 필드의 Getter / Setter 메소드를 어노테이션 기반으로 쓰기 위해서 사용되는 라이브러리, 이걸 쓰면 Getter / Setter 메소드를 쓰지 않아도 된다, 나중에 사용할 라이브러리
- WebSocket 라이브러리 : 체크하면 WebSocket 라이브러리가 추가됨
- NoSql 카테고리 라이브러리들 : 체크하면 Spring Boot - NoSQL 연동
- SQL 카테고리 라이브러리들 : DB 연결시 필요한 라이브러리
- MyBatis Framework 라이브러리 : 체크하면 Spring Boot - MyBatis 연동
- Oracle Driver 라이브러리 : 오라클 연동시 Oracle Driver (오라클용 JDBC 드라이버) 체크
- [boot] 가 붙은 것은 Spring Boot 프로젝트임을 의미
- 프로젝트를 생성하며 체크했던 라이브러리들이 Maven 환경설정 파일인 pom.xml 에 들어간다
- web.xml, servlet-context.xml, root-context.xml 환경설정 파일들이 없다
Spring Boot 프로젝트 실행 방법
- 프로젝트를 오른쪽 마우스 클릭 -> Run As -> Spring Boot App 로 실행해야한다
- 아래와 같이 출력되면 실행 성공
파일들 살펴보기 : pom.xml
- Maven 을 선택했으므로 현재 프로젝트 하위에 Maven 환경설정 파일 pom.xml 이 만들어짐
- pom.xml
<?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.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>demo</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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 프로젝트 생성 시 Spring Web 을 선택했으므로 spring-boot-starter-web, spring-boot-starter-tomcat 라이브러리 등이 들어가있다
Spring 에는 있지만 Spring Boot 에는 없어진 환경설정 파일
- web.xml : webapp 폴더 가 비어있다, 즉 WEB-INF 폴더도 없고, 그 안에 있어야할 web.xml 파일도 없다
- web.xml 에서 불러왔던 spring 환경설정 파일 2개인 servlet-context.xml, root-context.xml 파일도 없다
- Spring Boot 프로젝트에서는 web.xml, servlet-context.xml, root-context.xml 파일이 없다
- Spring 의 6개 환경설정 파일 중에서 pom.xml, Mapper 파일(.xml) 만 Spring Boot 에 있다, Configuration.xml 파일도 필요없다
Spring 에는 없지만 Spring Boot 에서 새로 생긴 환경설정 파일 : application.properties
- application.properties 파일에 직접 환경설정을 해야한다
- 없어진 3개의 파일의 환경설정 내용을 여기에 작성해야함
- 프로젝트 생성 직후 application.properties 에는 아무 내용없다
Spring 에는 없지만 Spring Boot 에서 새로 생긴 폴더 설명
resources폴더 하위 static 폴더
- CSS, JS, 이미지(images) 디자인 관련 파일들을 저장함
- 공유가 되는 폴더, 이 폴더에 저장되면 쉽게 CSS, JS, 이미지들을 불러올 수 있다
- 이 폴더에 있는 파일은 다른 페이지에서 쉽게 불러올 수 있다
+ Python 의 장고 프로젝트가 Spring Boot 프로젝트와 비슷한 구조, 장고에도 static 폴더가 있다
resources 폴더 하위 templates 폴더
- EL, JSTL 대신하는게 타임리프, EL, JSTL 대신 타임리프 지원 태그들을 사용
- 타임리프를 쓸때 여기에 View 파일인 HTML 파일들을 저장해야한다
- Spring Boot 에서는 JSTL 을 사용하든지 타임리프를 사용하든지 선택 가능
- 타임리프 사용시 View 파일을 JSP 가 아닌 HTML 파일을 사용해야함
- 그 HTML 인 VIew 페이지들이 여기 저장되어야함
+ HTML 파일을 쓰므로 EL, JSTL 을 사용 못하게되는 것이다
Controller를 추가해서 Hello World 출력
- src/main/java/com/example/demo/controller – SampleController.java 생성
- index 파일에서 값을 요청했을때 여기서 처리해보자
- top-level 패키지 하위인 com.example.demo 아래에 controller 폴더 생성 후 SampleController.ajva 파일 생성
- 그냥 일반 클래스로 만듬
- SampleController.java
package com.example.demo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SampleController {
@RequestMapping("/")
public String hello() {
return "Hello World~!!";
}
}
+ @RestController = @Controller + @ResponseBody ,
- 현재 프로젝트 Run As -> Run on Server 로 실행 한 후 웹브라우저에 http://localhost:8080 요청한다.
- Hello World ~!! 가 브라우저 나타나면 잘 실행된 것
- web.xml 에서 설정하던 Dispatcher Servlet 매핑을 하지 않았으므로, 그냥 여기 Controller 클래스의 @RequestMapping 만 맞으면 바로 찾아간다
- 현재는 / 이므로 아무거나 요청해도 다받음
- 이후 프로젝트를 Run As - Run on Server 로 실행
- View 로 가서 출력하는 대신 @ResponseBody 에 의해 return 만 하면 바로 브라우저에 출력됨
- @RestController 를 썼으므로 @ResponseBody 에 의해 return 시 요청한 브라우저로 바로 찾아가서 돌려준다, 즉 브라우저에 바로 출력된다
port 번호 설정
- Spring boot 에 내장된 tomcat 은 기본 port 가 8080, 현재 8080 은 오라클에서 쓰고 있는 포트이다
- 포트충돌 가능성, 오류 생긴다면 내장 tomcat port 번호를 80 으로 바꿔줘야한다
- tomcat port 번호를 바꾸기 위해 application.properties 에 server.port = 80 한줄을 추가해준다
- application.properties
- 이후 다시 실행시 실행 됨Spring Boot 예제 : 프로젝트 boot01
실습 준비
- 클라우드의 프로젝트 boot01 을 다운받아 압축 해제 후 import
Spring Boot 프로젝트 import 하는 방법
- Spring 과 같은 방법으로 import 하면 된다
- boot01 프로젝트 import
- 프로젝트 오류 발생, 현재 설정된 JAVA 가 11 버전으로 되어있고, 우리가 설치한 JAVA 는 8 점대 버전이라서 오류 발생
프로젝트의 JAVA 버전 설정 수정 방법
- 현재 프로젝트 오른쪽 버튼 -> Properties
- 설치된 JAVA 버전인 8 점대 (=1.8) 선택 후 적용
- 그럼 JAVA 버전이 8점대 (1.8) 로 변경되고 오류가 사라진다\
흐름 설명
- Run As -> Run on Server 로 프로젝트 boot1 을 실행
- 이전에 Spring 에서 했었던 예제
- 누르는 메뉴에 따라 다른 화면이 나타남
ex) '구구단' 클릭시 랜덤 단 으로 구구단 나타남
파일들 살펴보기 : pom.xml
<?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.2.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>boot01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>boot01</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- jsp 파일을 사용하기 위한 의존 라이브러리-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!-- jstl -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</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-tomcat</artifactId>
<scope>provided</scope>
</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>
- Maven 의 환경설정 파일, Spring Boot 프로젝트 생성시 체크했던 의존 라이브러리만 추가됨
- 원하는 라이브러리를 직접 추가할 수도 있다
- JAVA 는 1.8 버전, Spring Boot 는 2.2.9 버전 사용 중
pom.xml 에 새로 추가된 라이브러리 : JSTL 라이브러리
<!-- jstl -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
- 결과를 JSTL 로 출력하기 위해 JSTL 라이브러리 jstl 을 추가
+ 현재는 JSTL 로 출력하기, 나중에 타임리프 로 출력시엔 타임리프 라이브러리를 추가해아함
파일들 살펴보기 : application.properties
# port
server.port=80
# prefix and suffix
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
- 이 환경설정 파일 application.properties 은 resources 폴더 안에 있다
- 대부분의 환경설정을 이 파일에서 해야한다
- 서버(tomcat) 의 포트번호를 80 으로 설정하고 있다
- Spring 에서 JSTL 로 출력하기 위해 servlet-context.xml 에서 VIewResolver 를 설정헀던 것처럼, Spring Boot 에서는 prefix, suffix 를 이 파일 application.properties 안에 설정해야한다
- prefix 는 View 파일들이 저장될 최상위 폴더, suffix 는 View 파일 확장자 를 설정
- 설정된 prefix 위치에 해당하는 폴더가 webapp 폴더 하위에 생성되어있어야함, 기준은 webapp 폴더
top-level 패키지
- Spring Boot 패키지 생성시 지정한다
- 그 안에서 각각의 기능에 따른 폴더 (controller, service 등) 를 만들고 그 안에 JAVA 파일들을 넣는다, Spring 과 같다
- Sample 로 만들어진 Boot01Application.java, ServletInitializer.java 는 건드리면 서버 구동 오류 생길 수 있다, 건드리지 않기
파일들 살펴보기 : index 파일
- index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script>
$(document).ready(function(){
$("#sel").change(function(){
var sel = $("#sel").val();
location.href=sel;
});
});
</script>
</head>
<body>
선택 :
<select id="sel">
<option value="">메뉴</option>
<option value="hi">hi</option>
<option value="welcome">welcome</option>
<option value="abc">abc</option>
<option value="hello">hello</option>
<option value="gugu">구구단</option>
</select>
</body>
</html>
- index 파일은 webapp 폴더 하위에 있어야한다
- select-option 으로 만들어져있고, select 의 id 값이 "sel" 로 되어있다, 이벤트를 발생시킨 태그인 select 태그를 가져온다
- change 이벤트가 발생, change() 함수안에서 선택된 option 의 value 값을 구해온다, 그 value 값이 요청이름값이 됨
- Dispatcher Servlet Mapping 을 설정했던 web.xml 이 없으므로, Controller 클래스의 @RequestMapping 과 이름값만 같으면 Controller 클래스로 찾아간다
+ Controller 클래스가 여러개 있어도 요청값만 다르게 설정하면 된다
- 현재 프로젝트 선택 - Run As - Run on Server 클릭시 index 파일이 자동 실행된다
파일들 살펴보기 : Controller 클래스들
- HelloController.java
package com.example.demo.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;
@RestController
public class HelloController {
@RequestMapping("/welcome")
public String welcome() {
return "welcome";
}
}
- SampleController.java
package com.example.demo.controller;
import java.io.IOException;
import java.util.Random;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
//@RestController
@Controller
public class SampleController {
@RequestMapping("/hi")
@ResponseBody
public void hello(HttpServletResponse response) throws IOException {
response.getWriter().print("Hello world~!!!");
}
@RequestMapping("/abc")
@ResponseBody
public String abc() {
return "hi abc";
}
@RequestMapping("/hello")
public String hello() {
return "hello";
}
@RequestMapping("/gugu")
public String gugu(Model model) {
Random r = new Random();
int dan = r.nextInt(8) + 2; // 2 ~ 9단
model.addAttribute("dan", dan);
return "gugu";
}
}
@ResponseBody 어노테이션
- @ResponseBody 를 썼으므로 return 을 하면, 요청한 브라우저로 바로 돌려준다
- @ResponseBody 는 요청한 브라우저에 결과를 바로 돌려줘서, 브라우저에 결과가 바로 나타나게된다
- Spring 과 마찬가지로 요청을 Controller 클래스에서 받는다
ex) "hi" 로 요청이 오면 out 객체를 만들고, 브라우저에 "Hello World~!!!" 메세지를 출력, 이렇게 출력된 메세지를 "hi" 를 요청한 곳에 바로 돌려준다
- View 로 가서 출력하는 대신 @ResponseBody 에 의해 return 만 하면 바로 브라우저에 출력됨
- @ResponseBody 에 의해 return 시 요청한 브라우저로 바로 찾아가서 돌려준다, 즉 브라우저에 바로 출력된다
- @ResponseBody 가 붙은 "hi", "abc" 요청은 요청한 브라우저로 결과를 바로 돌려준다
ex) select-option 에서 "hi" 선택시
- 브라우저 창 URL 을 보면 View 로 이동하지 않고, 요청한 곳(브라우저) 인 http://localhost/boot01/hi 로 돌려줬음을 확인 가능
- @ResponseBody 가 붙지 않은 "hello", "gugu" 는 View 페이지로 돌려줘야한다
- 이땐 Spring 과 마찬가지로 prefix, suffix 를 뺀 경로를 적는다
ex) select-option 에서 "hello" 선택시
- Controller 에서 WEB-INF/views/hello.jsp 로 이동하였고 hello.jsp 인 View 파일의 내용이 출력됨
- @ResponseBody 가 붙지 않은 "hello", "gugu" 는 View 페이지로 돌려줘야한다
- 이땐 Spring 과 마찬가지로 prefix, suffix 를 뺀 경로를 적는다
ex) select-option 에서 "gugu" 선택시
- SampleController 의 @ResquestMapping("/gugu") 로 요청받음
- View 인 WEB-INF/views/gugu.jsp 로 이동하고, 값을 가져가기 위해 Model 객체에 저장
+ Random 클래스 nextInt(8) 메소드 호출 시 0 ~ 7 까지 랜덤 숫자를 발생시킴
- 실행할때마다 난수에 의해 랜덤으로 한개의 단이 나옴
- gugu.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>구구단 출력</title>
</head>
<body>
[ ${dan} 단 ] <br><br>
<c:forEach var="i" begin="1" end="9">
${dan} * ${i} = ${dan * i} <br>
</c:forEach>
</body>
</html>
- JSTL forEach 태그를 사용하고 있다, JSTL 을 사용하기 위해 pom.xml 에 JSTL 라이브러리 추가했다
Spring Boot 의 Lombok 기능 예제 1
- 먼저 Lombok 기능을 쓰지 않고 해보고, Lombok 기능을 써서도 프로젝트 만들어보기
Lombok 기능
- DTO 클래스에서 접근 제어자가 private 인 필드들의 Getter / Setter 메소드를 쓰지 않고 어노테이션으로 자동 처리
- @Getter, @Setter 어노테이션으로 각 필드마다 Getter / Setter 메소드를 자동으로 만들어줌
Lomboc 기능 쓰지 않고 만드는 예제 : 프로젝트 boot02
- 프로젝트 boot02 생성
- Spring Web , Lombok , MyBatis Framework, Oracle Driver 를 추가
- 현재는 DB 연동을 하지 않는 예제이므로 Spring Web, Lombok 만 있으면 된다
- 여기서 체크를 하면 pom.xml 에 Lombok 라이브러리가 추가된다, 하지만 바로 사용가능하지 못함, 설치를 해야한다
- Finish를 하면 원격 저장소에서 로컬 저장소로 라이브러리를 다운로드
파일들 살펴보기 : pom.xml (JSP, JSTL 의존 라이브러리 추가 전)
<?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.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>boot02</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>boot02</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-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
- Spring Boot 프로젝트 생성 시 설정했던 내용들이 있다
- Spring Web 에 체크했으므로 spring-boot-starter-web 등 관련 라이브러리들이 추가되어있다
- Oracle Driver 에 체크했으므로 ojdbc8 라이브러리가 추가되어있고, 다운받아져있다
- Lombok 에 체크했으므려 lombok 라이브러리가 추가되어있고, 다운받아져있다
- MyBatis 에 체크했으므로 MyBatis 관련 라이브러리들이 추가되어있고, 다운받아져있다
+ 지금 프로젝트는 lombok 기능 쓰지 않을 것, 그냥 추가시켜둔 것이다
라이브러리 추가
- 현재 pom.xml 에 없는 의존 라이브러리를 dependencies 안에 추가
- JSP 파일을 사용하기 위한 의존 라이브러리와 JSTL 을 사용하기 위한 의존 라이브러리를 추가
환경 설정 파일 application.properties 수정
- 이 파일은 /main/resources 폴더 하위에 있다
- 이 파일 안에 필요한 내용 추가함
- 서버의 포트번호 설정과 prefix, suffix 설정
prefix 로 설정한 패키지(폴더) 만들기
- application.properties 에서 설정한 prefix 폴더들이 반드시 만들어져 있어야한다
- webapp 폴더를 기준으로 prefix 에 설정된 폴더를 생성해야한다
- webapp 하위에 WEB-INF 폴더, WEB-INF 폴더 하위에 views 폴더 생성
DTO 클래스 생성
- /main/java/com/example/demo/model - Member.java 생성
- com.example.demo 패키지 하위에 model 폴더 생성 후 Member.java (DTO) 파일 생성
- Member.java
package com.example.demo.model;
public class Member {
private String id;
private String passwd;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}
- 접근제어자가 private 인 필드들(원래는 테이블 컬럼명과 같은 이름으로 만듬) 생성, Getter / Setter 생성
- 현재는 Lombok 기능을 사용하지 않고 DTO 클래스를 만들었다
+ Lombok 기능 사용시 Getter / Setter 메소드를 직접 사용하지 않음
Controller 클래스 생성
- /main/java/com/example/demo/controller- SampleController.java 생성
- com.example.demo 패키지 하위에 controller폴더 생성 후 SampleController.java (Controller) 파일 생성
- SampleController.java (수정 전)
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
@Controller
public class SampleController {
}
- @Controller 어노테이션을 붙인다
- 나중에 요청 받는 부분을 작성하고, @RequestMapping 어노테이션 사용할 것
index 파일 생성
- webapp 폴더 하위에 index.jsp 파일 생성
- index 파일 자동실행을 위해서는 반드시 webapp 폴더 하위에 있어야한다
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<script>
location.href="main";
</script>
</body>
</html>
- 다시 Controller 클래스로 돌아가서 "main" 요청을 받는 코드를 작성하자
- SampleController.java (수정 후 1)
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class SampleController {
@RequestMapping("main")
public String main() {
return "main";
}
}
- 이때 "main" 은 webapp/WEB-INF/views 하위에 들어가야할 JSP 파일명
- webapp/WEB-INF/views 하위에 main.jsp 파일 생성
- main.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인</title>
</head>
<body>
<form method="post" action="send">
ID : <input type="text" name="id"><br>
Password : <input type="text" name="passwd"><br>
<input type="submit" value="가입">
</form>
</body>
</html>
- form 안에 아이디와 비번 입력 양식을 만들자, 입력 후 "가입" 클릭 시 "send" 로 요청
- 아이디 입력 양식의 name 값은 DTO 프로퍼티명과 같은 "id" 로 설정해야한다, 그래야 @ModelAttribute 로 값 전달 가능
- 비밀번호도 마찬가지
- 다시 이 "send" 요청을 받을 코드를 Controller 클래스에 추가
- SampleController.java (수정 후 2)
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.demo.model.Member;
@Controller
public class SampleController {
@RequestMapping("main")
public String main() {
return "main";
}
@RequestMapping("send")
public String send(Member member, Model model) {
System.out.println("id:" + member.getId());
System.out.println("passwd:" + member.getPasswd());
model.addAttribute("member", member);
return "result";
}
}
- send() 메소드 매개변수에서 앞의 main.jsp 에서 넘어온 아이디, 비밀번호들을 @ModelAttribute (생략) 으로 DTO Member 객체 member 로 바로 받음
+ 값이 잘 넘어왔는지 넘어온 아이디와 비밀번호를 콘솔창에 찍어보고 있다
- Model 객체에 객체 member 를 저장해서 result.jsp 로 이동
- webapp/WEB-INF/views 하위에 result.jsp 파일 생성
- result.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
회원 가입 결과 : <br><br>
id : ${member.id} <br>
pass : ${member.passwd} <br>
</body>
</html>
+ JSTL 과 다르게 EL 은 라이브러리 없이 사용 가능
프로젝트 실행해보기
- DB 연결 안하므로 pom.xml 에서 MyBatis, Oracle 관련 라이브러리를 주석으로 막고 실행해야 오류가 발생하지 않음
- 그 후 프로젝트 실행
- 잘 실행된다
한글값 인코딩
+ Spring 에서는 web.xml 에서 한글값 인코딩 처리 코드를 넣었다
- Spring Boot 에서는 한글값 인코딩은 자동 처리된다
+ 서버에 돌아가는 프로젝트 Add and Remove 하는 방법
- 오류 생길때 사용
Spring Boot 의 Lombok 기능 예제 2
- 앞에서 했던 프로젝트 boot02 예제에 Lombok 기능을 사용해보기
Lombok 라이브러리
- java 라이브러리중 하나
- 멤버 변수에 대한 getter / setter method, toString(), Equals() 등과 생성자 코드를 불필요하게 반복적 만드는 대신 lombok 라이브러리를 사용하면 Annotation(어노테이션) 기반으로 자동으로 메소드를 생성해준다
- lombok 라이브러리를 사용하면 DTO같은 클래스에서 getter 와 setter 메소드를 자동으로 생성해 준다.
Lombok 기능
- DTO 클래스에서 접근 제어자가 private 인 필드들의 Getter / Setter 메소드를 쓰지 않고 어노테이션으로 자동 처리
- @Getter, @Setter 어노테이션으로 각 필드마다 Getter / Setter 메소드를 자동으로 만들어줌
Lombok 라이브러리 설치
- Eclipse 나 STS 에서 Lombok 을 사용하기 위해선 Lombok 라이브러리만으로 동작하지 않고 설치를 해야 동작한다
- pom.xml 에는 아까 추가한 Lombok 라이브러리가 추가되어있다
- pom.xml 에 추가 이후 Lombok 라이브러리를 다운 받아서 Eclipse / STS 콘솔창에서 lombok 파일을 명령 프롬프트 창에서 한번 실행시켜줘야함
- 이렇게 Lombok 라이브러리를 한번은 설치해야 사용 가능
Eclipse / STS 에 Lombok 라이브러리 설정
- Eclipse 나 STS에서 Lombok을 이용하기 위해서 Lombok 을 설치, 설정해야한다
1. Lombok 사이트(http://projectlombok.org/all-versions) 에서 lombok-1.16.18.jar 파일을 다운로드 받는다.
- 클릭해서 다운 받으면 C:\Users\admin(내 계정)\Downloads 폴더 하위에 다운된다
2. 다운로드 받은 lombok-1.16.18.jar 파일 실행
c:\> cd C:\Users\admin\Downloads # 다운로드 받은 위치로 이동
c:\> java -jar lombok.jar # lombok 파일 실행
- 엔터시 새로운 창이 나타난다
- STS 에 lombok 을 설치할 것이므로 Eclipse 는 체크 해제해주고 STS 에 체크해서 Install / Update 누르기
- Install / Update 를 누르면 에 lombok 프로그램이 STS 실행파일 위치에 설치되어 저장된다
- 이 설치 작업은 한번만 수행하면 된다
- 설치가 완료되면 lombok 프로그램이 STS 실행파일 위치에 설치되어 저장된다
- 이후 lombok 라이브러리가 필요한 프로젝트의 pom.xml 에 lombok 라이브러리를 추가하면 lombok 을 사용 가능하다
- 프로젝트 boot02 의 pom.xml 부분
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
Lombok 기능을 써서 DTO 작성하기
- Lombok 기능을 써서 기존 프로젝트 boot02 의 DTO 클래스를 수정하자
- Member.java (DTO, lombok 사용해서 수정)
package com.example.demo.model;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
//@Getter
//@Setter
@Data
public class Member {
private String id;
private String passwd;
// public String getId() {
// return id;
// }
// public void setId(String id) {
// this.id = id;
// }
// public String getPasswd() {
// return passwd;
// }
// public void setPasswd(String passwd) {
// this.passwd = passwd;
// }
}
Lombok 주요 어노테이션
- @Getter 어노테이션은 코드가 컴파일 될 때 속성들에 대해서 Getter 메소드들을 만들어준다
- @Setter 어노테이션은 코드가 컴파일 될 때 속성들에 대해서 Setter 메소드들을 만들어준다
- @ToSTring 어노테이션은 코드가 컴파일 될 때 속성들에 대해서 toString() 메소드를 생성
- @Data 어노테이션은 @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequriedArgsConstructor 를 한꺼번에 설정해주는 어노테이션
+ 위의 어노테이션들은 설치했던 lombok 클래스에서 지원하는 어노테이션들이다
프로젝트 실행
- STS 를 Restart 시키고 다시 실행해야한다
+ STS Restart 방법 : [File] - Restart
- 위 코드처럼 DTO 클래스를 수정 후 프로젝트 boot02 를 실행해보자