[새싹] 현대IT&E 231115 기록 - JSP / Servlet

최정윤·2023년 11월 15일
0

새싹

목록 보기
22/67
post-custom-banner

Servlet 특징

  • 클라이언트의 요청에 대해 동적으로 작동하는 웹 어플리케이션 컴포넌트html을 사용하여 요청에 응답한다.
  • Java Thread를 이용하여 동작한다.
  • MVC 패턴에서 Controller로 이용된다.
  • HTTP 프로토콜 서비스를 지원하는 javax.servlet.http.HttpServlet 클래스를 상속받는다.
  • UDP보다 처리 속도가 느리다.
  • HTML 변경 시 Servlet을 재컴파일해야 하는 단점이 있다.

웹 애플리케이션 아키텍쳐

1. MVC 패턴

  • Model-View-Controller 패턴을 줄인 것으로 제록스 연구소의 트뤼그베린즈커그가 처음으로 소개한 개념이다.
  • 관심사의 분리를 통한 업무의 분할을 위한 디자인 패턴이다.
  • 원래 데스크톱 애플리케이션에서 사용하기 위해 디자인되었다.
  • 프레젠테이션 레이어를 여러 컴포넌트로 분리해 각 컴포넌트가 특정 기능을 담당한다.
  • 먼저 렌더링된 뷰를 통해 사용자는 컨트롤러 액션을 요청한다.
  • 컨트롤러는 모델을 업데이트 하고, 모델은 뷰에게 렌더링을 요청한다.
  • 다시 사용자에게 응답된다.

MVC 컴포넌트의 기능

컴포넌트기능
컨트롤러폼 액션이나 링크 클릭등을 통하여 사용자가 요청하는 액션을 받아들여서 모델과 뷰를 통해서 요청을 처리한 후 응답한다.
모델모델은 뷰가 렌더링하는데 필요한 데이터이다. 예를 들면 게시판 리스트에 출력하기 위한 게시물 목록이 모델에 해당한다.
실제로 유저에게 보여주기위한 화면이며 모델을 이용하여 렌더링을 한다. JSP, XML, JSON, PDF 등으로 웹 페이지를 표현한다.

가위바위보 프로그램 만들기

실습코드

mvc.fx/dispatcher-servlet.properties

/pilot/form = pilot.controller.FormController
/pilot/process = pilot.controller.ProcessController
/game/ready = game.controller.ReadyController
/game/result = game.controller.ResultController

mvc.fx/dispatcherServlet.java

package mvc.fx;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.servlet.RequestDispatcher;
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 pilot.controller.FormController;
import pilot.controller.ProcessController;

import game.controller.ReadyController;
import game.controller.ResultController;

@WebServlet(
   urlPatterns = {"/pilot/*",
		   		  "/game/*"
   },
   loadOnStartup = 10)
public class DispatcherServlet extends HttpServlet {
   
