Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

개발자입니다

[비트캠프] 84일차(18주차2일) - Spring Framework(Thymeleaf, ControllerAdvice, log4j 2), myapp-63 본문

네이버클라우드 AIaaS 개발자 양성과정 1기/Spring Framework, Spring Boot

[비트캠프] 84일차(18주차2일) - Spring Framework(Thymeleaf, ControllerAdvice, log4j 2), myapp-63

끈기JK 2023. 3. 7. 13:03

 

62. Thymeleaf

 

① 태그 반복 생성

<tr data-th-each="student : ${students}">

...                           for(Student student : request.getAttribute("students")) {...} 와 같다.

</tr>

 

② 태그의 콘텐트 설정

<td>100</td>

시작태그, 콘텐트, 끝태그  |  모두를 element=tag 라 부른다.

<td data-th-text="${student.no}">1</td>  에서 ${student.no} 값이 1 부분에 설정된다.

 

 

③ 링크 설정

<a href="view?no=1">홍길동</a>

<a href="view?no=1" data-th-href="@{view(no=${student.no})}"   에서 view: 상대 경로, no 부분: 요청 파라미터

                                  data-th-text="${student.name}">이름</a> 에서 ${student.name} 를 이름 부분에 설정한다.

 

context root => /view  →  /web/view    context root = web app. 경로

relative path => view  →  /web/app/student/view    현재 경로

absolute path => ~/view  →  http://localhost:8080/view    서버 루트

 

 

④ switch 문법

<td><span>전공</span></td>  에서 span 은 UI 에 영향을 끼치지 않는다.

 

<td data-th-switch="${student.level}">

  <span data-th-case="~">비전공자</span>  에서 span 사용하는 이유는 data-th 를 태그에 붙여야 하기 때문이다.

 

 

⑤ 보관소에서 값 꺼내기

${param.keyword}  → request.getParameter("keyword") 

${student}  → request.getAttribute("student")  // ServletRequest. request 보관소는 생략 가능

${session.loginUser}  → session.getAttribute("loginuser")  // HttpSession

${application.boardService}  → application.getAttribute("boardService")  // ServletContext

 

 

 

63. 예외 처리와 @ControllerAdvice

 

각 Controller 마다 @ExceptionHandler 를 붙일 수 있다.

handler(...) 는 자신이 소속된 Controller의 request handler에서 예외 발생할 때 호출된다.

 

@ControllerAdvice ControllerAdvice 에 @ExceptionHandler 를 붙일 수 있다.

해당 메서드 본문은 모든 컨트롤러의 request handler 에서 예외가 발생할 때 호출된다.

 

 

 

log4j2

 

central.sonatype.com 에서 "log4j core" 검색해서 코드 복사하고 build.gradle 에 붙인 다음 $ gradle eclipse 한다.

Referenced Libraries 에 아래 추가된다.

 

 

myapp-server/src/main/resources/ 에 xml 저장하는데 반드시 log4j2.xml 이름으로 파일 생성해야 한다.

 

 

 

63. Log4j 2.x

 

① level

로그의 의미

 

 All < Trace < Debug < Info < Warn < Error < Fatal

Trace : 실행 흐름을 상세하게 추적할 때

Debug : 개발자가 실행 상태 확인을 위해 기록하는 것

Info : 실행 상황 기록. 운영자에게 알릴 내

Warn : 실행은 성공했지만 문제가 발생할 수 있는 여지는 있는 경우. 관리자의 주의가 필요한 경우

Error : 실행 오류

Fatal : 시스템을 더이상 실행할 수 없는 상황. App. 재시작이 필요한 경우

 

 

② Log 사용법

Logger log = LogManager.getLogger(getClass());  // org.apache.logging.log4j

 

<Root level="warn"> ~ </Root> 에서 level 에 설정한 수준 이상으로 로깅한다.

trace 는 trace ~ fatal 까지 모두 로깅한다.

debug 는 debug ~ fatal 까지 모두 로깅한다.

warn 은 warn ~ fatal 까지 모두 로깅한다.

 

 

 

### 63. 기타 스프링 관련 설정하기 

 

### 63. 기타 스프링 관련 설정하기 
- 요청 핸들러에서 발생한 예외를 처리하는 방법 
- log4j 2.x 설정하고 사용하는 방법 
- mybatis에 log4j 적용하는 방법

 

로그 출력을 위해 log4j2.xml 파일 정의한다.

