<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>dragonlab</title>
    <link>https://dragonlab.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 7 Apr 2026 14:30:37 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>who-is-hu</managingEditor>
    <image>
      <title>dragonlab</title>
      <url>https://tistory1.daumcdn.net/tistory/5143635/attach/9fa8e955028c45388af5f982afc4c5fb</url>
      <link>https://dragonlab.tistory.com</link>
    </image>
    <item>
      <title>[토비의 스프링] 6장 AOP (2)</title>
      <link>https://dragonlab.tistory.com/17</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;빈 후처리기를 사용한 자동생성 DefaultAdvisorAutoProxyCreator&lt;/h2&gt;
&lt;pre id=&quot;code_1709569139048&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- 서비스마다 반복되는 설정 --&amp;gt;
&amp;lt;bean id=&quot;userService&quot; class=&quot;org.springframework.aop.framework.ProxyFactoryBean&quot;&amp;gt;
    &amp;lt;property name=&quot;target&quot; ref=&quot;userServiceImpl&quot; /&amp;gt;
    &amp;lt;property name=&quot;interceptorNames&quot;&amp;gt;
        &amp;lt;list&amp;gt;
            &amp;lt;value&amp;gt;transactionAdvisor&amp;lt;/value&amp;gt;
        &amp;lt;/list&amp;gt;
    &amp;lt;/property&amp;gt;