   private Map<String, AbstractController> controllerMap = new HashMap<>();
   @Override
   public void init() throws ServletException {
      
      Properties prop = new Properties();
      
      try {
         prop.load(new FileInputStream(this.getClass().getResource("dispatcher-servlet.properties").getPath()));
         for(Object oKey : prop.keySet()) {
            String key = ((String)oKey).trim();
            Class<?> className = null;
            try {
               className = Class.forName(prop.getProperty(key).trim());
               controllerMap.put(key, (AbstractController) className.getConstructor().newInstance());
               System.out.println("🧡 loaded : " + className + " 🧡");
            } catch (Exception e) {
               e.printStackTrace();
               System.out.println("💔 error : " + className + " 💔");
            }
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
   
   @Override
   protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      String requestURI = request.getRequestURI();
      System.out.println(requestURI);
      String contextPath = request.getContextPath();
      System.out.println(contextPath);
      String action = requestURI.substring(contextPath.length());
      
//      AbstractController controller = null;
//      ModelAndView mav = null;
      
//      if(action.equals("/pilot/form")) {
//    	  controller = new FormController();
//    	  mav = controller.handleRequestInternal(request, response);
//      } else if (action.equals("/pilot/process")) {
//    	  controller = new ProcessController();
//    	  mav = controller.handleRequestInternal(request, response);
//      }
      
      /*
      String action = requestURI.substring(contextPath.length());
      */
      
      AbstractController controller = controllerMap.get(action);
      ModelAndView mav = controller.handleRequestInternal(request, response);
      
      if (mav != null) {
         
         String viewName = mav.getViewName();
         if (viewName.startsWith("redirect:")) {
            response.sendRedirect(viewName.substring(9));
         } else {
            Map<String, Object> model = mav.getModel();
            for(String key : model.keySet()) {
               request.setAttribute(key, model.get(key));
            }            
            RequestDispatcher dispatcher = request.getRequestDispatcher(viewName);
            dispatcher.forward(request, response);
         }
      } else {
         System.out.println("RequestDispatcher에서 길을 잃었다네~");
      }
   }
}

ReadyController.java

package game.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import mvc.fx.AbstractController;
import mvc.fx.ModelAndView;

public class ReadyController extends AbstractController {

	@Override
	public ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {
		// TODO Auto-generated method stub
		return new ModelAndView("/WEB-INF/game/ready.jsp");
	}

}

ready.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>
<form action="result" method="post">
	<h1>가위, 바위,</h1>
	<select name="you">
		<option value="1">가위</option>
		<option value="2">바위</option>
		<option value="3"></option>
	</select>
	<button type="submit">확인</button>
</form>
</body>
</html>

ResultController.java

package game.controller;

import java.util.Random;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import mvc.fx.AbstractController;
import mvc.fx.ModelAndView;

public class ResultController extends AbstractController {

	@Override
	public ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {
        // Get user's choice
        int you = Integer.parseInt(request.getParameter("you"));

        // Generate a random choice for the computer
        int com = new Random().nextInt(3) + 1;

        // Determine the winner
        String result = determineWinner(you, com);

        // Set the result and choices as attributes
        ModelAndView mav = new ModelAndView();
        mav.setViewName("/WEB-INF/game/result.jsp");
        mav.addObject("you", getChoiceString(you));
        mav.addObject("com", getChoiceString(com));
        mav.addObject("result", result);

        return mav;
	}
	
    private String determineWinner(int you, int com) {
        if (you == com) {
            return "비겼습니다!";
        } else if ((you == 1 && com == 3) || (you == 2 && com == 1) || (you == 3 && com == 2)) {
            return "당신이 이겼습니다!";
        } else {
            return "컴퓨터가 이겼습니다!";
        }
    }
	
    private String getChoiceString(int choice) {
        switch (choice) {
            case 1:
                return "가위";
            case 2:
                return "바위";
            case 3:
                return "보";
            default:
                return "";
        }
    }

}

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>
	<h1>결과</h1>
	<p>당신의 선택: ${you}</p>
	<p>컴퓨터의 선택: ${com}</p>
	<p>${result}</p>
</body>
</html>

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>
</head>
<body>
<h1>mvc</h1>
<a href="pilot/form">form</a><br/>
<a href="game/ready">ready</a><br/>
<%-- <h1>Hello Servlet/JSP</h1>
<%
	out.println("Hello World");
%> --%>
</body>
</html>

게시물 프로그램 만들기

action

  • /article/insert article.controller.Insert 게시물 입력 폼 출력
  • /article/insertAction article.controller.ArticleInsert 게시물 생성. 입력 폼에서 전송된 파라메터를 테이블에 저장.
    .
  • /acticle/list article.controller.ArticleList 게시물 리스트 출력.
  • /article/article?no=10 article.controller.ArticleDetail 게시물 상세보기.
    .
  • /article/update?no=10 article.controller.ArticleUpdate 게시물 수정 폼 출력(입력된 게시물 내용 출력)
  • /article/updateAction?no=10 article.controller.ArticleUpdateAction 게시물 수정. 수정 폼에서 전송된 파라메터를 테이블에 수정.
    .
  • /article/delete?no=10 article.controller.ArticleDelete 게시물 수정 폼 출력(입력된 게시물 내용 출력)
  • /article/deleteAction?no=10 article.controller.ArticleDeleteAction 게시물 삭제. 수정 폼에서 전송된 파라메터를 테이블에 삭제.

log4j.properties

#DEBUG, INFO, WARN, ERROR, FATAL
log4j.rootCategory=DEBUG, console, filelog

log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%-5p,%d{yyyy/MM/dd HH:mm:ss.SSS},%-5X{APP}, %-8X{TSC}, %m %n

log4j.appender.filelog=org.apache.log4j.DailyRollingFileAppender

log4j.appender.filelog.Append=true
log4j.appender.filelog.DatePattern='.'yyyy-MM-dd-HH
log4j.appender.filelog.File=/Users/jeong-yoon/dev/logs/devlog.txt
log4j.appender.filelog.layout=org.apache.log4j.PatternLayout
log4j.appender.filelog.layout.ConversionPattern=%-5p,%d{yyyy/MM/dd HH:mm:ss.SSS},%-5X{APP}, %-8X{TSC}, %m %n

dispatcher-servlet.properties

### Test
/pilot/form = pilot.controller.FormController
/pilot/process = pilot.controller.ProcessController
/game/ready = game.controller.ReadyController
/game/result = game.controller.ResultController

### Article
/article/insert=article.controller.ArticleInsert
/article/insertAction=article.controller.ArticleInsertAction
/article/list=article.controller.ArticleList

DispatcherServlet.java

package mvc.fx;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.servlet.RequestDispatcher;
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 pilot.controller.FormController;
import pilot.controller.ProcessController;

import game.controller.ReadyController;
import game.controller.ResultController;

@WebServlet(
   urlPatterns = {"/pilot/*",
		   		  "/game/*",
		   		  "/article/*"
   },
   loadOnStartup = 10)
public class DispatcherServlet extends HttpServlet {
   
   private Map<String, AbstractController> controllerMap = new HashMap<>();
   @Override
   public void init() throws ServletException {
      
      Properties prop = new Properties();
      
      try {
         prop.load(new FileInputStream(this.getClass().getResource("dispatcher-servlet.properties").getPath()));
         for(Object oKey : prop.keySet()) {
            String key = ((String)oKey).trim();
            Class<?> className = null;
            try {
               className = Class.forName(prop.getProperty(key).trim());
               controllerMap.put(key, (AbstractController) className.getConstructor().newInstance());
               System.out.println("🧡 loaded : " + className + " 🧡");
            } catch (Exception e) {
               e.printStackTrace();
               System.out.println("💔 error : " + className + " 💔");
            }
         }
      } catch (Exception e) {
    	  System.out.println(e.getMessage());
         e.printStackTrace();
      }
   }
   
   @Override
   protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      String requestURI = request.getRequestURI();
      System.out.println(requestURI);
      String contextPath = request.getContextPath();
      System.out.println(contextPath);
      String action = requestURI.substring(contextPath.length());
      
//      AbstractController controller = null;
//      ModelAndView mav = null;
      
//      if(action.equals("/pilot/form")) {
//    	  controller = new FormController();
//    	  mav = controller.handleRequestInternal(request, response);
//      } else if (action.equals("/pilot/process")) {
//    	  controller = new ProcessController();
//    	  mav = controller.handleRequestInternal(request, response);
//      }
      
      /*
      String action = requestURI.substring(contextPath.length());
      */
      
      AbstractController controller = controllerMap.get(action);
      if (controller == null) throw new RuntimeException("controller 찾지 못함");
      ModelAndView mav = controller.handleRequestInternal(request, response);
      if (mav == null) throw new RuntimeException("controller 찾지 못함");
      
      if (mav != null) {
         
         String viewName = mav.getViewName();
         if (viewName.startsWith("redirect:")) {
            response.sendRedirect(viewName.substring(9));
         } else {
            Map<String, Object> model = mav.getModel();
            for(String key : model.keySet()) {
               request.setAttribute(key, model.get(key));
            }            
            RequestDispatcher dispatcher = request.getRequestDispatcher(viewName);
            dispatcher.forward(request, response);
         }
      } else {
         System.out.println("RequestDispatcher에서 길을 잃었다네~");
      }
   }
}

ArticleInsert.java

package article.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import mvc.fx.AbstractController;
import mvc.fx.ModelAndView;

public class ArticleInsert extends AbstractController {

