[spring] Maven, STS, log4j, Tiles

sang·2024년 4월 19일

Maven

자동으로 스프링 라이브러리 기능 관리
프로젝트 구조와 내용을 기술하는 선언적 접근 방식의 오픈 소스 빌드 툴
프로젝트 종속 라이브러리, dependency 자원 등 프로젝트 전반 리소스 관리
설정 파일, 표준 디렉토리 구조를 일관된 형태로 구성하여 관리

Maven 프로젝트 구조

  • 프로젝트명
    • pom.xml
    • src
      • main
        • java: java
        • resource: property, xml 등
      • test: JUnit 등의 테스트 파일

pom.xml 태그

groupId: 프로젝트 그룹 id (도메인 이름)
artifactId: 프로젝트 아티팩트 id (패지키 이름)
version: 프로젝트 버전 설정
packging: 애플리케이션 배포 시 패키징 타입 설정 (war)

dependencies 태그

dependency: 의존 라이브러리 정보
qroupId: 의존 프로젝트 그룹 id
artifactId: 의존 프로젝트 아티팩트 id
version: 의존 프로젝트 버전 정보

pom.xml

프로젝트 정보 표시, 스프링 라이브러리 설정 및 다운로드

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/200
1/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <groupId>com.spring</groupId>
  <artifactId>mytest2</artifactId>
  <name>spring_test2</name>
  <packaging>war</packaging>
  <version>1.0.0-BUILD-SNAPSHOT</version>
  
  <properties>
    <java-version>1.6</java-version>
    <org.springframework-version>4.0.0.RELEASE</org.springframework-version>
    <org.aspectj-version>1.6.10</org.aspectj-version>
    <org.slf4j-version>1.6.6</org.slf4j-version>
  </properties>
  
  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${org.springframework-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${org.springframework-version}</version>
    </dependency>
  </dependencies>
</project>


STS

Spring Tool Suite
이클립스 스프링 기반 애플리케이션 개발용 도구

편리한 개발 환경
STS 프로젝트 생성 후 Maven 적용해서 사용 가능

STS 프로젝트 구조

  • 프로젝트명
    • Java Resources
      • com.example.패키지명: 패지키 별 java 파일
      • src/main/resources: log, mybatis, 다국어 파일
    • src
      • main
        • java
        • resource: js, jQuery, img 등
        • WEB-INF
          • classes
          • spring: 스프링 관련 설정 파일 디렉토리 등
          • views: jsp
          • web.xml
      • test: JUnit 등의 테스트 파일
    • target
    • pom.xml

xml 설정

pro27/src/main/webapp/WEB-INF/web.xml

...

  <!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring/root-context.xml</param-value>
  </context-param>

  <!-- Creates the Spring Container shared by all Servlets and Filters -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!-- Processes application requests -->
  <servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

pro27/src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml

...
  <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
  <!-- Enables the Spring MVC @Controller programming model -->
  <annotation-driven />

  <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
  <!-- jsp에서 사용할 js, img 파일 경로 지정 -->
  <resources mapping="/resources/**" location="/resources/" />

  <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
  <!-- InternalResourceViewResolver bean 생성 및 jsp 경로 지정-->
  <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <beans:property name="prefix" value="/WEB-INF/views/" />
    <beans:property name="suffix" value=".jsp" />
  </beans:bean>
  <!-- 패키지, 애너테이션 지정 -->
  <context:component-scan base-package="com.spring.pro27" />
/beans:beans>

java, jsp

pro27/src/main/java/com/myspring/pro27/HomeController.java

package com.myspring.pro27;
...

/* Handles requests for the application home page. */
@Controller
public class HomeController {
  private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
  
  /* Simply selects the home view to render by returning its name. */
  @RequestMapping(value = "/", method = RequestMethod.GET) //모든 요청 시
  public String home(Locale locale, Model model) {
    logger.info("Welcome home! The client locale is {}.", locale);
    
    Date date = new Date();
    DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG,
    DateFormat.LONG, locale);
    String formattedDate = dateFormat.format(date);
    model.addAttribute("serverTime", formattedDate ); //브라우저 요청 시각
    
    return "home"; //jsp 이름 반환
  }
}

pro27/src/main/webapp/WEB-INF/views/home.jsp

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>

<html>
<head>
  <title>Home</title>
