본문 바로가기

토비의 스프링 정리

토비의 스프링 - 6.3 다이내믹 프록시와 팩토리 빈

6.3.1 프록시

  1. 전략 패턴 적용을 통한 부가기능 구현의 분리
    • 부가적 기능을 위임을 통해 분리
    • 기능을 사용하는 코드는 핵심 코드와 함께 남아 있음
  2. 부가기능과 핵심기능의 분리
    • 트랜잭션은 비즈니스 로직과 성격이 다르기 때문에 분리 가능
    • 트랜잭션을 UserServiceTx로 분리하여 UserServiceImpl에는 비즈니스 로직만 남음
  3. 핵심기능 인터페이스 적용
    • 부가기능과 핵심기능을 같은 인터페이스를 구현하게 만듦
    • 클라이언트는 인터페이스를 통해서만 핵심기능 사용
    • 부가기능 코드를 먼저 거쳐 비즈니스 로직 부분을 핵심기능에 위임
    • 실제 대상인 것처럼 위장해서 클라이언트의 요청을 받기 때문에 프록시(Proxy)라도 부름

6.3.2 프록시 패턴 설명

  • 부가기능핵심기능같은 인터페이스구현
  • 프록시를 통해 최종적으로 요청을 위임받아 처리하는 오브젝트를 타깃(Target) or 실체(Real Subject)라고 함
  • 프록시 사용 목적 두 가지
    1. 클라이언트가 타깃에 접근 방법 제어
    2. 타깃에 부가기능 부여

6.3.3 데코레이터 패턴 설명

  • 타깃에 부가 기능런타임시에 다이내믹하게 부여하기 위해 프록시를 사용하는 패턴
  • 코드상에 어떤 방법순서로 프록시와 타깃이 연결되어 사용되는지 정해지지 않음
  • 새로운 기능을 추가할 때 유용함
  • 데코레이터 패턴은 프록시의 개수를 제한받지 않음
  • 따라서, 여러 부가 기능을 부여할 수 있음
  • 데코레이터는 위임하는 대상에도 인터페이스로 접근하기 때문에 런타임시 주입받을 수 있음

  • InputStream 구현 클래스는 데코레이터 패턴이 사용된 대표적인 예시임
  • InputStream을 구현하는 FileInputStream에 버퍼 읽기 기능을 제공하는 BufferedInputStream이라는 데코레이터를 적용한 예시임
//InputStream : 인터페이스 
//FileInputStream : 타깃
//BufferedInputStream : 데코레이터
InputStream is = new BufferedInputStream(new FileInputStream("a.txt"));
  • 인터페이스를 통한 데코테이터 정의와 주입은 스프링의 DI 방식을 이용하면 편리함
  • UserServiceTx 클래스로 선언 된 UserService 빈은 데코레이터임
  • UserServiceTx는 UserService 타입의 오브젝트를 DI 받아 비즈니스 기능은 위임하지만, 그 과정에서 트랜잭션 경계설정 기능을 부여함
<beans 
		.../>
    <!--데코레이터-->
    <bean id="userService" class="com.ksb.spring.UserServiceTx">
        <property name="transactionManager" ref="transactionManager"/>
        <property name="userService" ref="userServiceImpl"/>
    </bean>
		
    <!--타깃-->
    <bean id="userServiceImpl" class="com.ksb.spring.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
        <property name="mailSender" ref="mailSender"/>
    </bean>
<beans/>

6.3.4 프록시 패턴

  • 일반적으로 사용하는 프록시프록시 패턴은 구분할 필요가 있음
  • 일반적 프록시는 클라이언트와 사용자 사이의 대리인 역할
  • 프록시 패턴은 타깃에 대한 접근 방법을 제어
  • 타깃의 기능을 확장 및 추가를 하지 않지만, 클라이언트가 타깃에 접근하는 방식을 변경함
  • 클라이언트가 타깃을 사용할 때, 프록시가 타깃 오브젝트를 생성하고 요청을 위임함
  • 다른 서버의 오브젝트를 사용할 때 프록시 패턴 사용
  • 원격 오브젝트에 대한 프록시를 만들어두고, 클라이언트는 마치 로컬에 존재하는 오브젝트를 쓰는 것 처럼 프록시를 사용할 수 있음
  • 💡 이유는 프록시원격 오브젝트같은 인터페이스구현하기 때문임
  • 또는, 특별한 상황에서 타킷에 대한 접근권한을 제어하기 우해 프록시 패턴 사용
  • 프록시를 거치게 하여 특정 메소드를 사용하려고 할 때, 예외를 던져 막을 수 있음
  • 구조적으로 프록시데코레이터유사
  • 다만, 프록시는 코드에서 자신이 만들거나 접근할 타깃 클래스의 정보를 아는 경우가 많음. 하지만, 프록시 패턴이라도 인터페이스를 통해 위임하게 할 수 있음

  • 앞으로 타깃과 동일한 인터페이스를 구현하고, 클라이언트와 타깃 사이에 존재하며, 부가 또는 접근 제어를 담당하는 오브젝트를 모두 프록시라 하겠음

