기록
[Spring] 7. Log, Tiles 본문

엔코아 플레이데이터(Encore Playdata) Backend 2기 백엔드 개발 부트캠프 (playdata.io)
백엔드 개발 부트캠프
백엔드 기초부터 배포까지! 매력있는 백엔드 개발자 포트폴리오를 완성하여 취업하세요.
playdata.io
1. Log
기존에 콘솔에 출력하기 위해 사용하던 System.out.println()은 static 변수로, PrintStream 클래스를 사용한다.
이 클래스 내부 구조는, 출력을 하기 위해 BufferedStream을 사용한다. 즉, Buffer에 출력할 데이터를 임시 저장 후 출력 하는 구조로 되어있기 떄문에, Buffer에 데이터가 들어올 때 까지 스레드에 lock을 걸어놓게 된다.
만일 실무에서 기존 출력을 사용하게 된다면, 기능 동작 중에 출력을 위해 스레드에 lock이 걸리기 때문에, 성능이 매우 저하되는 문제가 발생할 수 있다. 때문에 일반 출력이 아니라 Log를 사용한 출력을 사용해야한다.
1. log4j.xml
스프링 프로젝트 생성 시 자동으로 만들어주는 log4j.xml이다.
기존과 달라진 점은, 날짜나 시간 주기로 Log를 생성하여 기록하기 위한 라이브러리를 추가한 것과 root의 default값이 warn이었던 것을 debug로 변경한 점이다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "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="%-5p: %c - %m%n" />
</layout>
</appender>
<!-- 날짜나 시간 주기로 Log를 생성하여 기록하기 위해 -->
<appender class="org.apache.log4j.DailyRollingFileAppender"
name="dailyFileAppender">
<param name="File" value="C:\\spring\\logs\\output.log" />
<param name="Append" value="true" />
<layout class="org.apache.log4j.PatternLayout">
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<param name="ConversionPattern" value="[%d{HH:mm:ss}][%-5p](%F:%L)-%m%n" />
</layout>
</appender>
<!-- Application Loggers -->
<logger name="com.lhs.sts">
<level value="info" />
</logger>
<!-- 3rdparty Loggers -->
<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 -->
<!-- default값이 info인 부분을, SQL Query를 찍어보기 위해 debug로 변경 -->
<root>
<priority value="debug" />
<appender-ref ref="console" />
</root>
</log4j:configuration>
2. AOP로 구현한 Log 실습
AOP를 구현하기 위해 필요한 것은 다음과 같다.
1. aspect
공통 기능 클래스의 내부 기능 메서드를 의미한다. ex) invoke()
2. advice
공통 기능 클래스 자체를 의미한다. ex) LoggingAdvice
3. joinpoint
기능 실행 전(before) or 후(after) or 전 + 후(invoke) 와 같이 언제 공통 기능을 붙일지를 의미한다.
method 결합점만 제공한다 = 클래스 레벨에는 공통 기능을 붙일 수 없다. 메서드 레벨에만 가능하다.
4. pointcut
공통 기능을 붙일 대상을 지정한다. ex) <property name="interceptorNames"> 로 가져온 메서드들
5. target
공통 기능을 붙일 클래스를 지정한다. ex) <property name="target" ref="calcTarget"></property>
6. weaving
proxy를 통해 공통 기능 클래스 + 메인 기능 클래스를 붙이는 것을 의미한다.
1. @Component
만든 보조기능을, 스프링의 Component Scan 대상으로 하기 위해 @Component를 붙인다.
2. @Aspect
aspect는 보조기능 클래스를 의미하기 때문에, 클래스 레벨에 해당 어노테이션을 붙인다.
3. @Before("execution(* com.lhs.sts.*.service.*.*(..)) or execution(* com.lhs.sts.*.dao.*.*(..))")
AOP에서 pointcut은 공통 기능을 붙일 메서드, target은 공통 기능을 붙일 클래스를 의미한다.
이때, 가장 정교한 pointcut을 만들기 위해 execution을 사용하였다.
1. service.*.*(..)
첫 번째 * : service 패키지 내 클래스들 -> [target]
두 번째 * : service 패키지 클래스 내 메서드들 -> [pointcut]
(..) : 파라미터들
2. dao.*.*(..)
첫 번째 * : dao 패키지 내 클래스들 -> [target]
두 번째 * : dao 패키지 클래스 내 메서드들 -> [pointcut]
(..) : 파라미터들
4. public void startLog(JoinPoint jp)
보조기능과 메인기능의 결합점을 의미한다. 즉, 메인 기능에서 로그를 호출하면 보조 기능으로 들어와 실행하는 것을 의미한다.
1. 컨트롤러에서 logger 호출
logger.info("viewName: {}", viewName);
2. advice로 들어와, 해당 logger를 출력한다.
logger.info("1:" + Arrays.toString(jp.getArgs()));
이러한 메서드를 통해 메인기능에서 log를 호출할 시 파라미터를 받아와 출력을 하게 된다.
5. weaving
컨트롤러 내에서 logger.info("viewName: {}", viewName); 를 실행하는 시점을 의미한다.
* 주의할 점은, @Around가 붙은 메서드는 Object값을 return해야 한다는 점이다.
@Component
@Aspect
public class LoggingAdvice {
private static final Logger logger = LoggerFactory.getLogger(LoggingAdvice.class);
// target 메서드의 파라미터 등 정보를 출력.
// execution으로 정의한 메서드 호출 전에 로그를 찍기 위한 @Before
@Before("execution(* com.lhs.sts.*.service.*.*(..)) or execution(* com.lhs.sts.*.dao.*.*(..))")
public void startLog(JoinPoint jp) {
logger.info("-------------------------------------");
logger.info("-------------------------------------");
// 전달되는 모든 파라미터들을 Object의 배열로 가져온다.
logger.info("1:" + Arrays.toString(jp.getArgs()));
// 해당 Advice의 타입을 알아낸다.
logger.info("2:" + jp.getKind());
// 실행하는 대상 객체의 메소드에 대한 정보를 알아낼 때 사용.
logger.info("3:" + jp.getSignature().getName());
// target 객체를 알아낼 때 사용.
logger.info("4:" + jp.getTarget().toString());
// Advice를 행하는 객체를 알아낼 때 사용.
logger.info("5:" + jp.getThis().toString());
}
// 메서드 호출 후에 로그를 찍기 위한 @After
@After("execution(* com.lhs.sts.*.service.*.*(..)) or " + "execution(* com.lhs.sts.*.dao.*.*(..))")
public void after(JoinPoint jp) {
logger.info("-------------------------------------");
logger.info("-------------------------------------");
// 전달되는 모든 파라미터들을 Object의 배열로 가져온다.
logger.info("1:" + Arrays.toString(jp.getArgs()));
// 해당 Advice의 타입을 알아낸다.
logger.info("2:" + jp.getKind());
// 실행하는 대상 객체의 메소드에 대한 정보를 알아낼 때 사용.
logger.info("3:" + jp.getSignature().getName());
// target 객체를 알아낼 때 사용.
logger.info("4:" + jp.getTarget().toString());
// Advice를 행하는 객체를 알아낼 때 사용
logger.info("5:" + jp.getThis().toString());
}
// 예외가 발생했을 땐 @Before, @After로 해결이 불가능 하기 때문에 @Around 추가, 예외 처리를 위해 Throwable
// 어떤 쿼리를 실행할 때 동작 시간을 파악하기 위한 currentTimeMillis()
@Around("execution(* com.lhs.sts.*.service.*.*(..)) or " + "execution(* com.lhs.sts.*.dao.*.*(..))")
public Object timeLog(ProceedingJoinPoint pjp) throws Throwable {
long startTime = System.currentTimeMillis();
logger.info(Arrays.toString(pjp.getArgs()));
// 실제 타겟을 실행하는 부분이다. 이 부분이 없으면 advice가 적용된 메소드가 동작하지 않는다.
Object result = pjp.proceed(); // proceed는 Exception 보다 상위 Throwable을 처리해야 한다.
long endTime = System.currentTimeMillis();
// target 메소드의 동작 시간을 출력한다.
logger.info(pjp.getSignature().getName() + " : " + (endTime - startTime));
logger.info("==============================");
// Around를 사용할 경우 반드시 Object를 리턴해야 한다.
return result;
}
}
3. Tiles
기존에는 jsp 페이지를 호출에 전체 페이지를 보여주는 방식이었다면, tiles는 화면을 레이아웃 단위로 분할하여 고정해두고, 요청에 따라 body 부분을 갈아끼우는 방식을 의미한다.
즉, header.jsp, footer.jsp, side.jsp를 만들어 layout.jsp에 고정하고, 요청에 따라 layout.jsp의 body부분을 갈아끼우며 화면에 출력하는 방식이다.