	@Override
	public ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {

		return new ModelAndView("/WEB-INF/article/insert.jsp");
	}

}

insert.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/png" sizes="16x16" href="/webdev/kitty.png">
<title>Insert title here</title>
</head>
<body>
<form name="myform" method="post" action="insertAction">
<table>
   <caption>게시물 글쓰기</caption>
<tr>
   <th>제목</th>
   <td><input type="text" name="title" autofocus required /></td>
</tr>
<tr>
   <th>이름</th>
   <td><input type="text" name="name" required /></td>
</tr>
<tr>
   <th>비밀번호</th>
   <td><input type="password" name="password" required /></td>
</tr>
<tr>
   <th>내용</th>
   <td><textarea name="content" rows="5" cols="40"></textarea></td>
</tr>
<tr>
   <td colspan="2" align="center">
      <input type="submit" value="완료" />
   </td>
</tr>
</table>
</form>
</body>
</html>

ArticleInsertAction.java

package article.controller;

import java.sql.SQLException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import article.dto.ArticleDTO;
import article.service.ArticleService;
import lombok.extern.java.Log;
import lombok.extern.log4j.Log4j;
import mvc.fx.AbstractController;
import mvc.fx.ModelAndView;

//@Log4j
@Log

public class ArticleInsertAction extends AbstractController {
	private static final Logger log = LoggerFactory.getLogger(ArticleService.class);
	