6.3.5 프록시 패턴의 문제점

  • 프록시는 기존 코드에 영향을 주지 않고, 타깃의 기능 확장 또는 접근 방법을 제어할 수 있는 유용한 방법임
  • 하지만, 매번 새로운 클래스를 정의해야 함
  • 또한, 인터페이스에 구현해야 하는 메소드가 많을 경우 일일이 모든 메소드를 구현해야 함
  • UserServiceTx에서 트랜잭션이라는 부가기능을 수행하고, 비즈니스로직은 UserServiceImpl에 위임함
  • 부가기능 사이에 위임하는 부분이 존재하기 때문에, 부가기능을 재활용하는 것이 어려움
  • 때문에, 다른 메소드에서 동일한 트랜잭션 부가기능을 필요하면 똑같이 구현해야 해서 코드 중복이 발생함
  • 목 프레임워크 처럼 java.lang.reflect 패키지 안에 프록시를 쉽게 만들도록 하는 클래스들이 있음

6.3.6 리플렉션

  • 다이내믹 프록시는 리플렉션 기능을 이용해서 프록시를 만들어줌
  • 리플렉션자바의 코드 자체추상화해서 접근하도록 만든것임
  • 자바의 모든 클래스는 클래스 자체에 구성정보를 담은 Class 타입의 오브젝트를 하나씩 가지고 있음
  • “클래스이름.class” 또는 getClass()를 통해 Class 타입의 오브젝트를 가져올 수 있음
  • 클래스 오브젝트를 이용하면 클래스 코드에 대한 메타정보를 가져오거나 오브젝트를 조작할 수 있음
  • 클래스 이름, 상속한 클래스, 타입, 필드 등의 정보를 알 수 있음
  • 리플렉션 API 중에서 메소드에 대한 정의를 담은 Method 인터페이스를 이용하면 메소드를 호출할 수 있음

6.3.7 Method 인터페이스

  • 메소드에 대한 자세한 정보를 담고 있음
  • 스트링이 가진 메소드 중에서 “length”라는 이름을 갖고 있고, 파라미터가 없는 메소드 정보를 가져오는 코드는 다음과 같음
Method lengthMethod = String.class.getMethod("length");
  • 특정 오브젝트의 메소드를 실행할 수 있음
  • Method 인터페이스에 정의된 invoke()를 사용하면 됨
  • invoke()는 메소드를 실행시킬 대상 오브젝트(obj)와 파라미터 목록(args)를 받아 메소드를 호출한 뒤에 그 결과를 Object 타입으로 돌려줌
public Object invoke(Object obj, Object... args)

int length = lengthMethod.invoke(name) // int length = name.length();
public class ReflectionTest {
    @Test
    public void invokeMethod() throws Exception {
        String name = "Spring";

        //length()
        assertThat(name.length(), is(6));

        Method lengthMethod = String.class.getMethod("length");
        assertThat((Integer)lengthMethod.invoke(name), is(6));

        //charAt()
        assertThat(name.charAt(0), is('S'));

        Method charAtMethod = String.class.getMethod("charAt", int.class);
        assertThat((Character)charAtMethod.invoke(name, 0), is('S'));
    }
}

6.3.8 프록시 클래스

  • 프록시를 적용할 간단한 타킷 클래스와 인터페이스를 만들것임
  • 해당 코드는 인터페이스의 모든 메소드 구현 및 위임과, 부가기능이 모든 메소드에 중복돼서 나타남
  • 즉, 프록시 적용의 일반적인 두 가지문제점 모두 보유
