개발자입니다
[비트캠프] 84일차(18주차2일) - Spring Framework(Thymeleaf, ControllerAdvice, log4j 2), myapp-63 본문
[비트캠프] 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 가 이쁘면 된다.
과제
/