	ArticleService service = new ArticleService();
	
	@Override
	public ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {
		String name = request.getParameter("name");
		String title = request.getParameter("title");
		String password = request.getParameter("password");
		String content = request.getParameter("content");

		ArticleDTO articleDTO = new ArticleDTO();
		articleDTO.setName(name);
		articleDTO.setTitle(title);
		articleDTO.setPassword(password);
		articleDTO.setContent(content);
		
//		log.info(articleDTO);
		try {
			service.insertArticle(articleDTO);
			return new ModelAndView("redirect:list");
		} catch(Exception e){
//			log.info(e.getMessage());
			ModelAndView mav = new ModelAndView("/WEB-INF/article/result.jsp");
			mav.addObject("msg", "게시물 입력 실패\\n입력 폼으로 되돌아갑니다.");
			mav.addObject("url", "javascript:history.back();");
			return mav;
		}
//		System.out.println(articleDTO);
//		return null;
	}
}

ArticleDTO.java

package article.dto;

import java.io.Serializable;
import java.util.Date;

import org.apache.commons.codec.digest.DigestUtils;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

//@Getter
//@Setter
//@ToString
public class ArticleDTO implements Serializable {
	public long getNo() {
		return no;
	}
	public void setNo(long no) {
		this.no = no;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = DigestUtils.sha512Hex(password);
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	public Date getRegdate() {
		return regdate;
	}
	public void setRegdate(Date regdate) {
		this.regdate = regdate;
	}
	public int getReadcount() {
		return readcount;
	}
	public void setReadcount(int readcount) {
		this.readcount = readcount;
	}
	private long no;
	private String title;
	private String name;
	private String password;
	private String content;
	private Date regdate;
	private int readcount;
	
//	public void setPassword(String password) {
//		this.password = DigestUtils.sha512Hex(password);
//	}
	
	
}

ArticleService.java

package article.service;

import java.sql.SQLException;
import java.util.List;

import article.dao.ArticleDAO;
import article.dto.ArticleDTO;
import lombok.extern.java.Log;
import lombok.extern.log4j.Log4j;

@Log4j
public class ArticleService {
	//private static final Logger log = LoggerFactory.getLogger(ArticleService.class);
	
	private static ArticleService service = new ArticleService();
	
	private ArticleDAO dao = ArticleDAO.getInstance();
	private ArticleService() {}
	
	public static ArticleService getInstance() {
		return service;
	}

	public void insertArticle(ArticleDTO articleDTO) throws SQLException {
		try {
			dao.insertArticle(articleDTO);
		} catch (SQLException e) {
//			log.info(e.getMessage());
			throw e;
		}
	}

	public List<ArticleDTO> getArticleList() throws Exception {
		try {
			return dao.getArticleList();
		} catch (Exception e) {
//			log.info(e.getMessage());
			throw e;
		}
	}
}

ArticleDAO.java

package article.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import article.dto.ArticleDTO;
import lombok.extern.java.Log;

@Log
public class ArticleDAO {
	private static ArticleDAO dao = new ArticleDAO();
	