<Configuration status="WARN"> 설정시 log4j2가 설정 파일을 읽고 구성을 초기화하는 동안 출력할 메시지의 수준을 의미한다.

<!-- resources/log4j2.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">

  <!-- 로그 출력 형태를 정의한다. -->
  <Appenders>
   
    <!-- 표준 출력 장치인 콘솔로 출력하는 방식을 정의한다. -->
    <Console name="stdout" target="SYSTEM_OUT">
      <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd} [%t] %c{1} - %msg%n" />
    </Console>
    
    <File name="file" fileName="./logs/file/sample.log" append="false">
      <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd} [%t] %c{1} - %msg%n" />
    </File>
  </Appenders>
     
  <!-- 로그 출력을 적용할 대상과 로그 출력 레벨을 지정한다. -->
  <Loggers>
    <Logger name="bitcamp.myapp.dao" level="debug" additivity="false">
      <AppenderRef ref="stdout" /> 
    </Logger>          

    <Logger name="bitcamp.myapp" level="trace" additivity="false">
      <AppenderRef ref="stdout" /> 
    </Logger>
        
    <!-- Root => 모든 대상에 적용할 기본 로그 출력 형식과 레벨 -->
    <Root level="warn" additivity="false">
      <AppenderRef ref="stdout" /> <!-- 로그를 출력할 때 사용할 출력 방식 지정 -->
    </Root>
  </Loggers>
</Configuration>

 

Logger 객체를 현재 클래스 명으로 가져온다.

객체 생성은 trace 로 로깅한다.

SqlSessionFactory 에 Mybatis 로깅 기능을 활성화시킨다.

package bitcamp.myapp.config;

import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ITemplateResolver;


@ComponentScan(
    value = "bitcamp.myapp",
    excludeFilters = {
        @Filter(
            type = FilterType.REGEX,
            pattern = {"bitcamp.myapp.controller.*"})
    })
@PropertySource("classpath:/bitcamp/myapp/config/jdbc.properties")
@MapperScan("bitcamp.myapp.dao")
@EnableTransactionManagement
public class RootConfig {

  Logger log = LogManager.getLogger(getClass());

  {
    log.trace("RootConfig 생성됨!");
  }

  @Bean
  public DataSource dataSource(
      @Value("${jdbc.driver}") String jdbcDriver,
      @Value("${jdbc.url}") String url,
      @Value("${jdbc.username}") String username,
      @Value("${jdbc.password}") String password) {
    log.trace("DataSource 생성됨!");
    DriverManagerDataSource ds = new DriverManagerDataSource();
    ds.setDriverClassName(jdbcDriver);
    ds.setUrl(url);
    ds.setUsername(username);
    ds.setPassword(password);
    return ds;
  }

  @Bean
  public PlatformTransactionManager transactionManager(DataSource dataSource) throws Exception {
    log.trace("PlatformTransactionManager 객체 생성! ");
    return new DataSourceTransactionManager(dataSource);
  }

  @Bean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource, ApplicationContext appCtx) throws Exception {
    log.trace("SqlSessionFactory 객체 생성!");

    // Mybatis 로깅 기능을 활성화시킨다.
    org.apache.ibatis.logging.LogFactory.useLog4J2Logging();

    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource);
    factoryBean.setTypeAliasesPackage("bitcamp.myapp.vo");
    factoryBean.setMapperLocations(appCtx.getResources("classpath*:bitcamp/myapp/mapper/*Mapper.xml"));
    return factoryBean.getObject();
  }

  @Bean
  public TilesConfigurer tilesConfigurer() {
    log.trace("TilesConfigurer 객체 생성!");
    TilesConfigurer configurer = new TilesConfigurer();
    configurer.setDefinitions("/WEB-INF/defs/app-tiles.xml", "/WEB-INF/defs/admin-tiles.xml");
    return configurer;
  }

  // Thymeleaf 템플릿에 관한 정보를 설정한다.
  @Bean
  public SpringResourceTemplateResolver templateResolver(ApplicationContext applicationContext){
    log.trace("SpringResourceTemplateResolver 객체 생성!");
    SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
    templateResolver.setApplicationContext(applicationContext);
    templateResolver.setPrefix("/WEB-INF/thymeleaf/");
    templateResolver.setSuffix(".html");
    templateResolver.setTemplateMode(TemplateMode.HTML);
    templateResolver.setCacheable(false);
    return templateResolver;
  }

  // Thymeleaf 템플릿을 실행할 엔진을 만든다.
  @Bean
  public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver){
    log.trace("SpringTemplateEngine 객체 생성!");
    SpringTemplateEngine templateEngine = new SpringTemplateEngine();
    templateEngine.setTemplateResolver(templateResolver);
    templateEngine.setEnableSpringELCompiler(true);
    return templateEngine;
  }

}
package bitcamp.myapp.config;

