Java Property 는 기본적으로 String 으로 인식 하기 때문에, 다른 타입의 값을 setter 하는 경우 SpringExpressionLanguage (SpEL) 문법을 사용하여 설정 파일에서 타입 캐스팅을 해줘야 사용이 가능 하다.


<beans:property name="port" value="#{T(java.lang.Integer).parseInt(config['key'])}" 


Primitive Type 은 Null 값을 허용할 수 없기 때문에, Wrapper 클래스로 타입 캐스팅을 해서 사용 하자. Web 환경에서는 Null 값을 setter 할 수 없어 예외 상황이 발생 하기도 한다.


Primitive Wrapper Class Constructor Argument
boolean Boolean boolean or String
byte Byte byte or String
char Character char
int Integer int or String
float Float float, double or String
double Double double or String
long Long long or String
short Short short or String


Wrapper 클래스 종류는 위 표를 참고 하자.


참고 사이트


Spring MVC 를 사용하여 Web Application 을 개발 할 때에, 통상 Database 와 연동을 해서 개발 한다. 데이터의 시발점이기도 하기 때문에, 문제가 발생 하는 경우 다른 무엇보다 최우선으로 확인 한다. 이 때 필요한 것은 로그 이다. 개발자의 생명줄 이나 다름없다.


log4jdbc 는 로그를 상세하게 출력 한다. 라이브러리를 추가 하자. 0.2.7 이 stable 버전 이다.


<dependency>
    <groupId>org.lazyluke</groupId>
    <artifactId>log4jdbc-remix</artifactId>
    <version>0.2.7</version>
</dependency>


그 다음은 DataSource 이다. driverClassName 을 변경 하면 지원 하는 Database 가 제한적 이기 때문에, 보존 하면서 설정 하는 방법 이다.


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">

    <!-- ConnectionPool Information -->
    <beans:bean id="dataSourceSpied" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <beans:property name="driverClassName" value="#{config['jdbc.driver']}" />
        <beans:property name="url" value="#{config['jdbc.url']}" />
        <beans:property name="username" value="#{config['jdbc.username']}" />
        <beans:property name="password" value="#{config['jdbc.password']}" />

        <beans:property name="defaultAutoCommit" value="false"/>

        <beans:property name="initialSize" value="1"/>
        <beans:property name="maxActive" value="1"/>
        <beans:property name="maxIdle" value="1"/>
        <beans:property name="maxWait" value="20000"/>
        <beans:property name="logAbandoned" value="true"/>
        <beans:property name="removeAbandoned" value="true"/>
        <beans:property name="removeAbandonedTimeout" value="60"/>
        <beans:property name="validationQuery" value="SELECT 1 FROM DUAL" />
        <beans:property name="testOnBorrow" value="true" />
        <beans:property name="testOnReturn" value="true" />
    </beans:bean>

    <beans:bean id="dataSource" class="net.sf.log4jdbc.Log4jdbcProxyDataSource">
        <beans:constructor-arg ref="dataSourceSpied" />
        <beans:property name="logFormatter">
            <beans:bean class="net.sf.log4jdbc.tools.Log4JdbcCustomFormatter">
                <beans:property name="loggingType" value="MULTI_LINE" />
                <beans:property name="margin" value="19" />
                <beans:property name="sqlPrefix" value="SQL:::" />
            </beans:bean>
        </beans:property>
    </beans:bean>
</beans>


Database 연결 정보를 가지고 있는 dataSource 는 Proxy 클래스를 생성하여 추가적으로 로그를 처리하는 부분이 구현 되어 있으며, Log4jdbcProxyDataSource 는 DataSource 를 상속 형태로 구현하여 확장되어 있다. CGLIB Proxy 라고 불 수 있다.


<logger name="jdbc.connection">
    <level value="info" />
</logger>
<logger name="jdbc.sqltiming">
    <level value="debug" />
</logger>
<logger name="jdbc.resultsettable">
    <level value="info" />
</logger>


log4j.xml 에 위 내용을 추가 하자. 접속 정보, Query, 응답 데이터에 대한 상세 로그가 출력 된다.


2014-10-23 04:19:39,130  INFO connection - 17. Connection opened
2014-10-23 04:19:39,130 DEBUG getBoardList - ooo Using Connection [net.sf.log4jdbc.ConnectionSpy@378c3efb]
2014-10-23 04:19:39,130 DEBUG getBoardList - ==>  Preparing: select no, title, content, create_dt from tb_board
2014-10-23 04:19:39,130 DEBUG getBoardList - ==> Parameters: 
2014-10-23 04:19:39,132 DEBUG sqltiming -  sun.reflect.GeneratedMethodAccessor36.invoke(null:-1)
17. select no, title, content, create_dt from tb_board  {executed in 1 msec}
2014-10-23 04:19:39,135  INFO resultsettable - |-------|--------|---------|----------------------|
2014-10-23 04:19:39,135  INFO resultsettable - |NO     |TITLE   |CONTENT  |CREATE_DT             |
2014-10-23 04:19:39,135  INFO resultsettable - |-------|--------|---------|----------------------|
2014-10-23 04:19:39,135  INFO resultsettable - |1      |title1  |ko       |2014-10-07 12:04:16.0 |
2014-10-23 04:19:39,136  INFO resultsettable - |2      |title2  |en       |2014-10-07 12:04:24.0 |
2014-10-23 04:19:39,136  INFO resultsettable - |3      |title3  |cn       |2014-10-07 12:04:31.0 |
2014-10-23 04:19:39,136  INFO resultsettable - |-------|--------|---------|----------------------|
2014-10-23 04:19:39,138  INFO connection - 17. Connection closed