public interface Hello {
    String sayHello(String name);
    String sayHi(String name);
    String sayThankYou(String name);
}

public class HelloTarget implements Hello{
    @Override
    public String sayHello(String name) {
        return "Hello "+name;
    }

    @Override
    public String sayHi(String name) {
        return "Hi "+name;
    }

    @Override
    public String sayThankYou(String name) {
        return "Thank You "+name;
    }
}

public class HelloUppercase implements Hello {
    Hello hello;

    public HelloUppercase(Hello hello) {
        this.hello = hello;
    }

    //toUpperCase()가 모든 메소드에서 중복. 즉, 부가기능이 중복됨
    @Override
    public String sayHello(String name) {
        return this.hello.sayHello(name).toUpperCase();
    }

    @Override
    public String sayHi(String name) {
        return this.hello.sayHi(name).toUpperCase();
    }

    @Override
    public String sayThankYou(String name) {
        return this.hello.sayThankYou(name).toUpperCase();
    }
}

public class ReflectionTest {
    ...
    @Test
    public void simpleProxy(){
        Hello hello = new HelloTarget();
        assertThat(hello.sayHello("Toby"), is("Hello Toby"));
        assertThat(hello.sayHi("Toby"), is("Hi Toby"));
        assertThat(hello.sayThankYou("Toby"), is("Thank You Toby"));
			
        //프록시를 통한 타깃 접근
        Hello proxiedHello = new HelloUppercase(new HelloTarget());
        assertThat(proxiedHello.sayHello("Toby"), is("HELLO TOBY"));
        assertThat(proxiedHello.sayHi("Toby"), is("HI TOBY"));
        assertThat(proxiedHello.sayThankYou("Toby"), is("THANK YOU TOBY"));

    }
}

6.3.9 다이내믹 프록시

  • 다이내믹 프록시는 프록시 팩토리에 의해 런타임 시 다이내믹하게 만들어지는 오브젝트
  • 다이내믹 프록시는 기존의 프록시처럼 타깃의 인터페이스와 동일한 인터페이스 구현
  • 프록시 팩토리에게 인터페이스 정보만 제공하면 해당 인터페이스를 구현한 클래스의 오브젝트 자동 생성함
  • 부가기능 InvocationHandler구현오브젝트에서 직접 작성
  • InvocationHandler는 invoke() 메소드만 가지는 인터페이스임
  • invoke()는 리플렉션의 Method 인터페이스를 파라미터로 받음
  • 다이내믹 프록시 오브젝트는 클라이언트의 모든 요청을 리플렉션 정보로 변환해서 invoke() 메소드에 전달
  • 모든 요청이 invoke()로 집중되기 때문에 중복되는 기능 효과적 제공가능
  • 리플랙션은 Method파라미터 정보가 있으면 메소드를 호출할 수 있음
Method charAtMethod = String.class.getMethod("charAt", int.class);
assertThat((Character)charAtMethod.invoke(name, 0), is('S'));
  • 리플렉션에는 메소드와 파라미터 정보(메타정보)를 모두 갖기있기 때문에 타깃 오브젝트 메소드 호출 가능
  • 즉, InvocationHandler구현 오브젝트가 타깃 오브젝트 레퍼런스를 가지고 있으면 리플렉션으로 간단히 위임 코드를 만들수 있음

6.3.10 다이내믹 프록시 적용

  • 다이내믹 프록시로부터 요청을 전달받으려면 InvocationHandler구현 해야함
  • 메소드는 invoke() 하나 뿐임
  • 프록시가 제공하려는 부가기능인 리턴 값을 대문자 변경
  • 다이내믹 프록시 생성은 Proxy 클래스의 newProxyInstance() 스태픽 팩토리 메소드를 사용하면 됨
    • 첫 번째 파라미터는 클래스 로더 제공
    • 두 번째 파라미터는 프록시가 구현해야 할 인터페이스. 다이내믹 프록시는 한 번에 하나 이상의 인터페이스를 구현할 수 있어 배열 사용
    • 세 번째 파라미터는 InvocationHandler 구현 오브젝트
public class UppercaseHandler implements InvocationHandler {
    Hello target;

    public UppercaseHandler(Hello target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        String ret = (String)method.invoke(target, args);
        return ret.toUpperCase(); //부가기능
    }
}