</head>
<body>
  <h1> Hello world! </h1>
  <P> The time on the server is ${serverTime}. </P>
</body>
</html>

MyBatis 설정

pro27/pom.xml

...
  <!-- datasource 관련 라이브러리 -->
  <dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.8.0</version>
  </dependency>
  <dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.2.2</version>
  </dependency>
  <dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib-nodep</artifactId>
    <version>2.2</version>
  </dependency>
 
  <!-- MyBatis 관련 라이브러리 -->
  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.1.0</version>
  </dependency>
  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.1.0</version>
  </dependency>
  
  <!-- oracle driver 라이브러리 -->
  <dependency>
    <groupId>jdbc.oracle</groupId>
    <artifactId>OracleDriver</artifactId>
    <version>12.1.0.2.0</version>
    <scope>system</scope>
    <systemPath>${basedir}/src/main/webapp/WEB-INF/lib/ojdbc6.jar</systemPath>
  </dependency>
</dependencies>
...

pro27/src/main/webapp/WEB-INF/web.xml

...
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring/action-mybatis.xml</param-value>
  </context-param>

  <!-- Creates the Spring Container shared by all Servlets and Filters -->
  <listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>
  ...

pro27/src/main/webapp/WEB-INF/spring/action-mybatis.xml

...
  <bean id="propertyPlaceholderConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
      <value>/WEB-INF/config/jdbc/jdbc.properties</value>
    </property>
  </bean>
  ...
</beans>

pro27/src/main/resources/mybatis/model/modelConfig.xml

..
<configuration>
  <typeAliases>
    <typeAlias type="com.myspring.pro27.member.vo.MemberVO" alias="memberVO" />
  </typeAliases>
</configuration>
MemberVO alias 설정

pro27/src/main/java/com/myspring/pro27/member/controller/MemberControllerImpl.java

@Controller
package com.myspring.pro27.member.controller;
...
@Controller("memberController")
public class MemberControllerImpl implements MemberController {
  @Autowired
  private MemberService memberService;
  @Autowired
  private MemberVO memberVO;
  
    @Override
  @RequestMapping(value="/member/listMembers.do" ,method = RequestMethod.GET)
  public ModelAndView listMembers(HttpServletRequest request, HttpServletResponse response) throws Exception {
    String viewName = getViewName(request);
    List membersList = memberService.listMembers();
    ModelAndView mav = new ModelAndView(viewName);
    mav.addObject("membersList", membersList);
    return mav;
  }

  ...
  private String getViewName(HttpServletRequest request) throws Exception {
    ...
    if(viewName.lastIndexOf("/") != -1) {
      viewName = viewName.substring(viewName.lastIndexOf("/",1), viewName.length());
    }
    return viewName;
  }
}


log4j

콘솔 메세지 출력 대신 코드 정상 작동을 확인하기 위해 사용하는 로그

log4j 태그

Appender: 로그 출력 위치
Layout: 출력 형식
Logger: 로그 레벨, 로그 출력 여부

Appender 클래스

ConsoleAppender: 로그 메세지 위치를 콘솔에 지정
FileAppender: 로그 메세지 위치를 파일에 지정
RollingFileAppender: 파일 크기가 일정 기준을 넘을 시 백업 후 다시 기록
DailyRollingAppender: 설정 기간 단위마다 새 파일 생성하여 기록

PatternLayout 출력 속성

%p 로그 레벨 이름
%n 로그 메세지
%d 로깅 이벤트 발생 시각
%F 로깅 발생 프로그램 파일 이름
%l 로깅 발생 caller 정보
%L 로깅 발생 caller 라인 수
%M 로깅 발생 method 이름
%c 전체 패키지명 또는 파일명 + 로깅 메세지

로그 레벨

최고 수준(심각)의 레벨부터 설정한 레벨까지 출력
DEBUG 이하로 설정 시 SQL 로그 출력 가능

FATAL: 심각, 시스템 문제, 애플리케이션 작동 불가
ERROR: 실행 중 문제 발생
WARN: 경고 메세지, 향후 시스템 오류 발생 가능
INFO: 정보 메세지, 애플리케이션 운영 정보 (로그인, 상태 변경 등)
DEBUG: 디버깅 용도
TRACE: 상세 로깅 정보

pro27/src/main/resources/log4j.xml

...
<!-- Console 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>