import java.nio.charset.StandardCharsets;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
import org.springframework.web.servlet.view.UrlBasedViewResolver;
import org.springframework.web.servlet.view.tiles3.TilesView;
import org.thymeleaf.spring5.ISpringTemplateEngine;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import bitcamp.myapp.controller.StudentController;
import bitcamp.myapp.controller.TeacherController;
import bitcamp.myapp.web.interceptor.AuthInterceptor;

//@Configuration

@ComponentScan(
    value = "bitcamp.myapp.controller",
    excludeFilters = {
        @Filter(
            type = FilterType.ASSIGNABLE_TYPE,
            classes = {StudentController.class, TeacherController.class})
    })

// WebMVC 관련 설정을 처리하고 싶다면 다음 애노테이션을 추가하라!
// => WebMVC 관련 설정을 수행하는 클래스를 정의했으니,
//    WebMvcConfigurer 구현체를 찾아
//    해당 인터페이스에 정의된대로 메서드를 호출하여
//    설정을 수행하라는 의미다!
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {

  Logger log = LogManager.getLogger(getClass());

  {
    log.trace("AppConfig 생성됨!");
  }

  @Bean
  public MultipartResolver multipartResolver() {
    log.trace("MultipartResolver 생성됨!");
    return new StandardServletMultipartResolver();
  }

  @Bean
  public ViewResolver viewResolver() {
    log.trace("InternalResourceViewResolver 생성됨!");
    // 페이지 컨트롤러가 jsp 경로를 리턴하면
    // viewResolver가 그 경로를 가지고 최종 jsp 경로를 계산한 다음에
    // JstlView를 통해 실행한다.
    InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
    viewResolver.setViewClass(JstlView.class);
    viewResolver.setPrefix("/WEB-INF/jsp/");
    viewResolver.setSuffix(".jsp");
    viewResolver.setOrder(3);
    return viewResolver;
  }

  @Bean
  public ViewResolver tilesViewResolver() {
    log.trace("UrlBasedViewResolver 생성됨!");
    UrlBasedViewResolver vr = new UrlBasedViewResolver();

    // Tiles 설정에 따라 템플릿을 실행할 뷰 처리기를 등록한다.
    vr.setViewClass(TilesView.class);

    // request handler가 리턴한 view name 앞에 일반 페이지임을 표시하기 위해 접두사를 붙인다.
    vr.setPrefix("app/");

    // 뷰리졸버의 우선 순위를 InternalResourceViewResolver 보다 우선하게 한다.
    vr.setOrder(2);
    return vr;
  }

  // 실행할 Thymeleaf 템플릿을 결정하는 일을 한다.
  @Bean
  public ThymeleafViewResolver viewResolver(ISpringTemplateEngine templateEngine){
    log.trace("ThymeleafViewResolver 생성됨!");
    ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
    viewResolver.setTemplateEngine(templateEngine);

    // Content-Type을 설정한다.
    // => 만약 설정하지 않는다면 자바의 Uncode2(UTF-16)을 ISO-8859-1로 변환시킨다.
    //    즉 영어는 제대로 변환되지만 한글을 '?'로 변환된다.
    viewResolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
    viewResolver.setOrder(1);

    // 페이지 컨트롤러의 request handler가 무엇을 리턴하든지 간에
    // Thymeleaf 템플릿 엔진을 사용하겠다는 의미다!
    viewResolver.setViewNames(new String[] {"*"});
    //viewResolver.setViewNames(new String[] {".html", ".xhtml"});
    return viewResolver;
  }


  // WebMvcConfigurer 규칙에 맞춰 인터셉터를 등록한다.
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    log.trace("AppConfig.addInterceptors() 호출됨!");
    registry.addInterceptor(
        new AuthInterceptor()).addPathPatterns("/**/*insert", "/**/*update", "/**/*delete");
  }
}
package bitcamp.myapp.config;