public class ReflectionTest {
		@Test
    public void simpleProxy(){
        ...
        //프록시를 통한 타깃 접근
        Hello proxiedHello = (Hello) Proxy.newProxyInstance(
                getClass().getClassLoader(),
                new Class[] {Hello.class},
                new UppercaseHandler(new HelloTarget()) 
        );
        assertThat(proxiedHello.sayHello("Toby"), is("HELLO TOBY"));
        assertThat(proxiedHello.sayHi("Toby"), is("HI TOBY"));
        assertThat(proxiedHello.sayThankYou("Toby"), is("THANK YOU TOBY"));
    }
}

 

 

  • 위에서 구현한 UppercaseHandler는 하나의 메소드만 구현하면서 재사용할 수 있음.
  • 즉, 기존 프록시 패턴의 수 많은 메소드 구현과 기능 중복을 해결함

6.3.11 다이내믹 프록시의 확장

  • 다이내믹 프록시를 사용함으로써 인터페이스의 모든 메소드를 구현안해도 됨
  • 다이내믹 프록시가 만들어질 때 인터페이스의 메소드가 자동적으로 추가되고, 부가기능은 invoke()에서 처리됨
  • UppercaseHandler는 모든 메소드의 리턴 타입이 스트링이라 가정하고 있음
  • 만약 스트링이 아닌 리턴타입을 갖는 메소드가 추가되면 런타임 시 캐스팅 오류가 발생
  • 리플렉션은 매우 유연하고 막강해 주의 깊게 사용해야 함
  • 따라서, Method를 이용한 타깃 오브젝트의 메소드 호출후 리턴타입을 확인하는 작업 필요
  • 또한, InvocationHandler 는 타깃의 종류 상관없이 적용 가능
  • 어쩌피 리플렉션의 Method 인터페이스를 이용해 타깃 메소드를 호출하기 때문
  • 즉, 타깃의 타입을 Hello가 아닌 Object로 지정해도 됨
public class UppercaseHandler implements InvocationHandler {
    Object target;

    public UppercaseHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object ret = method.invoke(target, args);
        if(ret instanceof String)
            return (String)((String) ret).toUpperCase();
        else
            return ret;
    }
}
  • 리턴 타입 뿐만이 아니라, 메소드 이름으로 조건을 걸 수 있음
  • Method 파라미터에서 메소드 이름을 가져와 확인하면 됨
public class UppercaseHandler implements InvocationHandler {
    ...
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object ret = method.invoke(target, args);
        if(ret instanceof String && 
                method.getName().startsWith("say"))
            return (String)((String) ret).toUpperCase();
        else
            return ret;
    }
}

6.3.12 다이내믹 프록시를 이용한 트랜잭션 부가기능

  • 기존의 UserServiceTx는 인터페이스의 메소드를 모두 구현해야 함
  • 또한, 트렌잭션이 필요한 메소드마다 트랜잭션 부가기능 코드 중복되서 나타남
  • 다이내믹 프록시를 사용하여 위 두 가지 문제를 해결
  • InvocationHandler를 구현한 TransactionHandler는 요청을 위임할 타깃을 Object 타입으로 DI로 제공받음
  • 따라서 UserServiceImpl 외에 트랜잭션이 필요한 곳을 타깃으로 할 수 있음
  • pattern 변수를 통해 해당 패턴으로 시작하는 조건을 걸 수 있음
  • 리플렉션에서 발생하는 예외가 발생하면 InvocationTargetException포장되서 던져지기 때문에 해당 예외를 받은 후 getTargetException() 으로 예외를 가져와야 함
public class TransactionHandler implements InvocationHandler {
    private Object target;
    private PlatformTransactionManager transactionManager;
    private String pattern;

    public void setTarget(Object target){
        this.target = target;
    }

    public void setTransactionManager(
            PlatformTransactionManager transactionManager){
        this.transactionManager = transactionManager;
    }

    public void setPattern(String pattern){
        this.pattern = pattern;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        if(method.getName().startsWith(pattern)){
            return invokeTransaction(method, args);
        }else{
            return method.invoke(target, args);
        }
    }

    private Object invokeTransaction(Method method, Object[] args)
            throws Throwable{
        TransactionStatus status =
                this.transactionManager.getTransaction(
                        new DefaultTransactionDefinition());
        try{
            Object ret = method.invoke(target, args);
            this.transactionManager.commit(status);
            return ret;
        }catch (InvocationTargetException e){
            this.transactionManager.rollback(status);
            throw e.getTargetException();
        }
    }
}

