How to make Homepage for login,
with only Servlet.
안녕하세요. 저번에 서블릿과 JSP그리고 JSTL태그에 대한 전반적인 내용을 간단하게 다뤄봤습니다.
이번에는 좀 더 실질적으로 어떻게 웹페이지를 구현하는지 (Servlet으로) 다뤄보고자 합니다.
1. 메인 홈페이지 구현
package kr.co.woojin;
import java.io.IOException;
import java.io.PrintWriter;
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 javax.servlet.http.HttpSession;
@WebServlet("/Index")
public class Index extends HttpServlet {
private static final long serialVersionUID = 1L;
public Index() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
response.setContentType("text/html; charset=UTF-8" );
PrintWriter pw = response.getWriter();
pw.println("<!DOCTYPE html");
pw.println("<html>");
pw.println("<head>");
pw.println("<script>");
pw.println("function confirmm(){");
pw.println("if(confirm('정말 로그아웃 하시겠습니까?')){");
pw.println("return true;");
pw.println("}return false;}");
pw.println("</script>");
pw.println("<title>servelte HOMEPAGE</title>");
pw.println("<style>");
pw.println("h1 {font-weight:200; width:650px; margin:0 auto;}");
pw.println("div {text-align:center;}");
pw.println("a {font-size:40px; text-decoration:none;}");
pw.println("a:hover {color:green}");
pw.println("</style>");
pw.println("</head>");
pw.println("<body>");
pw.println("<h1>연우진이 만든 서블릿 웹페이지 - 로그인 구현해보기</h1>");
pw.println("<div>");
HttpSession session = request.getSession();
String username = (String)session.getAttribute("USERNAME");
String userid = (String)session.getAttribute("ID");
if(username==null || username=="") {
pw.println("<a href='"+request.getContextPath()+"/login'>LOGIN</a>");
}else {
pw.println(username+"님 안녕하세요 :) <br>");
pw.println(userid+" ");
pw.println("<a href='"+request.getContextPath()+"/logout' onclick='return confirmm();'>Logout</a>");
}
pw.println("</div>");
pw.println("</body>");
pw.println("</html>");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
서블릿은 간단하게 말하면 JAVA코드안에 HTML,CSS,JavaScript를 쓰는것이라고 했습니다.
그래서 저번에 다뤘던 JSP와 정반대로, 이번에는 프론트영역의 언어들을 특정 객체를 생성해 메소드를 호출하여 그안에 HTML코드와 CSS,JavaScript를 쓰는것을 볼 수 있습니다.
여기서 생성한 객체는 ⇒ PrintWrite클래스
io클래스에 상속되있는 클래스로 입출력을 담당하고 있습니다.
자주 사용되는 메서드로는
print : boolean, int, char, float, double 등등의 데이터형을 String으로 변환해서 쓰지 않고 직접 입력해줍니다.
(메모장에 int형 변수값을 변수를 '숫자'의 String으로 변환하여 넣는것과는 다릅니다.)
println : print할 데이터 뒤에 /r/n을 추가하여, 데이터 와 함께 개행을 출력합니다.
PrintWriter pw = response.getWriter();
pw.println("<!DOCTYPE html");
pw.println("<html>");
pw.println("<head>");
pw.println("<script>");
pw.println("function confirmm(){");
pw.println("if(confirm('정말 로그아웃 하시겠습니까?')){");
pw.println("return true;");
pw.println("}return false;}");
pw.println("</script>");
이렇게 프론트 언어를 일일히 다 써줘야하는 번거로움이 있습니다…..
하지만 번거로워 보여도 장점은 존재합니다.
바로 비지니스 로직에 초점을 두고 개발을 할 수 있다는겁니다.
저희가 기존에 JSP를 통해서 프론트영역에 자바코드를 쓸때는 스크립트 릿 영역을 생성하고
그안에 자바코드를 썼는데, 이렇게 되면 실질적인 기능을 쓰는 코드 영역을 계속해서 생성해줘야하고,
프론트에 맞게 릿 영역을 열었다 닫았다 , 하는 경우가 빈번하게 생깁니다.
그렇게 되면 실제로 백엔드 개발자로써는 구현해야될 비지니스 로직에 온 신경을 다 쏟아서 하기에는,
프론트 영역이랑 백엔드 영역을 계속해서 구별해가면서 해야 하기에 번거로움이 생각보다 있습니다.
비지니스 로직?
비즈니스 로직 : (Business logic)은 컴퓨터 프로그램에서 실세계의 규칙에 따라 데이터를 생성·표시·저장·변경하는 부분을 일컫는다. 이 용어는 특히 데이터베이스, 표시장치 등 프로그램의 다른 부분과 대조되는 개념으로 쓰인다.
<aside> 💡 이제 좀 이해가 가죠?? 하지만 그렇다 치더라도 무언가 복잡하고 대단한 로직을 구성할게 아니라면은, 여전히 JSP가 편한건 사실입니다. 개인적인 생각이지만 풀스택이 목표라면 JSP를 활용하여 웹페이지를 구현하지 않을까 싶습니다.
</aside>
1-2(Session 생성 및 저장, 세션값에 따른 로그인 화면 변경)
HttpSession session = request.getSession();
String username = (String)session.getAttribute("USERNAME");
String userid = (String)session.getAttribute("ID");
if(username==null || username=="") {
pw.println("<a href='"+request.getContextPath()+"/login'>LOGIN</a>");
}else {
pw.println(username+"님 안녕하세요 :) <br>");
pw.println(userid+" ");
pw.println("<a href='"+request.getContextPath()+"/logout' onclick='return confirmm();'>Logout</a>");
}
pw.println("</div>");
pw.println("</body>");
pw.println("</html>");
if문을 활용하여, 만약에 세션에서 get한 값이 비어있거나, 공백이라면 로그인창을 띄워주게 했고,
만약에 세션으로 가져온 username에 값이 있다면, 데이터베이스에서 where조건문 쿼리에 일치한 데이터가 있었다는 의미이기에, 해당 클라이언트는 로그인 정보가 일치하여 세션에 데이터가 저장되 있는 상태이니, 로그인 상태와 로그아웃버튼을 만들어 주는겁니다.
(확실히 비지니스 로직을 설계할때는 조금 더 코드가 간편하죠?)
2. 로그인 페이지 구현
package kr.co.woojin;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
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 javax.servlet.http.HttpSession;
/**
* Servlet implementation class Login
*/
@WebServlet("/Login")
public class Login extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public Login() {
super();
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html; charset=UTF-8");
PrintWriter pt = response.getWriter();
pt.println("<!DOCTYPE html>");
pt.println("<html>");
pt.println("<head>");
pt.println("<title>LOGIN</title>");
pt.println("<style>");
pt.println(".main {color:green}");
pt.println("div {text-align:center;}");
pt.println("div>input {height:20px; line-height:20px; margin-top:10px;}");
pt.println(".submit{margin-right:60px;}");
pt.println(".pw{margin-right:8px;}");
pt.println("</style>");
pt.println("</head>");
pt.println("<body>");
pt.println("<div class='main'>CSS참 X같네요 ^^!</div>");
pt.println("<div>");
pt.println("<form method='post' action='"+request.getContextPath()+"/login'>");
pt.println("ID : <input type='text' name='id'/><br>");
pt.println("PW : <input type='password' class='pw' name='pw'/><br>");
pt.println("<input type='submit' class='submit' value='LOGIN'/>");
pt.println("</form>");
pt.println("</div>");
pt.println("</body>");
pt.println("</html>");
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
String id = request.getParameter("id");
String pw = request.getParameter("pw");
Connection conn=null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe","scott","tiger");
String sql="SELECT username, userid FROM member WHERE userid=? AND userpwd=?";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, id);
pstmt.setString(2, pw);
rs = pstmt.executeQuery();
response.setContentType("text/html; charset=UTF-8");
PrintWriter pt = response.getWriter();
if(rs.next()) {
HttpSession session = request.getSession();
session.setAttribute("USERNAME", rs.getString(1));
session.setAttribute("ID", rs.getString(2));
pt.println("<script>");
pt.println("alert('Login Sucessed!');");
pt.println("location.href='"+request.getContextPath()+"/home';");
pt.println("</script>");
}else {
pt.println("<script>");
pt.println("alert('Login Denied.');");
pt.println("history.back();");
pt.println("</script>");
}
}catch(Exception e) {
e.printStackTrace();
}finally {
try {
if(rs!=null) rs.close();
if(pstmt!=null) pstmt.close();
if(conn!=null) conn.close();
}catch(Exception e) {
e.printStackTrace();
}
}
}
}
코드가 너무 길죠? 이유는 바로 프론트 영역의 코드를 작성하는 영역이 동일하기에 그렇습니다.
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html; charset=UTF-8");
PrintWriter pt = response.getWriter();
pt.println("<!DOCTYPE html>");
pt.println("<html>");
pt.println("<head>");
pt.println("<title>LOGIN</title>");
pt.println("<style>");
pt.println(".main {color:green}");
pt.println("div {text-align:center;}");
pt.println("div>input {height:20px; line-height:20px; margin-top:10px;}");
pt.println(".submit{margin-right:60px;}");
pt.println(".pw{margin-right:8px;}");
pt.println("</style>");
pt.println("</head>");
pt.println("<body>");
pt.println("<div class='main'>CSS참 X같네요 ^^!</div>");
pt.println("<div>");
pt.println("<form method='post' action='"+request.getContextPath()+"/login'>");
pt.println("ID : <input type='text' name='id'/><br>");
pt.println("PW : <input type='password' class='pw' name='pw'/><br>");
pt.println("<input type='submit' class='submit' value='LOGIN'/>");
pt.println("</form>");
pt.println("</div>");
pt.println("</body>");
pt.println("</html>");
}
여기에 있는 모든 코드가 프론트에 해당되는 언어들이고, 앞서 말했듯이 PrintWriter의 객체를 생성하여,
getWriter라는 메소드를 requset하여 객체에 저장을 해주고, 메소드를 호출하여 사용했습니다.
2-1 DB와 연결 및 유효성 검사
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
String id = request.getParameter("id");
String pw = request.getParameter("pw");
Connection conn=null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe","scott","tiger");
String sql="SELECT username, userid FROM member WHERE userid=? AND userpwd=?";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, id);
pstmt.setString(2, pw);
rs = pstmt.executeQuery();
response.setContentType("text/html; charset=UTF-8");
PrintWriter pt = response.getWriter();
if(rs.next()) {
HttpSession session = request.getSession();
session.setAttribute("USERNAME", rs.getString(1));
session.setAttribute("ID", rs.getString(2));
pt.println("<script>");
pt.println("alert('Login Sucessed!');");
pt.println("location.href='"+request.getContextPath()+"/home';");
pt.println("</script>");
}else {
pt.println("<script>");
pt.println("alert('Login Denied.');");
pt.println("history.back();");
pt.println("</script>");
}
}catch(Exception e) {
e.printStackTrace();
}finally {
try {
if(rs!=null) rs.close();
if(pstmt!=null) pstmt.close();
if(conn!=null) conn.close();
}catch(Exception e) {
e.printStackTrace();
}
}
}
}
목차를 작성하여 조금 더 쉽게 이해하도록 해보겠습니다 (아무래도 코드가 길다보니 읽기 싫을꺼 같아서)
(JSP에서 필요한 데이터를 모두 VO를 생성하여 담고, 각각 필요한 기능에 따라 DAO를 만들어서 DB와 접속을 하고 저장하고 삭제하고 추가하는 메소드를 편하게 사용할 수 있게 만들었었죠? (캡슐화, 코드중복성 없애기, 유지보수 측면 향상)
서블릿 또한 그렇게 만들 수 있습니다. 하지만 지금은 단순히 로그인과 로그아웃을 구현하는거기에, 하나의 DAO, VO 만 있으면 되기에 굳이 클래스를 따로 생성하지 않고 진행했습니다.)
- 앞에 영역에서 post방식으로 submit을 해줬으니 서블릿코드가 이를 인지하고 doPost영역으로 가져옵니다. 그래서 doPost메소드 안에 코드를 작성함으로 requset하여 form태그와 함께 전송된 데이터를 받아옵니다. 받아온 데이터는 변수에 저장해둡니다 ⇒ 추후에 계속 사용하게 될 수 있으니 미리 코드의 중복성을 없앤다. 라고 생각하면 좋을거 같습니다.
protected void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
// TODO Auto-generated method stub
String id = request.getParameter("id");
String pw = request.getParameter("pw");
- DB에 접속할때 필요한 객체들을 생성합니다. (PreaparedStatement, Connection, ResultSet,,)
Connection conn=null;
PreparedStatement pstmt = null;
ResultSet rs = null;
- DB연결을 위해 드라이브를 로딩하고 PreaparedStatement객체에 쿼리문을 담아서 Connection 객체를 통해 연결을 합니다.
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe","scott","tiger");
String sql="SELECT username, userid FROM member WHERE userid=? AND userpwd=?";
pstmt = conn.prepareStatement(sql);
- 쿼리문에 들어갈 데이터를 set해줍니다. (? 순서에 따라 1,2,3,,,,,으로 지정해주면 됩니다)
pstmt.setString(1, id);
pstmt.setString(2, pw);
- 실행한 쿼리문은 SELECT문으로 해당 쿼리문 실행시 선택한 데이터를 받아서 사용할 수 있어야 합니다. 그렇기에 ResultSet이 필요한것이고, executeUpdate가 아닌 executeQuery메소드를 사용했습니다. (해당 데이터의 인코딩 타입을 설정해줘야 합니다.)
rs = pstmt.executeQuery();
response.setContentType("text/html; charset=UTF-8"); // 인코딩
PrintWriter pt = response.getWriter();
- 그렇게 실행한 쿼리문의 결과 값이 ResultSet객체에 담기게 되고 객체에 데이터가 있다면, 아이디와 비밀번호가 일치하냐는 쿼리문 where조건절에 set한 데이터가(클라이언트가 입력한 아이디, 비밀번호) 데이터베이스에 있는 정보와 일치한다는거기 때문에, next메소드는 true값을 리턴합니다.
if(rs.next()) {
HttpSession session = request.getSession();
session.setAttribute("USERNAME", rs.getString(1));
session.setAttribute("ID", rs.getString(2));
pt.println("<script>");
pt.println("alert('Login Sucessed!');");
pt.println("location.href='"+request.getContextPath()+"/home';");
pt.println("</script>");
- 로그인 정보는 클라이언트의 세션에 저장하여, 사용자가 브라우저를 종료하기 전까지 세션값이 소멸되지 않고 존재하여, 웹페이지를 끄더라도 재가 설정한 시간만큼( DEFAULT 30min) 존재하게 만들어 편의성을 높혀줍니다.
HttpSession session = request.getSession();
session.setAttribute("USERNAME", rs.getString(1));
session.setAttribute("ID", rs.getString(2));
- 현재 rs라는 객체에 쿼리문 실행 결과값들이 순차적으로 담겨 있기에 get(변수타입) 을 통하여 데이터를 세션에 setAttribute 해줍니다.
session.setAttribute("ID", rs.getString(2));
- PrintWriter객체를 통해서 프론트 코드를 작성하여 사용자에게 로그인 실패, 성공 여부를 보여주는 자바스크립트 언어를 작성합니다.
pt.println("<script>");
pt.println("alert('Login Sucessed!');");
pt.println("location.href='"+request.getContextPath()+"/home';");
pt.println("</script>");
}else {
pt.println("<script>");
pt.println("alert('Login Denied.');");
pt.println("history.back();");
pt.println("</script>");
}
- 로그인 성공시 메인페이지로 가게 하고 실패시, 로그인 페이지에 머물게 합니다.
3. 로그아웃 구현저희는 해당 클라이언트의 정보들을 세션에 저장을 해뒀기에 메인 홈페이지도 계속 로그인이 되 있는
로그아웃 페이지가 실행이 되면, 현재 클라이언트의 session을 소멸시킵니다.그상태에서 로그아웃이 되면 다시 홈페이지로 가야겠죠? sendRedirect를 통해 메인페이지로 보내줍니다.package kr.co.woojin; import java.io.IOException; 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 javax.servlet.http.HttpSession; /** * Servlet implementation class Logout */ @WebServlet("/Logout") public class Logout extends HttpServlet { private static final long serialVersionUID = 1L; public Logout() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session = request.getSession(); session.invalidate(); response.sendRedirect(request.getContextPath()+"/home"); } }
하지만 여기서 이상한 점이 있다는걸 눈치를 못채셨나요?아니 그러면 어떻게 저 주소를 찍어서 메인 페이지로 보낸다는거지??? 404가 뜨질 않나요???눈치를 못채셨다고요?? 한번 보여드릴게요 🙂해당 서버페이지의 파일명은 Index, Login, Logout 입니다.http://localhost:9090/WoojinServlet/Index(물론 webapp 파일 안에 있다는 가정하에 입니다 ^^ 지금은 애초에 파일이 자바소스에 들어있기에 경로는 당연히 다릅니다.)http://localhost:9090/WoojinServlet/home그쵸?? 이게 바로 서블릿 태그에 있는 mapping이라는 기능입니다.
위와 같이 일단 파일은 web.xml를 통하여 설정합니다.Welcome to Tomcat Welcome to Tomcat home kr.co.woojin.Index home /home Login kr.co.woojin.Login Login /login logout kr.co.woojin.Logout logout /logout
서블릿은 “서블릿의 이름”, “사용하는 클래스”, “공개 주소"와 같은 정보를 여기에 기술해야 합니다.<servlet> 태그<servlet> <servlet-name>home</servlet-name> <servlet-class>kr.co.woojin.Index</servlet-class> </servlet> <servlet-mapping> <servlet-name>home</servlet-name> <url-pattern>/home</url-pattern> </servlet-mapping>
- <servlet-name> 서블릿의 이름을 등록한다.
- <servlet-class> 사용하는 서블릿 클래스를 작성한다.
- <servlet-name> 서블릿 이름을 지정한다.
- <url-pattern> 공개하는 주소를 설명한다.
이렇게 태그의 기능을 알고나면 다음과 같은 코드에 흐름이 보입니다.
이렇게 보니 간단하죠? 위에 코드들이 한쌍을 이루어서 작동하는것입니다.보안때문이겠죠???서블릿 태그입니다!</aside><servlet-mapping> <servlet-name>home</servlet-name> // 위에 작성한 서블릿태그의 name과 일치하면 <url-pattern>/home</url-pattern> // 아래 기술한 /home이라는 url을 입력시 위에 설정한 페이지로 이동합니다. </servlet-mapping>
오늘은 이렇게 간단하게 로그인, 로그아웃을 구현한 페이지를 서블릿으로 작성하여 구동시켜봤습니다 🙂- 메인 페이지
- 로그인 페이지
- 잘못된 아이디, 비밀번호 입력시.
- 로그인 성공시
- 로그인 완료시 이동하는 메인페이지
- (로그인이 되있냐 안되있냐에 따라 표기되는 웹페이지가 다름)
- 로그아웃 클릭시 한번 더 물어보기확인 : 세션을 소멸시키게 된 상태에서 메인 페이지로 이동.
- 취소 : return false 현재 페이지에 머물기.
- 잘 작동하네요.jpeg
- 왜냐면 오타로 인한 디버깅이 대부분인데, 이러한 경우 너무 찾기가 힘들었습니다 😟
- 그리고 DB와 연동하여 구현하는것은 어렵지 않았습니다만, 실제로 만들어보면서 느끼는것은 역시
- 일단 서블릿으로 작성하면서 제일 힘들었던것은
- 마지막으로 느낀점..
- 너무 간단해서 보여드릴것도 없지만, 그래도 어떻게 동작하는지 한번 보여드리고 마무리 짓도록 하겠습니다. 다들 즐거운 코딩 되세요 😂
- <aside> 💡 그래서 위에 작성한 모든 코드들은 실제 페이지의 파일경로를 기술 하지 않고, 매핑할때 쓴 패턴을 기술하여 이동이 가능하게 한겁니다.
- 그래서 이렇게 실제 주소는 감추고 특정 pattern에 따라서 페이지를 이동시키는 기능을 하게 해주는
- 저희가 실제로 파일경로를 url에 노출하게 되는건 그다지 좋은일이 아닙니다.
- <servlet> <servlet-name>home</servlet-name> // servlet-mapping과 연결할 servlet이름을 설정합니다. <servlet-class>kr.co.woojin.Index</servlet-class> // url에 따른 실제 페이지 이동을 할 주소를 패키지를 포함하여 작성합니다. </servlet>
- 서블릿 URL 매핑(어떤 주소로 공개하는지)을 설정하기 위한 것입니다. 이 안에 이하 두개의 태그가 준비되어 있는데, 이것으로 서블릿을 지정된 주소로 게시할 수 있습니다.
- 서블릿의 등록을 위한 것입니다. 이 중에는 다음과 같은 두 개의 태그가 준비되어 있습니다. 서블릿 클래스는 단순히 클래스 이름뿐 아니라 패키지도 포함하여 작성한다.
- 안에 있는 태그들의 기능을 간단하게 살펴보겠습니다.
- 아래와 같이 양식이 정해져 있습니다.
- 위에 있는 기본 양식은 저의 톰캣이 설치되있는 파일에 있는 web.xml과 똑같습니다.
- 한번 바로 보도록 해보죠!
- 하지만 실제로 접속할때는 파일명이 아닌 home 이라는 주소명을 사용해서 접속했습니다!
- 위와 같은 주소여야 하겠죠?
- 그렇다면 아무런 설정을 안했다면 당연히 접속해야하는 URL은
- 여기서 이제 마지막으로 해줘야 하는게 바로 mapping이라는것 입니다.
- 바로 제가 홈페이지 경로를 파일명과 다르게 해놨다는겁니다.
- invalidate(); 메소드가 그 역할을 해줍니다.
- 상태입니다. 그렇다면? 해당 세션을 소멸시키면 자동으로 로그아웃이 되겠죠?
- 로그아웃이 제일 간단합니다.
- (여기서 history.back()을 쓴 이유는 전 페이지에 아이디를 입력했던 정보들을 남겨두기 위해 그렇습니다
'JSP' 카테고리의 다른 글
일단 서버란 뭘까요…? 서블릿이란?…뭘까요오오??.. (0) | 2023.01.16 |
---|---|
JSP ⇒ JSTL/EL 문법과 사용법…허허 (0) | 2023.01.16 |
JSP 내장 객체의 종류, 영역 ? (0) | 2023.01.16 |
WHAT IS JSP?? Java Server Page (0) | 2023.01.16 |