'JAVA의 정석'의 저자 남궁성의 Spring 강의를 듣고 정리하였습니다.
source: https://github.com/castello/spring_basic/tree/main
GitHub - castello/spring_basic: 남궁성의 스프링 기본 강의 @fastcampus.co.kr
남궁성의 스프링 기본 강의 @fastcampus.co.kr. Contribute to castello/spring_basic development by creating an account on GitHub.
github.com
1. 관심사의 분리
OOP 5대 설계 원칙 - SOLID : SRP(단일 책임 원칙)
하나의 메서드는 하나의 책임만 진다.
분리사항
- 관심사
- 변하는 것, 자주 변하지 않는 것
- 공통(중복) 코드
👀 위 부분을 고려하여 객체지향 코드로 개선하자.
2. 입력의 분리
✅ 입력부분은 처리하는 것이 거의 비슷하기때문에 공통으로 처리할 수 있다.
Controller에 입력,처리,출력을 반복해서 처리하지 않고, 입력 부분만 앞으로 빼고 Controller에는 처리와 출력부분만 하도록 한다.
Spring은 입력 값을 처리하는데 여러 기능을 제공한다.
YoilTeller (기존 코드)
Spring은 입력 처리 뿐만 아니라 타입 자동 변환까지 가능하다.
=> request에서 얻은 파라미터 값들을 int 타입으로 변환한다.
Spring을 이용한 YoilTeller (기존 코드)
입력 처리가 훨씬 간편해졌다.
3. 출력(view)의 분리 - 변하는 것과 변하지 않는 것의 분리
관심사가 다르면 별도의 코드로 분리하고 분리된 코드들 간의 데이터 전달을 위해 Model이 필요하다.
처리하는 코드를 Controller로, 출력하는 코드를 View로 분리한다.
분리된 코드들간의 데이터 전달을 위해 Model을 이용한다.
입력 후 처리와 출력하는 과정을 MVC 패턴으로 바꾸어 처리한다.
입력(Model 생성) - 처리(Controller) - 출력(View)
1. 사용자 요청이 들어오면 DispatcherServlet가 입력을 처리하여 모델(Model)을 만든다.
2. 모델을 해당 컨트롤러(Controller)에 넘긴 후 처리 결과를 반환하면 결과를 모델에 저장한다.
3. 작업 결과를 View에게 전달한다. View는 작업 결과로 응답을 만들어 Client에게 전달한다.
4. MVC 패턴
1. 요청이 오면 DispatcherServlet이 입력과 변환을 자동으로 처리해주고 2. 모델(결과 저장소)을 생성한다.
3. Controller가 작업을 처리하고 작업 결과를 Model에 저장하고 View를 지정한다.
4. View는 model로 값을 채워 client에게 응답을 보낸다. - jsp파일
=> 상황에 따라 다른 View를 보여줄 수 있다.✨ - pdf, csc, excel 여러가지 view로 다르게 보여줄 수 있음
❓Controller는 Model을 인자로 받았는데 View는 Model을 인자로 받지않고 어떻게 Model의 값을 얻을 수 있을까?
jsp 파일에서는 request의 getAttribute("key")를 이용하여 model에 저장된 값을 사용할 수 있다.
YoilTellerMVC.java
package com.fastcampus.ch2;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class YoilTellerMVC { //http://localhost:8080/ch2/getYoilMVC?year=2021&month=10&day=1
@RequestMapping("/getYoilMVC")
// public void main(HttpServletRequest request,HttpServletResponse response) throws IOException {
public String main(int year, int month, int day, Model model) throws IOException {
//1. 유효성 검사
if(!isValid(year, month, day))
return "yoilError";
// 2. 요일 계산
char yoil = getYoil(year, month, day);
// 3. 계산한 결과를 model에 저장
model.addAttribute("year", year);
model.addAttribute("month", month);
model.addAttribute("day", day);
model.addAttribute("yoil", yoil);
return "yoil"; //WEB-INF/views/yoil.jsp
}
private boolean isValid(int year, int month, int day) {
// TODO Auto-generated method stub
return true;
}
private char getYoil(int year, int month, int day) {
Calendar cal = Calendar.getInstance();
cal.set(year, month - 1, day);
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
return " 일월화수목금토".charAt(dayOfWeek);
}
}
0. 입력값을 int 타입으로 편리하게 받고, 생성된 모델을 받는다.
1. 입력값이 유효한지 검사하고
2. 요일을 계산한다.
3. 계산 결과를 모델에 담고 view를 만든 jsp파일로 지정한다.
✅ 입력 부분을 Spring으로 편리하게 처리했다.
✅ 출력부분을 jsp파일로 따로 분리하였다.
✅ Model로 값을 담아 분리된 파일간에 데이터를 연결한다.
❄️ jsp로 반환타입을 지정하지 않으면 view는 매핑된 url의 jsp파일을 기본으로 가져온다. => getYoilMVC.jsp
src.main/spring.appServlet/servlet-context.xml
view 이름의 경로가 지정되어 있다.
yoil.jsp
<%@ page contentType="text/html;charset=utf-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>Home</title>
</head>
<body>
<P> ${year }년 ${month }월 ${day }일은 ${yoil }입니다. </P>
</body>
</html>
❕utf-8을 붙여줘야 한글이 깨지지 않는다.
yoilError.jsp
<%@ page contentType="text/html;charset=utf-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>
잘못된 요청입니다. 년, 월, 일을 모두 올바르게 입력해주세요.
</h1>
</body>
</html>
결과 화면
ModelAndView
model을 인자로 받지 않고 Controller 상에서 만드는 방법
package com.fastcampus.ch2;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;
import javax.servlet.http.HttpServletRequest;
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.servlet.ModelAndView;
@Controller
public class YoilTellerMVC { //http://localhost:8080/ch2/getYoilMVC?year=2021&month=10&day=1
@RequestMapping("/getYoilMVC")
// public void main(HttpServletRequest request,HttpServletResponse response) throws IOException {
public ModelAndView main(int year, int month, int day) throws IOException {
ModelAndView mv = new ModelAndView();
//1. 유효성 검사
// if(!isValid(year, month, day))
// return "yoilError";
// 2. 요일 계산
char yoil = getYoil(year, month, day);
// 3. 계산한 결과를 model에 저장
mv.addObject("year", year);
mv.addObject("month", month);
mv.addObject("day", day);
mv.addObject("yoil", yoil);
//4. 결과를 보여줄 view를 지정
mv.setViewName("yoil");
return mv; //WEB-INF/views/yoil.jsp
}
private boolean isValid(int year, int month, int day) {
// TODO Auto-generated method stub
return true;
}
private char getYoil(int year, int month, int day) {
Calendar cal = Calendar.getInstance();
cal.set(year, month - 1, day);
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
return " 일월화수목금토".charAt(dayOfWeek);
}
}
- addAttribute가 아닌 addObject로 함수명이 다르다.
- ModelAndView에 view도 지정해주어야한다.
- 잘 사용하지는 않는다.
컨트롤러 메서드의 반환타입
- String -> 뷰 이름 반환
- void -> 맵핑된 URL의 끝 단어
- ModelAndView -> Model과 View 이름을 ModelAndView에 담아 반환
관심사의 분리와 MVC 패턴 - 원리
메서드 정보 얻는 코드
package com.fastcampus.ch2;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.StringJoiner;
public class MethodInfo {
public static void main(String[] args) throws Exception{
// 1.YoilTeller 클래스의 객체를 생성
Class clazz = Class.forName("com.fastcampus.ch2.YoilTeller");
Object obj = clazz.newInstance();
// 2. 모든 메서드 정보를 가져와서 배열에 저장
Method[] methodArr = clazz.getDeclaredMethods();
for(Method m : methodArr) {
String name = m.getName(); // 메서드의 이름
Parameter[] paramArr = m.getParameters(); // 매개변수 목록
// Class[] paramTypeArr = m.getParameterTypes();
Class returnType = m.getReturnType(); // 반환 타입
StringJoiner paramList = new StringJoiner(", ", "(", ")"); //구분자, 시작문자, 끝나는 문자
for(Parameter param : paramArr) {
String paramName = param.getName();
Class paramType = param.getType();
paramList.add(paramType.getName() + " " + paramName);
}
System.out.printf("%s %s%s%n", returnType.getName(), name, paramList);
}
} // main
}
/* [실행결과]
java.lang.String main(java.lang.String year, java.lang.String month, java.lang.String day, org.springframework.ui.Model model)
boolean isValid(int year, int month, int day)
*/
출력시 arg0, arg1으로 나온다.
파라미터 이름 저장 옵션
ClassFile Generation에서 마지막 옵션(Store info~) 체크하면 파라미터 이름도 저장된다.
(평소에는 파라미터는 이름보다는 타입이 중요하므로 이름까지 저장되지 않는다.)
pom.xml
pom.xml 변경 후에는 프로젝트 업데이트하기
설정 이후 파라미터 이름까지 볼 수 있다.
매개변수 이름 얻는 방법
1. Reflection API (우리가 위에서 사용한 방법)
JAVA 8 이후, JDK 1.8의 -parameters 옵션
2. Classfile 읽어오기
target/classes/YoilTeller.class
MethodCall.java
package com.fastcampus.ch2;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
public class MethodCall {
public static void main(String[] args) throws Exception{
HashMap map = new HashMap();
System.out.println("before:"+map); //빈 map
ModelController mc = new ModelController();
String viewName = mc.main(map); //map에 값 채워넣기
System.out.println("after :"+map); //map 출력
render(map, viewName);
}
static void render(HashMap map, String viewName) throws IOException {
String result = "";
// 1. 뷰의 내용을 한줄씩 읽어서 하나의 문자열로 만든다.
Scanner sc = new Scanner(new File(viewName+".txt"));
while(sc.hasNextLine())
result += sc.nextLine()+ System.lineSeparator();
// 2. map에 담긴 key를 하나씩 읽어서 template의 ${key}를 value바꾼다.
Iterator it = map.keySet().iterator();
while(it.hasNext()) {
String key = (String)it.next();
// 3. replace()로 key를 value 치환한다.
result = result.replace("${"+key+"}", (String)map.get(key));
}
// 4.렌더링 결과를 출력한다.
System.out.println(result);
}
}
class ModelController {
public String main(HashMap map) {
//작업 결과를 map에 저장
map.put("id", "asdf");
map.put("pwd", "1111");
// 뷰 이름 반환
return "txtView2";
}
}
txtView2.txt
id:${id}, pwd:${pwd}
실행 결과
MethodCall2.java
package com.fastcampus.ch2;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;
import org.springframework.ui.Model;
import org.springframework.validation.support.BindingAwareModelMap;
public class MethodCall2 {
public static void main(String[] args) throws Exception{
// 1. YoilTellerMVC의 객체 생성
Class clazz = Class.forName("com.fastcampus.ch2.YoilTellerMVC");
Object obj = clazz.newInstance();
// 2. main 메서드의 정보를 가져온다.
Method main = clazz.getDeclaredMethod("main", int.class, int.class, int.class, Model.class);
//3. Model 생성
Model model = new BindingAwareModelMap();
System.out.println("[before] model="+model);
//4. main 메서드를 호출
// String viewName = obj.main(2021, 10, 1, model); // 아래 줄과 동일
String viewName = (String)main.invoke(obj, new Object[] { 2021, 10, 1, model }); //Reflection API를 이용한 호출
System.out.println("viewName="+viewName);
// Model의 내용을 출력
System.out.println("[after] model="+model);
// 텍스트 파일을 이용한 rendering
render(model, viewName);
} // main
static void render(Model model, String viewName) throws IOException {
String result = "";
// 1. 뷰의 내용을 한줄씩 읽어서 하나의 문자열로 만든다.
Scanner sc = new Scanner(new File("src/main/webapp/WEB-INF/views/"+viewName+".jsp"), "utf-8");
while(sc.hasNextLine())
result += sc.nextLine()+ System.lineSeparator();
// 2. model을 map으로 변환
Map map = model.asMap();
// 3.key를 하나씩 읽어서 template의 ${key}를 value바꾼다.
Iterator it = map.keySet().iterator();
while(it.hasNext()) {
String key = (String)it.next();
// 4. replace()로 key를 value 치환한다.
result = result.replace("${"+key+"}", ""+map.get(key));
}
// 5.렌더링 결과를 출력한다.
System.out.println(result);
}
}
실행 결과
MethodCall3.java
package com.fastcampus.ch2;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;
import org.springframework.ui.Model;
import org.springframework.validation.support.BindingAwareModelMap;
public class MethodCall3 {
public static void main(String[] args) throws Exception{
//1. 요청할 때 제공된 값 - request.getParmameterMap();
Map map = new HashMap();
map.put("year", "2021");
map.put("month", "10");
map.put("day", "1");
Model model = null;
Class clazz = Class.forName("com.fastcampus.ch2.YoilTellerMVC");
Object obj = clazz.newInstance();
// YoilTellerMVC.main(int year, int month, int day, Model model)
Method main = clazz.getDeclaredMethod("main", int.class, int.class, int.class, Model.class);
Parameter[] paramArr = main.getParameters(); //메인 메서드의 매개변수 목록 가져온다.
Object[] argArr = new Object[main.getParameterCount()]; //매개변수 개수와 같은 길이의 Object 배열을 생성
for(int i=0;i<paramArr.length;i++) {
String paramName = paramArr[i].getName(); //파라미터 이름
Class paramType = paramArr[i].getType(); //파라미터 타입
Object value = map.get(paramName); // map에서 못찾으면 value는 null
// paramType중에 Model이 있으면, 생성 & 저장
if(paramType==Model.class) {
argArr[i] = model = new BindingAwareModelMap();
} else if(value != null) { // map에 paramName이 있으면,
// value와 parameter의 타입을 비교해서, 다르면 변환해서 저장
argArr[i] = convertTo(value, paramType); //파라미터 타입으로 값 타입 변환
}
}
System.out.println("paramArr="+Arrays.toString(paramArr));
System.out.println("argArr="+Arrays.toString(argArr));
// Controller의 main()을 호출 - YoilTellerMVC.main(int year, int month, int day, Model model)
String viewName = (String)main.invoke(obj, argArr);
System.out.println("viewName="+viewName);
// Model의 내용을 출력
System.out.println("[after] model="+model);
// 텍스트 파일을 이용한 rendering
render(model, viewName);
} // main
private static Object convertTo(Object value, Class type) {
if(type==null || value==null || type.isInstance(value)) // 타입이 같으면 그대로 반환
return value;
// 타입이 다르면, 변환해서 반환
if(String.class.isInstance(value) && type==int.class) { // String -> int
return Integer.valueOf((String)value);
} else if(String.class.isInstance(value) && type==double.class) { // String -> double
return Double.valueOf((String)value);
}
return value;
}
private static void render(Model model, String viewName) throws IOException {
String result = "";
// 1. 뷰의 내용을 한줄씩 읽어서 하나의 문자열로 만든다.
Scanner sc = new Scanner(new File("src/main/webapp/WEB-INF/views/"+viewName+".jsp"), "utf-8");
while(sc.hasNextLine())
result += sc.nextLine()+ System.lineSeparator();
// 2. model을 map으로 변환
Map map = model.asMap();
// 3.key를 하나씩 읽어서 template의 ${key}를 value바꾼다.
Iterator it = map.keySet().iterator();
while(it.hasNext()) {
String key = (String)it.next();
// 4. replace()로 key를 value 치환한다.
result = result.replace("${"+key+"}", ""+map.get(key));
}
// 5.렌더링 결과를 출력한다.
System.out.println(result);
}
}
결과
MyDispatcherServlet.java
package com.fastcampus.ch2;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.ui.Model;
import org.springframework.validation.support.BindingAwareModelMap;
@WebServlet("/myDispatcherServlet") // http://localhost/ch2/myDispatcherServlet?year=2021&month=10&day=1
public class MyDispatcherServlet extends HttpServlet {
@Override
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
Map map = request.getParameterMap();
Model model = null;
String viewName = "";
try {
Class clazz = Class.forName("com.fastcampus.ch2.YoilTellerMVC");
Object obj = clazz.newInstance();
// 1. main메서드의 정보를 얻는다.
Method main = clazz.getDeclaredMethod("main", int.class, int.class, int.class, Model.class);
// 2. main메서드의 매개변수 목록(paramArr)을 읽어서 메서드 호출에 사용할 인자 목록(argArr)을 만든다.
Parameter[] paramArr = main.getParameters();
Object[] argArr = new Object[main.getParameterCount()];
for(int i=0;i<paramArr.length;i++) {
String paramName = paramArr[i].getName();
Class paramType = paramArr[i].getType();
Object value = map.get(paramName);
// paramType중에 Model이 있으면, 생성 & 저장
if(paramType==Model.class) {
argArr[i] = model = new BindingAwareModelMap();
} else if(paramType==HttpServletRequest.class) {
argArr[i] = request;
} else if(paramType==HttpServletResponse.class) {
argArr[i] = response;
} else if(value != null) { // map에 paramName이 있으면,
// value와 parameter의 타입을 비교해서, 다르면 변환해서 저장
String strValue = ((String[])value)[0]; // getParameterMap()에서 꺼낸 value는 String배열이므로 변환 필요
argArr[i] = convertTo(strValue, paramType);
}
}
// 3. Controller의 main()을 호출 - YoilTellerMVC.main(int year, int month, int day, Model model)
viewName = (String)main.invoke(obj, argArr);
} catch(Exception e) {
e.printStackTrace();
}
// 4. 텍스트 파일을 이용한 rendering
render(model, viewName, response);
} // main
private Object convertTo(Object value, Class type) {
if(type==null || value==null || type.isInstance(value)) // 타입이 같으면 그대로 반환
return value;
// 타입이 다르면, 변환해서 반환
if(String.class.isInstance(value) && type==int.class) { // String -> int
return Integer.valueOf((String)value);
} else if(String.class.isInstance(value) && type==double.class) { // String -> double
return Double.valueOf((String)value);
}
return value;
}
private String getResolvedViewName(String viewName) {
return getServletContext().getRealPath("/WEB-INF/views") +"/"+viewName+".jsp";
}
private void render(Model model, String viewName, HttpServletResponse response) throws IOException {
String result = "";
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
// 1. 뷰의 내용을 한줄씩 읽어서 하나의 문자열로 만든다.
Scanner sc = new Scanner(new File(getResolvedViewName(viewName)), "utf-8");
while(sc.hasNextLine())
result += sc.nextLine()+ System.lineSeparator();
// 2. model을 map으로 변환
Map map = model.asMap();
// 3.key를 하나씩 읽어서 template의 ${key}를 value바꾼다.
Iterator it = map.keySet().iterator();
while(it.hasNext()) {
String key = (String)it.next();
// 4. replace()로 key를 value 치환한다.
result = result.replace("${"+key+"}", map.get(key)+"");
}
// 5.렌더링 결과를 출력한다.
out.println(result);
}
}
앞 예제와 거의 같은데 year=2021&year=2020의 경우 year의 값을 모두 저장하기위해 배열로 value를 저장하고,
요청 파라미터에 request와 response도 처리한다는 것이 다르다.
결과
@WebServlet 에러 발생시
build path - configure build path - class path - add library - server runtime - tomcat
=> tomcat 라이브러리 추가하면 해석 가능하다.
@WebServlet = @Controller+ @RequestMapping
✅ Servlet은 클래스 단위로만 매핑 가능..(메서드X) => 클래스 많이 만들어야함
서블릿과 JSP
1. 서블릿과 컨트롤러의 비교
JSP과 서블릿은 비슷 => 서블릿 발전시킨것이 Spring
- @WebServlet = @Controller + @RequestMapping (클래스에 매핑까지)
- Controller는 상속 안받음
- 서블릿은 url 매핑을 클래스 단위로
- 컨트롤러는 url 매핑을 메서드 단위로 => 더 편리하다.
컨트롤러는 서블렛의 발전 형태
Servlet
service 함수, 상속 존재
Controller
url 매핑시 메서드 단위이므로 새로운 url 매핑시에도 같은 클래스 내에 메서드 하나만 더 만들어주면 된다.
2. 서블릿의 생명 주기
source-override/implement methods
HelloServlet.java
package com.fastcampus.ch2;
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;
@WebServlet("/hello")
public class HelloServlet extends HttpServlet{
@Override
public void init() throws ServletException {
// 서블릿이 초기화될때 자동 호출되는 메서드
// 1. 서블릿의 초기화 작업 담당
System.out.println("[HelloServlet] init() is called.");
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 입력
// 2. 처리
// 3. 출력
System.out.println("[HelloServlet] service() is called.");
}
@Override
public void destroy() {
//3. 뒷정리 - 서블릿이 메모리에서 제거될 때 서블릿 컨테이너에 의해 자동 호출
System.out.println("[HelloServlet] destroy() is called.");
}
}
init() -> service() ..... -> destroy()
결과
init()은 Servlet 로딩시 초기화하면서 한번만 호출되고 그 후 반복되는 요청은 service()가 처리한다.
✅ Servlet은 Singleton으로 1개의 인스턴스를 초기에 만들어서 재활용한다.
3. JSP(Java Server Pages)란?
JSP 작성시 => 자동으로 서블릿으로 변환된다.
jsp 작성시 <%! %>안에는 클래스 영역, <% %> 안에는 service() 메서드 영역을 작성한다.
<%= %>로 변수명을 작성한다.
jsp 파일 생성
new-other-jsp file
twoDice.jsp
<%@ page contentType="text/html;charset=utf-8"%>
<%@ page import="java.util.Random" %>
<%-- <%! 클래스 영역 %> --%>
<%!
int getRandomInt(int range){
return new Random().nextInt(range)+1;
}
%>
<%-- <% 메서드 영역 - service()의 내부 %> --%>
<%
int idx1 = getRandomInt(6);
int idx2 = getRandomInt(6);
%>
<html>
<head>
<title>twoDice.jsp</title>
</head>
<body>
<img src='resources/img/dice<%=idx1%>.jpg'>
<img src='resources/img/dice<%=idx2%>.jpg'>
</body>
</html>
결과
4. JSP의 호출 과정
서블릿 인스턴스가 없으면 jsp 파일을 서블릿 소스 파일(.java)로 만들어서 컴파일한 후 class 파일을 만든다.
그 후 서블릿 인스턴스를 만든다.
✅ 첫번째 호출 또는 변경 후에서만 시간 지연이 발생하고 그 후 호출부터는 빠르게 응답한다.
서블릿 vs 스프링 초기화 방식
- 서블릿은 lazy - init (늦은 초기화) : 요청이 올때 객체를 생성한다.
- Spring은 early - init : 요청이 오지 않아도 미리 객체를 생성한다.
5. JSP의 기본 객체
생성없이 사용할 수 있는 객체
jsp에서 서블릿으로 변환시에 지역변수로 선언되므로 jsp에서 기본객체는 선언없이 바로 사용할 수 있다.
6. 유효 범위(scope)와 속성(attribute)
HTTP 특징
상태 정보 저장X = stateless <-> stateful (상태 정보 저장)
유효 범위(scope)
4개 저장소가 존재한다.
1. 접근 범위
2. 생존 기간
로 구분 가능
저장소
pageContext
지역변수 저장하는 저장소
ex ) 기본 객체 (request, response..) 저장
같은 페이지 안에서만 접근(read / write) 가능
- login.jsp의 pageContext에서 write.jsp의 pageContext에 접근 불가
${ }인 EL은 lv(지역 변수) 직접 접근 불가, EL을 쓰기 위해 pageContext 저장소에 지역변수를 저장해야한다.
application
전체 웹 어플리케이션에서 접근가능한 저장소 - 공통 저장소, 1개만 존재
setAttribute() : 쓰기 (저장)
getAttribute() : 읽기
session
client마다 1개 존재하는 개별 저장소 - id,pw, 장바구니 저장 가능
❕ 사용자마다 1개이므로 서버 부담이 크다. 그러므로 session 저장공간에는 최소한의 data만 저장한다.
request
✅ request는 요청할때마다 생긴다.
✅ request를 한 jsp에서 다른 jsp에게 넘겨줄 수 있다. (forward)
👍 페이지들간의 데이터 전달시 메모리 부담이 큰 session보다 부담이 적은 request를 사용하는 것이 좋다.
(세션 사용시 사용 후 지우는 방법도 있다.)
정리
pageContext, application, session, request 존재
7. URL 패턴
✅ loadOnStartup=1 : 서블릿은 기본적으로 lazy init(늦은 초기화), 위 옵션으로 이른 초기화 설정 가능 (1은 우선순위)
✅ 우선순위순으로 정렬 (exact mapping이 가장 우선순위 높음)
- http://localhost/ch2/login/hello.do 일 경우 /login/hello.do가 없으면 /login/*이 매칭되고, 매칭이 없을 경우 *.do, / 순으로 매칭된다.
- http://localhost/ch2/login/hello 일 경우 /hello.do와 달리 확장자가 다르므로 exact mapping에 매칭되지 않는다.
서블릿 등록시 children(서블릿)에 등록되고, servletMappings에 url 패턴에 매핑된다.
- /hello와 *.jsp는 동적 리소스(서블릿)
- 일치하는 것이 없으면 DefaultServlet이 처리 - 정적 리소스(image, css, txt)
Servlet은 DefaultServlet 아닌 DispatcherServlet을 사용한다.
스프링은 Servlet과 jsp 파일을 사용하지않으므로 모든 요청은 default로 연결된 DispatcherServlet로 처리한다.
8. EL(Expression Language)
el.jsp
<%@ page contentType="text/html;charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page import="com.fastcampus.ch2.*" %>
<%
Person person = new Person();
request.setAttribute("person", person);
request.setAttribute("name", "남궁성");
request.setAttribute("list", new java.util.ArrayList());
%>
<html>
<head>
<title>EL</title>
</head>
<body>
person.getCar().getColor()=<%=person.getCar().getColor()%> <br>
person.getCar().getColor()=${person.getCar().getColor()} <br>
person.getCar().getColor()=${person.car.color} <br>
name=<%=request.getAttribute("name")%> <br>
name=${requestScope.name} <br>
name=${name} <br>
id=<%=request.getParameter("id")%> <br>
id=${pageContext.request.getParameter("id")} <br>
id=${param.id} <br>
"1"+1 = ${"1"+1} <br>
"1"+="1" = ${"1"+="1"} <br>
"2">1 = ${"2">1} <br>
null = ${null}<br>
null+1 = ${null+1} <br>
null+null = ${null+null} <br>
"" + null = ${""+null} <br>
""-1 = ${""-1} <br>
empty null=${empty null} <br>
empty list=${empty list} <br>
null==0 = ${null==0} <br>
null eq 0 = ${null eq 0} <br>
name == "남궁성"=${name=="남궁성"} <br>
name != "남궁성"=${name!="남궁성"} <br>
name eq "남궁성"=${name eq "남궁성"} <br>
name ne "남궁성"=${name ne "남궁성"} <br>
name.equals("남궁성")=${name.equals("남궁성")} <br>
</body>
</html>
<%= .. %> 를 간단하게 ${ }로 작성가능하다.
id=<%=request.getParameter("id")%> <br>
id=${pageContext.request.getParameter("id")} <br>
id=${param.id} <br>
EL은 지역변수를 사용할 수 없으므로 바로 request를 사용할 수 없다.
pageContext.request에서 접근해야한다.
✅ EL은 null을 출력하지않으므로 위 출력값은 null 공백 공백 이다.
"1"+1 = ${"1"+1} <br>
"1"+="1" = ${"1"+="1"} <br>
"2">1 = ${"2">1} <br>
+는 숫자로 계산되고 문자열로 계산하려면 += 기호를 사용해야한다.
"1"+1 = ${"1"+1} <br>
"1"+="1" = ${"1"+="1"} <br>
"2">1 = ${"2">1} <br>
null = ${null}<br>
null+1 = ${null+1} <br>
null+null = ${null+null} <br>
"" + null = ${""+null} <br>
""-1 = ${""-1} <br>
empty null=${empty null} <br>
empty list=${empty list} <br>
null==0 = ${null==0} <br>
null eq 0 = ${null eq 0} <br>
빈 문자열과 null은 숫자 0으로 바뀐다.
empty는 null 또는 빈 컬렉션 배열일때 true이다
출력 결과
9. JSTL(JSP Standard Tag Library)
<% ~ %>을 태그로 간단하게 작성하기 위해서 만들어졌다.
jstl.jsp
<%@ page contentType="text/html;charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
<title>JSTL</title>
</head>
<body>
<c:set var="to" value="10"/>
<c:set var="arr" value="10,20,30,40,50,60,70"/>
<c:forEach var="i" begin="1" end="${to}">
${i}
</c:forEach>
<br>
<c:if test="${not empty arr}">
<c:forEach var="elem" items="${arr}" varStatus="status">
${status.count}. arr[${status.index}]=${elem}<BR>
</c:forEach>
</c:if>
<c:if test="${param.msg != null}">
msg=${param.msg}
msg=<c:out value="${param.msg}"/>
</c:if>
<br>
<c:if test="${param.msg == null}">메시지가 없습니다.<br></c:if>
<c:set var="age" value="${param.age}"/>
<c:choose>
<c:when test="${age >= 19}">성인입니다.</c:when>
<c:when test="${0 <= age && age < 19}">성인이 아닙니다.</c:when>
<c:otherwise>값이 유효하지 않습니다.</c:otherwise>
</c:choose>
<br>
<c:set var="now" value="<%=new java.util.Date() %>"/>
Server time is <fmt:formatDate value="${now}" type="both" pattern="yyyy/MM/dd HH:mm:ss"/>
</body>
</html>
출력 결과
저장소에 값을 저장 + 출력하는 코드
<c:set var="to" value="10"/>
<c:set var="arr" value="10,20,30,40,50,60,70"/>
<c:forEach var="i" begin="1" end="${to}">
${i}
</c:forEach>
EL은 지역변수를 사용할 수 없으므로 저장소에 저장해야한다.
to를 key로, value를 10으로 하여 map에 저장한다.
forEach 구문으로 1부터 10까지 출력한다.
배열의 원소를 출력하는 코드
<c:if test="${not empty arr}">
<c:forEach var="elem" items="${arr}" varStatus="status">
${status.count}. arr[${status.index}]=${elem}<BR>
</c:forEach>
</c:if>
if문으로 배열이 비어있지 않으면 하나씩 출력한다.
varStatus로 count는 1부터 시작하고 index는 0부터 시작한다.
<c:if test="${param.msg != null}">
msg=${param.msg}
msg=<c:out value="${param.msg}"/>
</c:if>
msg=${param.msg} 는 입력받은 값을 태그까지 해석하여 출력하지만
msg=<c:out value="${param.msg}" /> 는 태그를 해석하지않고 문자로 그대로 출력한다. => script 공격을 막을 수 있다.
10. Filter
전처리와 후처리간의 중복코드를 없애고 처리부분에서만 Filter를 이용해 각각 서블릿 호출하도록 함.
Filter 여러개일 경우
전처리와 후처리를 차례대로 처리한다.
PerformanceFilter.java
package com.fastcampus.ch2;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
// 필터를 적용할 요청의 패턴 지정 - 모든 요청에 필터를 적용.
@WebFilter(urlPatterns="/*")
public class PerformanceFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 초기화 작업
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 1. 전처리 작업
long startTime = System.currentTimeMillis();
// 2. 서블릿 또는 다음 필터를 호출
chain.doFilter(request, response);
// 3. 후처리 작업
System.out.print("["+((HttpServletRequest)request).getRequestURI()+"]");
System.out.println(" 소요시간="+(System.currentTimeMillis()-startTime)+"ms");
}
@Override
public void destroy() {
// 정리 작업
}
}
urlPattern="/*" 의 뜻은 모든 요청에 대해 filter를 적용한다는 뜻이다.
출력결과
'Spring > 카테캠 - TIL' 카테고리의 다른 글
TIL [0526-0528] Spring MVC 4 (5) | 2023.05.28 |
---|---|
TIL [0521] Spring MVC 3 (0) | 2023.05.21 |
TIL [0511- 0514] : Spring Basic + Spring MVC 1 (2) | 2023.05.14 |
TIL [0504-0505] : 객체지향 프로그래밍(예외처리, 로그, IO 스트림, 직렬화) (0) | 2023.05.06 |
TIL [0503-0504] : 객체지향 프로그래밍(내부클래스, 람다식, 스트림) (0) | 2023.05.05 |