public class UserServiceTest {
    ...
    @Test
    public void upgradeAllOrNothing() {
        UserServiceImpl.TestUserService testUserService =
                new UserServiceImpl.TestUserService(users.get(3).getId());
        testUserService.setUserDao(this.userDao);
        testUserService.setMailSender(this.mailSender);
        
        TransactionHandler txHandler = new TransactionHandler();
        txHandler.setTarget(testUserService);
        txHandler.setTransactionManager(transactionManager);
        txHandler.setPattern("upgradeLevels");
        UserService txUserService = (UserService) Proxy.newProxyInstance(
                getClass().getClassLoader(),
                new Class[] {UserService.class},
                txHandler
        );

        userDao.deleteAll();
        for (User user : users) userDao.add(user);

        try {
            txUserService.upgradeLevels(); //txHandler를 통한 upgradeLevels()실행
            fail("TestUserServiceException expected");
        } catch (UserServiceImpl.TestUserServiceException e) {
        }

        checkLevelUpgraded(users.get(0), false);
    }
}

6.3.13 다이내믹 프록시를 위한 팩토리 빈

  • TransactionHandler와 다이내믹 프록시를 스프링 DI를 통해 사용할 수 있게 해야함
  • 스프링 빈은 일반적으로 클래스 이름과 프로퍼티로 정의됨
  • 스프링지정된 클래스 이름을 가지고 리플렉션을 이용해서 해당 클래스의 오브젝트를 만듦
  • Class의 newInstance()는 클래스의 파라미터가 없는 생성자를 호출하고, 그 결과 생성되는 오브젝트를 돌려주는 리플렉션 API임
Date now = (Date) Class.forName("java.util.Date").newInstance();
  • 스프링내부적으로 리플렉션 API를 이용해서 빈 정의에 나오는 클래스 이름을 가지고 빈 오브젝트를 생성함
  • 다이내믹 프록시는 위 방법으로 프록시 오브젝트가 생성되지 않음
  • 클래스 자체도 내부적으로 다이내믹하게 새로 정의해서 사용해 다이내믹 프록시 오브젝트의 클래스가 어떤 클래스인지 알지 못함
  • 때문에, 일반적인 방법으로 다이내믹 프록시는 스프링 빈에 등록할 방법이 없음
  • 다이내믹 프록시는 Proxy 클래스의 스태틱 메소드인 newProxyInstance()만 사용해 만들 수 있음

6.3.14 팩토리 빈

  • 사실 스프링은 클래스의 정보를 가지고 빈을 만들 수 있는 여러가지 방법 제공하고 있음
  • 이에 대한 대표적인 예시가 팩토리 빈
  • 팩토리 빈이란, 스프링을 대신해서 오브젝트 생성로직 담당하는 특별한
  • 팩토리 빈을 만드는 방법은 여러가지가 있지만, 가장 간단한 방법은 FactoryBean이라는 인터페이스 구현하는 것임
  • FactoryBean를 구현한 클래스를 스프링 빈으로 등록하면 팩토리 빈으로 동작함
//스프링 내부에 존재하기 때문에 구현X
package org.springframework.beans.factory;

public Interface FactoryBean<T>{
	T getObject() throws Exception; //빈 오브젝트를 생성해서 돌려줌
	Class<? extends T> getObjectType(); //생성되는 오브젝트의 타입을 알려줌
	boolean isSingleton(); //getObject()가 싱글톤인지 알려줌
}

6.3.15 다이내믹 프록시를 만들어주는 팩토리 빈

  • 다이내믹 프록시 빈은 Proxy의 newProxyInstance()를 통해서만 생성이 가능
  • 때문에, 다이내믹 프록시 오브젝트는 일반적으로 스프링에 등록 불가
  • 대신, 팩토리 빈사용하면 다이내믹 프록시 오브젝트를 스프링 빈에 등록 가능
  • getObject()에 프록시 오브젝트를 만들어주는 코드를 넣으면 됨
  • 스프링 빈에 팩토리 빈과 UserServiceImpl만 등록
  • 팩토리 빈에 다이내믹 프록시가 위임할 타깃인 UserServiceImpl의 레퍼런스를 프로퍼티를 통해 DI 받아야 함
  • 다이내믹 프록시와 함께 생성할 TransactionHandler에게 타깃 오브젯트를 전달해야하기 때문임