Database 와 연결 되는 시나리오가 상세하게 로그로 출력 되고 있어 개발, 검증 단계에서 편리하게 사용할 수 있다.


실 서버 적용 에는 sqltiming 부분만 사용하면 장애 대응이나 데이터에 대한 문의도 편리하게 대응이 가능 하다.


log4jdbc 를 사용할 수 있는 환경이라면, 주저 말고 사용 해 보자.


참고 사이트


Java Web Application 은 /webapps 하위 에 있는 /WEB-INF 는 직접 적으로 접근이 불가능 하기 때문에 Servlet 을 통과하게 되어 있다. 단순하게 페이지만 보여주는 용도라면 모든 case by case 를 Controller 에 작성 하는 것은 불편 하기 때문에 view-controller 를 사용하면 편리하게 개발이 가능 하다.


dispatcher-servlet 를 설정 하는 xml 에 추가를 하자.


<mvc:view-controller path="/" view-name="/sample"/>


path, view-name 을 인자로 받는다. path 는 @RequestMapping 에서 사용 했던 value 값 이고, view-name 은 ModelAndView 에 setViewName(String viewName) 이라고 보면 된다.


<mvc:view-controller path="/" view-name="redirect:sample/"/>


redirection 하는 경우에는 view-name 의 앞에 redirect: 를 작성 하자.


@Controller, @Service, @Repository, @Component 를 사용하여 Bean 을 생성 하는 도중 중복 되어 충돌이 나는 현상 이다.


Annotation-specified bean name 'class' for bean class [class] conflicts with existing, non-compatible bean definition of same name and class [class]


해당 Annotation 의 경우 value 값을 지정 하지 않으면 Class 의 첫번째 문자열을 소문자로 하여 Bean ID 값을 생성 한다. 같은 Class 를 value 값을 지정하지 않았기 때문 이다.


@Controller("myController") 이와 같이 value 값을 설정 하여 해결 하자.


JSP Form 태그의 enctype="multipart/form-data" 속성을 적용 하지 않은 경우 발생 한다.


해당 속성을 추가 하도록 하자.


AOP pointcut 에 1개 이상의 조건을 사용 하는 경우 AND&&, OR|| 를 사용 한다. Java Annotation 방식으로 사용 하지 않고, XML 에서 사용 하는 경우 예외가 발생 한다.


XML은 기본적으로 <, >, & 등 사용 되고 있으므로, 이스케이프 문자로 변환 해야 한다.


  • &lt; <
  • &gt; >
  • &amp; &


아래와 같이 &amp; 로 수정해서 사용 하자.


<aop:pointcut id="test" expression="execution(public * *..*Sample.*(..)) &amp;&amp; !execution(public * *..*Sample.exclude(..))"/>


참고 사이트


Spring MVC 기능을 기반으로 개발 하면, 핵심 기능인 DI(Dependency Injection) 기능을 많이 사용하게 된다. 의존성 주입 이라고 보면 되겠다. setSample(new Sample()) 강제적으로 setter 할 필요 없이, Spring Container 가 @Inject, @Autowired, @Resource 를 사용하여 대신 setter 를 해준다고 보면 된다.


생각을 반대로 바꿔보자. Spring Container 에게 특정 객체를 달라고 요청 하는 것이다. 반대로 객체를 받아오는 경우를 DL(Dependency Lookup) 이라 한다. Spring ApplicationContext 에게 getBean(String id, class<?> clazz) 함수를 호출하여 반대로 필요한 객체를 받게 된다.


객체는 Bean Scope 의 영향을 받는다.


  • singleton: 그 말대로 하나의 객체만 존재 한다.
  • prototype: 객체를 매번 생성 하기 때문에, 다른 객체가 제공 된다.
  • request: http 요청을 기준으로 객체를 제공 한다. http 요청 생명 주기에 단 하나의 객체만 존재 한다. 다른 http 요청에 영향 받지 않는다.
  • session: session 발생을 기준으로 객체를 제공 한다. session 생명 주기에 단 하나의 객체만 존재 한다.
  • global session: 테스트를 해봐야 할 것 같다. (WAS의 ServletContext 와 같은 개념인듯 싶다.)


DL(Dependency Lookup) 의 사용을 위해서는 ApplicationContextAware 인터페이스 구현을 필요로 한다.


public class Sample implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void sample() {
        Target target = this.applicationContext.getBean("ID", Target.class); // DL

        // Doing...
    }
}


위 샘플 소스를 참조 하면 ApplicationContext 를 받아 왔다. 이제 반대로 객체를 받아서 사용해 보자. 중앙에서 관리 하는 기능을 구현해야 할 일이 있다면, 편하게 사용되는 것 같다.


JSP 페이지 에서 날짜 값을 파라미터로 전달 할 때, 서버에서 String 으로 오는 날짜 값을 파싱하여, Date 객체로 넣는 작업이 예외 발생 시 Bad Request 가 발생 하였다. 이를 해결 하기 위해서는 직접 파싱 후 바인딩 하는 작업을 해줘야 한다. @initBinder 기능을 사용하면 되겠다.


@InitBinder
public void initBinder(WebDataBinder binder) throws Exception {
    binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {

        public void setAsText(String text) throws IllegalArgumentException {
            try {
                setValue(new SimpleDateFormat("yyyy-MM-dd").parse(text));
            } catch (ParseException e) {
                setValue(null);
            }
        }
    });
}


요청을 받는 Controller 에 해당 코드를 추가 한다. setAsText(String text) 구현은 자유롭게 하면 되겠다. 위 코드는 샘플 이다. String 으로 전달되는 값을 Date 객체로 변경하여 넣어준다. 위 코드를 작성 했다면, Date 로 전달 되는 값을 받아볼 수 있게 된다.


+ Recent posts