본문 바로가기

회고록(TIL&WIL)

TIL 2023.02.20 Java Web 9 (log4j, Filter, Listener, download, junit, DispatchServlet, JdbcTemplate)

log4j

의존성 추가



    log4j
    log4j
    1.2.17

controller

@WebServlet("/ex01.do")
public class Ex01Controller extends HttpServlet {
	Logger log = Logger.getLogger("com.bit.controller.Ex01Controller");
	
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		log.debug("출력");
		response.getWriter().append("Served at: ").append(request.getContextPath());
	}

}

log4j 기본설정

src/main/resources/log4j.properties 파일 생성

# log4j.properties

# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=DEBUG, A1

# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender

# A1 uses PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n

실행 결과

log4j pattern

%p debug, info, warn, error, fatal 등의 priority 가 출력된다.
%m 로그내용이 출력됩니다
%d 로깅 이벤트가 발생한 시간을 기록합니다. 포맷은 %d{HH:mm:ss, SSS}, %d{yyyy MMM dd HH:mm:ss, SSS}같은 형태로 사용하며 SimpleDateFormat에 따른 포맷팅을 하면 된다
%t 로그이벤트가 발생된 쓰레드의 이름을 출력합니다.
%% % 표시를 출력하기 위해 사용한다.
%n 플랫폼 종속적인 개행문자가 출력된다. rn 또는 n 일것이다.
%c 카테고리를 표시합니다 예) 카테고리가 a.b.c 처럼 되어있다면 %c{2}는 b.c가 출력됩니다.
%C 클래스명을 포시합니다. 예)클래스구조가 org.apache.xyz.SomeClass 처럼 되어있다면 %C{2}는 xyz.SomeClass 가 출력됩니다
%F 로깅이 발생한 프로그램 파일명을 나타냅니다.
%l 로깅이 발생한 caller의 정보를 나타냅니다
%L 로깅이 발생한 caller의 라인수를 나타냅니다
%M 로깅이 발생한 method 이름을 나타냅니다.
%r 어플리케이션 시작 이후 부터 로깅이 발생한 시점의 시간(milliseconds)
%x 로깅이 발생한 thread와 관련된 NDC(nested diagnostic context)를 출력합니다.
%X 로깅이 발생한 thread와 관련된 MDC(mapped diagnostic context)를 출력합니다. %X{key} 형태.
ex) [%d{MM-dd hh:mm:ss}][%C] - %m%n

로그 레벨 예외 설정

# log4j.properties
# 전체 모드는 ERROR만 뜨도록 설정
log4j.rootLogger=ERROR, A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout

# Print the date in ISO 8601 format
log4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p %c - %m%n

# 해당 패키지 이하의 클래스는 예외로 debug모드로 찍히도록
log4j.logger.com.bit.controller=debug

콘솔에 출력할 것과 파일에 저장할 것을 분리해서 설정가능

stdout은 콘솔에 출력되는 설정, R은 파일에 저장하는 설정

log4j.rootLogger=debug, stdout, R

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

# Pattern to output the caller's file name and line number.
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n

log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=E:/webspace/day52/log/test.log #로그 내용을 저장할 파일

log4j.appender.R.MaxFileSize=100KB
# Keep one backup file
log4j.appender.R.MaxBackupIndex=1

log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n

Servlet Filter

필터를 사용하여 반복되는 공통의 작업, 중복코드를 없앨 수 있다.

  1. 인증(사용자 인증) 필터
  2. 로깅 및 감시(Audit) 필터
  3. 이미지 변환 및 데이터 압축 필터
  4. 암호화 필터
  5. XML 컨텐츠를 변형하는 XSLT 필터
  6. URL 및 기타 정보들을 캐싱하는 필터
    단, filter의 Servlet은 HttpServlet이 아니기 때문에 캐스팅을 한다음 사용해야 하는 메서드도 있다.
----------------- web.xml
  <filter>
  	<filter-name>ex01</filter-name>
  	<filter-class>com.bit.util.filter.Ex01Filter</filter-class>
  </filter>
  <filter-mapping>
  	<filter-name>ex01</filter-name>
  	<url-pattern>/ex02.do</url-pattern>
  </filter-mapping>
----------------- Filter
public class Ex01Filter implements Filter{
	Logger log = Logger.getLogger("com.bit.util.filter.Ex01Filter");
	
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		log.debug("시작 할 때");
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		log.debug("before ex01Filter");
		chain.doFilter(request, response);
		log.debug("after ex01Filter");
	}

	@Override
	public void destroy() {
		log.debug("종료 시");
	}

}
----------------- Filter 2
@WebFilter(value = {"/ex01.do"})
public class Ex03Filter implements Filter {
	Logger log = Logger.getLogger(this.getClass().getCanonicalName());

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		// TODO Auto-generated method stub

	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest req = (HttpServletRequest)request;
		req.setCharacterEncoding("utf-8");
		log.debug(req.getSession().getId());
		chain.doFilter(request, response);
	}

	@Override
	public void destroy() {
		// TODO Auto-generated method stub

	}

}

