07_Framework
원래: System.out.println(userId);
== 효율이 좋은 편은 아님
log4j == 옛날에 사용 (문법적으로 쉬움)
slf4j == 요즘 사용
logback 위 두개를 서로 호환해줌
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<!-- Appenders : 끝에 붙이겠다는 의미 -->
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%p] %m%n" /> <!-- 로깅시간 및 로그 레벨, 메세지 나타내는 것 -->
</layout>
</appender>
<!-- //날짜별 로그 파일 생성 하기 -->
<appender name="dailyRollingFile" class="org.apache.log4j.DailyRollingFileAppender"> <!-- 날마다 로그 파일을 하나씩 만드는 것 -->
<param name="File" value="/logs/runtime.log" /> <!-- 파일 만들지 않아도 logs 파일 자동 생성해줌 -->
<param name="Append" value="true" />
<param name="encoding" value="UTF-8" />
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%p] %m%n" />
</layout>
</appender>
<!-- log level
trace -> debug -> info -> warn -> error -> fatal
- trace : 추적
- debug : 어느 시점에 무슨 값이 들어있는지 찍기 위한 용도
- info : ex.어떤 시점에 어떤 controller가 잘 돌고 있는지
- warn : 경고: 추후에 문제 생길 수 있음
- error : 정상 작동 X > 우리가 다룰 수 있는 범위
- fatal : 치명적인, 심각한 ERROR, 프로그램적으로 할 수 없는 것
-->
<!-- Application Loggers -->
<logger name="edu.kh.project">
<level value="debug" />
<appender-ref ref="dailyRollingFile"/>
</logger>
<!-- 3rdparty Loggers : 부가적인 log들 -->
<logger name="org.springframework.core">
<level value="info" />
</logger>
<logger name="org.springframework.beans">
<level value="info" />
</logger>
<logger name="org.springframework.context">
<level value="info" />
</logger>
<logger name="org.springframework.web">
<level value="info" />
</logger>
<!-- Root Logger -->
<root>
<priority value="debug" />
<appender-ref ref="console" />
</root>
</log4j:configuration>
package edu.kh.project.member.model.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import edu.kh.project.member.model.dao.MemberDAO;
import edu.kh.project.member.model.dto.Member;
@Service // 비즈니스 로직(데이터 가공, DAO 호출, 트랜잭션 제어)처리하는 클래스 명시
// + Bean으로 등록하는 어노테이션
public class MemberServiceImpl implements MemberService{
// org.slf4j.Logger : 로그를 작성할 수 있는 객체
// org.slf4j.LoggerFactory // 현재 클래스명 넣어주어야 함! (현재 클래스에 logger 사용하겠다)
private Logger logger = LoggerFactory.getLogger(MemberServiceImpl.class);
// @Autowired : 작성된 필드와
// Bean으로 등록된 객체 중 타입이 일치하는 Bean을
// 해당 필드에 자동 주입(Injection) 하는 어노테이션.
// == DI(Dependency Injection, 의존성 주입)
// -> 객체를 직접 만들지 않고, Spring이 만든걸 주입함(Spring에 의존)
@Autowired
private MemberDAO dao;
@Autowired // bean으로 등록된 객체 중 타입이 일치하는 객체를 DI(의존성 주입)
private BCryptPasswordEncoder bcrypt;
// 암호화가 필요한 곳? 로그인, 회원가입
@Override
public Member login(Member inputMember) {
// 로그 출력
logger.info("MemberServiceImpl.login() 실행"); // 정보 출력
logger.debug("memberEmail : " + inputMember.getMemberEmail() );
logger.warn("이건 경고 용도");
logger.error("이건 오류 발생 시");
// 암호화가 다 다른지 test 용도
// System.out.println("암호화 확인 1:" + bcrypt.encode( inputMember.getMemberPw() ) );
// System.out.println("암호화 확인 2:" + bcrypt.encode( inputMember.getMemberPw() ) );
// System.out.println("암호화 확인 3:" + bcrypt.encode( inputMember.getMemberPw() ) );
// System.out.println("암호화 확인 4:" + bcrypt.encode( inputMember.getMemberPw() ) );
// System.out.println("암호화 확인 5:" + bcrypt.encode( inputMember.getMemberPw() ) );
// dao 메서드 호출
Member loginMember = dao.login(inputMember);
// 입력받은 비밀번호(평문) --비교-- DB에서 조회한 비밀번호(암호화)
if(loginMember != null) { // 아이디가 일치하는 회원이 조회된 경우
// 입력한 pw, 암호화된 pw 같은지 확인
// 같을 경우 // 로그인 시 입력 // DB에서 조회한 것
if(bcrypt.matches(inputMember.getMemberPw(), loginMember.getMemberPw())) {
// 비밀번호를 유지하지 않기 위해서 로그인 정보에서 제거
loginMember.setMemberPw(null);
} else { // 다를 경우(비밀번호 잘못쳤을 경우)
loginMember = null;
}
}
return loginMember;
}
// 회원 가입 서비스
@Transactional
@Override
public int signUp(Member inputMember) {
// 비밀번호 암호화 (Bcrypt) 후 다시 inputMember 세팅
String encPw = bcrypt.encode(inputMember.getMemberPw());
inputMember.setMemberPw(encPw);
return dao.signUp(inputMember);
}
}
-> 바꾼 패턴대로 콘솔창에 출력됨
-> 서버 실행 후 로그인 시, 해당 내용 출력됨
-> 파일도 함께 생성됨
package edu.kh.project.common.aop;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
@Aspect // 공통 관심사(코드에서 똑같이 수행해야 되는 기능)가 작성된 클래스임을 지정
// Pointcut(타겟지정) + Advice(수행할 코드)
public class BeforeAspect {
private Logger logger = LoggerFactory.getLogger(BeforeAspect.class);
// execution([접근제한자] 리턴타입 클래스명 메소드명 ([파라미터]))
@Before("execution(* edu.kh.project..*Impl*.*(..))")
public void beforeLog(JoinPoint jp) { // Advice (수행할 코드)
// 매개변수 JoinPoint : AOP의 부가 기능이 적용된 대상의
// 객체, 메서드, 파라미터 정보를 얻을 수 있게 해주는 객체
// 대상 객체의 간단한 클래스명(패키지명 제외)
String className = jp.getTarget().getClass().getSimpleName();
// 메서드 선언부(== 메서드 시그니처)에서 메서드명만 얻어옴
String methodName = jp.getSignature().getName();
String str = "-----------------------------------------------\n";
str += "[Start] : " + className + " - " + methodName + "()\n";
// [Start] : MemberServiceImpl - login()
// jp.getArgs() : 파라미터 묶음(배열)
str += "[Parameter] : " + Arrays.toString(jp.getArgs()) + "\n";
logger.info(str);
}
}
== 위 메소드와 패턴이 일치할 경우, console 창에 찍힘
package edu.kh.project.common.aop;
import org.aspectj.lang.annotation.Pointcut;
// Pointcut을 모아둘 클래스
public class CommonPointcut {
@Pointcut("execution(* edu.kh.project..*Impl*.*(..))")
public void serviceImplPointcut() {}
}
package edu.kh.project.common.aop;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
@Aspect // 공통 관심사(코드에서 똑같이 수행해야 되는 기능)가 작성된 클래스임을 지정
// Pointcut(타겟지정) + Advice(수행할 코드)
public class BeforeAspect {
private Logger logger = LoggerFactory.getLogger(BeforeAspect.class);
// execution([접근제한자] 리턴타입 클래스명 메소드명 ([파라미터]))
//@Before("execution(* edu.kh.project..*Impl*.*(..))")
@Before("CommonPointcut.serviceImplPointcut()")
public void beforeLog(JoinPoint jp) { // Advice (수행할 코드)
// 매개변수 JoinPoint : AOP의 부가 기능이 적용된 대상의
// 객체, 메서드, 파라미터 정보를 얻을 수 있게 해주는 객체
// 대상 객체의 간단한 클래스명(패키지명 제외)
String className = jp.getTarget().getClass().getSimpleName();
// 메서드 선언부(== 메서드 시그니처)에서 메서드명만 얻어옴
String methodName = jp.getSignature().getName();
String str = "-----------------------------------------------\n";
str += "[Start] : " + className + " - " + methodName + "()\n";
// [Start] : MemberServiceImpl - login()
// jp.getArgs() : 파라미터 묶음(배열)
str += "[Parameter] : " + Arrays.toString(jp.getArgs()) + "\n";
logger.info(str);
}
}
== 동일하게 정상 작동됨
package edu.kh.project.common.aop;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import edu.kh.project.member.model.dto.Member;
@Component
@Aspect // 공통 관심사(코드에서 똑같이 수행해야 되는 기능)가 작성된 클래스임을 지정
// Pointcut(타겟지정) + Advice(수행할 코드)
public class BeforeAspect {
private Logger logger = LoggerFactory.getLogger(BeforeAspect.class);
// execution([접근제한자] 리턴타입 클래스명 메소드명 ([파라미터]))
//@Before("execution(* edu.kh.project..*Impl*.*(..))")
@Order(1) // 순서, 하나의 타겟에 대한 여러 advice 수행 시 순서 지정
@Before("CommonPointcut.serviceImplPointcut()")
public void beforeLog(JoinPoint jp) { // Advice (수행할 코드)
// 매개변수 JoinPoint : AOP의 부가 기능이 적용된 대상의
// 객체, 메서드, 파라미터 정보를 얻을 수 있게 해주는 객체
// 대상 객체의 간단한 클래스명(패키지명 제외)
String className = jp.getTarget().getClass().getSimpleName();
// 메서드 선언부(== 메서드 시그니처)에서 메서드명만 얻어옴
String methodName = jp.getSignature().getName();
String str = "-----------------------------------------------\n";
str += "[Start] : " + className + " - " + methodName + "()\n";
// [Start] : MemberServiceImpl - login()
// jp.getArgs() : 파라미터 묶음(배열)
str += "[Parameter] : " + Arrays.toString(jp.getArgs()) + "\n";
try {
// 접속자 IP 얻어오기
HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
Member loginMember = (Member)req.getSession().getAttribute("loginMember");
str += "[ip]" + getRemoteAddr(req);
if(loginMember != null) {
str += "(email:" + loginMember.getMemberEmail() + ")";
}
}catch (Exception e) {
str += "[스프링 스케쥴러]";
}
logger.info(str);
}
public static String getRemoteAddr(HttpServletRequest request) {
String ip = null;
ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-RealIP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("REMOTE_ADDR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
== ip 콘솔창에 잘 찍힘
package edu.kh.project.common.aop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AfterAspect {
private Logger logger = LoggerFactory.getLogger(AfterAspect.class);
@Order(3)
@After("CommonPointcut.serviceImplPointcut()")
public void afterLog() {
logger.info("----------------------------------------------\n\n");
}
}
** 실행 순서 **
Before (1)
Around (2) = Before + After
After(3)
== 실행시간 측정 시 많이 사용
package edu.kh.project.common.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AroundAspect {
private Logger logger = LoggerFactory.getLogger(AroundAspect.class);
// 전처리, 후처리를 모두 해결하고자 할 때 사용 하는 어드바이스
// proceed() 메소드 호출 전 : @Before advice 작성
// proceed() 메소드 호출 후 : @After advice 작성
// 메소드 마지막에 proceed()의 반환값을 리턴해야함.
@Order(2)
@Around("CommonPointcut.serviceImplPointcut()")
public Object aroundServiceLogs(ProceedingJoinPoint pp) throws Throwable {
// @Around advice는 JoinPoint Interface가 아닌
// 하위 타입인 ProceedingJoinPoint를 사용해야 함.
long startMs = System.currentTimeMillis(); // 서비스 시작 시의 ms 값
Object obj = pp.proceed(); // 여기가 기준
long endMs = System.currentTimeMillis(); // 서비스 종료 시의 ms 값
String str = "Running Time : " + (endMs- startMs) + "ms";
logger.info(str);
return obj; // Around는 꼭 Object로 반환! 반환되는 Object에는 proceed가 꼭 담겨져 있어야 함! -> 그렇지 않으면 오류 발생
}
}