JAVA 웹개발 과정 국비 84,85일차 정리

84,85일차

※ 배우는 과정이므로 정확하지 않을 수 있습니다.
잘못된 점은 알려주시면 수정하도록 하겠습니다!


공식적인 국비 수업 진도가 모두 끝났다…
우울..ㅠㅠ

1. Spring Framework 이어서..

1) Login Validation (로그인 검사)

① 필요성

로그인을 한 사람만 볼 수 있는 게시판이 있다고 했을 때, 다이렉트로 localhost/board/list 라고 검색창에 url을 쳐서 접속한다면 로그인을 하지 않아도 게시판으로 바로 이동이 가능하다.
권한이 없는 사람도 url만 알고있다면 바로 접속이 가능하다는 것이 문제인데, 이런 상황 때문에 Login Validation이 필요하다.

② Interceptor

그렇다면 각각의 메소드에 if문을 걸어줘서 로그인을 안하면 메인 페이지로 돌려보내야 할까?
검사 로직을 필요한 메소드에 다 넣기에는 너무 번거롭다.
이때 로그인을 검사하기 위한 방법으로 Interceptor라는 것을 사용하면 편하다.
Interceptor는 Servlet Filter와 비슷한 개념인데 적용되는 시점이 다르다.
Filter는 Client에서 Dispatcher로 갈 때 적용된다면, Interceptor는 Dispatcher에서 Controller로 갈 때 적용된다.
많은 부분이 비슷하지만, Filter는 스프링 컨테이너에 들어가기 전에 작업하기 때문에 bean을 참조할 수 없는 반면 Interceptor는 스프링에 있는 기능들을 사용할 수 있다는 점이 다르다.

  Filter Interceptor
적용시점 Client -> Dispatcher Dispatcher -> Controller
위치 스프링 컨테이너 바깥 스프링 컨테이너 안
예시 @Autowired 사용 불가 @Autowired 사용 가능

③ 사용 방법

ⓐ Interceptor 클래스 생성
  1. 패키지를 만들고, 클래스를 생성한다.
  2. HandlerInterceptor를 구현한다.
  3. preHandle 메소드를 Override 한다.
    • preHandle : 컨트롤러를 거치기 전 실행되는 메소드
    • postHandle : 컨트롤러를 거친 후 실행되는 메소드
  4. HttpSession을 @Autowired 한다. (이 작업을 Filter에서는 하기 어렵다)
  5. 검사 로직을 적어준다.
package kh.spring.interceptor;

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;

public class TestInterceptor implements HandlerInterceptor {
	
	@Autowired
	private HttpSession session;
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		
		String id = (String)session.getAttribute("loginID");
		if(id == null) {
			response.sendRedirect("/");
			System.out.println("로그인이 필요합니다.");
			return false;
		}
		
		return HandlerInterceptor.super.preHandle(request, response, handler);
	}
	
}
ⓑ Interceptor 설정

servlet-context.xml에 아래와 같이 적어준다.
mapping path는 어떤 url을 interceptor를 거치게 만들 건지 설정한다.
exclude-mapping path는 interceptor를 거치지 않을 url을 설정한다.
preHandle 메소드는 누가 가지고 있는지 알려주는 bean 코드를 추가한다.

<interceptors>
	<interceptor>
		<mapping path="/**"/>
		<exclude-mapping path="/"/>
		<exclude-mapping path="/login"/>
		<exclude-mapping path="/toInput"/>
		<exclude-mapping path="/images/**"/>
		<exclude-mapping path="/css/**"/>
		<beans:bean class="kh.spring.interceptor.TestInterceptor"/>
	</interceptor>
</interceptors>

2) Service Layer

① Service Layer란?

컨트롤러에서 직접 DAO를 참조하지 않도록 Service 레이어를 추가해준다.
DAO 하나, 즉, 테이블 하나당 서비스 레이어도 하나씩 만들어준다.
컨트롤러는 Web Tier 작업만 하게하고, DAO는 DB 관련 작업만 하게해주기 위해서이다.
간단히 말하자면 유지보수적 장점을 가지기 위해서이다.
그러나 어디에도 끼지 않는 코드들이 존재하기 때문에, 그런 코드들을 모두 Service Layer에 보내버려서 해결한다. 예를 들면 페이징 처리하는 알고리즘 등..

어디까지의 작업을 Web Tier 작업으로 볼 것이냐는 주관적인 해석이 들어갈 수 있기 때문에 유연하게 대처해서 작업하도록 하자!

② 사용 방법

  1. 패키지를 만든 뒤, 클래스를 생성한다.
  2. @Service 어노테이션을 붙여준다.
  3. 컨트롤러는 dao 대신 service를 @Autowired한다.
  4. 서비스 클래스에 dao에 만들 메소드를 동일하게 만들어준다.
  5. 컨트롤러가 서비스를 이용하고, 서비스가 dao를 이용하면 끝!

3) AOP

Aspect Oriented Programming : 관점 지향 개발

종단관심사와 반대되는 횡단관심사 기준으로 중복되는 기능의 코드를 떼어내서 분리하여 관리하자는 개념.
개념적으로 정리하자면 어려운데, 막상 해보면 이해가 된다는 강사님의 말씀이 맞는 것 같다..^^;
주로 로깅(기록 남기기), 보안, 트랜잭션 처리 등에 사용한다.

① 라이브러리 의존성 추가

AspectJ Weaver라는 라이브러리를 사용한다.
properties의 aspectj-version을 동일하게 맞춰준다.
${org.aspectj-version} 으로 AspectJ Weaver dependency version을 변경해준다.

② advisor 패키지와 LogAdvisor 클래스 생성

