스프링에서는 클래스패스를 이용해서 리소스를 가져오도록 설정하는 경우가 많다. 설정파일 처럼 애플리케이션의 일부인 경우에는 환경에 따라서 달라지는 파일시스템 패스보다 클래스패스를 사용해서 파일의 위치를 지정하는 것이 더 낫기 때문이다.

대표적인 예는 XML설정파일을 읽어서 초기화 하는 ClasspathXmlApplicationContext이다. 이름 그대로 클래스패스로 된 설정파일 위치를 파라미터로 받는다. 그렇다면 다음과 같은 코드에서 context.xml 파일의 위치는 어디일까?

new ClassPathXmlApplicationContext(“context.xml”)

이 때 context.xml은 /context.xml과 마찬가지이다. 다시 말해서 context.xml은 클래스패스 루트에 있어야 한다.

만약 서브패키지에 있는 파일이라면 다음과 같이 경로를 직접 적어줘야 한다.

new ClassPathXmlApplicationContext(“a/b/c/context.xml”)

보통은 클래스패스 루트에 설정파일을 두기 때문에 별 문제는 없지만, 테스트 등에서라면 특정 패키지 안에 있는 설정파일을 사용할 경우가 있는데 이 때 패키지를 일일이 지정해주는 것은 번거롭다. 이 때는 같은 클래스패스에 있는 클래스 정보를 넘겨주는 방법이 편리하다. 예를 들어 Hello 클래스가 a.b.c라는 패키지에 있다면 위의 설정은 다음과 같이 바꿀 수 있다.

new ClassPathXmlApplicationContext(“context.xml”, Hello.class)

Hello클래스의 클래스패스로부터 상대적인 위치를 찾는 것이다.

그런데 3.0에서 ClassPathXmlApplicationContext 대신 사용할 것을 권장하는 GenericXmlApplicationContext에는 이렇게 클래스 파라미터를 주는 방법이 없다. 그렇다면 항상 풀 클래스패스를 적어줘야 할까? 물론 그렇다.

하지만 스프링의 ClassUtil에서 제공하는 유틸리티 메소드를 사용하면 다음과 같이 작성할 수도 있다.