tiles를 사용하기 위해선, 몇 가지 설정을 추가해야한다.
1. pom.xml
<!-- ADD tiles -->
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-core</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-jsp</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-servlet</artifactId>
<version>2.2.2</version>
</dependency>
2. servlet-context.xml
<beans:bean id="tilesConfigurer"
class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
<beans:property name="definitions">
<beans:list>
<beans:value>classpath:tiles/*.xml</beans:value> <!-- 2. -->
</beans:list>
</beans:property>
<beans:property name="preparerFactoryClass"
value="org.springframework.web.servlet.view.tiles2.SpringBeanPreparerFactory" />
</beans:bean>
<beans:bean id="viewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<beans:property name="viewClass"
value="org.springframework.web.servlet.view.tiles2.TilesView" />
</beans:bean>
이 때 주의할 점은, 기존엔 jsp 파일명을 return했기 때문에 InternalResourceViewResolver를 사용했지만,
이젠 tiles를 거쳐서 jsp파일로 가야하기 때문에 UrlBasedViewResolver 를 사용해야한다.
즉, 기존의 ViewResolver를 주석처리 후 사용하면 된다.
3. tiles_member.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN" "http://tiles.apache.org/dtds/tiles-config_2_0.dtd">
<tiles-definitions>
<!-- 화면에 기본으로 배치할 레이아웃들 -->
<definition name="baseLayout" template="/WEB-INF/views/common/layout.jsp">
<put-attribute name="title" value="" />
<put-attribute name="header" value="/WEB-INF/views/common/header.jsp" />
<put-attribute name="side" value="/WEB-INF/views/common/side.jsp" />
<put-attribute name="body" value="" />
<put-attribute name="footer" value="/WEB-INF/views/common/footer.jsp" />
</definition>
<!-- 갈아끼울 화면 정보들 -->
<definition name="main" extends="baseLayout">
<put-attribute name="title" value="메인페이지" />
<put-attribute name="body" value="/WEB-INF/views/main.jsp" />
</definition>
<definition name="/member/listMembers" extends="baseLayout">
<put-attribute name="title" value="회원목록창" />
<put-attribute name="body" value="/WEB-INF/views/member/listMembers.jsp" />
</definition>
<definition name="/member/memberForm" extends="baseLayout">
<put-attribute name="title" value="회원가입창" />
<put-attribute name="body" value="/WEB-INF/views/member/memberForm.jsp" />
</definition>
<definition name="/member/loginForm" extends="baseLayout">
<put-attribute name="title" value="로그인창" />
<put-attribute name="body" value="/WEB-INF/views/member/loginForm.jsp" />
</definition>
</tiles-definitions>
기존엔 Controller에서 ModelAndView에 view 정보를 담아 return하면, 해당 jsp 파일을 찾아갔다.
하지만 이젠 사용하는 ViewResolver가 바뀌었기 때문에, tiles_member.xml의 name에 적힌 경로를 통해 들어와 title과 body의 정보가 적힌 jsp로 이동, 갈아끼우게 된다.

4. RedirectAttributes
Controller 내에서 redirect 시, 데이터를 포함하며 redirect 하고 싶은 경우가 있다.
기존까지 배운 내용에 따르면, redirect:"uri?data='data'" 의 방식으로 redirect를 하면 되지만, 이 방식은 데이터가 화면에 노출되는 치명적인 단점이 존재한다.
때문에 스프링은 RedirectAttributes라는 객체를 지원하는데, 해당 객체를 사용하면 마치 모델을 사용하듯 데이터를 가지고 원하는 곳으로 redirect를 할 수 있게 된다.
@Override
@RequestMapping(value = "/member/login.do", method = RequestMethod.POST)
public ModelAndView login(@ModelAttribute("member") MemberVO member, RedirectAttributes rAttr,
HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mav = new ModelAndView();
memberVO = memberService.login(member);
if (memberVO != null) {
HttpSession session = request.getSession();
session.setAttribute("member", memberVO);
session.setAttribute("isLogOn", true);
mav.setViewName("redirect:/member/listMembers.do");
}
else {
rAttr.addAttribute("result", "loginFailed");
mav.setViewName("redirect:/member/loginForm.do");
}
return mav;
}
5. 33일차 후기
표준 출력의 어플리케이션 전체 성능을 떨어트릴 수 있는 점을, Log를 사용하면 해결할 수 있다. 또한, Log를 사용하면 console 뿐만아니라 Log를 파일로 남길 수 있는 장점도 존재한다.
때문에 실무에선 표준 출력이 아니라 모든 기록을 Log를 사용해 해결하게 된다.
tiles는 화면을 구조화하여 간단히 바꿔치기하여 사용할 수 있는, 프론트엔드에서 많이 사용하는 개념이다. 비록 현재 백엔드 부트캠프를 진행 중이기 때문에 해당 기능을 자세하게 뜯어볼 필요는 없지만, 어떤 구조로 tiles들을 배치하는지 알아둔다면 추후 간단한 화면을 구성해 테스트를 진행하는데 도움이 될 것 같다는 생각이 들었다.
'Bootcamp > Spring' 카테고리의 다른 글
| [Spring] 9. REST, Interceptor (0) | 2023.08.23 |
|---|---|
| [Spring] 8. File Up / Download, Thumbnail (0) | 2023.08.22 |
| [Spring] 6. STS (0) | 2023.08.19 |
| [Spring] 5. Transaction, Annotation (0) | 2023.08.17 |
| [Spring] 4. Spring Mybatis (0) | 2023.08.16 |