import java.nio.charset.StandardCharsets;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
import org.springframework.web.servlet.view.UrlBasedViewResolver;
import org.springframework.web.servlet.view.tiles3.TilesView;
import org.thymeleaf.spring5.ISpringTemplateEngine;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import bitcamp.myapp.controller.AuthController;
import bitcamp.myapp.controller.BoardController;
import bitcamp.myapp.controller.DownloadController;
import bitcamp.myapp.web.interceptor.AdminCheckInterceptor;
import bitcamp.myapp.web.interceptor.AuthInterceptor;

//@Configuration

@ComponentScan(
    value = "bitcamp.myapp.controller",
    excludeFilters = {
        @Filter(
            type = FilterType.ASSIGNABLE_TYPE,
            classes = {AuthController.class, BoardController.class, DownloadController.class})
    })
@EnableWebMvc // 프론트 컨트롤러 각각에 대해 설정해야 한다.
public class AdminConfig implements WebMvcConfigurer {

  Logger log = LogManager.getLogger(getClass());

  {
    log.trace("AdminConfig 생성됨!");
  }

  @Bean
  public ViewResolver viewResolver() {
    log.trace("InternalResourceViewResolver 생성됨!");
    InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
    viewResolver.setViewClass(JstlView.class);
    viewResolver.setPrefix("/WEB-INF/jsp/");
    viewResolver.setSuffix(".jsp");
    viewResolver.setOrder(3);
    return viewResolver;
  }

  @Bean
  public ViewResolver tilesViewResolver() {
    log.trace("UrlBasedViewResolver 생성됨!");
    UrlBasedViewResolver vr = new UrlBasedViewResolver();
    vr.setViewClass(TilesView.class);
    vr.setPrefix("admin/");
    vr.setOrder(2);
    return vr;
  }

  @Bean
  public ThymeleafViewResolver viewResolver(ISpringTemplateEngine templateEngine){
    log.trace("ThymeleafViewResolver 생성됨!");
    ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
    viewResolver.setTemplateEngine(templateEngine);
    viewResolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
    viewResolver.setOrder(1);
    viewResolver.setViewNames(new String[] {"*"});
    return viewResolver;
  }