new GenericXmlApplicationContext(ClassUtils.classPackageAsResourcePath(clazz) + "/context.xml”);

classPackageAsResourcePath() 메소드는 클래스의 패키지를 클래패스 리소스 포맷으로 바꿔주는 메소드이다.

 

그런데 한가지 주의할 사항이 있다. 바로 테스트 컨텍스트 프레임워크를 사용할 경우이다.

스프링 테스트를 이용해서 다음과 같이 설정파일을 읽어서 동작하는 테스트 클래스를 작성했다고 해보자.

@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration("context.xml") 
public class MyTest {

이 때도 context.xml은 /context.xml과 마찬가지일까?

아니다. @ContextConfiguration은 독특하게도 /로 시작하지 않는 클래스패스는 테스트 클래스의 클래스 패스로부터 상대 위치로 인식한다. 클래스패스 루트에 있는 파일을 지정하려면 명시적으로 /context.xml이라고 적어야 한다. 테스트에서는 클래스별로 별도의 설정파일을 가져가는 경우가 만고, 이 때는 클래스와 같은 위치에 파일을 넣는 것이 편하기 때문에 이를 디폴트로 지정한 것 같다. 테스트 컨텍스트 프레임워크를 개발한 사람은 로드 존슨이나 유겐 횔러가 아닌, 새로운 개발자였기 때문일 수도 있고.

 

같은 클래스패스 파라미터을 받는 경우지만 이렇게 차이가 있다는 점에 주의하자. 두 가지 경우를 구분해서 사용하는 것이 귀찮으면 클래스패스 루트는 항상 /로 시작하는 습관을 들이는 것도 좋은 방법이다. API문서를 참고해서 메소드 파라미터가 어떻게 해석되는지 참고해보는 습관을 들이는 것도 좋을 것이다.


출처 - http://toby.epril.com/?p=1015




Posted by linuxism
,


자바에서는 클래스는 하나 이상의 생성자를 가질수 있다생성자를 이용하여 해당 빈을 속성값을 셋팅할수 있다.

생성자를 통해 속성값을 셋팅하는 것은 강한 의존성을 가지는 반면세터를 이용하여 셋팅하는 것은 어떤 속성이 필수인지 아닌지를 설정할 수가 없는 점이 발생한다.

세터 주입의 경우에는 주입하고자 하는 특성을 <property> 요소를 사용하여 정의했다.

생성자 주입의 경우도 마찬가지인데다만 빈이 인스턴스화될때 생성자에 전달할 인자를 <bean>  하위요소로서 <constructor-arg> 사용하여 지정한다는 점이 다르다.

또한 <constructor-arg> <property> 요소와는 달리 name 속성을 갖지 않는다

 

다음은 생성자 주입을 사용하는 설정예제이다.

 

<bean id = "foo"

class="com.springinaction.Foo">

<constructor-arg>

<value>42</value>

</constructor-arg>

</bean>

 

<bean id = "foo"

class="com.springinaction.Foo">

<constructor-arg>

<ref bean = "bar" />

</constructor-arg>

</bean>

 

모호한 생성자 인자의 처리

 

만약 생성자의 인자들의 값들이 동일한 타입이라면 어떤 인자에 어떤값을 넣을지 Spring 컨테이너는 어떻게   있을까?

 

예제를 보면

 

<bean id = "foo"

class="com.springinaction.Foo">

<constructor-arg>

<value>http://www.maning.com </value>

</constructor-arg>

<constructor-arg>

<value>http://www.springinaction.com </value>

</constructor-arg>

</bean>

 

 

빈에서는 다음과 같이…

 

public class Foo {

Public Foo(String arg1, java.net.URL arg2){

...

}

}

 

이라는 가정하에서 보자.

Spring 빈을 묶을   인자값을 어떻게 할당할까요?

답은 spring  org.springframework.beans.factory.UnsatisfiedDependencyException 던져 사용자에게 생성자의 인자에 모호함을 알려준다.

생성자 인자를 다룰  있는 방법에는 2가지 존재하는데 하나는 색인을 이용하는 것과 타입을 이용하는 것이다.

 

1. Index 이용

<bean id = "foo"

class="com.springinaction.Foo">

<constructor-arg   index = "1">

<value>http://www.maning.com </value>

</constructor-arg>

<constructor-arg   index = "0">

<value>http://www.springinaction.com </value>

</constructor-arg>

</bean>

 

Index 0 부터 시작이다 0번이 첫번째 인자값에 할당 되고 1번째가 두번째 인자값에 할당된다.

 

2. Type 이용

 

<bean id = "foo"

class="com.springinaction.Foo">

<constructor-arg   type = "java.lang.String">

<value>http://www.maning.com </value>

</constructor-arg>

<constructor-arg   type = "java.net.URL">

<value>http://www.springinaction.com </value>

</constructor-arg>

</bean>

 

인자에 맞는 형에 할당되어진다만약 같은 형이 2 이상 존재한다면 반드시 index 이용해야 한다.

 

생성자와 세터 비교

 

1. 생성자의 장점

생성자 주입은 강한 의존성 계약을 강제한다요컨데빈은 모든 의존성에 대해 총족되지 않으면 인스턴스화될  없다일단 인스턴스화되면 빈은 완벽하게 유효하며 즉시 사용 가능한 상태가 된다물론 이는 빈의 생성자가 빈의 모든 의존성에 해당하는 파라미터 목록을 갖고 있다는 가정하에 그렇다.

빈의 모든 의존성이 생성자를 통해 설정되기 때문에불필요한 세터 메소드를 가질 필요가 없다이는 코드의 양을 최소로 유지시켜주는 장점이 있다.

오직 생성자를 통해서만 특성을 설정할  있도록 함으로써자연히  특성을 변경되지 않는 특성이 되는 효과가 있다.

 

2. 생성자의 단점

- 빈이 여러 개의 의존성을 갖는 경우에는 생성자의 파라미터 목록이 매우 길어진다.

유효한 객체를 구성하는 다양한 방법이 존재한다면오직 파라미터의 수와 타입에 의해 생성자의 시그니처(signature) 다양해질 것이므로 특정한 생성자를 식별하기가 어려워진다.

만약 생성자가 동일한 타입의 두개 이상의 파라미터를 취한다면 파라미터의 목적을 파악하기 어려워질 것이다.

생성자 주입은  자체로 즉시 상속에서 사용할  없다부모 객체의 private 특성을 설정하기 우해서는 빈의 생성자에서 항상 파라미터를 super() 넘겨야  것이다.

 

세터와 생성자중 어느것을 선택해도 상관이 없다가장 좋은 방안은 spring 설정 파일의 명확하게 만들주기만 하면 된다.

참고로 위의 장단점을 파악하여 좋은것을 취하는 방법이 우선적으로 하고 단점이 생기지 않도록 설정파일을 만들면 될것 같다.


출처 - http://www.jakartaproject.com/board-read.do?boardId=jsptip&boardNo=122000236700501&command=READ&t=1352791540902




Posted by linuxism
,


Proxy에 대하여..

Spring AOP는 두가지 Type의 Proxy를 지원하고 있다. 그 첫번째는 JDK의 Proxy 기능을 이용하는 것이고, 두번째 방법은 CGLIB의 Enhancer 클래스를 이용하는 것이다. 이 두가지 Proxy의 차이점을 이해하고 사용하는 Spring AOP를 제대로 사용하는 것이 될 것이다.

Proxy의 핵심적인 기능은 원하는 메써드가 호출(Invocation)될 때 이 메써드를 가로채어 우리가 원하는 특정 기능들을 추가할 수 있도록 지원하는 것이다.

JDK Proxy

JDK Proxy는 인터페이스에 대한 Proxy만을 지원하며, 클래스에 대한 Proxy를 지원할 수 없다는 것이 큰 단점이다. 자바에서 인터페이스를 사용하는 것이 좋은 설계임에는 틀림없지만 모든 애플리케이션에서 인터페이스를 사용한다는 제약을 두는 것은 좋지 않다. 특히 국내와 같이 인터페이스를 많이 사용하지 않고, 이에 대한 인식이 없는 상태에서 Proxy 기능을 사용하기 위하여 인터페이스를 사용해야 한다고 강요할 경우 개발자들에게 상당한 반발을 가져올 수 밖에 없다.

또한 이미 구현되어 있는 애플리케이션에 Proxy기능을 추가할 때 JDK Proxy를 사용한다면 클래스로 구현되어 있는 소스에서 인터페이스를 추출한 다음 Proxy를 적용할 수 밖에 없다는 단점이 있다. 이와 같이 클래스에 Proxy를 적용하고자 할 경우에는 CGLIB Proxy만을 사용해야 한다.

JDK Proxy가 가지는 또 하나의 단점은 Target 클래스에 Proxy를 적용할 때 PointCut에 정보에 따라 Advice되는 메써드와 그렇지 않은 메써드가 존재한다. 그러나 JDK Proxy를 사용할 경우 Target 클래스에 대한 모든 메써드 호출이 일단 JVM에 Intercept한 다음 Advice의 invoke 메써드를 호출하게 된다. 그 후에 이 메써드가 Advice되는 메써드인지 그렇지 않은지를 판단하게 된다. 이 과정에서 JVM에 의하여 Intercept한 다음 invoke 메써드를 호출할 때 JDK의 reflection을 이용하여 호출하게 되는것이다. 이는 Proxy를 사용할 때 실행속도를 상당히 저하시키는 원인이 된다.

Spring 프레임워크에서 JDK Proxy를 사용하고자 한다면 ProxyFactory의 setProxyInterfaces() 메써드에 사용할 인터페이스를 전달하면 JDK Proxy를 이용할 수 있다. 그러나 이 메써드를 통하여 인터페이스를 전달하지 않을 경우 기본적인 Proxy는 CGLIB Proxy가 된다.

CGLIB Proxy

CGLIB Proxy 또한 JDK Proxy처럼 Runtime시에 Target 메써드가 호출될 때 해당 메써드의 Advice적용 여부를 결정하게 된다. 그러나 CGLIB는 메써드가 처음 호출 되었을때 동적으로 bytecode를 생성하여 이후 호출에서는 재사용하는 과정을 거치게 된다. 이 같은 과정을 통하여 두번째 호출이후부터는 실행속도의 향상을 가져올 수 있는 방법을 사용하고 있다.

또한 CGLIB Proxy는 클래스에 대한 Proxy가 가능하다.

JDK Proxy와 CGLIB Proxy의 성능비교

ISimpleBean.java
public interface ISimpleBean {

    public void advised();
    public void unadvised();
    
}
SimpleBean.java
public class SimpleBean implements ISimpleBean {

    private long dummy = 0;
    
    public void advised() {
        dummy = System.currentTimeMillis();
    }

    public void unadvised() {
        dummy = System.currentTimeMillis();
    }
}
NoOpBeforeAdvice.java
import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

public class NoOpBeforeAdvice implements MethodBeforeAdvice {

    public void before(Method method, Object[] args, Object target) throws Throwable {
        // no-op
    }
}
TestPointcut.java
import java.lang.reflect.Method;

import org.springframework.aop.support.StaticMethodMatcherPointcut;

public class TestPointcut extends StaticMethodMatcherPointcut {

    public boolean matches(Method method, Class cls) {
        return ("advised".equals(method.getName()));
    }

}
ProxyPerfTest.java
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class ProxyPerfTest {

    public static void main(String[] args) {
        ISimpleBean target = new SimpleBean();

        Advisor advisor = new DefaultPointcutAdvisor(new TestPointcut(),
                new NoOpBeforeAdvice());

        runCglibTests(advisor, target);
        runCglibFrozenTests(advisor, target);
        runJdkTests(advisor, target);
    }

    private static void runCglibTests(Advisor advisor, ISimpleBean target) {
        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(target);
        pf.addAdvisor(advisor);
             
        ISimpleBean proxy = (ISimpleBean)pf.getProxy();
        System.out.println("Running CGLIB (Standard) Tests");
        test(proxy);
    }
    
    private static void runCglibFrozenTests(Advisor advisor, ISimpleBean target) {
        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(target);
        pf.addAdvisor(advisor);
        pf.setFrozen(true);
        
        ISimpleBean proxy = (ISimpleBean)pf.getProxy();
        System.out.println("Running CGLIB (Frozen) Tests");
        test(proxy);
    }
    
    private static void runJdkTests(Advisor advisor, ISimpleBean target) {
        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(target);
        pf.addAdvisor(advisor);
        pf.setInterfaces(new Class[]{ISimpleBean.class});
        
        ISimpleBean proxy = (ISimpleBean)pf.getProxy();
        System.out.println("Running JDK Tests");
        test(proxy);
    }
    
    private static void test(ISimpleBean bean) {
        long before = 0;
        long after = 0;
        
        // test advised method
        System.out.println("Testing Advised Method");
        before = System.currentTimeMillis();
        for(int x = 0; x < 500000; x++) {
            bean.advised();
        }
        after = System.currentTimeMillis();;
        
        System.out.println("Took " + (after - before) + " ms");
        
        // testing unadvised method
        System.out.println("Testing Unadvised Method");
        before = System.currentTimeMillis(); 
        for(int x = 0; x < 500000; x++) {
            bean.unadvised();
        }
        after = System.currentTimeMillis();;
        
        System.out.println("Took " + (after - before) + " ms");
        
        // testing equals() method
        System.out.println("Testing equals() Method");
        before = System.currentTimeMillis(); 
        for(int x = 0; x < 500000; x++) {
            bean.equals(bean);
        }
        after = System.currentTimeMillis();;
        
        System.out.println("Took " + (after - before) + " ms");
        
        // testing hashCode() method
        System.out.println("Testing hashCode() Method");
        before = System.currentTimeMillis(); 
        for(int x = 0; x < 500000; x++) {
            bean.hashCode();
        }
        after = System.currentTimeMillis();;
        
        System.out.println("Took " + (after - before) + " ms");
        
        // testing method on Advised
        Advised advised = (Advised)bean;
        
        System.out.println("Testing Advised.getProxyTargetClass() Method");
        before = System.currentTimeMillis(); 
        for(int x = 0; x < 500000; x++) {
            advised.getProxyTargetClass();
        }
        after = System.currentTimeMillis();;
        
        System.out.println("Took " + (after - before) + " ms");
        
        System.out.println(">>>\n");
    }
}

output : 
Running CGLIB (Standard) Tests
Testing Advised Method
Took 310 ms
Testing Unadvised Method
Took 60 ms
Testing equals() Method
Took 30 ms
Testing hashCode() Method
Took 20 ms
Testing Advised.getProxyTargetClass() Method
Took 20 ms
>>>

Running CGLIB (Frozen) Tests
Testing Advised Method
Took 191 ms
Testing Unadvised Method
Took 60 ms
Testing equals() Method
Took 30 ms
Testing hashCode() Method
Took 20 ms
Testing Advised.getProxyTargetClass() Method
Took 10 ms
>>>

Running JDK Tests
Testing Advised Method
Took 410 ms
Testing Unadvised Method
Took 271 ms
Testing equals() Method
Took 260 ms
Testing hashCode() Method
Took 70 ms
Testing Advised.getProxyTargetClass() Method
Took 131 ms
>>>

위 결과를 보면 Advised 메써드의 경우 JDK보다 CGLIB(Standard) Proxy를 사용할 경우 25%이상 더 빠른 것을 확인할 수 있다. Advised 메써드의 경우에는 25%정도이지만 다른 UnAdvised되는 메써드를 확인해 보면 그 차이는 상당히 크다는 것을 확인할 수 있다. CGLIB Proxy에 비해 JDK Proxy가 4배 이상 느린 것을 확인할 수 있다. 우리들이 모든 Object에서 가지는 equals()와 hashCode()에서도 비슷한 결과가 나타난 것을 확인할 수 있다.

이 같은 결과가 나타나는 가장 큰 이유는 JDK Proxy가 UnAdvised 메써드임에도 불구하고 매번 자바의 reflection을 이용하여 Advice의 invoke 메써드를 호출하기 때문이다.


출처 - http://wiki.javajigi.net/pages/viewpage.action?pageId=1065


Posted by linuxism
,