<!-- DailyRollingFile Appenders -->
<!-- 로그 위치: 파일 / 설정 기간마다 새 파일 생성 -->
<appender name="dailyFileAppender" class="org.apache.log4j.DailyRollingFileAppender">
  <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 -->
<!-- name 위치 로그 레벨 value 설정 -->
<logger name="com.myspring.pro27">
  <level value="info" />
</logger>

...
<!-- Root Logger -->
<!-- 애플리케이션 전체 로그 설정 -->
<root>
  <priority value="info" /> <!-- 로그 레벨 info 설정 -->
  <appender-ref ref="console" /> <!-- 콘솔 출력 -->
  <appender-ref ref="dailyFileAppender" /> <!-- 파일 출력 -->
</root>

pro27/src/main/java/com/myspring/pro27/member/controller/MemberControllerImpl.java

...
@Controller("memberController")
@RequestMapping("/member")
public class MemberControllerImpl implements MemberController {
  //Logger 객체
  private static final Logger logger = LoggerFactory.getLogger(MemberControllerImpl.class);
  ...
  
  @Override
  @RequestMapping(value="/listMembers.do" ,method = RequestMethod.GET)
  public ModelAndView listMembers(HttpServletRequest request, HttpServletResponse response) throws Exception {
    /* 요청받은 뷰 이름 확인 */
    String viewName = getViewName(request);
    logger.info("info 레벨 : viewName = "+ viewName); //info 레벨 로그 메소드
    logger.debug("debug 레벨 : viewName = "+ viewName); //debug 레벨 로그 메소드
    
    List membersList = memberService.listMembers();
    ModelAndView mav = new ModelAndView(viewName);
    mav.addObject("membersList", membersList);
    
    return mav;
  }
  ...


Tiles

JSP 페이지 레이아웃을 위한 프레임워크

쉽고 단순하게 페이지 레이아웃 구현 가능
공통 레이아웃 사용을 통한 쉬운 유지관리

pro27/pom.xml

...
  <!-- 타일즈 관련 라이브러리 -->
  <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>
  ...

pro27/src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml

...
  <!-- InternalResourceViewResolver 사용안함 -->
  
  <!-- TilesConfigurer bean 생성 -->
  <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> <!-- tiles 패지키 xml 파일 -->
      </beans:list>
    </beans:property>
    <beans:property name="preparerFactoryClass" value="org.springframework.web.servlet.view.tiles2.SpringBeanPreparerFactory"/>
  </beans:bean>
  
  <!-- TilesView UrlBasedViewResolver 화면 표시 -->
  <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>
  
  <context:component-scan base-package="com.myspring.pro27" />
</beans:beans>

JSP + Tiles

  • tiles.xml
  • layout.jsp
  • content.jsp

pro27/src/main/resources/tiles/tiles_member.xml

layout jsp 설정

...
<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>
</tiles-definitions>

pro27/src/main/webapp/WEB-INF/views/common/layout.jsp

레이아웃 형태

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %> <!-- Tiles 사용 시 필요 -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

...
<title> <tiles:insertAttribute name="title" /> </title>
</head>
<body>
  <div id="container">
    <div id="header"> <tiles:insertAttribute name="header"/> </div>
    <div id="sidebar-left"> <tiles:insertAttribute name="side"/> </div>
    <div id="content"> <tiles:insertAttribute name="body"/> </div>
    <div id="footer"> <tiles:insertAttribute name="footer"/> </div>
  </div>
</body>
</html>

pro27/src/main/webapp/WEB-INF/views/common/header.jsp
pro27/src/main/webapp/WEB-INF/views/common/side.jsp
pro27/src/main/webapp/WEB-INF/views/common/footer.jsp

각 레이아웃 부분


pro27/src/main/webapp/WEB-INF/views/common/main.jsp

메인 내용


pro27/src/java/com/myspring/pro27/HomeController.java

뷰 요청 컨트롤러

...
@Controller
public class HomeController {
  private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
  