Servlet Listener - ServletContextListener

----------------- web.xml
  <listener>
  	<listener-class>com.bit.util.listen.Ex01Listener</listener-class>
  </listener>
----------------- Listener
public class Ex01Listener implements ServletContextListener {
	Logger log = Logger.getLogger(this.getClass().getCanonicalName());
	
	@Override
	public void contextInitialized(ServletContextEvent sce) {
		log.debug("listener init");
	}

	@Override
	public void contextDestroyed(ServletContextEvent sce) {
		log.debug("listener destroy");
	}

}
------------------ Listener 2
//@WebListener
public class Ex02Listener implements ServletRequestListener {
	Logger log = Logger.getLogger(this.getClass().getCanonicalName());

	@Override
	public void requestDestroyed(ServletRequestEvent sre) {
		log.debug("listen req destroy");
	}

	@Override
	public void requestInitialized(ServletRequestEvent sre) {
		log.debug("listen req init");
	}


}

Encoding Decoding

인코딩 디코딩 처리는 보통 브라우저가 해주나 해주지않는 브라우저를 사용할 때는 직접해줘야한다.
java -> URLEncoder.encode("한글", "utf-8"); URLdecoder
JS -> encodeURI() - 주소의 표현방식 맞춰 인코딩, encodeURIComponent() - value값으로 사용하도록 모든 문자를 인코딩

protected void doDo(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  String param = req.getParameter("id");
  log.debug(param);
  resp.setContentType("text/html; charset=utf-8");
  resp.setCharacterEncoding("utf-8");
  try(PrintWriter out = resp.getWriter();){
    out.println("Ex02 Controller... param:"+param);

  }
}

file download

@WebServlet("/download.do")
public class Ex03Controller extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String name = req.getParameter("fname");
		if(name!=null) {
			String path = "E:/webspace/day52/download/";
			File f = new File(path+name);
			if(f.exists()) {
				resp.setContentType("application/octet-stream"); //무조건 다운로드 받도록
				resp.setHeader("Content-Disposition", "attachment; filename=\""+name+"\"");
				try(InputStream is = new FileInputStream(f);
						OutputStream os = resp.getOutputStream();){
					int cnt = -1;
					while((cnt=is.read())!=-1) {
						os.write(cnt);
					}
				}
			} else {
				resp.setContentType("text/html; charset=utf-8");
				resp.getWriter().append("<h1>파일 없음</h1>");
			}
		}
	}
}

FrameWork

junit

test 순서제어 (4버젼에선 한개뿐)

@FixMethodOrder(MethodSorters.NAME_ASCENDING)

@Before @BeforeClass

@BeforeClass - 테스트 실행 시 한번만 실행 (static이 반드시 붙어야한다.)
@Before - 테스트마다 실행

 

assertEquals 을 제대로 쓰기 위해서 DTO의 equals를 재정의 해줘야한다! (자동완성 기능)

package com.bit.model;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class DeptDaoTest {
	DeptDao dao;
	static DeptDto target;
	
	@BeforeClass
	public static void beforeClass() {
		target = new DeptDto();
		target.setDeptno(9999);
		target.setDname("test1");
		target.setLoc("test2");
	}

	@Before
	public void before() {
		dao = new DeptDao();
	}
	
	@Test
	public void test1SelectAll() {
		assertNotNull(dao.selectAll());
		assertTrue(dao.selectAll().size()>0);
	}


	@Test
	public void test2InsertOne() {
		assertSame(1, dao.insertOne(target.getDeptno(), target.getDname(), target.getLoc()));
	}
	
	@Test
	public void test3SelectOne() {
		assertEquals(target, dao.selectOne(target.getDeptno()));
	}

	@Test
	public void test4UpdateOne() {
		target.setDname("test1");
		target.setLoc("test2");
		assertSame(1, dao.updateOne(target.getDeptno(), target.getDname(), target.getLoc()));
	}

	@Test
	public void test5DeleteOne() {
		assertSame(1, dao.deleteOne(target.getDeptno()));
	}

}

command pattern - DispatchSevlet

모든 요청을 커맨드로써 접근하게하는 것

-------------- Interface
public interface Controller {
	public String execute(HttpServletRequest res);
}

------------- Controllers
public class AddController implements Controller {
	@Override
	public String execute(HttpServletRequest res) {
		return "emp/add.jsp";
	}
}