@Component 어노테이션 사용
@Aspect 어노테이션 사용

③ before 메소드 생성

@Before 어노테이션 사용
beforeLog() 는 advice 이다.
어노테이션 안 괄호에 PointCut 표현식 사용
within : 클래스 단위로 선별

@Before("within(kh.spring.controller.HomeController)")
public void beforeLog(JoinPoint jp) {
	Signature sign = jp.getSignature();
	String logTime = sdf.format(System.currentTimeMillis());
	System.out.print(logTime+ " ");
	System.out.println(sign.getDeclaringTypeName() + "클래스에서 " + sign.getName() + "메서드 실행");
}

JoinPoint jp를 매개변수로 주면 beforeLog가 동작되는 시점, PointCut 메소드의 이름 등의 여러가지 정보들을 얻을 수 있다.
아래는 출력 예시이다.

2022/01/05 11:33:01 kh.spring.controller.HomeController 클래스에서 home 메소드 실행

④ 설정 추가

servlet-context.xml으로 이동
Namespaces 탭으로 이동하여 aop 체크하여 선택
<aop:aspectj-autoproxy/> 추가
메소드 왼쪽에 주황색 화살표 생겼는지 확인

⑤ after 메소드 생성

before와 동일하게 설정
execution : 메소드 단위로 선별, 리턴값과 메소드명, 매개변수 갯수를 적어준다.
*을 사용한 다양한 표현식 사용 가능
execution(String kh.spring.controller.HomeController.*(*)) 매개변수 1개, 리턴값이 String인 모든 메소드
execution(* kh.spring.controller.HomeController.outputProc(*)) 매개변수 1개인 outputProc 메소드
execution(* kh.spring.controller.HomeController.outputProc(*, *)) 매개변수 2개인 outputProc 메소드
execution(* kh.spring.controller.HomeController.outputProc(..)) 매개변수 0개 이상인 outputProc 메소드
execution(* kh.spring.controller.*Controller.outputProc(*)) ~컨트롤러의 모든 outputProc 메소드(매개변수 1개)

JoinPoint jp를 사용하면 beforeLog 메소드와 동일하게 동작한다.

@After("execution(String kh.spring.controller.HomeController.*(*))")
public void afterLog(JoinPoint jp) {
	Signature sign=jp.getSignature();
	String logTime = sdf.format(System.currentTimeMillis());
	System.out.print(logTime+ " ");
	System.out.println(sign.getDeclaringTypeName() + "클래스에서 " + sign.getName() + "메서드 종료");
}

jp.getArgs()는 PointCut에 전달되는 argument를 가져올 수 있다.
예를 들면 inputProc 메소드가 실행될 때, 전달되는 인자값이 ContactDTO일 것이므로, args[0]의 값은 ContactDTO이다.
dto의 name값을 출력해보면 잘 출력되는 것을 알 수 있다.

@After("execution(String kh.spring.controller.HomeController.inputProc(*))")
public void afterLog2(JoinPoint jp) {
	Signature sign=jp.getSignature();
	
	Object[] args = jp.getArgs();
	ContactDTO dto = (ContactDTO)args[0];
	System.out.println(dto.getName());
	
	String logTime = sdf.format(System.currentTimeMillis());
	System.out.print(logTime+ " ");
	System.out.println(sign.getDeclaringTypeName() + "클래스에서 " + sign.getName() + "메서드 종료");
}

⑥ around 메소드 생성

before와 after를 합쳐놓은 메소드이다.
단순히 합쳐놓았을 뿐 아니라 PointCut으로 전달되는 인자값 수정, 리턴값 변경, PointCut 메소드 동작을 막는 등 강력한 기능을 가지고 있다.
인자값으로는 ProceedingJoinPoint를 사용하고, 리턴값으로는 Object를 사용한다.
proceed는 Around Advice가 PointCut 메소드를 호출하는 메소드이다.
proceed를 부르지 않으면 PointCut 실행이 되지 않는다. 그러나 실행시키지 않는 상황은 거의 없으므로 공식처럼 같이 나온다고 생각하면 된다.
proceed를 기준으로 앞쪽 코드가 before, 뒤쪽 코드가 after의 역할을 한다.

aroundLog 메소드의 리턴값으로 “home”을 쓰면, toOutput을 눌러도 리스트를 출력하는 페이지로 이동하지 않고 계속 home만 보여준다.
이유는 PointCut의 리턴값이 메소드 안에서 사라져버리기 때문이다.
따라서 제대로 동작하게 하려면 PointCut의 리턴값을 받아서 aroundLog의 리턴값으로 넘겨줘야한다.

@Around("execution(String kh.spring.controller.HomeController.outputProc(..))")
public Object aroundLog(ProceedingJoinPoint pjp) {
	
	Object returnValue = null;
	System.out.println("Around Before 입니다.");
	try {
		returnValue = pjp.proceed();
	} catch (Throwable e) {
		e.printStackTrace();
	}
	System.out.println("Around After 입니다.");
	return returnValue;
}

⑦ AOP 용어

용어들이 조금 생소해서 잘 알아둘 필요가 있다.

  • JoinPoint : Advice Method가 weaving될 수 있는 후보 메소드 전체
  • Weaving : advice 메소드가 적용되는 일련의 과정
  • PointCut 메소드 : JoinPoint 중에서 PointCut 표현식을 통해 선별된 Weaving Target 메소드
  • PointCut 표현식 : PointCut 메소드를 선별하기 위한 식
  • Advice : PointCut에 weaving 될 메소드
  • Advisor : PointCut과 advice 메소드를 하나의 클래스로 구성했을 때

댓글남기기