6.3.16 트랜잭션 프록시 팩토리 빈

  • 범용적으로 사용하기 위해 FactoryBean의 타입을 Object로 할당
  • 다이내믹 프록시를 생성할 때 구현하는 인터페이스를 serviceInterface 변수로 두고, Class<?>를 통해 UserService 외의 인터페이스를 가진 타깃에도 적용할 수 있게 함
  • xml에서 Class<?> 타입은 value로 넣으면 됨
  • 그러면 스프링이 Class 오브젝트로 자동 변환함
public class TxProxyFactoryBean implements FactoryBean<Object> {
    Object target;
    PlatformTransactionManager transactionManager;
    String pattern;
    Class<?> serviceInterface;

    public void setTarget(Object target){
        this.target = target;
    }

    public void setTransactionManager(
            PlatformTransactionManager transactionManager){
        this.transactionManager = transactionManager;
    }

    public void setPattern(String pattern){
        this.pattern = pattern;
    }

    public void setServiceInterface(Class<?> serviceInterface) {
        this.serviceInterface = serviceInterface;
    }

    @Override
    public Object getObject() throws Exception {
        TransactionHandler txHandler = new TransactionHandler();
        txHandler.setTarget(target);
        txHandler.setTransactionManager(transactionManager);
        txHandler.setPattern(pattern);
        return Proxy.newProxyInstance(
                getClass().getClassLoader(),
                new Class[] {serviceInterface},
                txHandler
        );
    }

    @Override
    public Class<?> getObjectType() {
        return serviceInterface;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}
<beans
		...>
    <bean id="userService" class="com.ksb.spring.TxProxyFactoryBean">
        <property name="target" ref="userServiceImpl"/>
        <property name="transactionManager" ref="transactionManager"/>
        <property name="pattern" value="upgradeLevels"/>
        <property name="serviceInterface" value="com.ksb.spring.UserService"/>
    </bean>
</beans>

6.3.17 트랜잭션 프록시 팩토리 빈 테스트

  • 추후에 내용 보강
public class UserServiceTest {
   
    @Autowired
    ApplicationContext context;

    @Test
    @DirtiesContext
    public void upgradeAllOrNothing() throws Exception {
        UserServiceImpl.TestUserService testUserService =
                new UserServiceImpl.TestUserService(users.get(3).getId());
        testUserService.setUserDao(this.userDao);
        testUserService.setMailSender(this.mailSender);

        //빈 자체를 가져올 때 &사용
        TxProxyFactoryBean txProxyFactoryBean =
                context.getBean("&userService", TxProxyFactoryBean.class);
        txProxyFactoryBean.setTarget(testUserService);

        //변경된 타깃 설정을 이용해 다이내믹 프록시 오브젝트 다시 생성
        UserService txUserService = (UserService) txProxyFactoryBean.getObject();

        userDao.deleteAll();
        for (User user : users) userDao.add(user);

        try {
            txUserService.upgradeLevels(); 
            fail("TestUserServiceException expected");
        } catch (UserServiceImpl.TestUserServiceException e) {
        }

        checkLevelUpgraded(users.get(0), false);
    }
}

6.3.18 설정 변경을 통한 트랜잭션 기능 부가

  • TransactionHandler를 이용하는 다이내믹 프록시를 생성해주는 TxProxyFactoryBean은 코드의 수정 없이도 다양한 클래스에 적용할 수 있음
  • 타깃 오브젝트에 맞는 프로퍼티 정보를 설정해서 빈으로 등록해주기만 하면 됨
  • 하나 이상의 TxProxyFactoryBean을 동시에 빈으로 등록해도 상관없음

6.3.19 프록시 팩토리 빈 방식의 장점과 한계

  • 장점
    • 타깃 인터페이스를 구현하는 클래스를 일일이 만들지 않아도 됨
    • 부가기능 코드 중복 제거
  • 한계
    • 부가기능은 메소드 단위로만 동작하기 때문에 여러 클래스에 공통적인 부가기능을 제공하는 방법은 불가능하여 프록시 팩토리 빈의 설정이 중복될 가능성이 높음
    • TransactionHandler 오브젝트가 프록시 팩토리 빈 개수만큼 만들어짐