Reflection(리플렉션)
실행중인 프로그램의 클래스,메서드,필드 등에 대한 정보를 얻거나 조작하는 기능을 제공하는 API
사용 상황
1. 런타임시 동적으로 클래스 로드
2. 클래스의 구조, 메서드, 필드 등에 대한 정보를 가져와서 분석
3. 클래스의 인스턴스를 생성하거나 메서드를 호출하거나 필드의 값을 변경해야하는 경우
Annotation(어노테이션)
@어노테이션이름(속성1=값1, 속성2=값2, ...)
프로그램 코드에 메타 데이터를 추가하는 기능을 제공하는 것으로, 컴파일러나 런타임 시에 코드를 처리하는 도구들에게 추가적인 정보 제공
아픈 나무에게 약을 주어야한다.
모든 나무를 살펴서 아픈 나무를 찾는 것보다, 아픈 나무는 깃발로 표시해서 바로 찾을수 있으면 좋을 것이다.
여기서 깃발은 어노테이션(힌트), 약은 메서드(행위)이다.
즉, 어노테이션은 컴파일러에게 이 코드는 어떤 역할을 하는지 힌트를 준다.
+ 코드의 가독성을 높이고, 자동화된 코드 생성, 테스트, 디버깅 등에 활용된다.
어노테이션 종류
빌트인 어노테이션
컴파일시 작동
- @Override (상위 클래스, 메서드 오버라이드)
- @Deprecated (더이상 사용X)
- @SuppressWarnings( 컴파일러 경고 무시)
메타 어노테이션
다른 어노테이션 안의 어노테이션
- @Retention (유지정책 지정)
- @Target (부착대상-클래스,메서드 필드 지정)
- @Documented (javadoc으로 문서화)
- @Inherited (상속가능)
커스텀 어노테이션
개발자가 직접 정의한 어노테이션
리플렉션, 어노테이션 실습 (협업 상황)
ex01
1차 개발자
2차 개발자
1차 개발자(App) -> 2차 개발자(Controller)
구현할 클래스명과 메서드 명을 제공해야한다.
=>그대신, 1차 개발자가 만든 인터페이스를 제공해 구현한다면 더 편리할 것이다.
인터페이스 작성 및 구현
2차 개발자는 훨씬 편하게 메서드를 작성할 수 있다.
메서드 추가하는 상황
코드를 추가할 상황이 생긴다면, 2차 개발자가 코드를 수정한 후
1차 개발자에게 인터페이스에 추가하고 작동 코드(해당 메서드에 대한 path)를 만들어달라고 해야한다.
=> 계속 기존 코드를 수정해야하며 OCP를 위배한다.
ex02 : 어노테이션 사용
1차 개발자
2차 개발자
위 코드는 @RequestMapping 어노테이션을 붙인 메서드의 uri와 입력 uri가 일치하면 메서드를 실행시키는 코드이다.
=> 메서드를 추가할때마다 uri 처리방법을 지정하지않아도 되므로 발전했다 !
하지만 UserController 안의 메서드만 findUri 함수를 적용할 수 있다. (컨트롤러 추가 불가)
ex03 : 컴포넌트 스캔
package ex03;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
public class App {
//지정된 패키지내의 클래스 스캔
public static Set<Class> componentScan(String pkg) throws Exception {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); //클래스 로더
Set<Class> classes = new HashSet<>();
URL packageUrl = classLoader.getResource(pkg); //패키지 위치
File packageDirectory = new File(packageUrl.toURI()); //패키지 디렉토리
for (File file : packageDirectory.listFiles()) { //패키지 파일 각각에 대해
if (file.getName().endsWith(".class")) { //클래스 파일 가져와서 저장
String className = pkg + "." + file.getName().replace(".class", "");
//System.out.println(className);
Class cls = Class.forName(className);
classes.add(cls);
}
}
return classes;
}
public static void findUri(Set<Class> classes, String uri) throws Exception {
boolean isFind = false;
for (Class cls : classes) {
if (cls.isAnnotationPresent(Controller.class)) { //클래스에 컨트롤러 어노테이션 있으면
Object instance = cls.newInstance();
Method[] methods = cls.getDeclaredMethods();
for (Method mt : methods) {
Annotation anno = mt.getDeclaredAnnotation(RequestMapping.class); //@RequestMapping 어노테이션 있으면
RequestMapping rm = (RequestMapping) anno;
if (rm.uri().equals(uri)) {
isFind = true;
mt.invoke(instance); //메서드 실행
}
}
}
}
if(isFind == false){
System.out.println("404 Not Found");
}
}
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
String uri = sc.nextLine();
Set<Class> classes = componentScan("ex03");
findUri(classes, uri);
}
}
컴포넌트 스캔을 하여 패키지 안의 클래스 파일들을 가져오고 그 안의 Controller, 메서드들로 uri를 분석한다.
컨트롤러를 추가해도 잘 작동된다.
컴포넌트 스캔
스프링 프레임워크에서 제공하는 기능으로, 자동으로 컴포넌트를 검색하고 등록하는 과정이다.
애플리케이션의 구성 요소들을 찾아서 스프링 컨테이너에 자동으로 등록하여 관리할 수 있도록 도와준다.
@ComponentScan을 이용해 어떤 패키지에서 컴포넌트를 스캔할 것인지 지정하면,
스프링은 지정된 패키지 안에서 @Component 어노테이션이 붙은 클래스들을 찾아서 스프링 빈으로 등록한다.
특정한 역할을 가지는 컴포넌트
- @Controller : MVC 컨트롤러로 사용될 클래스
- @Service : 비지니스 로직을 수행하는 서비스 클래스
- @Repository : 데이터베이스 접근을 처리하는 리포지토리 클래스
구현 코드
package shop.mtcoding._core;
import shop.mtcoding.annotation.Component;
import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
public class ComponentScanner {
// 클래스를 스캔하는 메소드
public static Set<Class> scanClass(String pkg) throws Exception {
// 현재 패키지 위치를 받아옴 (shop.mtcoding._core)
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 클래스들을 담을 컬렉션
Set<Class> classes = new HashSet<>();
// 매개변수로 전달받은 패키지 위치
URL packageUrl = classLoader.getResource(pkg.replace(".", "/"));
// 지정한 패키지의 모든 파일 불러오기
File packageDirectory = new File(packageUrl.toURI());
// 패키지의 클래스 스캔
scanPackage(pkg, packageDirectory, classes);
return classes;
}
// 패키지의 클래스 스캔
private static void scanPackage(String pkg, File directory, Set<Class> classes) throws Exception {
for (File file : directory.listFiles()) {
String fileName = file.getName();
// 해당 파일이 패키지일경우
if (file.isDirectory()) {
// 재귀적으로 사용하여 하위 패키지를 스캔한다.
scanPackage(pkg + "." + fileName, file, classes);
// 해당 파일이 클래스 일경우
} else if (fileName.endsWith(".class")) {
String className = pkg + "." + fileName.replace(".class", "");
Class cls = Class.forName(className);
// 해당 클래스가 어노테이션이면 검사하지 않는다.
if(cls.isAnnotation()){
return;
}
// 해당 클래스의 어노테이션이 메타 어노테이션으로 Component를 갖고 있거나, Component 어노테이션을 갖고 있을 경우
if (hasMetaAnnotation(cls, Component.class) || cls.isAnnotationPresent(Component.class)) {
// 매개변수로 전달받은 컬렉션에 저장 (해당 클래스가 어노테이션이 아닐 경우만)
classes.add(cls);
}
}
}
}
// 클래스의 어노테이션의 메타데이터를 확인하는 메소드
private static boolean hasMetaAnnotation(Class<?> cls, Class<? extends Annotation> metaAnnotation) {
// 클래스의 어노테이션들을 갖고온다.
for (Annotation annotation : cls.getDeclaredAnnotations()) {
// 해당 어노테이션이 매개변수로 전달받은 어노테이션을 메타데이터로 가지고 있으면
if (annotation.annotationType().isAnnotationPresent(metaAnnotation)) {
// true 리턴
return true;
}
}
return false;
}
}
DispatcherServlet
스프링 프레임워크에서 웹 애플리케이션의 요청과 응답을 처리하는 핵심 컴포넌트
클라이언트의 요청을 적절한 Controller로 분배하고, 실행 결과를 기반으로 적절한 응답을 생성한다.
역할
- 요청 분배
클라이언트의 요청을 분석하여 애플리케이션 내의 적절한 핸들러(Controller)로 요청 전달
2. 뷰 선택 및 렌더링
핸들러의 실행 결과로 생성된 데이터를 바탕으로 적절한 뷰(html, json, xml)를 선택하고 렌더링
구현 코드
package shop.mtcoding._core;
import shop.mtcoding.annotation.Controller;
import shop.mtcoding.annotation.RequestMapping;
import shop.mtcoding.annotation.ResponseBody;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Set;
public class DispatcherServlet {
public static String findUri(Set<Class> classes, String uri) throws Exception {
for (Class cls : classes) {
// 해당 클래스가 Controller 어노테이션을 가지고 있을 경우
if (cls.isAnnotationPresent(Controller.class)) {
// 해당 클래스 인스턴스
Object instance = cls.newInstance();
// 클래스의 메소드 배열에 저장
Method[] methods = cls.getDeclaredMethods();
for (Method mt : methods) {
// 해당 메소드에 RequestMapping 어노테이션 갖고오기
Annotation anno = mt.getDeclaredAnnotation(RequestMapping.class);
RequestMapping rm = (RequestMapping) anno;
// 매개변수로 전달받은 URI와 RequestMapping의 uri 변수가 동일한지 확인
if (rm.uri().equals(uri)) {
// ResponseBody 어노테이션이 붙어있으면 메시지 컨버터 발동
if (mt.isAnnotationPresent(ResponseBody.class)) {
Object result = mt.invoke(instance);
return MessageConverter.convert(result);
}
// 그게 아니라면 뷰 리졸버 발동
else {
String fileName = (String) mt.invoke(instance);
return ViewResolver.convert(fileName);
}
}
}
}
}
return "404 Not Found";
}
}
@ResponseBody는 http Body를 통째로 전달받을 수 있다.
위 코드에서는 @ResponseBody가 붙어있으면 JSON으로, 그렇지 않으면 뷰 파일로 반환한다.
MessageConverter
HTTP 요청과 응답의 메세지를 변환하는 역할을 담당하는 컴포넌트
MappingJackson2HttpMessageConverter이 등록되어있다.
package shop.mtcoding._core;
import com.google.gson.Gson;
public class MessageConverter {
public static String convert(Object object) {
Gson gson = new Gson();
String result = gson.toJson(object);
return result;
}
}
java object를 json으로 변환한다.
예시 코드
ViewResolver 구현
클라이언트 요청의 결과를 보여줄 View를 찾아주는 역할을 수행하는 컴포넌트
HTML, XML, JSON등의 다양한 형식의 뷰를 렌더링한다.
스프링에서는 Thymeleaf, JSP, Freemarker, Mustache 등의 템플릿 엔진과의 통합을 위한 ViewResolver를 제공한다.
역할
1. View 결정
클라이언트 요청에 대한 처리 후, ViewResolver는 어떤 뷰를 사용하여 결과를 표시할지 결정한다.
2. 뷰 렌더링
선택된 View를 렌더링하여 클라이언트에게 반환한다.
뷰 렌더링은 템플릿 엔진을 사용하여 동적으로 데이터를 채우고, 최종적으로 클라이언트에게 제공할 HTML, XML, JSON등의 결과를 생성한다.
구현
package shop.mtcoding._core;
import java.io.*;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
public class ViewResolver {
public static String convert(String fileName) throws Exception {
// 프로젝트의 root 경로를 찾음 (자바 소스 파일을 찾을 때는 ClassLoader 사용)
String projectRootPath = System.getProperty("user.dir");
// mac 과 window 와 linux 파일 시스템이 달라서 / or \ 등을 운영체제 시스템에 맞추는 방법 : File.separator
String resourcePath = projectRootPath + File.separator+"build"+ File.separator+"resources"+ File.separator+"main"+ File.separator+"templates"+File.separator;
fileName = fileName.replace("/", File.separator);
return htmlFileRead(resourcePath+fileName+".html");
}
private static String htmlFileRead(String HTMLFilePath) throws Exception { //html 파일 읽기
StringBuilder contentBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(HTMLFilePath))) {
String line;
while ((line = reader.readLine()) != null) {
contentBuilder.append(line).append("\n");
}
}
return contentBuilder.toString();
}
}
기억해 둘 것
1. 어노테이션 : 리플렉션을 편하게 하려고 깃발을 다는 것
tomcat(소켓)으로 요청을 하면 request 객체를 만들어서 필터로 이동하고 dispatcherServlet(spring web 라이브러리에 존재)에게 보낸다.
dispatcherServlet은 컨트롤러에서 메서드를 찾고, 요청 및 공통 작업을 처리한다.
우리가 할 것: 그 후 controller, service,repository, db연결..
서버
🫥 서버는 수동적이다. 요청이 오면 응답해준다.
클라이언트 - 서버 : byte stream 전달
애플리케이션에서 버퍼엣 hello\n를 write하여 소켓으로 보내면, 브라우저는 버퍼를 read한다.
✅ /n을 기준으로 read한다.
byte stream을 보내면 서버는 처리한다.
Tomcat (WAS)
jsp 파일을 요청하면 컴파일->렌더링 후 html로 응답 전송한다. 그 후 브라우저는 해석할 수 있다.
톰캣은 소켓 통신 수행 : 2명의 요청자가 있다면 3개의 스레드가 필요하다.(2개의 요청+리스너 스레드)
✳️ 우리가 알아야할 것 = 1. 스프링 작동 원리 2. 스프링의 깃발들(어노테이션)
스프링에서 MVC 패턴
1. 클라이언트의 요청을 디스패처 서블릿이 받는다.
2. 요청 정보를 통해 요청을 위임할 컨트롤러를 찾아낸다.
3. 요청을 컨트롤러로 위임할 핸들러 어댑터로 전달한다.
4. 핸들러 어댑터가 해당 컨트롤러로 요청을 위임한다.
5. 비지니스 로직을 처리한다.
6. 컨트롤러가 반환값을 핸들러 어댑터로 반환한다.
7. 핸들러 어댑터는 디스패처 서블릿으로 반환값을 전달한다.
8. 디스패처 서블릿은 서버의 응답을 클라이언트에 전달한다.
상품등록은 요구사항에 없음 (쿼리 X)
FK 외래키 제약조건
장점:안전,무결성
단점:연산속도
그래도 우리는 쓰자...
INNER JOIN vs OUTER JOIN
JOIN
옵션을 조회하는데 해당 상품 정보까지 조회하고 싶을때 조인 사용
INNER JOIN
중복된 것이 있는 것만 테이블에 포함
조인시 중복되지 않은 값은 사라진다.
SELECT ot.id option_id, ot.option_name, pt.id product_id, pt.product_name, pt.image
FROM product_option_tb ot
INNER JOIN product_tb pt
ON ot.product_id = pt.id
WHERE ot.product_id = 1;
⬇️
필요한 데이터를 필터링한 후 조인하는 코드 (더 효율적)
SELECT ot.id option_id, ot.option_name, pt.id product_id, pt.product_name, pt.image
FROM (SELECT * FROM option_tb WHERE product_id = 1) ot
INNER JOIN product_tb pt
ON ot.product_id = pt.id;
✅ join 후 where절보다는 조인 전 where절 걸어서 조인할 데이터를 줄이는 것이 좋다.
결과
OUTER JOIN
테이블의 모든 데이터를 가져온다. 없으면 null
ex) A LEFT OUTER JOIN B : A의 모든 행을 가져온 후 B와 조인
드라이빙 테이블
driving table = join시 먼저 액세스되는 테이블
driven table = join시 나중에 액세스되는 테이블
⭐️ 드라이빙 테이블이 결정됨에 따라 속도 차이가 크게 날 수 있다.
- PK가 있는 테이블은 index가 존재한다. index가 있는 테이블이 driven table이 되면 빠르게 데이터를 조회할 수 있다. (full scan이 아닌 single row scan,random access를 한다.)
- index가 없다면 FROM에 먼저 나온 테이블이 driving table이다.
- 찾는 데이터가 많이 존재할 경우 index를 참고해서 접근하는 것보다는 full scan이 훨씬 빠르다.
driving table이 연관관계의 주인(1:N에서 N, FK 가진 테이블)이 되는 것이 좋다.
한 테이블이 FK를 가지면 driven table은 PK(인덱스 존재)를 가지므로 조회 속도가 빠르다.
인증 방식
1. 길동이는 헬스장 처음 갔다. ->인증X
2. 길동이는 key를 받았지만 가져오지 않았다. ->인증X
3. key를 가져왔지만 헬스장에서 길동이의 신원을 파악할 수 없음 ( stateless라 등록 고객 정보 없음) ->인증X
4. 헬스장에 고객 정보가 저장되어있어 key를 가져온 길동이를 인증할 수 있다. (stateful)-> 인증O
stateful : 고객 상태 정보 가짐, 가진 것과 대조
단점 : 확장이 힘들다. (세션 정보를 다 나누어 가져야함)
-서버 동기화(복제) or sticky(갔던 헬스장만 가야함)
=> 안좋음..
5. 헬스장 개수가 늘어나면 첫번째 헬스장이 아닌 다른 헬스장에서는 길동이의 정보가 없으므로 이용이 불가능하다. ->다른 헬스장 인증X
6. key 저장하는 곳을 메모리 데이터베이스(ex) redis, 멤캐시)에 저장하여 세션을 공유한다. (먼 곳에서 db 접근해야하므로 느리다.)
✅ session 저장 영역: 안사라짐, 다음 요청에도 세션 저장영역이 있어야함
7. key에 신원정보를 저장한 후 토큰으로 발급한다.(서버의 개인키로 서명하여 토큰 생성), 헬스장은 key의 위조 여부만 파악한다.(stateless)
stateless : 고객 상태 정보 X, (key+정보)를 서버의 개인키로 암호화(서명)해서 토큰 생성
서버는 위조 확인(자신이 만든 토큰인지 확인)
비대칭키 암호화
암호화 : 공개키로 암호화
서명 : 개인키로 암호화, 부인방지 막음 =>공개키로 복호화되면 인증된 사람이 암호화했는지 알 수 있음
'Spring > 카테캠 - TIL' 카테고리의 다른 글
카테캠 : 1주차 코드리뷰 (0) | 2023.07.04 |
---|---|
TIL [0704] - 2주차 강의 2 : Spring Security (0) | 2023.07.04 |
TIL [0630] : 1주차 - 연관관계 파악 및 ER-Diagram 작성 (0) | 2023.06.30 |
TIL [0629] :1주차 - DB 테이블 Schema (MySQL) (0) | 2023.06.29 |
TIL [0627] [0628] : 1주차 - 요구사항 분석, API 매칭 (0) | 2023.06.28 |