&amp;lt;/bean&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 설정파일을 보면 이전에 ProxyFactoryBean 을 이용해서 중복코드를 많이 줄여냈지만 그래도 여전히 프록시가 필요할때마다 설정파일이 어느정도 반복되고있다. 중복을 발견했으니 또 없애보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링에서 제공하는 애플리케이션 컨텍스트에 대한 확장포인트인 BeanPostProcessor 인터페이스를 구현하면되는데 DefaultAdvisorAutoProxyCreator가 해당 인터페이스를 구현하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;빈후처리기?&lt;br /&gt;빈으로 등록해두면 모든 빈들이 등록될때마다 후처리기에 넘어가서 특정 작업을 수행한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DefaultAdvisorAutoProxyCreator 를 빈으로 등록해두면 새로 등록되는 모든 빈에대해서 빈에 등록해둔 어드바이저의 포인트컷을 사용해서 작업을 수행할지 결정하고 해당한다면 프록시를 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포인트컷 인터페이스를 보면 클래스도 필터링 할수 있다. 이 인터페이스의 구현체를 빈으로 등록하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1709569714440&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface PointCut {
    ClassFilter getClassFilter();
    MethodMatcher getMethodMatcher();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709744835471&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- 빈 후처리기 --&amp;gt;
&amp;lt;bean class=&quot;org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator&quot; /&amp;gt;
	
&amp;lt;!-- 부가기능 --&amp;gt;
&amp;lt;bean id=&quot;transactionAdvice&quot; class=&quot;springbook.user.service.TransactionAdvice&quot;&amp;gt;
	&amp;lt;property name=&quot;transactionManager&quot; ref=&quot;transactionManager&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;
	
&amp;lt;!-- 포인트 컷 --&amp;gt;
&amp;lt;bean id=&quot;transactionPointcut&quot; class=&quot;springbook.user.service.NameMatchClassMethodPointcut&quot;&amp;gt;
	&amp;lt;property name=&quot;mappedClassName&quot; value=&quot;*ServiceImpl&quot; /&amp;gt;
	&amp;lt;property name=&quot;mappedName&quot; value=&quot;upgrade*&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;

&amp;lt;!-- 포인트컷 만족하는 타겟에 어떤 부가기능 줄지 묶은 어드바이저 --&amp;gt;
&amp;lt;bean id=&quot;transactionAdvisor&quot; class=&quot;org.springframework.aop.support.DefaultPointcutAdvisor&quot;&amp;gt;
	&amp;lt;property name=&quot;advice&quot; ref=&quot;transactionAdvice&quot; /&amp;gt;
	&amp;lt;property name=&quot;pointcut&quot; ref=&quot;transactionPointcut&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;애스펙트 지향 프로그래밍이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애스펙트란 애플리케이션의 핵심기능은 아니지만 중요한 기능이고 핵심기능과 같이 있을때 의미를 가지는 모듈을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 객체지향 방법으로 분리하기 어려운 트랜잭션 경계설정과 같은 부분을 애스펙트라 보는것이다. 이것도 결국 OOP 를 더 잘하기 위한 보조기능인 것&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;-프록시 방식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속 살펴보던 프록시, 데코레이터, 빈 후처리 등을 사용해서 AOP를 지원하는 방법&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;-바이트코드조작 방식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AspectJ 가 있다. 바이트코드 조작을 통해서 직접 타깃 오브젝트를 고치는 방법.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 프록시를 타지않고 타겟안에서 서로 메서드 호출시엔 어드바이스를 적용할 수 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 애플리케이션 컨텍스트를 이용해야만 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 프록시 방식의 한계점을 극복할 수 있겠지만 그만큼 사용하기 까다롭다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트랜잭션 속성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TransactionDefinition 이라는 인터페이스는 트랜잭션의 동작을 정하는 네가지 속성을 가지고 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;트랜잭션 전파속성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PROPAGATION_REQUIRED: 이전에 생성된 트랜잭션이 있다면 참여하고 없으면 새로 생성한다.&lt;/li&gt;
&lt;li&gt;PROPAGATION_REQUIRE_NEW: 독립적인 트랜잭션을 생성한다.&lt;/li&gt;
&lt;li&gt;PROPAGATION_NOT_SUPPORTED: 진행중인 트랜잭션이 있어도 무시한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;격리수준
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매번 까먹는 트랜잭션 격리수준&amp;nbsp;&lt;/li&gt;
&lt;li&gt;기본설정은 DataSource에 정의된 속성을 따르게되어있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;제한시간
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랜잭션 수행 제한시간을 둘 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;읽기전용&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랜잭션안에서 데이터 조작시 예외를 던짐으로서 막아줄수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트랜잭션 인터셉터&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션 동작원리를 배우기 위해 TansactionAdivce를 열심히 만들었지만 사실 스플링에선 해당기능을 TransactionInterceptor 를 통해 제공하고 있다. PlatformTransactionManager와 Properties 를 주입해주어야 하는데 Properties는 TransactionDefinition 인터페이스의 4개 속성 + rollbackOn 을 가지고 있다. 그리고 이름패턴을 통해 각각 메서드에 설정을 다르게 해줄수있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;기본적으로 TransactionInterceptor는 checked 예외가 나왔을땐 예외 상황을 복구했을것이라 생각하기때문에 롤백하지 않는다. 처리할수 없는 RuntimeException이 발생했을때 롤백한다. 때문에 checked 예외에 롤백시키고 싶을때 rollbackOn 을 사용할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709905823840&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!--  TransactionAdvice -&amp;gt; TransactionInterceptor --&amp;gt;
&amp;lt;bean id=&quot;transactionAdvice&quot; class=&quot;org.springframework.transaction.interceptor.TransactionInterceptor&quot;&amp;gt;
    &amp;lt;property name=&quot;transactionManager&quot; ref=&quot;transactionManager&quot; /&amp;gt;
    &amp;lt;property name=&quot;transactionAttributeSource&quot;&amp;gt;
        &amp;lt;props&amp;gt;
        	&amp;lt;!-- 메서드 이름별로 속성을 다르게 설정 가능 --&amp;gt;
            &amp;lt;prop key=&quot;save*&quot;&amp;gt;PROPAGATION_REQUIRED&amp;lt;/prop&amp;gt;
            &amp;lt;prop key=&quot;get*&quot;&amp;gt;PROPAGATION_REQUIRED,readOnly&amp;lt;/prop&amp;gt;
        &amp;lt;/props&amp;gt;
    &amp;lt;/property&amp;gt;
&amp;lt;/bean&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;어노테이션 속성과 포인트컷&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 xml로 트랜잭션 설정 방법을알아봤지만 보통 @Transactional 로 많이 쓰는데 그 원리는 아래 순서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. TransactionAttributeSourcePointcut 이 해당 어노테이션이 붙은 모든 메소드, 클래스를 타겟으로 지정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. AnnotationTransactionAttributeSource 가 @Transactional이 가지고 있는 트랜잭션 설정정보를 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. TransactionInterceptor 에서 해당 설정을 사용해서 프록시를 만든다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 @Transactional 은 여러군데 붙힐수 있기때문에 특정한 우선순위를 가지는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현 메서드 &amp;gt; 구현 클래스 &amp;gt; 인터페이스 메서드 &amp;gt; 인터페이스 클래스 순이다.&lt;/p&gt;
&lt;pre id=&quot;code_1709906547396&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional // 4
public interface ITest {
    @Transactional // 3
    void test();
}
@Transactional // 2
public class Test implements ITest {
    @Transactional // 1
    @Override
    public void test() { }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발/spring</category>
      <category>책 정리</category>
      <category>토비의스프링</category>
      <author>who-is-hu</author>
      <guid isPermaLink="true">https://dragonlab.tistory.com/17</guid>
      <comments>https://dragonlab.tistory.com/17#entry17comment</comments>
      <pubDate>Fri, 8 Mar 2024 23:03:01 +0900</pubDate>
    </item>
    <item>
      <title>[토비의 스프링] 6장 AOP (1)</title>
      <link>https://dragonlab.tistory.com/16</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;6장은 양이 많아서 두번에 나눠서 정리!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시, 데코레이터&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;0. 이전코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션코드를 분리하고 싶은 욕구가 든다.&lt;/p&gt;
&lt;pre id=&quot;code_1708954116914&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserService {
    public void upgradeLevels() {
        TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
        try {
            List&amp;lt;User&amp;gt; users = userDao.getAll();
            for (User user : users) {
                if (canUpgradeLevel(user)) {
                    upgradeLevel(user);
                }
            }
            this.transactionManager.commit(status);
        } catch (RuntimeException e) {
            this.transactionManager.rollback(status);
            throw e;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 프록시와 DI를 이용한 트랜잭션 코드 분리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시패턴 - 프록시가 타겟의 접근방법을 제어하는 용도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데코레이터패턴 - 프록시가 타겟에 부가기능을 추가하는 용도&lt;/p&gt;
&lt;pre id=&quot;code_1708954834365&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserServiceTx implements UserService {
	UserService userService; // 타깃
	PlatformTransactionManager transactionManager; // 부가기능

	...

	public void add(User user) {
		this.userService.add(user); // 위임
	}

	public void upgradeLevels() {
		TransactionStatus status = this.transactionManager
				.getTransaction(new DefaultTransactionDefinition());
		try {

			userService.upgradeLevels();

			this.transactionManager.commit(status);
		} catch (RuntimeException e) {
			this.transactionManager.rollback(status);
			throw e;
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserServiceImpl 을 타깃으로넣고 트랜잭션 부가기능을 추가하고 컨텍스트에서 UserService를 가져오면 UserServiceTx를 가져오게끔해서 트랜잭션 코드 분리를 성공했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 코틀린에서 위임패턴을 지원해주는 꿀팁 ( 필요한 메서드만 재정의하고 나머지는 구현을 target에게 위임한다 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하지만 아쉬운점이 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 위임 코드 작성하기 귀찮음&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 프록시클래스를 작성해줘야한다. 여러개의 클래스에 같은 기능을 주고 싶으면 AAAServiceTx, BBBServiceTx... 계속 추가해야한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;코틀린 Tip&lt;/blockquote&gt;
&lt;pre id=&quot;code_1708955305439&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserServiceTx(
    private val target: UserService,
) : UserService by target {
    override fun upgradeLevels() {
        // transaction code..
        target.upgradeLevels()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 같은 기능을 여러 메서드에 하려면 코드중복이 발생함&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가령 UserService의 add 에도 트랜잭션을 추가해야한다면 proxy 클래스에서 메서드마다 작성해줘야햔다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 리플레션 이용한 다이내믹 프록시 적용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링엔 리플렉션을 사용해 프록시를 쉽게 만들어주는 방법이 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1708955571013&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class TransactionHandler implements InvocationHandler {
	Object target; // 타깃
	PlatformTransactionManager transactionManager;
	String pattern; // 부가기능 발동 조건

	...

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

	private Object invokeInTransaction(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();
		}
	}
}


// 만약 생성한다면
TransactionHandler txHandler = new TransactionHandler();
txHandler.set(new TransactionStatus())
txHandler.setTarget(new UserServiceImpl())
...

UserService txUserService = (UserService) Proxy.newInstance(
	getClass().getClassLoader(),
    new Class[] { UserService.class },
    new TransactionHandler()
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부가기능을 InvocationHandler에 구현하고 그것을 인자로 넘겨서 프록시 객체를 만들어 낼 수 있다. 이렇게 해서 모든 메서드가 invoke 를 타게되면서 메서드에 같은 부가기능을 구현할때 중복 코드를 만들지 않을 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하지만 또 아쉬운점이 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성방식이 복잡해서 쉽게 애플리케이션 컨텍스트에 등록할수가 없음 =&amp;gt; 자유롭게 DI 해서 쓸수가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 팩토리 빈 사용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 문제점을 해결하기 위해서 스프링에선 팩토리패턴을 사용해서 복잡한 객체를 생성하고 Bean으로 등록해줄수 있는 FactoryBean 이라는 인터페이스를 제공한다.&lt;/p&gt;
&lt;pre id=&quot;code_1708956477103&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class TxProxyFactoryBean implements FactoryBean&amp;lt;Object&amp;gt; {
	Object target;
	PlatformTransactionManager transactionManager;
	String pattern;
	Class&amp;lt;?&amp;gt; serviceInterface;
	
	// setters
    ...

	// 호출시 새로 프록시를 만든다
	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);
	}

	public Class&amp;lt;?&amp;gt; getObjectType() {
		return serviceInterface;
	}
    
    // getObject는 매번 새로운 객체를 만들어준다는 뜻
	public boolean isSingleton() {
		return false;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 FactoryBean 으로 감싸서 애플리케이션 컨텍스트에 등록해줄수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;많이 좋아졌지만 또 문제점이 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 여러개의 부가기능을 주고싶으면 더 복잡해진다&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708957455319&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- 트랜잭션이 필요한 서비스마다 이 코드가 필요 --&amp;gt;
&amp;lt;bean id=&quot;userService&quot; class=&quot;springbook.user.service.TxProxyFactoryBean&quot;&amp;gt;
    &amp;lt;property name=&quot;target&quot; ref=&quot;userServiceImpl&quot; /&amp;gt;
    &amp;lt;!-- 여러 개의 프로퍼티 설정이 서비스 마다 반복 --&amp;gt;
    &amp;lt;property name=&quot;transactionManager&quot; ref=&quot;transactionManager&quot; /&amp;gt;
    &amp;lt;property name=&quot;performanceCheck&quot; ref=&quot;transactionManager&quot; /&amp;gt;
    &amp;lt;property name=&quot;securityManager&quot; ref=&quot;transactionManager&quot; /&amp;gt;
    ...
    &amp;lt;property name=&quot;pattern&quot; value=&quot;upgradeLevels&quot; /&amp;gt;
    &amp;lt;property name=&quot;serviceInterface&quot; value=&quot;springbook.user.service.UserService&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. ProxyFactoryBean&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링은 프록시를 빈에 등록하는 방법을 추상화한 방법을 제공한다. 어드바이스와 포인트컷(옵션)을 만들어서 ProxyFactoryBean에 넘겨주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1708957909876&quot; class=&quot;aspectj&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class TransactionAdvice implements MethodInterceptor {
	PlatformTransactionManager transactionManager;

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

	public Object invoke(MethodInvocation invocation) throws Throwable {
		TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
		try {
			Object ret = invocation.proceed();
			this.transactionManager.commit(status);
			return ret;
		} catch (RuntimeException e) {
			this.transactionManager.rollback(status);
			throw e;
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전과 다르게&amp;nbsp; 타깃의 대한 정보가 MethodInvocation 을 통해 추상화 되어 타깃을 몰라도 됨으로 코드 공유가 가능해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어드바이스&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타깃에 종속적이지 않은 부가기능 구현 방법이다. 타겟에 종속적이지 않기 때문에 코드를 공유할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MethodHandler 가 템플릿 MethodInvocation이 콜백&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;포인트컷&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 메서드에 어드바이스를 적용할지 결정하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708958190543&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- 공유가능 --&amp;gt;

&amp;lt;bean id=&quot;transactionAdvice&quot; class=&quot;springbook.user.service.TransactionAdvice&quot;&amp;gt;
    &amp;lt;property name=&quot;transactionManager&quot; ref=&quot;transactionManager&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;

&amp;lt;bean id=&quot;transactionPointcut&quot; class=&quot;org.springframework.aop.support.NameMatchMethodPointcut&quot;&amp;gt;
    &amp;lt;property name=&quot;mappedName&quot; value=&quot;upgrade*&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;

&amp;lt;bean id=&quot;transactionAdvisor&quot; class=&quot;org.springframework.aop.support.DefaultPointcutAdvisor&quot;&amp;gt;
    &amp;lt;property name=&quot;advice&quot; ref=&quot;transactionAdvice&quot; /&amp;gt;
    &amp;lt;property name=&quot;pointcut&quot; ref=&quot;transactionPointcut&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;

&amp;lt;!-- 서비스 --&amp;gt;

&amp;lt;bean id=&quot;userService&quot; class=&quot;org.springframework.aop.framework.ProxyFactoryBean&quot;&amp;gt;
    &amp;lt;property name=&quot;target&quot; ref=&quot;userServiceImpl&quot; /&amp;gt;
    &amp;lt;property name=&quot;interceptorNames&quot;&amp;gt;
        &amp;lt;list&amp;gt;
            &amp;lt;value&amp;gt;transactionAdvisor&amp;lt;/value&amp;gt;
        &amp;lt;/list&amp;gt;
    &amp;lt;/property&amp;gt;
&amp;lt;/bean&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 &amp;nbsp;&amp;lt;property name=&quot;transactionManager&quot; ref=&quot;transactionManager&quot; /&amp;gt; 같은 부분이 서비스 마다 반복되었다면 지금은 서비스마다 advisor 만 추가해주면 되서 중복을 많이 줄일수있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6.5장부터 다음 글에 계속!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/spring</category>
      <category>책 정리</category>
      <category>토비의스프링</category>
      <author>who-is-hu</author>
      <guid isPermaLink="true">https://dragonlab.tistory.com/16</guid>
      <comments>https://dragonlab.tistory.com/16#entry16comment</comments>
      <pubDate>Mon, 26 Feb 2024 23:38:08 +0900</pubDate>
    </item>
    <item>
      <title>[토비의 스프링] 5장 서비스 추상화</title>
      <link>https://dragonlab.tistory.com/15</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;앞쪽의 내용은 유저등급을 올리는 upgradeLevels() 메서드의 리팩터링 과정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비즈니스 로직에서의 경계설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저들의 등급을 올리다가 하나에 문제가 생기면 전체를 롤백해달라는 요구사항에 대응해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재구조는 UserDao의 메서드가 사용될때마다 JdbcTemplate에 의해 트랜잭션이 생겼다 커밋됐다 반복함으로 롤백을 시킬수가 없어 어쩔수 없이 트랜잭션 경계를 UserService에서 설정해야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1708349195660&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void upgradeLevels() throws Exception {
    Connection c = dataSource.getConnection();

    try {
        // 유저 등급 업그레이드 비지니스 로직
        user.upgradeLevel();
        userDao.update(c, user); // jdbcTemplate에서 해당 커넥션을 이용하게 전달
    } catch (SQLException e) {
        throw e;
    }
    ..
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항 대응을 위해 이런식으로 수정하면 아래와 같은 단점들이 발생한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1.   Connection 직접 다뤄야해서 JdbcTemplate 사용 불가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초반에 jdbc api를 직접사용했던것 처럼 비즈니스로직에서 설정한 트랜잭션을 받아서 그것을 사용해서 db에 접근해야함으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저뿐만 아니라 열심히 리팩터링한 UserDao 코드까지 롤백해야하는 슬픈상황이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Connection 파라미터 계속 물고다님&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserDao 까지 Connection을 전달해줘야 하기 때문에 중간에 거치는 모든 함수들의 파라미터의 Connection 이 추가되어 지저분해진다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. Connection 파라미터가 dao에 들어가는 순간 기술 독립적이지 않음&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4장에서 예외도 래핑하고 UserDao도 인터페이스로 뽑으며 기술독립적이게 하기위해 노력했지만 메서드 시그니쳐에 Jdbc기술인 Connection이 들어가 버렸으니 추상화도 실패하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개선&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;트랜잭션 동기화로 파라미터 제거&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링에서 제공하는 스레드에 안전한 TransactionSychronizationManger 를 이용해서 비즈니스 로직에서 커넥션을 만들어 저장해두고 UserDao에서 꺼내쓰는 방식으로 파라미터 드릴링(?) 을 제거한다. JdbcTemplate은 트랜잭션 동기화 저장소에서 커넥션이나 트랜잭션을 먼저 찾기때문에 UserDaoJdbc 는 수정하지 않아도 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1708350064670&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void upgradeLevels() throws Exception {
    TransactionSynchronizationManager.initSynchronization();
    Connection c = DataSourceUtils.getConnection(dataSource);
    c.setAutoCommit(false);

    try {
        // 유저 등급 업그레이드 비지니스 로직
        c.commit();
    } catch (Exception e) {
        c.rollback();
        throw e;
    } finally {
        DataSourceUtils.releaseConnection(c, dataSource);
        TransactionSynchronizationManager.unbindResource(this.dataSource);
        TransactionSynchronizationManager.clearSynchronization();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;트랜잭션 기술을 추상화&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;파라미터는 제거했지만 여전히 Jdbc 의존적이다. JdbcTemplate을 쓰지않는 UserDao 구현체로 바꾸면 잘 동작하지 않을 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를들어 Connection 말고 Session을 쓰는 Hibernate를 사용한다거나 글로벌 트랜잭션을위해서 JTA를 쓰는 경우이다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그래서 트랜잭션 사용자체를 추상화해야한다.&amp;nbsp;Spring에서는 여러가지 트랜잭션 관리방식을 추상화 해놓은 인터페이스 PlatformTransactionManager 를 제공한다. 이를 주입받아 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1708351372425&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserService {
    private PlatformTransactionManager transactionManager;

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

    public void upgradeLevels() throws Exception {
        TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());

        try {
            // 유저 등급 업그레이드 비지니스 로직
            this.transactionManager.commit(status);
        } catch (Exception e) {
            this.transactionManager.rollback(status);
            throw e;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;UserDao &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;애플리케이션 컨텍스트에 등록할때 &lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;PlatformTransactionManager도 알맞은 구현체를 등록해주어 DI 하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;서비스 추상화 응용&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링에서 제공하는 추상화기능이 없는 기술들은 직접 추상화 할수밖에 없다. 책의 예제에선 JavaMail을 MailSender라는 인터페이스로 추상화해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 큐를 사용하는 메일전송기능을 만든다거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 테스트 대역(DummyMailSender)을 만들어 테스트를 더 용이하게 해주는 방법도 소개해준다.&lt;/p&gt;</description>
      <category>개발/spring</category>
      <category>책정리</category>
      <category>토비의스프링</category>
      <author>who-is-hu</author>
      <guid isPermaLink="true">https://dragonlab.tistory.com/15</guid>
      <comments>https://dragonlab.tistory.com/15#entry15comment</comments>
      <pubDate>Mon, 19 Feb 2024 23:11:26 +0900</pubDate>
    </item>
    <item>
      <title>[토비의 스프링] 4장 예외</title>
      <link>https://dragonlab.tistory.com/14</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;초난감 예외처리&lt;/h3&gt;
&lt;pre id=&quot;code_1707921100836&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 무책임한 회피 throws
public void method1() throws Exception {
    throw new Exception(&quot;This is a test&quot;);
}

public void method2() throws Exception {
    method1();
}

public void method3() throws Exception {
    method2();
}

// catch 하지만 아무것도 하지 않음
public void method4() throws Exception {
    try {
        method1();
    } catch (Exception e) {
        // Do nothing
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예외처리 방법&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;복구&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외를 잘 파악하고 정상흐름으로 돌리는 조치를 취하는것 (ex. 다른 대안을 제시, 적절한 재시도)&lt;/p&gt;
&lt;pre id=&quot;code_1707921303062&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void retry() {
    int retryCount = 0;
    while (retryCount-- &amp;lt; 3) {
        try {
            errorMethod();
            ...
            return;
        } catch (Exception e) {
            // logging
        } finally {
            // cleanup
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;회피&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외를 처리하지않고 처리책임을 클라이언트에게 넘길수있다. 하지만 주의해야할점은 분명한 의도를 가지고 회피해야하는 것이다. 단순히 적절한 조취를 취하기 귀찮아서 throws 하면 위에서 봤듯이 초난감 예외처리가 될 뿐이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전환&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회피와 비슷하지만 예외를 그대로 던지는게아니라 적절한 예외로 바꿔서 던져준다. 예외를 전환하는데는 두가지 이유가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 해당 예외상황에 더 명확한 의미를 가지는 예외로 바꿔주기 위해서&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SqlException 이름만 봤을땐 너무 범위가 넓고 어떤 상황인지 파악하기 힘들다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1707922874260&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun pay(command: PayOrderCommand): PayOrderResult {
    try {
        val res = pgClient.pay()
        return PayOrderResult()
    } catch (e: FeignClientException) {
    	when (e.cause.code) {
            PgClientExceptionCode.InsufficientBalance.value -&amp;gt; throw InsufficientBalanceException(e)
            else -&amp;gt; throw PayFailedException(e)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예제는 회사코드에서 비슷하게 작성한 코드인데 FeignClient를 사용해서 결제api 호출했을때 예외가 발생하면 예외안에 code를 읽어 의미가 명확한 에러로 바꿔서 전달해주는 것이다.&amp;nbsp; pay 메서드를 사용하는 클라이언트 코드는  추상화된 예외만 처리함으로서 FeignClient 같은 특정 기술의 예외를 몰라도 되는 장점도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 처리하기 쉽게 단순히 포장하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처리를 계속 강제하는 check 예외를 그대로 전파하지 않고 uncheck 예외로 전환해서 위와같은 무지성 throws를 방지하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어차피 복구할수 없고 비즈니스적으로 중요한 예외가 아닐때 throws를 반복하느니 시스템에 맡기는 방법이다. @Transactional 같은 애노테이션을 사용했다면 자동롤백의 효과도 가져갈수있다. ( check 는 기본설정으론 롤백하지 않음 )&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1707923543175&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;try {
} catch(Exception e) {
	RuntimeException(e)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;런타임 예외 보편화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘은 저런 피로한 check 예외 처리를 피하기 위해서 보통 RuntimeException 을 사용하고 꼭 필요하면 catch 해서 처리하는 식으로 흐름이 많이 바뀌었다 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Kotlin은 이런 Java 예외처리 방법을 통계내서 봤을때 단순히 check 예외를 회피하는 의미없는 코드가 대다수라 checked 예외 자체를 없애버려서 어떤 예외든 처리를 강제하지 않게 했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DataAccessException&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring에서 제공하는 데이터 접근기술에 독립적인 추상적인 예외구조다. JDBC, Hibernate 등 기술마다 SqlException이 내용이 다르고 아예 본인들만의 예외를 내기도 하기 때문에 DataAccessException 구조안의 예외로 변경해서 던지는것인데, 완벽하게 신용할수는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 키값 중복상황에서 JDBC는 SqlException의 db error code를 사용해서 매핑하지만 다른 기술은 독자적인 예외를 가지고 매핑하기 때문에 각 기술에서 주는 예외가 충분히 어떤 상황인지 알수 없다면 뭉뜽그려서 예외를 매핑해줄수 밖에 없는 한계가 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/spring</category>
      <category>책 정리</category>
      <category>토비의스프링</category>
      <author>who-is-hu</author>
      <guid isPermaLink="true">https://dragonlab.tistory.com/14</guid>
      <comments>https://dragonlab.tistory.com/14#entry14comment</comments>
      <pubDate>Thu, 15 Feb 2024 00:33:53 +0900</pubDate>
    </item>
    <item>
      <title>[토비의 스프링] 3장 템플릿</title>
      <link>https://dragonlab.tistory.com/13</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1장에서 초난감 dao 에서 커넥션 생성에관한 책임분리를 DI와 IOC에 대해 배우면서 진행했고 이번장에서는 탬플릿/콜백 패턴을 배우면서 쿼리만드는것을 제외한 모든 책임을 &amp;nbsp;분리하고 코드 중복을 없애는 리팩토링 과정을 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;0. 리소스 반환을 위해 try catch 범벅&lt;/h3&gt;
&lt;pre id=&quot;code_1707142930556&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void deleteAll() throws SQLException {
    Connection c = null;
    PreparedStatement ps = null;

    try {
      c = dataSource.getConnection();
      // 이 라인 빼고 반복되는 부분이다
      ps = c.prepareStatement(&quot;DELETE FROM users&quot;);
      ps.executeUpdate();
    } catch (SQLException e){
      throw e;
    } finally {
      if (ps != null) {
        try {
          ps.close();
        } catch (SQLException e) {}
      }
      if (c != null) {
        try {
          c.close();
        } catch (SQLException e) {}
      }
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 템플릿 메서드 패턴으로 반복되는 부분 재사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 만드는 부분만 상속을 이용해서 확장하고 나머지 부분은 재활용 하게 디자인패턴을 적용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일정한 흐름을 만들어놓고 특정부분만 자식클래스에서 구현해서 사용하게 하는것을 탬플릿메서드 패턴이라 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1707143657743&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;abstract public class UserDao {
  ...

  abstract protected PreparedStatement makePreparedStatement(Connection c) throws SQLException;
  
  public void deleteAll() throws SQLException {
    Connection c = null;
    PreparedStatement ps = null;

    try {
      c = dataSource.getConnection();
      ps = makePreparedStatement(c);
      ps.executeUpdate();
    } catch (SQLException e){
      throw e;
    } finally {
      if (ps != null) {
        try {
          ps.close();
        } catch (SQLException e) {}
      }
      if (c != null) {
        try {
          c.close();
        } catch (SQLException e) {}
      }
    }
  }
}

class UserDeleteAllDao extends UserDao {
  @Override
  protected PreparedStatement makePreparedStatement(Connection c) throws SQLException {
    return c.prepareStatement(&quot;delete from users&quot;);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바뀌는 부분만 상속을 통해 확장했지만 단점이 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;기능 확장시마다 서브클래스를 만들어어한다
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;userDeleteAllDao, UserDeleteOneDao, UserFindOneDao ...&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;컴파일 타임에 확장구조가 결정되어 버린데
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;여태까지 봤던 DI를 이용한 런타임의 유연한 확장들은 할 수 없다.&lt;/li&gt;
&lt;li&gt;하지만 명확하게 파악가능하기 때문에 이것은 좋은점도 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 전략 패턴을 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안바뀌는 부분(Context) 에서 바뀌는 부분(Strategy) 를 분리하고 그것을 인터페이스로 만들어 확장할수 있게하는 디자인 패턴이 전략패턴이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1707145522400&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface StatementStrategy {
  PreparedStatement makePreparedStatement(Connection c) throws SQLException;
}

class DeleteAllStatement implements StatementStrategy {
  public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
    return c.prepareStatement(&quot;delete from users&quot;);
  }
}

public class UserDao {
  private DataSource dataSource;
  private StatementStrategy statementStrategy;

  private void jdbcContextWithStatementStrategy(StatementStrategy stmt) throws SQLException {
    Connection c = null;
    PreparedStatement ps = null;

    try {
      c = dataSource.getConnection();
      ps = stmt.makePreparedStatement(c);
      ps.executeUpdate();
    } catch (SQLException e){
      throw e;
    } finally {
      if (ps != null) {
        try {
          ps.close();
        } catch (SQLException e) {}
      }
      if (c != null) {
        try {
          c.close();
        } catch (SQLException e) {}
      }
    }
  }

  public void deleteAll() throws SQLException {
    jdbcContextWithStatementStrategy(new DeleteAllStatement());
  }
  
  // 분리함으로서 쉽게 context 재사용 가능
  public void deleteOne(String id) throws SQLException {
    jdbcContextWithStatementStrategy(new DeleteOneStatement(id));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;코드 재사용관점에서 리팩터링할때 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;1단계에서&lt;span&gt; &lt;/span&gt;&lt;/span&gt;상속을 이용한 한것과 다르게 이번엔 전략패턴을 사용함으로서 바뀌는부분을 DI 받을 수 있게 되고 안바뀌는 부분을 분리할수있게 된다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;타입에 계층을 만드는것이 아니라면 상속보단 합성이 낫다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 익명 클래스 사용해서 클래스 파일 줄이기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;런타임에 유연하게 확장가능한 장점은 얻었고 함수로 코드를 잘 분리했지만 클래스파일이 많아지는 문제는 여전하다. 이것은 익명클래스로 해결 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1707146138560&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserDao {
  private DataSource dataSource;
  private StatementStrategy statementStrategy;

  private void jdbcContextWithStatementStrategy(StatementStrategy stmt) throws SQLException {
    ...
  }

  public void deleteAll() throws SQLException {
    jdbcContextWithStatementStrategy(new StatementStrategy() {
      @Override
      public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
        return c.prepareStatement(&quot;delete from users&quot;);
      }
    });
  }

  public void add(User user) throws SQLException {
    jdbcContextWithStatementStrategy(new StatementStrategy() {
      @Override
      public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
        PreparedStatement ps = c.prepareStatement(&quot;insert into users(id, name, password) values(?, ?, ?)&quot;);
        ps.setString(1, user.id);
        ps.setString(2, user.name);
        ps.setString(3, user.password);
        return ps;
      }
    });
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어차피 UserDao만 사용하고 재사용하지 않는 클래스이니 이렇게 파일수를 줄이는게 좋을것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Context를 클래스로 분리하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserDao 한개의 클래스에 jdbc 관련 컨텍스트와 쿼리만드는 코드가 같이 있다. 두가지의 책임을 가지고 있다 볼수있는데. 하나의 책임만 가지게 클래스 분리할수있다. 게다가 jdbc 관련 코드는 다른 Dao에서도 재사용 가능해 보이기때문에 일거양득이다.&lt;/p&gt;
&lt;pre id=&quot;code_1707146777379&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface StatementStrategy {
  PreparedStatement makePreparedStatement(Connection c) throws SQLException;
}

class JdbcContext {
  private DataSource dataSource;

  public void setDataSource(DataSource dataSource) {
    this.dataSource = dataSource;
  }

  public void workWithStatementStrategy(StatementStrategy stmt) throws SQLException {
    Connection c = null;
    PreparedStatement ps = null;

    try {
      c = dataSource.getConnection();
      ps = stmt.makePreparedStatement(c);
      ps.executeUpdate();
    } catch (SQLException e) {
      throw e;
    } finally {
      if (ps != null) {
        try {
          ps.close();
        } catch (SQLException e) {
        }
      }
      if (c != null) {
        try {
          c.close();
        } catch (SQLException e) {
        }
      }
    }
  }
}

public class UserDao {
  private JdbcContext jdbcContext;
  // 쿼리만 만든다!
  public void deleteAll() throws SQLException {
    jdbcContext.workWithStatementStrategy(new StatementStrategy() {
      @Override
      public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
        return c.prepareStatement(&quot;delete from users&quot;);
      }
    });
  }
}

// jdbcContext 재사용
public class OtherDao {
  private JdbcContext jdbcContext;

  public void deleteAll() throws SQLException {
  	// 옛날 책이라 람다 슬쩍 넣어봄
    jdbcContext.workWithStatementStrategy((Connection c) -&amp;gt; c.prepareStatement(&quot;delete from others&quot;));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 전략패턴을 잘 적용했다. 여기서 context를 template 그리고 strategy를 callback 에 대응해서 템플릿/콜백 패턴으로도 볼수있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 템플릿/콜백 패턴 적용해서 중복 좀더 제거&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전보단 조금 가볍게 패턴을 이용해서 PreparedStatement 만드는 부분의 중복을 좀더 개선할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 문자열이 콜백이고 PreparedStatement 생성부분은 템플릿으로 보고 진행하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1707147531272&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class JdbcContext {
  private DataSource dataSource;

  public void setDataSource(DataSource dataSource) {
    this.dataSource = dataSource;
  }

  public void executeSql(final String query) throws SQLException {
    workWithStatementStrategy((Connection c) -&amp;gt; c.prepareStatement(query));
  }

   void workWithStatementStrategy(StatementStrategy stmt) throws SQLException {
    ...
  }
}

public class UserDao {
  private JdbcContext jdbcContext;

  public void deleteAll() throws SQLException {
    jdbcContext.executeSql(&quot;delete from users&quot;);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 add 같은것에는 적용하지 못하지만 JdbcContext에 제네릭을 더하는 식으로 더 넓게 쓸수있게 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 잘 만들어둔 JdbcTemplate 적용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; add 같은것들도 JdbcTemplate으로 잘 만들어져있다 Context , Template 딱딱 들어맞는게 참 재밌다.&lt;/p&gt;
&lt;pre id=&quot;code_1707147875972&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserDao {
  private JdbcTemplate jdbcTemplate;

  public void deleteAll() throws SQLException {
    jdbcTemplate.update(&quot;delete from users&quot;);
  }
  
  public void add(User user) throws SQLException {
    jdbcTemplate.update(&quot;insert into users(id, name, password) values(?, ?, ?)&quot;, user.id, user.name, user.password);
  }
  
  // queryForInt, queryForObject, getAll 등등...
  // ResultSet과 객체 매핑을 위한 RowMapper
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인패턴을 적용하려고 좋은 코드 얻는 연습을 많이 해야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 자주 쓰는 디자인 패턴도 공부를 해봐야겠다.&lt;/p&gt;</description>
      <category>개발/spring</category>
      <category>Spring</category>
      <category>책 정리</category>
      <category>토비의스프링</category>
      <author>who-is-hu</author>
      <guid isPermaLink="true">https://dragonlab.tistory.com/13</guid>
      <comments>https://dragonlab.tistory.com/13#entry13comment</comments>
      <pubDate>Tue, 6 Feb 2024 00:48:41 +0900</pubDate>
    </item>
    <item>
      <title>[토비의 스프링] 2장 테스트</title>
      <link>https://dragonlab.tistory.com/12</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;테스트에 대한 사용법 같은 부분은 생략하고 새로 알게된 부분 위주로 정리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1장에서 살펴봤던 Ioc, DI 같은 객체지향 기술은 요구사항 변화에 유연하게 대응하는데도 도움이 되지만 테스트를 짜게 쉽게 만드는데도 도움이 된다. 그래서 테스트를 짜지 않는다면 스프링이 주는 가치중 많은 부분을 포기하는 셈이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ApplicationContext 매번생성되는 문제점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 테스트가 독립적으로 실행되는것을 보다 확실하게 보장하기 위해서 매번 오브젝트를 생성한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class UserDaoTest {
    private UserDao dao;

    @Before
    void setUp(){
        ApplicationContext context = new GenericXmlApplicationContext(&quot;applicationContext.xml&quot;)
        this.dao = context.getBean(&quot;userDao&quot;, UserDao.class);
    }

    @Test
    void test1(){}

    @Test
    void test2() {}

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 &lt;code&gt;test1&lt;/code&gt;, &lt;code&gt;test2&lt;/code&gt; 가 실행될때마다 새로운 오브젝트를 만들면서 &lt;code&gt;@Before&lt;/code&gt;에 의해 &lt;code&gt;setUp&lt;/code&gt;이 호출된다.&lt;br /&gt;그래서 applicationContext도 여러번 생성하게 되는데 몇가지 이유로 안좋을수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;빈 설정이 복잡해지면 어플리케이션 컨텍스트 생성에 시간이 필요해서 테스트 피드백이 느려진다&lt;/li&gt;
&lt;li&gt;어떤 빈들은 자체적인 초기화 작업이 있어서 시간이 더 걸릴 수 있다&lt;/li&gt;
&lt;li&gt;많은 리소스를 할당하거나 독자적인 스레드를 사용하는 빈이 있다면 리소스가 정리되지 않고 매번 어플리케이션 컨텍스트생성 할때마다 할당되면서 문제를 초래할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 추가하면 좋을듯&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스프링 테스트 컨텍스트 프레임워크 적용하여 해결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 컨텍스트 프레임워크 : 테스트에 사용되는 어플리케이션 컨텍스트를 생성하고 관리하고 테스트에 적용해주는 기능을 가진 프레임워크 =&amp;gt; 어플리케이션 컨텍스트를 테스트에서도 쉽게 사용가능하게 도와줌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링은 JUnit을 이용하는 테스트컨텍스트 프레임워크를 제공해서 적용가능하다&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1706535876833&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RunWith(SpringJUnit4ClassRunner.class) // test 실행하는 runner를 spring 걸로 바꿈
@ContextConfiguration(locations=&quot;/applicationContext.xml&quot;)
class UserDaoTest {
	@Autowired
    UserDao dao;
   	...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 매번생성하지 않고 하나의 어플리케이션 컨텍스트로 사용가능하다. 여러개의 클래스에서도 공유하기때문에 성능향상에 도움이 많이 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@DirtiesContext&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일전에 CountingConnectionMaker를 별다른 코드 변경없이 추가한것처럼 테스트에서도 DI를 응용할수있다.&lt;/p&gt;
&lt;pre id=&quot;code_1706540354860&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@RunWith(SpringJUnit4ClassRunner.class) // test 실행하는 runner를 spring 걸로 바꿈
@ContextConfiguration(locations = &quot;/applicationContext.xml&quot;)
@DirtiesContext // 붙혀서 어플리케이션컨텍스트 바꿨다고 알려줌
class UserDaoTest {
    @Autowired
    UserDao dao;

    @Before
    public void setUp() {
        DataSource dataSource = new SingleConnectionDataSource(&quot;jdbc-url&quot;, ...);
        dao.setDataSource(dataSource);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 테스트를 위한 SingleConnectionDataSource로 간단하게 바꿀수있는데 `@DirtiesContext` 를 붙혀서 같은 어플리케이션 컨텍스트를 사용하는 다른 테스트를 위해 해당 테스트가 끝나면 어플리케이션 컨텍스트를 삭제하고 다시만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/spring</category>
      <category>Spring</category>
      <category>책 정리</category>
      <author>who-is-hu</author>
      <guid isPermaLink="true">https://dragonlab.tistory.com/12</guid>
      <comments>https://dragonlab.tistory.com/12#entry12comment</comments>
      <pubDate>Tue, 30 Jan 2024 01:29:15 +0900</pubDate>
    </item>
    <item>
      <title>[토비의 스프링] 1장 오브젝트와 의존관계</title>
      <link>https://dragonlab.tistory.com/11</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;책에서 소개하는 예제를 통해서 스프링은 왜이렇게 생겼는가 이해해 볼수 있는 챕터&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스프링의 철학&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ejb 가 커지면서 잃어버렸던 객체지향을 다시 잘 적용해보자
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ejb 가 많은 기능을 제공하지만 서비스로직에 컨테이너를 쓰기위한 코드들이 많이 침투됐다함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체지향을 해야하니까 오브젝트에 많은 관심을 둔다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오브젝트가 어떻게 생기고 없어지고 사용되는지&lt;/li&gt;
&lt;li&gt;오브젝트를 다루는 베스트프랙티스를 프레임워크단에서 제공해버리기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;첫번째, 관심사의 분리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 해야 하는가?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소프트웨어는 여러가지 요구사항에 의해 항상 변화하고 개발자는 변화를 어느정도 대비하면서 개발을 해야함&lt;/li&gt;
&lt;li&gt;관심사를 분리하지 않으면 db 암호를 바꾸려고 모든 dao를 변경해야하는등 수정에 유연하지 못하게 됨&lt;br /&gt;응집도 높고 결합도 낮은 코드를 만들기 위해 노력하자&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;User 를 읽고 쓰는 UserDao 가 진짜 유저만 읽고 쓰게 만들어보는 과정&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;0단계 일단 되게..&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public class UserDao {

    void void add(User user){
        // 커넥션 연결 책임
        Class.forName(&quot;driver&quot;)
        Connection c = DriverMnager.getConnection(&quot;jdbcUrl&quot;)

        // query 만드는 책임
        PreparedStatement ps = c.preapredStatment(&quot;sql query&quot;)
        ps.setString(1, user.getId())
        ps.setString(1, user.getId())
        ps.setString(1, user.getId())

        // query 실행하고 닫는 책임
        ps.excuteUpdate();
        ps.clode();

        // 커넥션 닫는 책임
        c.close();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 되지만 함수에 책임이 너무 많고 여러가지 함수가 생기면 중복되는 코드들이 많다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1단계 함수로 코드중복 제거&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public void add(User user) {
        Connection c = getConnection()

        // query 만드는 책임
        PreparedStatement ps = c.preapredStatment(&quot;sql query&quot;)
        ps.setString(1, user.getId())
        ps.setString(1, user.getId())
        ps.setString(1, user.getId())

        // query 실행하고 닫는 책임
        ps.excuteUpdate();
        ps.clode();

        // 커넥션 닫는 책임
        c.close();
}

private Connection getConnection() {
    // 커넥션 연결 책임
        Class.forName(&quot;driver&quot;)
        Connection c = DriverMnager.getConnection(&quot;jdbcUrl&quot;)
    return connection
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수로 분리해서 &lt;code&gt;커넥션 생성&lt;/code&gt;에 대한 책임을 덜어냈다. 개념적으론 커넥션에 대한 변경사항이 들어와도 다른 함수를 건들지 않는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2단계 상속으로 확장가능하게&lt;/h3&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;public abstract class UserDao {
    public void add(User user) {}
    public void get(String id) {}

    public abstract Connection getConnection()
}

class UserDaoMySql extends UserDao {
        public abstract Connection getConnection(){
            // 커넥션생성
        }
}
class UserDaoOracle extends UserDao {
        public abstract Connection getConnection(){
            // 커넥션생성
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기는 책임을 더 덜어내지는 않았지만 &lt;b&gt;템플릿 메서드 패턴&lt;/b&gt;을 사용해서 함수분리만으로는 불가능한 &lt;code&gt;커넥션 생성방법&lt;/code&gt;에 대한 확장을 이뤄냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;팩토리 메서드 패턴&lt;/b&gt; 사용해서 &lt;b&gt;커넥션 생성에 대한 책임&lt;/b&gt;이 함수에서 하위 클래스로 옮겨졌다. 관심사가 함수보다 더 멀찍이 분리 됐다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3단계 합성을 사용해서 더 명확하게 분리&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 책임이 하나니까 네이밍도 아주 명확하다
public interface ConnectionMaker {
    public Connection getConnection()
}

class MySqlConnectionMaker implements ConnectionMaker {
    public Connection getConnection(){
            // 커넥션생성
        }
}

class OracleConnectionMaker implements ConnectionMaker {
    public Connection getConnection(){
            // 커넥션생성
        }
}

public class UserDao {
    private ConnectionMaker connectionMaker;

    public UserDao() {
        // 어떤 커넥션 만들지 고르는 책임은 아직 남아 있음
        connectionMaker = new MySqlConnectionMaker();
    }

    public void add(User user){
        Connection c = connectionMaker.getConnection();
        ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식클래스에게 떠넘겼던 커넥션 생성의 책임을 상속의 단점을 버리고 더 명시적이고 화끈하게 다른 클래스로 분리한다. 그러기 위해서 상속을 버리고 합성을 선택한다. 그리고 확장도 놓칠 수 없으니 인터페이스도 적용한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4단계 클라이언트에 책임 떠넘기기&lt;/h3&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public class UserDao {
    private ConnectionMaker connectionMaker;

    public UserDao(ConnectionMaker connectionMaker) {
        this.connectionMaker = connectionMaker;
    }
}

public class UserDaoTest { // 여기로 떠넘기기
    public void test(){ // 테스트
        ConnectionMaker connectionMaker = new MySqlConnectionMaker();

        UserDao dao = new UserDao(connectionMaker); 

        dao.add(new User())
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserDao 를 사용하는 클라이언트 코드로 책임을 떠넘겨서 분리했다. 하지만 client는 함수를 테스트한다는 또 본인만의 책임이 있을것이기 때문에 바람직하지는 않다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5단계 팩토리 적용해서 바람직하게 떠넘기기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 커넥션을 만들지 결정하는 책임만 수행하는 클래스를 만들어서 책임을 분리한다. 그런 역할을 하는 애들을 팩토리 라고 한다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// 여기로 떠넘기기
public class DaoFactory {
    public UserDao userDao(){
        ConnectionMaker connectionMaker = new MySqlConnectionMaker();
        UserDao dao = new UserDao(connectionMaker); 
        return dao;
    }
}

public class UserDaoTest { 
    public void test(){  
        // 커넥션 뭔지 모름
        UserDao dao = new DaoFactory().userDao();
        dao.add(new User())
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ioc 제어의 역전&lt;br /&gt;5단계를 거쳐 리팩터링하면서 계속 숨어있던 제어의 역전이라는 개념이 있다. 흐름 제어 구조가 뒤 바뀌는 것인데 UserDao가 모든 오브젝트를 생성하고 사용하며 모든 흐름을 제어하다가. 그 제어 흐름을 팩토리에 맡기고 본인의 역할만 수행함으로써 흐름제어가 뒤 바뀐 것이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그래서 Spring 은 왜쓰냐?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링에서 생성/조합 역할을 담당하는게 빈팩토리이고 그것을 확장한것이 ApplicationContext 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 관리되는 객체들을 스프링빈이라고 한다. (제어의 역전이 적용된 오브젝트들)&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Confinguration
public class DaoFactory {
    @Bean
    public UserDao userDao(){
        return new UserDao(connectionMaker()); 
    }

    @Bean
    public ConnectionMaker connectionMaker(){
        return new MySqlConnectionMaker();
    }
}

public class UserDaoTest { 
    public void test(){  
        ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
        UserDao dao = context.getBean(&quot;userDao&quot;, UserDao.class);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기 까지만 보면 딱히 쓸 필요가 없고 더 복잡하기만 해보이지만 스프링컨테이너에 등록해서 쓰면 아래와 같은 장점이 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 간편하게 싱글톤으로 관리해줘서 성능 이점이 있다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수많은 요청마다 객체를 만들면 낭비이기 때문에 싱글톤 클래스로 만들어 쓰는걸 고려할 수 있다. 그래서 직접 싱글턴 클래스로 만들면 아래와 같은 한계를 만난다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;private 생성자라 상속 불가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체지향의 이점을 누리지 못한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;테스트하기 힘들다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모킹등이 쉽지 않다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;전역상태를 만들 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아무곳에서나 사용이가능해서 전역상태로 될수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링을 써도 똑같은게 아닌가 생각한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다.&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분산환경에서는 독립적으로 생겨서 싱글톤으로 가치가 떨어진다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링을 써도 똑같은게 아닌가 생각한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;클래스 로더 구성에 따라 싱글톤이 아니게 될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1706013555474&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// GPT 가 짜준 예제
public class Main {
    public static void main(String[] args) throws Exception {
        // 첫 번째 클래스 로더
        ClassLoader classLoader1 = new CustomClassLoader(&quot;loader1&quot;);
        Class&amp;lt;?&amp;gt; mySingletonClass1 = classLoader1.loadClass(&quot;MySingleton&quot;);
        Object instance1 = mySingletonClass1.getMethod(&quot;getInstance&quot;).invoke(null);

        // 두 번째 클래스 로더
        ClassLoader classLoader2 = new CustomClassLoader(&quot;loader2&quot;);
        Class&amp;lt;?&amp;gt; mySingletonClass2 = classLoader2.loadClass(&quot;MySingleton&quot;);
        Object instance2 = mySingletonClass2.getMethod(&quot;getInstance&quot;).invoke(null);

        System.out.println(instance1 == instance2); // false
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 클라이언트에서 구체적인 팩토리클래스를 몰라도 된다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책에 있는 예제로는 잘 안 와닿아서&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;// 이거 bean이 아니라서 repository 주입을 못 받음
public class UserEntityListener {
    @PrePersist
    @PreUpdate
    public void prePersistAndPreUpdate(Object o){
        UserHistoryRepository userHistoryRepository = BeanUtils.getBean(UserHistoryRepository.class);

        User user = (User) o;
        UserHistory userHistory = new UserHistory();

        userHistoryRepository.save(userHistory);
    }
}

@Component
public class BeanUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        BeanUtils.applicationContext = applicationContext;
    }

    public static &amp;lt;T&amp;gt; T getBean(Class&amp;lt;T&amp;gt; clazz){
        return applicationContext.getBean(clazz);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기 EntityListener에서 Repository 만드는 Factory를 몰라도 된다. 일관성있게 &lt;code&gt;getBean&lt;/code&gt; 으로 bean을 얻을 수 있음(검색이 가능함)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. DI의 장점을 쉽게 누릴수있게&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 자주 쓰는 &lt;code&gt;@Autowired&lt;/code&gt; 자동주입 같은것을 프레임워크 단에서 지원하면서 쉽게 사용할수 있게 해준다. 추후에 나올 트랜잭션이나 aop 같은것도 DI의 장점을 많이 이용하는데 어노테이션만 가지고 쉽게 쓸수 있게 해주는 셈이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. XML&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;xml 파일에 bean 구성정보를 담아서 코드 변경없이 의존성 변경 가능&lt;/p&gt;</description>
      <category>개발/spring</category>
      <category>Spring</category>
      <category>책정리</category>
      <category>토비의스프링</category>
      <author>who-is-hu</author>
      <guid isPermaLink="true">https://dragonlab.tistory.com/11</guid>
      <comments>https://dragonlab.tistory.com/11#entry11comment</comments>
      <pubDate>Tue, 23 Jan 2024 21:41:19 +0900</pubDate>
    </item>
    <item>
      <title>Spring form-url-encoded 방식의 파라미터에 배열이 대괄호로 표기될때 NPE 발생 해결법</title>
      <link>https://dragonlab.tistory.com/5</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;실무중에 클라이언트에서 request를 form-url-encoded 방식으로 보내는데 그중에 배열의 표기가 [ ] 를 사용할때 매핑에러가 났었다&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;list[]=1&amp;amp;list[]=2 이런식으로 배열을 url 방식으로 표기할때 명확한 표준이 없는 것 같다. list=1&amp;amp;list=2도 가능하다&lt;/p&gt;
&lt;pre id=&quot;code_1697036457520&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
class TestController {
    @PostMapping(&quot;/url-encode&quot;)
    fun urlEncode(body: TestBody): String {
        println(body)
        return &quot;ok&quot;
    }

    @PostMapping(&quot;/json&quot;)
    fun json(@RequestBody body: TestBody): String {
        println(body)
        return &quot;ok&quot;
    }
}

data class TestBody(
	val str: String,
	val num: Int,
	val list: List&amp;lt;Int&amp;gt;,
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 테스트코드를 작성했을때 json 방식은 잘된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmgK5X/btsx45Igq96/dqYNtLhXkXJRuBVrnpKkhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmgK5X/btsx45Igq96/dqYNtLhXkXJRuBVrnpKkhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmgK5X/btsx45Igq96/dqYNtLhXkXJRuBVrnpKkhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmgK5X%2Fbtsx45Igq96%2FdqYNtLhXkXJRuBVrnpKkhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;568&quot; height=&quot;82&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;108&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 url-encoded 방식으로 보낼때는 이렇게 list=1&amp;amp;list=2 방식으로 보내면 잘 동작한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LE3nN/btsx49qiU6r/4VMC2FVqB7NIa1kSOgKmhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LE3nN/btsx49qiU6r/4VMC2FVqB7NIa1kSOgKmhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LE3nN/btsx49qiU6r/4VMC2FVqB7NIa1kSOgKmhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLE3nN%2Fbtsx49qiU6r%2F4VMC2FVqB7NIa1kSOgKmhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;671&quot; height=&quot;249&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;96&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cz48x5/btsx1RDOsOU/nOHRy6VjJYAFwL9oWKdms0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cz48x5/btsx1RDOsOU/nOHRy6VjJYAFwL9oWKdms0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cz48x5/btsx1RDOsOU/nOHRy6VjJYAFwL9oWKdms0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcz48x5%2Fbtsx1RDOsOU%2FnOHRy6VjJYAFwL9oWKdms0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;563&quot; height=&quot;119&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;96&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 아래처럼 list[]=1&amp;amp;list[]=2 방식으로 보내면 NPE 에러가난다. 일단 []를 매핑을 잘 못하는것 같다고 의심이된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XyKdk/btsyuJkQkCE/MCwjkjpRcCWEi6DQsPMCxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XyKdk/btsyuJkQkCE/MCwjkjpRcCWEi6DQsPMCxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XyKdk/btsyuJkQkCE/MCwjkjpRcCWEi6DQsPMCxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXyKdk%2FbtsyuJkQkCE%2FMCwjkjpRcCWEi6DQsPMCxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;627&quot; height=&quot;187&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;226&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1697469559511&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;java.lang.NullPointerException: Parameter specified as non-null is null: method com.example.learnmvc.TestBody.&amp;lt;init&amp;gt;, parameter list
	at com.example.learnmvc.TestBody.&amp;lt;init&amp;gt;(LearnMvcApplication.kt) ~[classes/:na]
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:na]
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499) ~[na:na]
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480) ~[na:na]
	at kotlin.reflect.jvm.internal.calls.CallerImpl$Constructor.call(CallerImpl.kt:41) ~[kotlin-reflect-1.8.22.jar:1.8.22-release-407(1.8.22)]
	at kotlin.reflect.jvm.internal.KCallableImpl.callDefaultMethod$kotlin_reflection(KCallableImpl.kt:188) ~[kotlin-reflect-1.8.22.jar:1.8.22-release-407(1.8.22)]
	at kotlin.reflect.jvm.internal.KCallableImpl.callBy(KCallableImpl.kt:111) ~[kotlin-reflect-1.8.22.jar:1.8.22-release-407(1.8.22)]
	at org.springframework.beans.BeanUtils$KotlinDelegate.instantiateClass(BeanUtils.java:892) ~[spring-beans-5.3.24.jar:5.3.24]
	at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:196) ~[spring-beans-5.3.24.jar:5.3.24]
	at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.constructAttribute(ModelAttributeMethodProcessor.java:332) ~[spring-web-5.3.24.jar:5.3.24]
	at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.createAttribute(ModelAttributeMethodProcessor.java:220) ~[spring-web-5.3.24.jar:5.3.24]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택트레이스를 쭉 따라가다보면 ModelAttributeMethodProcessor 가 나오는데 공식문서에서 말하길&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Resolve&amp;nbsp;@ModelAttribute&amp;nbsp;annotated method arguments and handle return values from&amp;nbsp;@ModelAttribute&amp;nbsp;annotated methods.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;모델어트리뷰트 어노테이션을 처리한다고 한다. 그래서 constructAttribute 이쪽을 좀 디버깅해봤다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;아래는 메서드의 일부를 뽑아온건데 dto의 생성자를 가지고 list라는 필드이름을 뽑아온후 request 객체에서 이름으로 값을 찾지만 list[] 이기 때문에 찾은값이 null 이여서 맨마지막 dto 클래스를 인스턴스화 하는 리턴문에서 예외가 터진다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1697470393406&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protected Object constructAttribute(Constructor&amp;lt;?&amp;gt; ctor, String attributeName, MethodParameter parameter,
			WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {

		...

		String[] paramNames = BeanUtils.getParameterNames(ctor); // 생성자에서 필드 이름 얻어옴 -&amp;gt; list
		Class&amp;lt;?&amp;gt;[] paramTypes = ctor.getParameterTypes(); // List
		Object[] args = new Object[paramTypes.length];
		WebDataBinder binder = binderFactory.createBinder(webRequest, null, attributeName);
		String fieldDefaultPrefix = binder.getFieldDefaultPrefix();
		String fieldMarkerPrefix = binder.getFieldMarkerPrefix();
		boolean bindingFailure = false;
		Set&amp;lt;String&amp;gt; failedParams = new HashSet&amp;lt;&amp;gt;(4);

		for (int i = 0; i &amp;lt; paramNames.length; i++) {
			String paramName = paramNames[i]; 
			Class&amp;lt;?&amp;gt; paramType = paramTypes[i];
			Object value = webRequest.getParameterValues(paramName); // list 이름으로 찾음

			if (ObjectUtils.isArray(value) &amp;amp;&amp;amp; Array.getLength(value) == 1) {
				value = Array.get(value, 0);
			}

			...
            
			try {
				MethodParameter methodParam = new FieldAwareConstructorParameter(ctor, i, paramName);
				if (value == null &amp;amp;&amp;amp; methodParam.isOptional()) {
					args[i] = (methodParam.getParameterType() == Optional.class ? Optional.empty() : null);
				}
				else {
					args[i] = binder.convertIfNecessary(value, paramType, methodParam);
				}
			}
			catch (TypeMismatchException ex) {
				...
			}
		}

		...

		return BeanUtils.instantiateClass(ctor, args); // args 가 다 null임
	}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1697471057568&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static String[] getParameterNames(Constructor&amp;lt;?&amp;gt; ctor) {
		ConstructorProperties cp = ctor.getAnnotation(ConstructorProperties.class);
		String[] paramNames = (cp != null ? cp.value() : parameterNameDiscoverer.getParameterNames(ctor));
		Assert.state(paramNames != null, () -&amp;gt; &quot;Cannot resolve parameter names for constructor &quot; + ctor);
		Assert.state(paramNames.length == ctor.getParameterCount(),
				() -&amp;gt; &quot;Invalid number of parameter names: &quot; + paramNames.length + &quot; for constructor &quot; + ctor);
		return paramNames;
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보면 생성자로 필드명을가져올때 @ConstructorProperties 에 있는 값을 우선시하는걸 볼 수 있다. 이 부분은 공식문서에도 아래처럼 설명되어 있었다.(이게 이뜻이었구나...)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Instantiated through a &amp;ldquo;primary constructor&amp;rdquo; with arguments that match to Servlet request parameters. Argument names are determined through JavaBeans&amp;nbsp;@ConstructorProperties&amp;nbsp;or through runtime-retained parameter names in the bytecode.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 아래처럼 TestBody 클래스에 명시적으로 어노테이션을 사용해서 지정해주면 잘 동작한다.&lt;/p&gt;
&lt;pre id=&quot;code_1697471372888&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class TestBody @ConstructorProperties(&quot;list[]&quot;) constructor(val list: List&amp;lt;Int&amp;gt;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 위 방식은 순서에 의존적이라 아래처럼 순서를 실수로 잘못쓴다면 또 에러가 발생하기때문에 그대로 실무에 쓰지는 못할 것 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1697472064307&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class TestBody @ConstructorProperties(&quot;test&quot;, &quot;list&quot;) constructor(
    val list: List&amp;lt;Int&amp;gt;, // =&amp;gt; test 로 찾음
    val test: String, //    =&amp;gt; list 로 찾음
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서를 보면 인스턴스를 만들고 WebDataBinder에 의해 바인딩된다하는데 아직 그 부분을 발견을 못한것 같아서 좀더 디버깅해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ModelAttributeMethodProcessor의 resolveArgument 메서드의 아래코드를 좀 더 살펴보면 createAttribute 통해서 위에 설명했던 어트리뷰트를 만드는 동작을하고 예외가 생기면 bindingResult를 변수에 담아둔다 그리고 예외가 없다면 bindRequestParameters 를 호출하며 바인딩을 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1698155895084&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*

// Create attribute instance
try {
    attribute = createAttribute(name, parameter, binderFactory, webRequest)
} catch ( ex:org.springframework.validation.BindException){
    if (isBindExceptionRequired(parameter)) {
        // No BindingResult parameter -&amp;gt; fail with BindException
        throw ex
    }
    // Otherwise, expose null/empty value and associated BindingResult
    if (parameter.getParameterType() == Optional::class.java) {
        attribute = Optional.empty&amp;lt;Any&amp;gt;()
    } else {
        attribute = ex.getTarget()
    }
    bindingResult = ex.getBindingResult()
}

if (bindingResult == null) {
	// Bean property binding and validation;
	// skipped in case of binding failure on construction.
	WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
	if (binder.getTarget() != null) {
		if (!mavContainer.isBindingDisabled(name)) {
			bindRequestParameters(binder, webRequest); // 여기서 바인딩
		}
   .....중략&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 타고들어가면 WebDataBinder의 doBind 메서드에 가게되는데 여기서 adaptEmptyArrayIndices 메서드를 호출한다. 주석에도 []를 쓰는 몇몇 클라이언트를 지원하기 위해 만들었다 써져있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;list[]를 발견하면 []를 제외한 이름을 추출하고 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;dto의 해당 필드가 가변인지 isWritableProperty 를 통해서 확인하는것같다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;이후 MutablePropertyValues에서 list[]를 제거하고 list를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1698156987156&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	/**
	 * Check for property values with names that end on {@code &quot;[]&quot;}. This is
	 * used by some clients for array syntax without an explicit index value.
	 * If such values are found, drop the brackets to adapt to the expected way
	 * of expressing the same for data binding purposes.
	 * @param mpvs the property values to be bound (can be modified)
	 * @since 5.3
	 */
	protected void adaptEmptyArrayIndices(MutablePropertyValues mpvs) {
		for (PropertyValue pv : mpvs.getPropertyValues()) {
			String name = pv.getName();
			if (name.endsWith(&quot;[]&quot;)) {
				String field = name.substring(0, name.length() - 2);
				if (getPropertyAccessor().isWritableProperty(field) &amp;amp;&amp;amp; !mpvs.contains(field)) {
					mpvs.add(field, pv.getValue());
				}
				mpvs.removePropertyValue(pv);
			}
		}
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 dto를 아래처럼 var로 선언해서 가변으로 바꿔주고 attribute에 list가 없어도 dto 객체가 생성될수 있게 초기화를 해주면 잘 동작하는 것을 볼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1698157291892&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class TestBody (
    var list: List&amp;lt;Int&amp;gt; = emptyList(),
    val test: String,
)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;82&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ducQRv/btsy3N8wfD6/qOevwTZrIcZzNjv51gIXHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ducQRv/btsy3N8wfD6/qOevwTZrIcZzNjv51gIXHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ducQRv/btsy3N8wfD6/qOevwTZrIcZzNjv51gIXHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FducQRv%2Fbtsy3N8wfD6%2FqOevwTZrIcZzNjv51gIXHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;483&quot; height=&quot;77&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;82&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;@ConstructorProperties 를 직접 써주는 방법은 너무 위험하기때문에 일단 이 방법으로 하고 있다.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;살짝 아쉬운점은&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;누가 굳이 값을 바꾸지는 않겠지만 그래도 DTO 인데 가변으로 열어둬야 한다는것이 슬프다.&lt;/li&gt;
&lt;li&gt;var 로 가변으로두고 빈 리스트로 초기화해둔다는것이 명시적으로 끝에 [] 가 붙는다는걸 의미하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;이번 포스트는 여기서 마무리하고 불변으로 쓸 방법이 떠오르면 또 글을 써봐야겠다. 당장 생각 나는건 어노테이션을 따로 만들어서 어찌저찌 할수 있지 않을까 생각이든다.(&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;스프링은 참 어려워)&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;혹시라도 좋은 방법이나 틀린정보같은게 있다면 피드백 바랍니다(_ _)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/트러블슈팅</category>
      <category>Spring</category>
      <category>Spring MVC</category>
      <category>트러블슈팅</category>
      <author>who-is-hu</author>
      <guid isPermaLink="true">https://dragonlab.tistory.com/5</guid>
      <comments>https://dragonlab.tistory.com/5#entry5comment</comments>
      <pubDate>Tue, 24 Oct 2023 23:32:33 +0900</pubDate>
    </item>
    <item>
      <title>kotlin let 블럭안은 스마트 캐스팅이 가능한 이유</title>
      <link>https://dragonlab.tistory.com/4</link>
      <description>&lt;pre id=&quot;code_1691676696474&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Test(){
    fun hi(){
        println(&quot;hi&quot;)
    }
}
var test: Test? = Test()

fun ifFunc(){
    if(test != null){
        println(test!!.hi()) // 가변이기 때문에 스마트캐스팅이 불가능
    }
}

fun letFunc(){
    test?.let{it.hi()}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 가변 변수이지만&lt;/p&gt;
&lt;pre id=&quot;code_1691676754789&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static final void ifFunc() {
    if (test != null) {
        Test var10000 = test;
        Intrinsics.checkNotNull(var10000);
        var10000.hi();
        Unit var0 = Unit . INSTANCE;
        System.out.println(var0);
    }

}

public static final void letFunc() {
    Test var10000 = test; // 로컬 변수를 하나 만들고 그걸 사용한다
    if (var10000 != null) {
        Test var0 = var10000;
        int var2 = false;
        var0.hi();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬변수를 하나 만들고 그것을 사용하기 때문에 스마트캐스팅이 가능하다. 불변 변수라면 불필요한 변수가 만들어지니 if 가 나을수도 있지만 가변은 확실한 장점이 있는 것 같다. 그리고 변수하나 만든다고 성능차이가 심하지 않을것 같아 경우에 따라 코드가 깔끔해지는 쪽으로 작성하면 될 것 같다.&lt;/p&gt;</description>
      <category>개발/kotlin</category>
      <category>이펙티브 코틀린</category>
      <category>코틀린</category>
      <author>who-is-hu</author>
      <guid isPermaLink="true">https://dragonlab.tistory.com/4</guid>
      <comments>https://dragonlab.tistory.com/4#entry4comment</comments>
      <pubDate>Thu, 10 Aug 2023 23:26:13 +0900</pubDate>
    </item>
    <item>
      <title>kotlin 프로퍼티의 getter는 스마트캐스트가 안되고 final 프로퍼티는 되는 이유</title>
      <link>https://dragonlab.tistory.com/3</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이펙티브 코틀린 아이템1 에서 &quot;값을 사용하는 시점에 따라서 다른 결과가 나올 수 있기 때문입니다&quot; 와 함께 보여준 예제가 잘 와닫지 않아서 예제를 만들고 자바로 변환해봄&lt;/p&gt;
&lt;pre id=&quot;code_1691675284709&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun nameOrNullByRandom(): String? {
    return listOf(null, &quot;huni&quot;).random()
}

val nameGetter: String?
    get() = nameOrNullByRandom()

val nameFinalProperty: String? = nameOrNullByRandom()

fun main() {
    if(nameGetter != null){
        println(nameGetter!!.length) // 캐스팅 안됌
    }
    if(nameFinalProperty != null){
        println(nameFinalProperty.length)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1691675506442&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final class TestKt {
   @Nullable
   private static final String nameFinalProperty = nameOrNullByRandom(); // static final로 한번만 초기화

   @Nullable
   public static final String nameOrNullByRandom() {
      Collection var0 = (Collection)CollectionsKt.listOf(new String[]{(String)null, &quot;huni&quot;});
      return (String)CollectionsKt.random(var0, (Random)Random.Default);
   }

   @Nullable
   public static final String getNameGetter() {
      return nameOrNullByRandom();
   }

   @Nullable
   public static final String getNameFinalProperty() {
      return nameFinalProperty;
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;getter는 함수이고 매번 호출할때마다 null을 반환할 수 있어서 스마트캐스팅이 불가능함 하지만 final 프로퍼티는 static final 상수에 한번만 초기화 됨으로 매번 값을 읽어도 똑같아서 스마트 캐스팅 가능!&lt;/p&gt;</description>
      <category>개발/kotlin</category>
      <category>이펙티브코틀린</category>
      <category>코틀린</category>
      <author>who-is-hu</author>
      <guid isPermaLink="true">https://dragonlab.tistory.com/3</guid>
      <comments>https://dragonlab.tistory.com/3#entry3comment</comments>
      <pubDate>Thu, 10 Aug 2023 22:55:06 +0900</pubDate>
    </item>
  </channel>
</rss>