  // WebMvcConfigurer 규칙에 맞춰 인터셉터를 등록한다.
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    log.trace("AdminConfig.addInterceptors() 호출됨!");
    registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/**");
    registry.addInterceptor(new AdminCheckInterceptor()).addPathPatterns("/**");
  }
}

 

GlobalControllerAdvice.java 파일을 생성한다.

@ControllerAdvice 애노테이션 붙인다.

@ExceptionHandler 애노테이션 붙여서 request handler 에서 오류 발생시 처리하도록 한다.

로깅 코드 삽입한다.

package bitcamp.myapp.controller;

import javax.servlet.http.HttpServletRequest;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

// 페이지 컨트롤러의 공통 설정을 수행하는 클래스
@ControllerAdvice
public class GlobalControllerAdvice {

  Logger log = LogManager.getLogger(getClass());

  {
    log.trace("GlobalControllerAdvice 생성!");
  }

  @ExceptionHandler
  public String handle(Exception e, HttpServletRequest request, Model model) {
    log.error(request.getRequestURI() + " 요청 처리 중 오류 발생!", e);
    model.addAttribute("url", request.getRequestURI());
    model.addAttribute("class", e.getClass().getName());
    model.addAttribute("message", e.getMessage());
    return "error";
  }
}

 

PageController 에 로깅 코드 추가한다.

package bitcamp.myapp.controller;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestMapping;
import bitcamp.myapp.service.StudentService;
import bitcamp.myapp.service.TeacherService;
import bitcamp.myapp.vo.Member;

@Controller
public class AuthController {

  Logger log = LogManager.getLogger(getClass());

  {
    log.trace("AuthController 생성됨!");
  }

  @Autowired private StudentService studentService;
  @Autowired private TeacherService teacherService;

  @RequestMapping("/auth/form")
  public void form(@CookieValue(required = false) String email,
      Model model,
      HttpSession session) {
    model.addAttribute("email", email);
    if (session.getAttribute("error") != null) {
      model.addAttribute("error", session.getAttribute("error"));
    }
  }

  @RequestMapping("/auth/login")
  public String login(
      String usertype,
      String email,
      String password,
      String saveEmail,
      HttpServletResponse response,
      HttpSession session,
      Model model) {

    if (saveEmail != null) {
      Cookie cookie = new Cookie("email", email);
      cookie.setMaxAge(60 * 60 * 24 * 30); // 30일 동안 유지
      response.addCookie(cookie);

    } else {
      Cookie cookie = new Cookie("email", "");
      cookie.setMaxAge(0);
      response.addCookie(cookie);
    }

    Member member = null;
    switch (usertype) {
      case "student":
        member = studentService.get(email, password);
        break;
      case "teacher":
        member = teacherService.get(email, password);
        break;
    }

    if (member != null) {
      session.setAttribute("loginUser", member);
      session.removeAttribute("error");
      return "redirect:../../";
    } else {
      session.setAttribute("error", "loginfail");
      return "redirect:form";
    }

  }

  @RequestMapping("/auth/logout")
  public String logout(HttpSession session) {
    session.invalidate();
    return "redirect:../../";
  }

  @RequestMapping("/auth/fail")
  public void fail() {
  }

}

 

package bitcamp.myapp.controller;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import bitcamp.myapp.service.BoardService;
import bitcamp.myapp.vo.Board;
import bitcamp.myapp.vo.BoardFile;
import bitcamp.myapp.vo.Member;

@Controller
@RequestMapping("/board")
public class BoardController {

  Logger log = LogManager.getLogger(getClass());

  {
    log.trace("BoardController 생성됨!");
  }

  // ServletContext 는 요청 핸들러의 파라미터로 주입 받을 수 없다.
  // 객체의 필드로만 주입 받을 수 있다.
  @Autowired private ServletContext servletContext;
  @Autowired private BoardService boardService;

  @GetMapping("form")
  public void form() {
  }

  @PostMapping("insert")
  public void insert(
      Board board,
      List<MultipartFile> files,
      Model model,
      HttpSession session) throws Exception{

    Member loginUser = (Member) session.getAttribute("loginUser");

    Member writer = new Member();
    writer.setNo(loginUser.getNo());
    board.setWriter(writer);

    List<BoardFile> boardFiles = new ArrayList<>();
    for (MultipartFile file : files) {
      if (file.isEmpty()) {
        continue;
      }

      String filename = UUID.randomUUID().toString();
      file.transferTo(new File(servletContext.getRealPath("/board/upload/" + filename)));

      BoardFile boardFile = new BoardFile();
      boardFile.setOriginalFilename(file.getOriginalFilename());
      boardFile.setFilepath(filename);
      boardFile.setMimeType(file.getContentType());
      boardFiles.add(boardFile);
    }
    board.setAttachedFiles(boardFiles);

    boardService.add(board);
  }

  @GetMapping("list")
  public void list(String keyword, Model model) {
    log.debug("BoardController.list() 호출됨!");
    model.addAttribute("boards", boardService.list(keyword));
  }

  @GetMapping("view")
  public void view(int no, Model model) {
    model.addAttribute("board", boardService.get(no));
  }

  @PostMapping("update")
  public String update(
      Board board,
      List<MultipartFile> files,
      Model model,
      HttpSession session) throws Exception {

    Member loginUser = (Member) session.getAttribute("loginUser");

    Board old = boardService.get(board.getNo());
    if (old.getWriter().getNo() != loginUser.getNo()) {
      return "redirect:../auth/fail";
    }

    List<BoardFile> boardFiles = new ArrayList<>();
    for (MultipartFile file : files) {
      if (file.isEmpty()) {
        continue;
      }

      String filename = UUID.randomUUID().toString();
      file.transferTo(new File(servletContext.getRealPath("/board/upload/" + filename)));

      BoardFile boardFile = new BoardFile();
      boardFile.setOriginalFilename(file.getOriginalFilename());
      boardFile.setFilepath(filename);
      boardFile.setMimeType(file.getContentType());
      boardFile.setBoardNo(board.getNo());
      boardFiles.add(boardFile);
    }
    board.setAttachedFiles(boardFiles);

    boardService.update(board);

    return "board/update";
  }

  @PostMapping("delete")
  public String delete(int no, Model model, HttpSession session) {
    Member loginUser = (Member) session.getAttribute("loginUser");

    Board old = boardService.get(no);
    if (old.getWriter().getNo() != loginUser.getNo()) {
      return "redirect:../auth/fail";
    }
    boardService.delete(no);

    return "board/delete";
  }

  @GetMapping("filedelete")
  public String filedelete(int boardNo, int fileNo, HttpSession session) {
    Member loginUser = (Member) session.getAttribute("loginUser");
    Board old = boardService.get(boardNo);
    if (old.getWriter().getNo() != loginUser.getNo()) {
      return "redirect:../auth/fail";
    } else {
      boardService.deleteFile(fileNo);
      return "redirect:view?no=" + boardNo;
    }
  }

}

 

package bitcamp.myapp.controller;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import bitcamp.myapp.service.BoardService;
import bitcamp.myapp.vo.BoardFile;

@Controller
public class DownloadController {

  Logger log = LogManager.getLogger(getClass());

  {
    log.trace("DownloadController 생성됨!");
  }

  @Autowired private BoardService boardService;

  @RequestMapping("/download/boardfile")
  public String execute(
      @RequestParam("fileNo") int fileNo,
      HttpServletRequest request,
      HttpServletResponse response) throws Exception {

    BoardFile boardFile = boardService.getFile(fileNo);
    if (boardFile == null) {
      throw new RuntimeException("파일 정보 없음!");
    }

    File downloadFile = new File(
        request.getServletContext().getRealPath("/board/upload/" + boardFile.getFilepath()));
    if (!downloadFile.exists()) {
      throw new RuntimeException("파일이 존재하지 않음!");
    }

    response.setContentType(boardFile.getMimeType());

    response.setHeader("Content-Disposition",
        String.format("attachment; filename=\"%s\"", boardFile.getOriginalFilename()));

    try (
        BufferedInputStream fileIn = new BufferedInputStream(new FileInputStream(downloadFile));
        BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());) {

      int b;
      while ((b = fileIn.read()) != -1) {
        out.write(b);
      }
      out.flush();
    }

    return null;
  }
}

 

package bitcamp.myapp.controller;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import bitcamp.myapp.service.StudentService;
import bitcamp.myapp.vo.Student;

@Controller
@RequestMapping("/student")
public class StudentController {

  Logger log = LogManager.getLogger(getClass());

  {
    log.trace("StudentController 생성됨!");
  }

  @Autowired private StudentService studentService;

  @GetMapping("form")
  public void form() {
  }

  @PostMapping("insert")
  public void insert(Student student, Model model) {
    studentService.add(student);
  }

  @GetMapping("list")
  public void list(String keyword, Model model) {
    model.addAttribute("students", studentService.list(keyword));
  }

  @GetMapping("view")
  public void view(
      int no,
      Model model) {
    model.addAttribute("student", studentService.get(no));
  }

  @PostMapping("update")
  public void update(Student student, Model model) {
    studentService.update(student);
  }

  @PostMapping("delete")
  public void delete(int no, Model model) {
    studentService.delete(no);
  }
}

 

package bitcamp.myapp.controller;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import bitcamp.myapp.service.TeacherService;
import bitcamp.myapp.vo.Teacher;

@Controller
@RequestMapping("/teacher")
public class TeacherController {

  Logger log = LogManager.getLogger(getClass());

  {
    log.trace("TeacherController 생성됨!");
  }

  @Autowired private TeacherService teacherService;

  @GetMapping("form")
  public void form() {
  }

  @PostMapping("insert")
  public void insert(Teacher teacher, Model model) {
    teacherService.add(teacher);
  }

  @GetMapping("list")
  public void list(Model model) {
    model.addAttribute("teachers", teacherService.list());
  }

  @GetMapping("view")
  public void view(int no, Model model) {
    model.addAttribute("teacher", teacherService.get(no));
  }

  @PostMapping("update")
  public void update(Teacher teacher, Model model) {
    teacherService.update(teacher);
  }

  @PostMapping("delete")
  public void delete(int no, Model model) {
    teacherService.delete(no);
  }

}

 

interceptor 에 trace 로 로깅한다. preHandler 에 더해 postHandler, afterCompletion 의 빈 메서드 추가해서 로깅한다.

package bitcamp.myapp.web.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import bitcamp.myapp.vo.Member;

public class AuthInterceptor implements HandlerInterceptor {

  Logger log = LogManager.getLogger(getClass());

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {
    log.trace("preHandle() 호출됨!");
    Member loginUser = (Member) request.getSession().getAttribute("loginUser");
    if (loginUser == null) {
      response.sendRedirect(request.getContextPath() + "/app/auth/form");
      return false;
    }
    return true;
  }

  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
      ModelAndView modelAndView) throws Exception {
    log.trace("postHandle() 호출됨!");
  }

  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
      Object handler, Exception ex) throws Exception {
    log.trace("afterCompletion() 호출됨!");
  }
}
package bitcamp.myapp.web.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import bitcamp.myapp.vo.Member;

public class AdminCheckInterceptor implements HandlerInterceptor {

  Logger log = LogManager.getLogger(getClass());

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {
    log.trace("preHandle() 호출됨!");
    Member loginUser = (Member) request.getSession().getAttribute("loginUser");
    if (!loginUser.getEmail().equals("admin@test.com")) {
      response.sendRedirect(request.getContextPath() + "/app/auth/form");
      return false;
    }
    return true;
  }

  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
      ModelAndView modelAndView) throws Exception {
    log.trace("postHandle() 호출됨!");
  }

  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
      Object handler, Exception ex) throws Exception {
    log.trace("afterCompletion() 호출됨!");
  }
}

 

 

 

 

실습 예제 코드

 

 

Log4j 2 사용법

 Log4j
 - 프로그램을 실행하는 중에 특정 변수의 값이나 실행 상태, 오류 내용을 
   콘솔이나 파일에 기록하고 싶을 때 사용하는 라이브러리이다.
 - 단계 별로 출력을 제어할 수 있다.
 - 프로그램 실행 과정을 추적하거나 디버깅 할 때 유용하다.
 
 사용법
 1) log4j 라이브러리 파일을 프로젝트에 포함한다.
    - search.maven.org 사이트에서 'log4j-core' 로 검색한다.
      예) org.apache.logging.log4j:log4j-core:2.14.1
    - 라이브러리 정보를 build.gradle 에 포함시킨다.
    - '$ gradle eclipse'를 실행하여 라이브러리를 가져오고, 이클립스 설정 파일을 갱신한다. 
 2) log4j 설정 파일을 준비 
    - CLASSPATH 루트 경로에 log4j2.xml 파일 작성
    - 문서를 참조하여 설정한다.

package com.eomcs.log4j;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Exam01 {

  // 기록을 남길 때 사용할 도구 준비
  // - 클래스 당 한 개의 도구만 있어도 된다.
  private static final Logger logger = LogManager.getLogger(Exam01.class); 

  public static void main(String[] args) {

    // 로깅 도구 없이 기록을 남기는 방법
    System.out.println("고전적인 방법으로 기록을 남기기!");

    // 로깅 도구를 사용하여 기록을 남기는 방법
    logger.fatal("내용 ==> {}, {}, {}", "FATAL", "aaa", 100);
    logger.error("내용 ==> ERROR");
    logger.warn("내용 ==> WARN");
    logger.info("내용 ==> INFO");
    logger.debug("내용 ==> DEBUG");
    logger.trace("내용 ==> TRACE");
  }
}


// 로그 레벨 중요도
//      trace < debug < info < warn < error < fatal
// 
// 1) trace
//    - 실행 과정을 순서대로 자세하게 확인하고 싶을 때 사용한다.
// 2) debug
//    - 개발자 입장에서 변수의 값을 확인하고 싶을 때 사용한다.
// 3) info
//    - 주요 메서드의 호출이나 실행 흐름을 확인하고 싶을 때 사용한다.
// 4) warn
//    - 시스템 실행에는 문제가 없으나 향후 예외가 발생할 수 있는 상황을 기록하고 싶을 때 사용한다.
// 5) error
//    - 실행 중 발생한 오류를 기록하고 싶을 때 사용한다.
// 6) fatal
//    - 복구가 불가능한, 즉 시스템을 즉시 종료해야 하는 상황을 기록할 때 사용한다.
//
// 출력 레벨 설정
// - 출력할 로그 레벨을 설정할 수 있다.
// - 출력 레벨을 지정하면 그 레벨 이상에 대해 출력을 허락한다.
// - 예) trace : trace < debug < info < warn < error < fatal
// - 예) debug : debug < info < warn < error < fatal
// - 예) error : error < fatal

 

resources/log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
  <Appenders>
    <Console name="STDOUT">
        <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd} [%t] %c{1} - %msg%n" />
    </Console>
  </Appenders>
  <Loggers>
    <Root level="info">
      <AppenderRef ref="STDOUT" />
    </Root>
  </Loggers>
</Configuration>

 

 

 


 

 

조언

 

*프로젝트 백엔드는 별거 없다. 중요한 건 HTML, CSS, Javascript 이다. 주제가 아쉬워도 UI 가 이쁘면 된다.

 

 


 

과제

 

/