  /* 메인화면 요청 */
  @RequestMapping(value = "/main.do", method = RequestMethod.GET)
  public String home(Locale locale, Model model) {
    return "main"; //Tiles xml 파일에서 설정한 jsp
  }
}


Tiles 회원 관리 실습

pro27/src/main/resources/tiles/tiles_member.xml

...
<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/loginForm" extends="baseLayout">
    <put-attribute name="title" value="로그인창" />
    <put-attribute name="body" value="/WEB-INF/views/member/loginForm.jsp" />
  </definition>
</tiles-definitions>

pro27/src/main/resources/mybatis/mappers/member.xml

...
  <!-- 로그인 id, pw 조회 -->
  <select id="loginById" resultType="memberVO" parameterType="memberVO" >
    <![CDATA[
      select * from t_member
      where id = #{id} and pwd = #{pwd}
    ]]>
  </select>
  ...

pro27/src/main/java/com/myspring/pro27/member/controller/MemberControllerImpl.java

...
@Controller("memberController")
public class MemberControllerImpl implements MemberController {
  private static final Logger logger = LoggerFactory.getLogger(MemberControllerImpl.class);

  @Autowired
  private MemberService memberService; //자동 주입
  @Autowired
  private MemberVO memberVO; //자동 주입

  /* 회원 목록 요청 */
  @Override
  @RequestMapping(value="/member/listMembers.do", method = RequestMethod.GET)
  public ModelAndView listMembers(HttpServletRequest request, HttpServletResponse response) throws Exception {
    String viewName = getViewName(request);
    logger.debug("debug레벨 : viewName = "+ viewName);
    
    List membersList = memberService.listMembers();
    ModelAndView mav = new ModelAndView(viewName); //viewName == listMembers
    mav.addObject("membersList", membersList);
    
    return mav;
  }
  
  /* 로그인 요청 */
  @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;
  }
  
  /* 로그아웃 요청 */
  @Override
  @RequestMapping(value = "/member/logout.do", method = RequestMethod.GET)
  public ModelAndView logout(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpSession session = request.getSession();
    session.removeAttribute("member");
    session.removeAttribute("isLogOn");
    ModelAndView mav = new ModelAndView();
    mav.setViewName("redirect:/member/listMembers.do");
    
    return mav;
  }
  
  /* 로그인 창 요청 */
  @RequestMapping(value = "/member/*Form.do", method = RequestMethod.GET)
  private ModelAndView form( @RequestParam(value= "result", required=false) String result, HttpServletRequest request, HttpServletResponse response ) throws Exception {
    String viewName = getViewName(request);
    ModelAndView mav = new ModelAndView();
    mav.addObject("result",result);
    mav.setViewName(viewName);
    
    return mav;
  }
  
  ...

pro27/src/main/java/com/myspring/pro27/member/service/MemberServiceImpl.java

controller -> service -> DAO

...
  @Override
  public MemberVO login(MemberVO memberVO) throws Exception{
    return memberDAO.loginById(memberVO);
  }
  ...

pro27/src/main/java/com/myspring/pro27/memberdao/MemberDAOImpl.java

service -> DAO -> VO

...
  public MemberVO loginById(MemberVO memberVO) throws DataAccessException {
    MemberVO vo = sqlSession.selectOne("mapper.member.loginById", memberVO);
    return vo;
  }
  ...

pro27/src/main/webapp/WEB-INF/views/common/header.jsp

...
    <td>
      <!-- <a href="#"><h3>로그인</h3></a> -->
      <c:choose>
        <!-- 로그인 상태 -->
        <c:when test="${isLogOn == true && member!= null}">
          <h3>환영합니다. ${member.name }님!</h3>
          <a href="${contextPath}/member/logout.do"><h3>로그아웃</h3></a>
        </c:when>
        
        <!-- 비로그인 상태 -->
        <c:otherwise>
          <a href="${contextPath}/member/loginForm.do"><h3>로그인</h3></a>
        </c:otherwise>
     </c:choose>
    </td>
  </tr>
</table>
...

pro27/src/main/webapp/WEB-INF/views/member/loginForm.jsp

...
<c:set var="result" value="${param.result}" />
...

<head>
  <meta charset="UTF-8">
  <title>로그인창</title>
  <!-- 로그인 실패 후 리로드 시 실패 메세지 표시 -->
  <c:choose>
    <c:when test="${result=='loginFailed' }">
      <script>
        window.onload=function(){
          alert("아이디나 비밀번호가 틀립니다. 다시 로그인 하세요!");
        }
      </script>
    </c:when>
  </c:choose>
</head>
...


*자바 웹을 다루는 기술

profile
CS 메모장

0개의 댓글