public class InsertController implements Controller {
	@Override
	public String execute(HttpServletRequest res) {
		return "redirect:list.do";
	}
}
-------------DispatchSevlet.java
package com.bit.framework.handler;

public class DispatchServlet extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doDo(req,resp);
	}
	
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doDo(req,resp);
	}
	
	protected void doDo(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String uri = req.getRequestURI();
		uri = uri.substring(req.getContextPath().length());
		Controller controller = null;
		String classInfo = this.getInitParameter(uri);
		
		try {
			Class cls = Class.forName(classInfo);
			controller = (Controller)cls.newInstance();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		String viewName = controller.execute(req);
		if(viewName.startsWith("redirect:")) {
			resp.sendRedirect(viewName.replace("redirect:", ""));
		} else {
			req.getRequestDispatcher("/WEB-INF/views/"+viewName).forward(req,resp);
		}
	}
}

template method pattern - JdbcTemplate

자바의 특성 다형성, 추상화를 이용하여 의존도를 없애고 코드 중복을 제거

----------------- DeptDAO
package com.bit.model;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import javax.sql.DataSource;

import com.bit.framework.jdbc.JdbcTemplate;
import com.mysql.cj.jdbc.MysqlDataSource;

public class DeptDao {
	String driver = "com.mysql.cj.jdbc.Driver";
	String url = "jdbc:mysql://localhost:3306/lecture";
	String user = System.getenv("MYSQL_USER");
	String password = System.getenv("MYSQL_PW");
	JdbcTemplate template = new JdbcTemplate() {
		@Override
		public Object mapper(ResultSet rs) throws SQLException {
			DeptDto bean = new DeptDto();
			bean.setDeptno(rs.getInt("deptno"));
			bean.setDname(rs.getNString("dname"));
			bean.setLoc(rs.getNString("loc"));
			return bean;
		}
	};
	
	private DataSource getDataSource(){
		MysqlDataSource dataSource = new MysqlDataSource();
		dataSource.setURL(url);
		dataSource.setUser(user);
		dataSource.setPassword(password);
		return dataSource;
	}
	
	public List<DeptDto> selectAll(){
		String sql = "select * from dept";
		template.setDataSource(getDataSource());
		return template.queryForAll(sql, new Object[] {});
	}
	
	public Object selectOne(int deptno) {
		String sql = "select * from dept where deptno=?";
		template.setDataSource(getDataSource());
		return template.queryForAll(sql, new Object[] {deptno}).get(0);
	}
	
	public int insertOne(int deptno, String dname, String loc) {
		String sql = "insert into dept values(?,?,?)";
		Object[] objs = {deptno, dname, loc};
		template.setDataSource(getDataSource());
		return template.update(sql, objs);
	}
	
	public int updateOne(int deptno, String dname, String loc) {
		String sql = "update dept set dname=?, loc=? where deptno=?";
		Object[] objs = {dname, loc, deptno};
		template.setDataSource(getDataSource());
		return template.update(sql, objs);
	}
	
	public int deleteOne(int deptno) {
		String sql = "delete from dept where deptno=?";
		template.setDataSource(getDataSource());
		return template.update(sql, new Object[] {deptno});
	}
}

----------------- JdbcTemplate.java
package com.bit.framework.jdbc;

// 추상메서드가 있기 떄문에 추상클래스가 되어야한다.
public abstract class JdbcTemplate {
	DataSource dataSource;
	PreparedStatement pstmt = null;
	ResultSet rs = null;
	
	public void setDataSource(DataSource dataSource) {
		this.dataSource = dataSource;
	}
	
	// insert, update, delete
	// 다형성을 이용하기 위해 Object[]을 이용
	public int update(String sql, Object[] objs) {
		Connection conn = null;
		try {
			conn = dataSource.getConnection();
			pstmt = conn.prepareStatement(sql);
			for(int i = 0; i < objs.length; i++) {
				pstmt.setObject(i+1, objs[i]);
			}
			return pstmt.executeUpdate();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			try {
				close(conn);
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		
		return 0;
	}
	
	public void close(Connection conn) throws SQLException {
		if(rs!=null) rs.close();
		if(pstmt!=null) pstmt.close();
		if(conn!=null) conn.close();
	}
	
	// select
	public List queryForAll(String sql, Object[] arr){
		List list = new ArrayList();
		Connection conn = null;
		try {
			conn = dataSource.getConnection();
			pstmt = conn.prepareStatement(sql);
			for(int i = 0; i < arr.length; i++) {
				pstmt.setObject(i+1, arr[i]);
			}
			rs = pstmt.executeQuery();
			while(rs.next()) {
				list.add(mapper(rs));
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			try {
				close(conn);
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		
		return list;
	}
	
	// 의존성을 없애기 위한 추상화
	public abstract Object mapper(ResultSet rs) throws SQLException;
}