	private ArticleDAO() {
		try {
			Class.forName("net.sf.log4jdbc.DriverSpy");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	public static ArticleDAO getInstance() {
		return dao;
	}

	private Connection getConnection() throws SQLException {
		return DriverManager.getConnection(
				"jdbc:log4jdbc:oracle:thin:@localhost:1521/xepdb1", "ace","ace");
	}
	
	public void insertArticle(ArticleDTO articleDTO) throws SQLException {
		String sql =
			"""
			insert into article(no, title, name, content, password)
			values(seq_article.nextval, ?, ?, ?, ?)
			""";
		try(Connection conn = getConnection();
			PreparedStatement pstmt = conn.prepareStatement(sql)) {
			pstmt.setString(1, articleDTO.getTitle());
			pstmt.setString(2, articleDTO.getName());
			pstmt.setString(3, articleDTO.getContent());
			pstmt.setString(4, articleDTO.getPassword());
			pstmt.executeQuery();
		}
	}

	public List<ArticleDTO> getArticleList() throws SQLException {
		String sql =
			"""
			SELECT
			    no,
			    title,
			    name,
			    regdate,
			    readcount
			FROM
			    article
			ORDER BY no DESC
			""";
		List<ArticleDTO> list = new ArrayList<>();
		try(Connection conn = getConnection();
			PreparedStatement pstmt = conn.prepareStatement(sql);
			ResultSet rs = pstmt.executeQuery()) {
			while(rs.next()) {
				ArticleDTO dto = new ArticleDTO();
				dto.setNo(rs.getLong("no"));
				dto.setTitle(rs.getString("title"));
				dto.setName(rs.getString("name"));
				dto.setRegdate(rs.getDate("regdate"));
				dto.setReadcount(rs.getInt("readcount"));
				list.add(dto);
			}
			return list;
		}
	}
}

ArticleList.java

package article.controller;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import article.dto.ArticleDTO;
import article.service.ArticleService;
import lombok.extern.log4j.Log4j;
import mvc.fx.AbstractController;
import mvc.fx.ModelAndView;

@Log4j
public class ArticleList extends AbstractController {
	
	ArticleService service = ArticleService.getInstance();
	
	@Override
	public ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {
		
		try {
			List<ArticleDTO> list = service.getArticleList();
			return new ModelAndView("/WEB-INF/article/list.jsp", "list", list);
			
		} catch (Exception e) {
			ModelAndView mav = new ModelAndView("/WEB-INF/article/result.jsp");
			mav.addObject("msg", "게시물 리스트 출력 실패");
			mav.addObject("url", "../");
			return mav;
		}
	}
}

list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<!-- 구글 폰트 -->
<link href="https://fonts.googleapis.com/css?family=Noto+Sans+KR" rel="stylesheet">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="../css/site.css">

<link rel="icon" type="image/png" sizes="16x16" href="/webdev/kitty.png">
<title>Insert title here</title>
</head>
<body>
<br/>
<div class="container">
   <div class="jumbotron"><h1>게시물 리스트</h1></div>
   
   <div style="text-align: right">1/1000</div>
   <table border="1" class="table">
   <thead class="thead-dark">
   <tr>
      <th scope="col">번호</th>
      <th scope="col">제목</th>
      <th scope="col">작성자</th>
      <th scope="col">작성일</th>
      <th scope="col">조회수</th>
   </tr>
   </thead>
   <c:forEach items="${list}" var="article">
   <tr>
      <th scope="row">${article.no}</th>
      <td>${article.title}</td>
      <td>${article.name}</td>
      <td>${article.regdate}</td>
      <td>${article.readcount}</td>
   </tr>
   </c:forEach>
   </table>
   <br/>
   <button type="button" class="btn btn-primary" onclick="javascript:location.href='insert'">글쓰기</button>
   
</div>
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

</body>
</html>

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>
</head>
<body>
<h1>mvc</h1>
<a href="pilot/form">form</a><br/>
<a href="game/ready">ready</a><br/>
<a href="article/insert">insert</a><br/>
<%-- <h1>Hello Servlet/JSP</h1>
<%
	out.println("Hello World");
%> --%>
</body>
</html>
profile
개발 기록장
post-custom-banner

1개의 댓글

comment-user-thumbnail
2023년 11월 15일

즐겁게 읽었습니다. 유용한 정보 감사합니다.

답글 달기