<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>다듬거나 나누거나</title>
    <link>https://sunimohs.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 15 Jun 2026 08:46:33 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>수니모스</managingEditor>
    <item>
      <title>UsernamePasswordFilter 사용자 정의와 세션</title>
      <link>https://sunimohs.tistory.com/25</link>
      <description>&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;Spring Security를 적용하다보면 제공되는 메소드만을 이용하는 프로젝트도 많지만 몇몇은 사용자 정의가 필요한 프로젝트가 있죠. &lt;/span&gt;이번 글에는 UsernamePasswordFilter를 사용자 정의하며 겪었던 이슈사항에 대하여 작성하였습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;nbsp;블로그에 작성된 코드는 샘플로 작성되어 내용 정리에 필요한 내용만 추려 담았습니다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[이슈 사항]&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;세션을 1개로 유지하기 위해 sessionManagement().maximumSessions(1) 설정을 하였으나 제대로 적용되지 않는 현상 발생.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 설정을 위한 SecurityConfig 클래스 생성&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;Spring Security를 이용하기 위해 @EnableWebSecurity, @Configuration 어노테이션을 추가하여 클래스를 작성하였습니다. protected void configure(HttpSecurity) throws Exception 메소드의 내용은 추후 작성하기로 하고 테스트를 위해 inMemoryAuthentication()에 사용자를 추가하였습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;PasswordEncoder는 다른 설정에 추가하여 @Bean으로 선언한 상태입니다. 코드 간소화를 위해 &lt;a href=&quot;https://projectlombok.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Lombok&lt;/a&gt;을 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1614685628986&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Profile(&quot;security&quot;)
@RequiredArgsConstructor
@EnableWebSecurity
@Configuration
@Slf4j
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  private final PasswordEncoder passwordEncoder;

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    // TODO something...
  }
  
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
      .withUser(new LoginUser(&quot;user1&quot;, passwordEncoder.encode(&quot;password1&quot;)))
      .withUser(new LoginUser(&quot;user2&quot;, passwordEncoder.encode(&quot;password2&quot;)));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. CustomUsernamePasswordFilter 클래스 생성&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;UsernamePasswordAuthenticationFilter를 확장한 CustomUsernamePasswordFilter를 작성합니다. 해당 클래스에는 내용은 없고 잘 들어왔는지 확인하기 위한 로그를 표시했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1614685491778&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  public static class CustomUsernamePasswordFilter extends UsernamePasswordAuthenticationFilter {
    
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
        throws AuthenticationException {
      
      // TODO something...
      log.info(&quot;checked&quot;);
      
      return super.attemptAuthentication(request, response);
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. CustomUsernamePasswordFilter 객체 생성&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;WebSecurityConfigurerAdapter에서 제공하는 authenticationManager() 메소드를 통해 AuthenticationManager를 할당하고 로그인 URL, 파라미터명 등을 설정했습니다. 성공 / 실패의 경우도 SimpleUrlAuthentication... 으로 생성하여 URL로 돌아갈 수 있도록 처리하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1614686411452&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @Bean
  public UsernamePasswordAuthenticationFilter authenticationFilter() throws Exception {
    CustomUsernamePasswordFilter filter = new CustomUsernamePasswordFilter();
    filter.setAuthenticationManager(authenticationManager());
    filter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(SecurityConstant.LOGIN_URL, &quot;POST&quot;));
    filter.setUsernameParameter(&quot;username&quot;);
    filter.setPasswordParameter(&quot;password&quot;);
    filter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler(SecurityConstant.LOGIN_SUCCESS_URL));
    filter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(SecurityConstant.LOGIN_URL));
    
    return filter;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. configure 내용 추가&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;1. 에서 만들어 두었던 SecurityConfig 클래스의 protected void configure(HttpSecurity)에 아래 내용을 추가합니다. LOGIN_URL(GET)에는 단순히 username, password를 입력할 수 있는 페이지고 LOGIN_SUCCESS_URL은 권한이 있는 사용자만 접근할 수 있습니다. 세션은 하나만 유지되도록 maximumSessions(1)을 추가하였고 사용자 정의한 UsernamePasswordAuthenticationFilter를 등록하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1614778069754&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    final UsernamePasswordAuthenticationFilter authenticationFilter = authenticationFilter();
    
    http
      .addFilterAt(authenticationFilter, UsernamePasswordAuthenticationFilter.class)
      .exceptionHandling()
        .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint(SecurityConstant.LOGIN_URL))
        .and()
      .authorizeRequests()
        .antMatchers(SecurityConstant.LOGIN_SUCCESS_URL).hasRole(SecurityConstant.ROLE_USER)
        .and()
      .sessionManagement()
        .maximumSessions(1)
        .maxSessionsPreventsLogin(true)
    ;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 이슈의 발생&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;위와 같이 등록하고 프로젝트를 실행한 뒤 서로 다른 브라우저 2개를 열어 같은 아이디로 로그인을 시도하였습니다. 서로 다른 브라우저에서는 쿠키를 공유하지 않기 때문에 기본적으로 설정되어 있는 쿠키 기반 세션은 서로 다른 세션으로 인식할 것입니다. 당연히 MaximumSessions를 1로 설정하였기 때문에 뒤에 접속한 브라우저는 접속이 차단될 것으로 예상했으나 동작하지 않았습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;원하는 동작은 세션이 1개만 생성되고 다른 접근은 차단하는 것이기 때문에 수정이 필요한 상황입니다. 수정을 하기 위해 위의 현상이 왜 발생하는지 확인할 필요가 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 이슈 원인 파악&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;6-1. Spring에서 제공하는 기본 동작 확인&lt;/h4&gt;
&lt;p&gt;&amp;nbsp;우선 Spring에서 제공하는 formLogin() 설정을 하여 동작을 확인하였습니다. 아래의 코드를 &lt;span style=&quot;color: #333333;&quot;&gt;protected void configure(HttpSecurity)에 추가하고 등록했던 CustomUsernamePasswordFilter를 주석으로 막고 실행해 보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1615096206153&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;      .formLogin()
        .loginPage(SecurityConstant.LOGIN_URL)
        .loginProcessingUrl(SecurityConstant.LOGIN_URL)
        .defaultSuccessUrl(SecurityConstant.LOGIN_SUCCESS_URL)
        .failureUrl(SecurityConstant.LOGIN_SUCCESS_URL)
        .and()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;2개의 브라우저에서 로그인 시 정상적으로 하나의 브라우저에서 로그인되고 다른 브라우저에서 로그인이 차단되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;6-2. 내부에서 사용하는 UsernamePasswordFilter 설정 확인&lt;/h4&gt;
&lt;p&gt;&amp;nbsp;formLogin() 메소드를 사용 시 FormLoginConfigurer 객체를 등록합니다. 그 안을 살펴보면 FormLoginConfigurer가 확장한 추상 클래스인 AbstractAuthenticationFilterConfigurer에서 내부에서 등록하는 UnsernamePasswordFilter의 설정이 있습니다. 아래의 코드에서 authFilter는 UsernamePasswordFilter 객체이며 setSessionAuthenticationStrategy(SessionAuthenticationStrategy) 메소드를 통해 세션 정책을 설정하는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1615097471784&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	@Override
	public void configure(B http) throws Exception {
		PortMapper portMapper = http.getSharedObject(PortMapper.class);
		if (portMapper != null) {
			authenticationEntryPoint.setPortMapper(portMapper);
		}

		RequestCache requestCache = http.getSharedObject(RequestCache.class);
		if (requestCache != null) {
			this.defaultSuccessHandler.setRequestCache(requestCache);
		}

		authFilter.setAuthenticationManager(http
				.getSharedObject(AuthenticationManager.class));
		authFilter.setAuthenticationSuccessHandler(successHandler);
		authFilter.setAuthenticationFailureHandler(failureHandler);
		if (authenticationDetailsSource != null) {
			authFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
		}
		SessionAuthenticationStrategy sessionAuthenticationStrategy = http
				.getSharedObject(SessionAuthenticationStrategy.class);
		if (sessionAuthenticationStrategy != null) {
			authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
		}
		RememberMeServices rememberMeServices = http
				.getSharedObject(RememberMeServices.class);
		if (rememberMeServices != null) {
			authFilter.setRememberMeServices(rememberMeServices);
		}
		F filter = postProcess(authFilter);
		http.addFilter(filter);
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;위의 내용을 통해 CustomUsernamePasswordFilter 객체에도 setSessionAuthenticationStrategy를 등록하면 세션 처리를 할 수 있는 것을 확인할 수 있었습니다. 'UsernamePasswordFilter에서 제공하는 메소드를 조금만 더 잘 살펴봤다면 내부의 내용을 확인하지 않고도 알 수 있었을텐데' 하는 아쉬움은 있었지만 이슈의 원인을 확인하여 이슈를 해결하기 위한 발판을 마련했습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 이슈의 해결&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7-1. 이슈 해결을 위한 고민&lt;/h4&gt;
&lt;p&gt;&amp;nbsp;위의 내용대로 setSessionAuthenticationStrategy를 설정하면 되지만 언제나 그렇듯 이슈를 해결하기 위해 여러가지 고민이 생기게 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;SessionAuthenticationStrategy interface를 구현했을 때 들어가는 비용&lt;/li&gt;
&lt;li&gt;SessionAuthenticationStrategy interface를 구현했을 때 누락할 수 있는 기능&lt;/li&gt;
&lt;li&gt;protected void configure(HttpSecurity)에 설정한 내용의 활용성&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;우선 SessionAuthenticationStrategy interface를 구현할 경우, 소스 작성 시간 + 기본적으로 필요한 기능들에 대한 사전 조사 + 코드 검증 등이 필요할 것입니다. 또한, sessionManagement()에서 기본적으로 제공하는 안정적인 코드를 포기해야하는 이슈가 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;UsernamePasswordFilter은 새로운 기능을 추가하기 위해 확장했다고 하지만 기존에 제공하는 기능까지 새로 구현해야 하는지에 대한 의문이 드는게 당연하겠죠.&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;SpringSecurity와 어플리케이션 통합이 원활하지 않을 것 같은 &lt;s&gt;느낌적인&lt;/s&gt; 느낌이 드는 것도 있겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7-2. 이슈 해결 방안&amp;nbsp;&lt;/h4&gt;
&lt;p&gt;&amp;nbsp;위처럼 많은 비용이 필요한 직접 구현 대신, SpringSecurity와 안정적인 통합을 할 수 있는 방안을 확인하기 위해 내부의 코드를 조금 더 살펴보기로 합니다. sessionManagement()에서 생성되는 SessionManagementConfigurer를 보면 아래와 같은 코드를 확인할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1615099899985&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	private SessionAuthenticationStrategy getSessionAuthenticationStrategy(H http) {
		if (this.sessionAuthenticationStrategy != null) {
			return this.sessionAuthenticationStrategy;
		}
		List&amp;lt;SessionAuthenticationStrategy&amp;gt; delegateStrategies = this.sessionAuthenticationStrategies;
		SessionAuthenticationStrategy defaultSessionAuthenticationStrategy;
		if (this.providedSessionAuthenticationStrategy == null) {
			// If the user did not provide a SessionAuthenticationStrategy
			// then default to sessionFixationAuthenticationStrategy
			defaultSessionAuthenticationStrategy = postProcess(
					this.sessionFixationAuthenticationStrategy);
		}
		else {
			defaultSessionAuthenticationStrategy = this.providedSessionAuthenticationStrategy;
		}
		if (isConcurrentSessionControlEnabled()) {
			SessionRegistry sessionRegistry = getSessionRegistry(http);
			ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy = new ConcurrentSessionControlAuthenticationStrategy(
					sessionRegistry);
			concurrentSessionControlStrategy.setMaximumSessions(this.maximumSessions);
			concurrentSessionControlStrategy
					.setExceptionIfMaximumExceeded(this.maxSessionsPreventsLogin);
			concurrentSessionControlStrategy = postProcess(
					concurrentSessionControlStrategy);

			RegisterSessionAuthenticationStrategy registerSessionStrategy = new RegisterSessionAuthenticationStrategy(
					sessionRegistry);
			registerSessionStrategy = postProcess(registerSessionStrategy);

			delegateStrategies.addAll(Arrays.asList(concurrentSessionControlStrategy,
					defaultSessionAuthenticationStrategy, registerSessionStrategy));
		}
		else {
			delegateStrategies.add(defaultSessionAuthenticationStrategy);
		}
		this.sessionAuthenticationStrategy = postProcess(
				new CompositeSessionAuthenticationStrategy(delegateStrategies));
		return this.sessionAuthenticationStrategy;
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;위의 private SessionAuthenticationStrategy getSessionAuthenticationStrategy(H) 메소드는 &lt;span style=&quot;color: #333333;&quot;&gt;public void init(H) 메소드에서 &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;객체를 SharedObject로 등록하는데 사용됩니다. 이것을 이용하면 Spring Security와 자연스럽게 통합할 수 있다는 생각이 듭니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;다행스럽게도 postProcess() 메소드를 호출하고 있습니다. postProcess() 메소드는 호출 시점에 등록되어 있는 모든 ObjectPostProcessor의 postProcess 메소드를 호출하게 됩니다. 다시 우리가 작성했던 SecurityConfig로 돌아와서 configure 메소드를 아래와 같이 수정합니다. addObjectPostProcessor() 메소드에 ObjectPostProcessor interface를 구현하여 우리가 생성한 filter에 Spring Security 내부에서 생성한 CompositeSessionAuthenticationStrategy를 할당합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1615100569133&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    final UsernamePasswordAuthenticationFilter authenticationFilter = authenticationFilter();
    
    http
//  formLogin() 동작 확인용
//      .formLogin()
//        .loginPage(SecurityConstant.LOGIN_URL)
//        .loginProcessingUrl(SecurityConstant.LOGIN_URL)
//        .defaultSuccessUrl(SecurityConstant.LOGIN_SUCCESS_URL)
//        .failureUrl(SecurityConstant.LOGIN_SUCCESS_URL)
//        .and()
      .addFilterAt(authenticationFilter, UsernamePasswordAuthenticationFilter.class)
      .exceptionHandling()
        .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint(SecurityConstant.LOGIN_URL))
        .and()
      .authorizeRequests()
        .antMatchers(SecurityConstant.LOGIN_SUCCESS_URL).hasRole(SecurityConstant.ROLE_USER)
        .and()
      .sessionManagement()
        .maximumSessions(1)
        .maxSessionsPreventsLogin(true)
        .and()
      .addObjectPostProcessor(new ObjectPostProcessor&amp;lt;CompositeSessionAuthenticationStrategy&amp;gt;() {
        @Override
        public &amp;lt;O extends CompositeSessionAuthenticationStrategy&amp;gt; O postProcess(O object) {
          CompositeSessionAuthenticationStrategy strategy = (CompositeSessionAuthenticationStrategy) object;
          
          authenticationFilter.setSessionAuthenticationStrategy(strategy);
          
          return object;
        }
      })
    ;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;8. 결과 확인 및 정리&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;위의 환경과 동일하게 각각 다른 브라우저를 2개 올린 상태에서 로그인 시도 시 하나만 로그인이 되어 원하는 결과가 나타난 것을 확인하였습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;이번 이슈를 해결하며 빠른 해결을 위해 ObjectPostProcessor를 구현하여 등록하는 방향으로 수정했지만 HttpSecurity.apply(C) 메소드로 사용자 정의한 설정을 하는 방법도 있는 것 같습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;다음에 기회가 된다면 해당 코드를 통해 통합하는 방향을 확인해야겠어요.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;개인적인 경험을 바탕으로 작성한 내용이기 때문에 내용 중 이상한 점이나 오류가 있을 경우 댓글 달아주시면 수정하도록 하겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>프로그래밍/Spring</category>
      <category>CustomUsernammePasswordFilter</category>
      <category>Java</category>
      <category>maximumSessions</category>
      <category>Spring Security</category>
      <category>UsernamePasswordFilter</category>
      <author>수니모스</author>
      <guid isPermaLink="true">https://sunimohs.tistory.com/25</guid>
      <comments>https://sunimohs.tistory.com/25#entry25comment</comments>
      <pubDate>Tue, 2 Mar 2021 19:58:48 +0900</pubDate>
    </item>
    <item>
      <title>값 전달 방식(Call by value, Call by reference)</title>
      <link>https://sunimohs.tistory.com/18</link>
      <description>&lt;p&gt;&amp;nbsp;안녕하세요. 이번에 작성한&amp;nbsp;내용은 값&amp;nbsp;전달 방식(Call by value, Call by reference)에 대한 내용입니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;코드를 작성하다보면 여러가지 메소드나 변수를 통해서 값을 주고 받으며 사용하게 됩니다. 그 때 메소드나 변수에 값이 전달되는 방식이 나뉘게 됩니다. 흔히 Call by value, Call by reference라고 부르는 것들이 그것이죠. 쉽게 설명하자면 Call by value는 값 자체를 전달하는 것이고 Call by reference는 참조해서 사용하도록 연결해주는 것입니다.&lt;/p&gt;&lt;p&gt;&amp;nbsp;자바에서는 이 부분이 매우 쉽게 되어있습니다. 기본 형식(primitive type)으로 된 변수는 Call by value, 객체(Object)로 되어있는 것들은 Call by reference로 동작합니다. 아래의 코드를 보시죠.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;1. ReferenceObject 형식 정의&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.tistory.sunimohs;

public class ReferenceObject {
  public int value = 0;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;2. 화면을 표시할 MainClass 정의&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.tistory.sunimohs;

public class MainClass {
  public static void main(String[] args) {
    printStudy1();
    
    printStudy2();
  }
  
  public static void printStudy1() {
    int value1 = 0;
    int value2 = value1;
    
    ReferenceObject object1 = new ReferenceObject();
    ReferenceObject object2 = object1;
    
    value1 = 5;
    object1.value = 5;
    
    System.out.println(&quot;print study1 start&quot;);
    
    System.out.println(&quot;value1 : &quot; + value1);
    System.out.println(&quot;value2 : &quot; + value2);
    
    System.out.println(&quot;object1.value : &quot; + object1.value);
    System.out.println(&quot;object2.value : &quot; + object2.value);
    
    System.out.println();
  }
  
  public static void printStudy2() {
    int value = 0;
    ReferenceObject ro = new ReferenceObject();
    
    System.out.println(&quot;print study2 start&quot;);
    
    System.out.println(&quot;value : &quot; + value);
    System.out.println(&quot;reference object value : &quot; + ro.value);
    callByValue(value);
    callByReference(ro);
    System.out.println(&quot;value : &quot; + value);
    System.out.println(&quot;reference object value : &quot; + ro.value);
  }
  
  public static void callByValue(int value) {
    value = 5;
    
    System.out.println(&quot;callByValue value : &quot; + value);
  }
  
  public static void callByReference(ReferenceObject referenceObject) {
    referenceObject.value = 5;
    
    System.out.println(&quot;callByReference value : &quot; + referenceObject.value);
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;위의 코드에서 printStudy1 메소드부터 살펴보도록 하죠. printStudy1 메소드에서 int 형 value1에 0값을 할당하고 value2에 value1을 할당합니다. 이 때 값 전달 방식은 기본 형식(primitive type)이기 때문에 Call by value와 같이 동작합니다. 값만 전달되는 방식이죠. 그 다음 ReferenceObject 형식 object1을 생성합니다. object2에 object1을 할당합니다. 이 때 전달 방식은 Call by reference와 같이 동작합니다. 아래는 printStudy1의 결과입니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre style=&quot;background-color:#282C28; color:white;&quot;&gt;print study1 start
value1 : 5
value2 : 0
object1.value : 5
object2.value : 5
&lt;/pre&gt;&lt;p&gt;&amp;nbsp;순서대로 value1, value2, object1의 value, object2의 value를 출력하였습니다. 여기서 이상한 것을 보실 수 있는데요. value1과 value2가&amp;nbsp;값이 다르다는 것입니다. value2에는 value1을 할당하였으나 값만 전달되었기 때문에 value1에 5를 할당하여도 value2에는 변화가 없고 object2에는 동일한 방식으로&amp;nbsp;object1을 할당하였으나 값 참조로 전달하였기 때문에 같이 바뀌게 된 것입니다.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;엄밀하게 말하면 '같이 바뀌었다.'라는 것은 맞지 않고 object1.value만 바뀌고 object2.value는 object1.value를 바라본다고 할 수 있겠네요. 값 참조라고 하면 뭔가 어려운 말로 들릴 수 있는데요. 이렇게 생각하시면 쉽습니다. object1에만 new ReferenceObject()를 통해 새로운 객체를 생성하였고 object2는 그&amp;nbsp;객체를 빌려서 사용하고 있다고요.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;마찬가지로 printStudy2도 동작 방식이 비슷합니다. 바뀐건 변수에 전달하느냐 메소드의 인자로 전달하느냐의 차이입니다. printStudy2의 결과를 보시죠.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre style=&quot;background-color:#282C28; color:white;&quot;&gt;print study2 start
value : 0
reference object value : 0
callByValue value : 5
callByReference value : 5
value : 0
reference object value : 5
&lt;/pre&gt;&lt;p&gt; int 형 value를&amp;nbsp;callByValue 메소드의 인자로 보내고 메소드 내부에서 value에 5를 할당합니다. 결과 화면에서도 보이듯이 메소드 내부에서는 5가 표시됩니다. ReferenceObject 형 ro를 callByReference 메소드의 인자로 보내고 메소드 내부에서 인자로 온 ReferenceObject형 객체에 값을 할당합니다. 2개의 메소드 실행 후 메소드에 인자로 보냈던 값을 각각 출력하였습니다. 여기서도 위와 비슷한 현상을 보실 수 있습니다. callByValue 메소드의 인자로 보내진 value는 값만 전달되었기 때문에 메소드 내부에서 변경된 값이 밖까지 적용되지 않습니다. 이와 반대로 ro 객체는 참조로 전달되었기 때문에 메소드 실행 후에도 값이 그대로 남아있는 것을 보실 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block;   height: auto; max-width: 100%;&quot;&gt;&lt;a href=&quot;https://t1.daumcdn.net/cfile/tistory/997052335A1FC69F1C&quot;&gt;&lt;img alt=&quot;&quot; src=&quot;https://i1.daumcdn.net/cfs.tistory/v/0/blog/image/extension/zip.gif&quot; style=&quot;vertical-align: middle;&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;/&gt;ReferenceStudy.zip&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;</description>
      <category>프로그래밍/Java 기초</category>
      <category>call by reference</category>
      <category>call by value</category>
      <category>Java</category>
      <category>variables</category>
      <category>변수</category>
      <category>자바</category>
      <author>수니모스</author>
      <guid isPermaLink="true">https://sunimohs.tistory.com/18</guid>
      <comments>https://sunimohs.tistory.com/18#entry18comment</comments>
      <pubDate>Thu, 30 Nov 2017 17:52:08 +0900</pubDate>
    </item>
    <item>
      <title>자바 제네릭(Generics)과 컬렉션(Collections)</title>
      <link>https://sunimohs.tistory.com/15</link>
      <description>&lt;p&gt;&amp;nbsp;안녕하세요. 이번 글은 자바의 제네릭과 컬렉션에 대하여 간결하게 작성하였습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;1. 자바 컬렉션(Java Collections Framework)&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;자바 컬렉션(Java Collections Framework)은 자료 구조를 사용하기 편하게 작성해놓은 클래스들의 집합이라고 보시면됩니다. 자료 구조를 배우려면&amp;nbsp;FIFO(First In First Out), LIFO(Last In First Out) 등으로 시작해서 여러가지 알고리즘과 head, body(data), tail 등으로 구성된 그림들을 많이 접해보셔야 할 것입니다. 자바에서는 이 모든 것을 간단하고 쉽게&amp;nbsp;사용할 수 있도록 구현되어 있습니다. 물론 위의 내용들이 필요없다는 것은 절대 아닙니다.&lt;sup class=&quot;footnote&quot;&gt;&lt;a href=&quot;#footnote_15_1&quot; id=&quot;footnote_link_15_1&quot; onmouseover=&quot;tistoryFootnote.show(this, 15, 1)&quot; onmouseout=&quot;tistoryFootnote.hide(15, 1)&quot; style=&quot;color:#f9650d; font-family: Verdana, Sans-serif; display: inline;&quot;&gt;&lt;span style=&quot;display: none;&quot;&gt;[각주:&lt;/span&gt;1&lt;span style=&quot;display: none;&quot;&gt;]&lt;/span&gt;&lt;/a&gt;&lt;/sup&gt;&amp;nbsp;많이 알수록 편한건 어딜가나 마찬가지죠. 자바 컬렉션에는 여러가지 인터페이스와 그를 구현한 클래스, 알고리즘 등이 포함됩니다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;2. 제네릭 (Generics)&lt;/p&gt;&lt;p&gt;&amp;nbsp; 제네릭은 자바 1.5버전에서 추가된 내용입니다. 현재는 1.5 미만의 버전을 쓰는 곳은 없을테니(있을리가...?)&amp;nbsp;무조건 포함된다고 보시면 되겠네요. 제네릭은 특정 형식 옆에 꺽쇠 기호(&amp;lt;&amp;gt;)로 추가되며 꺽쇠 기호 안에 특정 형식(type)을 지정하여 사용합니다. 제네릭으로 형식을 지정하면 지정한 형식의 데이터밖에 입력받을 수 없습니다. 왜 자바 컬렉션과 제네릭을 같이 설명하는지는 아래의 예제를 통해 살펴보도록 하겠습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.tistory.sunimohs.study;

import java.util.ArrayList;
import java.util.List;

public class MainClass {
  public static void main(String[] args) {
    List noGenericsList = new ArrayList();
    List&amp;lt;String&amp;gt; strList = new ArrayList&amp;lt;&amp;gt;();
    
    noGenericsList.add(&quot;aaaaaa&quot;);
    noGenericsList.add(1111);
    
    strList.add(&quot;문자다&quot;);
    strList.add(&quot;알기쉬운 문장이다&quot;);
    
    try {
      for (int i = 0; i &amp;lt; noGenericsList.size(); i++) {
        // 제네릭을 지정하지 않아 어떤 데이터 형식인지 판단하려면 입력된 곳을 찾아가야 알 수 있다.
        String str = (String) noGenericsList.get(i);
        System.out.println(&quot;index [&quot; + i + &quot;] 데이터 : &quot; + str);
      }
    } catch (Exception e) {
      System.out.println(&quot;오류가 발생하였다네..&quot;);
    }
    
    for (int i = 0; i &amp;lt; strList.size(); i++) {
      String str = strList.get(i);
      System.out.println(&quot;index [&quot; + i + &quot;] 데이터 : &quot; + str);
    }
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;위의 예제를 보면&amp;nbsp;제네릭을 지정하지 않은 List와 제네릭으로 String형식을 지정한 List를 각각 만들었습니다. 실행해보면 아래와 같은 결과가 나오며 제네릭을 사용하지 않은 곳은 오류가 발생함을 알 수 있습니다. 물론 예외를 발생시키기 위해 명시적으로 String 형식으로 타입 캐스팅을 하였지만 말이죠. 예제는&amp;nbsp;바로 위에 값을 저장하는 구문이 있어서 한 눈에 보이지만 코드가 길어지면 찾는 것도 많은 일이 될 것이 자명합니다(암요, 그렇고 말고요 ㅋㅋ).&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre style=&quot;background-color:#282C28; color:white;&quot;&gt;index [0] 데이터 : aaaaaa
오류가 발생하였다네..
index [0] 데이터 : 문자다
index [1] 데이터 : 알기쉬운 문장이다
&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;제네릭을 사용하면 위와 같은 불상사를 최소화할 수 있겠죠. 제네릭은 형식 선언부에는 꺽쇠(&amp;lt;&amp;gt;)안에 특정 형식을 넣고 객체 생성시에는 꺽쇠(&amp;lt;&amp;gt;)를 비워놓아도 됩니다. 꺽쇠를 비워두면 형식 선언에서 입력된 형식이 자동으로 할당되죠. 동일한 형식을 입력하는 불필요한 반복 동작을 자바에서 최소화 시켜줍니다.&lt;/p&gt;&lt;p&gt;&amp;nbsp;ex) ArrayList&amp;lt;String&amp;gt; arrayList = new ArrayList&amp;lt;&amp;gt;();&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;3. Collections 클래스&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;지난 블로그 중 배열과 관련된 글에서 Arrays 클래스를 소개한 적이 있습니다. Collections 클래스도 자바에서 제공되는 유틸리티 클래스 중 하나인데요. 마찬가지로 다양한 기능들을 제공하고 있습니다. 제 블로그에서는 역순 정렬을 하는 메소드만 실행해보도록 하겠습니다. 기타 다른 기능은 자바 docs&lt;sup class=&quot;footnote&quot;&gt;&lt;a href=&quot;#footnote_15_2&quot; id=&quot;footnote_link_15_2&quot; onmouseover=&quot;tistoryFootnote.show(this, 15, 2)&quot; onmouseout=&quot;tistoryFootnote.hide(15, 2)&quot; style=&quot;color:#f9650d; font-family: Verdana, Sans-serif; display: inline;&quot;&gt;&lt;span style=&quot;display: none;&quot;&gt;[각주:&lt;/span&gt;2&lt;span style=&quot;display: none;&quot;&gt;]&lt;/span&gt;&lt;/a&gt;&lt;/sup&gt;에서 확인하실 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.tistory.sunimohs.study;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CollectionsStudy {
  private List&amp;lt;String&amp;gt; strings = new ArrayList&amp;lt;&amp;gt;();
  
  public CollectionsStudy() {
    strings.add(&quot;a&quot;);
    strings.add(&quot;b&quot;);
    strings.add(&quot;c&quot;);
    strings.add(&quot;d&quot;);
    strings.add(&quot;e&quot;);
    strings.add(&quot;f&quot;);
    strings.add(&quot;g&quot;);
  }
  
  public void printWithComma() {
    printWithComma(strings);
  }
  
  private void printWithComma(List&amp;lt;String&amp;gt; list) {
    System.out.print(&quot;print result : &quot;);
    
    for(int i = 0; i &amp;lt; list.size(); i++) {
      String str = list.get(i);
      
      System.out.print(str);
      
      if (i + 1 &amp;lt; list.size()) {
        System.out.print(&quot;, &quot;);
      }
    }
    
    System.out.println();
  }
  
  public void printReverse() {
    List&amp;lt;String&amp;gt; copyList = new ArrayList&amp;lt;&amp;gt;();
    
    for(String str : strings) {
      copyList.add(str);
    }
    
    Collections.reverse(copyList);
    
    printWithComma(copyList);
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;위의 내용처럼 CollectionsStudy 클래스를 정의하고 MainClass에 아래와 같이 코드를 입력하여 실행해 보았습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;    CollectionsStudy cs = new CollectionsStudy();
    
    cs.printWithComma();
    cs.printReverse();&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;결과 화면은 아래와 같습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre style=&quot;background-color:#282C28; color:white;&quot;&gt;print result : a, b, c, d, e, f, g
print result : g, f, e, d, c, b, a
&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;Collections.reverse() 메소드를 통해 매우 간단하게 역순 정렬되는 것을 확인할 수 있었습니다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;소스가 필요하신 분은 링크를 클릭하여 다운 받으실 수 있습니다.&amp;nbsp;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;a style=&quot;&quot; class=&quot;txc-file&quot;&gt;&lt;br /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block;   height: auto; max-width: 100%;&quot;&gt;&lt;a href=&quot;https://t1.daumcdn.net/cfile/tistory/992F06335A1B67302A&quot;&gt;&lt;img alt=&quot;&quot; src=&quot;https://i1.daumcdn.net/cfs.tistory/v/0/blog/image/extension/zip.gif&quot; style=&quot;vertical-align: middle;&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;/&gt;CollectionsStudy.zip&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol class=&quot;footnotes&quot;&gt;
    &lt;li id=&quot;footnote_15_1&quot;&gt;배움의 길은 끝이 없고 ... 쏼라쏼라... 하기 때문입니다. ^^; &lt;a href=&quot;#footnote_link_15_1&quot;&gt;[본문으로]&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;footnote_15_2&quot;&gt;https://docs.oracle.com/javase/8/docs/api/ &lt;a href=&quot;#footnote_link_15_2&quot;&gt;[본문으로]&lt;/a&gt;&lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</description>
      <category>프로그래밍/Java 기초</category>
      <category>Colllections</category>
      <category>Generics</category>
      <category>Java</category>
      <category>자바</category>
      <category>제네릭</category>
      <category>컬렉션</category>
      <author>수니모스</author>
      <guid isPermaLink="true">https://sunimohs.tistory.com/15</guid>
      <comments>https://sunimohs.tistory.com/15#entry15comment</comments>
      <pubDate>Mon, 27 Nov 2017 10:17:00 +0900</pubDate>
    </item>
    <item>
      <title>어노테이션(Annotation)</title>
      <link>https://sunimohs.tistory.com/12</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;안녕하세요. 이번 글은 자바의 어노테이션에 관한 내용입니다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&amp;nbsp;자바의 코드를 보다보면 @가 붙은 내용들이 간혹 눈에 보이실텐데요. 이것이 어노테이션입니다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;어노테이션의 기본적인 목적은 자바의 메타데이터&lt;sup class=&quot;footnote&quot;&gt;&lt;a id=&quot;footnote_link_12_1&quot; href=&quot;#footnote_12_1&quot; onmouseover=&quot;tistoryFootnote.show(this,12,1)&quot; onmouseout=&quot;tistoryFootnote.hide(12,1)&quot; style=&quot;color:#f9650d;font-family:Verdana,Sans-serif;display:inline;&quot;&gt;&lt;span style=&quot;display:none&quot;&gt;[각주:&lt;/span&gt;1&lt;span style=&quot;display:none&quot;&gt;]&lt;/span&gt;&lt;/a&gt;&lt;/sup&gt;로 사용하기 위함입니다. 어노테이션 작성법은 '접근 제한자 @interface 어노테이션명 { 내용 }'입니다. 다른 용도로도 많이 사용합니다만 예제에서는 메타데이터 용도만 살펴보겠습니다.&amp;nbsp;이제 예제로 만든 어노테이션을 통해 사용법을 알아보도록 하죠.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br&gt;&lt;br&gt;1. CustomAnnotation&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.tistory.sunimohs.study;  import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;  /**  * 커스텀 어노테이션&amp;lt;br/&amp;gt;  * Documented : javadoc으로 문서 생성 시 현재 어노테이션 설명 추가 &amp;lt;br/&amp;gt;  * Retention : 어노테이션을 유지하는 정책 설정&amp;lt;br/&amp;gt;  * Target : 어노테이션 적용 위치  *   * @author sunimohs  */ @Documented @Retention(RetentionPolicy.SOURCE) @Target(ElementType.FIELD) public @interface CustomAnnotation {   /**    * 어노테이션 기본 값 value()로 설정된 데이터는 '@어노테이션명(파라메터 값)'로 설정 가능하다.&amp;lt;br/&amp;gt;    * 그 외의 데이터는 '@어노테이션(파라메터 명 = 파라메터 값)'으로 사용    */   String value();      /**    * 'default 파라메터 기본 값'의 형식으로 작성 시 입력하지 않을 경우 기본 값 적용&amp;lt;br/&amp;gt;    * default를 지정하지 않을 경우는 무조건 입력받아야 함.    */   String comment() default &quot;&quot;; }&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br&gt;&lt;br&gt;&amp;nbsp;여기서 살펴보면 작성한 CustomAnnotation위로 @Documented, @Retention, @Target 이라는 어노테이션이 보이는데요. 이 어노테이션들은 자바에 포함되어있는 기본 어노테이션에 속해 있습니다.&lt;/p&gt;&lt;p&gt;&amp;nbsp;하나씩 설명을 드리면 @Documented는 javadoc&lt;sup class=&quot;footnote&quot;&gt;&lt;a id=&quot;footnote_link_12_2&quot; href=&quot;#footnote_12_2&quot; onmouseover=&quot;tistoryFootnote.show(this,12,2)&quot; onmouseout=&quot;tistoryFootnote.hide(12,2)&quot; style=&quot;color:#f9650d;font-family:Verdana,Sans-serif;display:inline;&quot;&gt;&lt;span style=&quot;display:none&quot;&gt;[각주:&lt;/span&gt;2&lt;span style=&quot;display:none&quot;&gt;]&lt;/span&gt;&lt;/a&gt;&lt;/sup&gt;으로 api 문서를 만들 때 어노테이션에 대한 설명도 포함하도록 지정해주는 것입니다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&amp;nbsp;@Retention은 어노테이션의 유지 정책을 설정하는 것인데요. 괄호 안에 RetentionPolicy 형식의 값을 할당할 수 있습니다. RetentionPolicy는 열거형(enum)&lt;sup class=&quot;footnote&quot;&gt;&lt;a id=&quot;footnote_link_12_3&quot; href=&quot;#footnote_12_3&quot; onmouseover=&quot;tistoryFootnote.show(this,12,3)&quot; onmouseout=&quot;tistoryFootnote.hide(12,3)&quot; style=&quot;color:#f9650d;font-family:Verdana,Sans-serif;display:inline;&quot;&gt;&lt;span style=&quot;display:none&quot;&gt;[각주:&lt;/span&gt;3&lt;span style=&quot;display:none&quot;&gt;]&lt;/span&gt;&lt;/a&gt;&lt;/sup&gt;으로 되어있습니다. 선택할 수 있는 내용은 SOURCE, CLASS, RUNTIME 세 가지로 SOURCE는 말 그대로 소스까지 유지됩니다. 컴파일 시 사라지는 구조입니다. CLASS는 컴파일까지만 유지됩니다. 컴파일 후 생성되는 class 파일에는 남아있지만 자바가 실행되는 동안(runtime)에는 사용할 수 없습니다. RUNTIME은 자바가 VM에서 실행되는 동안에도 유지되는 것입니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;@Target은 어노테이션을 적용할 수 있는 위치라고 보시면 됩니다.&amp;nbsp;배열로 입력할 수도, 하나만 입력할 수도 있습니다. 입력할 수 있는 형식은 enum ElementType입니다. TYPE은 클래스, 인터페이스, enum에 적용할 수 있습니다. FIELD는 말 그대로 필드(인스턴스 변수)나 enum의 상수에 적용할 수 있습니다. METHOD는 메소드에 적용가능합니다. PARAMETER는 파라메터에, CONSTRUCTOR는 생성자에, LOCAL_VARIABLE은 지역 변수에 적용 가능합니다. ANNOTATION_TYPE은 어노테이션에만 적용할 수 있게됩니다. PACKAGE는 패키지에만 적용됩니다. 너무 당연한 설명이라 제외해도 될 뻔 했네요 ^^;&lt;br&gt;&lt;br&gt;&lt;br&gt;&amp;nbsp;이제 어노테이션의 내용을 어떻게 작성하는가에 대해 확인해보죠. 앞에 Retention등의 어노테이션을 사용하며 확인하셨을테지만&amp;nbsp;어노테이션을 특정한 곳에 사용할 경우에 받는 값들은 괄호 안에 작성하게 되어있습니다. 괄호안에 받는 값들은 어노테이션 내용에 메소드 정의만 작성하여 표기하죠. 위의 코드를 보시면 String value(); String comment() default &quot;&quot;;가 보이네요. 위와 같이 작성할 경우 @CustomAnnotation을 사용할 때 @CustomAnnotation(value = &quot;값&quot;, comment = &quot;값&quot;)의 형식으로 입력받을 수 있습니다. 메소드 명이 어노테이션 괄호 안에 입력할 수 있는 변수 명이 되는 것이죠.&lt;br&gt;&lt;br&gt;&lt;br&gt;2. CustomAnnotation2&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.tistory.sunimohs.study;  import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;  /**  * default 지정하지 않은 어노테이션  */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface CustomAnnotation2 {   String value();   String comment(); }&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br&gt;&lt;br&gt;&amp;nbsp;이 어노테이션은 @Retention을 RUNTIME으로 잡고 String comment()의 default를 제외했습니다. 위의 어노테이션과 다른 점은 아래에 작성하도록 하겠습니다.&lt;br&gt;&lt;br&gt;&lt;br&gt;3. CustomAnnotation3&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.tistory.sunimohs.study;  /**  * value() 사용 안 한 어노테이션  */ public @interface CustomAnnotation3 {   String[] values(); }&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br&gt;&lt;br&gt;&amp;nbsp;간단하게 작성해 보았습니다. 이 어노테이션에는 @Retention, @Target을 지정하지 않았습니다. @Retention은 지정하지 않을 경우 CLASS가 디폴트로 적용됩니다. 내용에는 String[] values();가 있네요.&lt;br&gt;&lt;br&gt;&lt;br&gt;4. AnnotationClass&lt;br&gt;&lt;br&gt;&lt;br&gt;&amp;nbsp;어노테이션을 적용해보기 위해 생성한 클래스입니다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.tistory.sunimohs.study;  /**  * 어노테이션을 적용한 변수들만 있는 클래스.  *   * @author sunimohs  */ public class AnnotationClass {   @CustomAnnotation(&quot;a&quot;)   private String a = &quot;a&quot;;      @CustomAnnotation2(value = &quot;b&quot;, comment = &quot;설명을 쓰세요.&quot;)   private String b = &quot;b&quot;;      @CustomAnnotation3(values = &quot;&quot;)   private String c = &quot;c&quot;;      @CustomAnnotation3(values = { &quot;&quot; })   private String d = &quot;d&quot;; }&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br&gt;&lt;br&gt;&amp;nbsp;여기보면 재미있는 것이 보이는데요. 이전에 @Retention, @Target의 괄호에 값을 입력할 때는 아무런 변수명을 입력하지 않고 바로 값만 넣었었죠? 여기에도 그런게 보이네요 a 변수를&amp;nbsp;보시면 @CustomAnnotation(&quot;a&quot;)라고 입력되어 있습니다. 이런게 가능한 것은 약속된 메소드를 사용하였기 때문인데요. String value()로 정의한게 바로 이 부분입니다. value()로 정의하게 되면 기본 입력으로 설정되어 아무런 변수명을 입력하지 않고도 입력이 가능합니다. 그리고 @CustomAnnotation에 정의했던 comment()는 default를 선언해 놓았기 때문에 아무런 값도 입력하지 않아도 됩니다. default에 입력했던 값이 적용되어 value = &quot;a&quot;, comment = &quot;&quot;와 같은 의미가 됩니다.&lt;br&gt;&amp;nbsp;@CustomAnnotation2는 comment()에 default를 정의하지 않았기 때문에 2개의 항목을 다 입력해야합니다.&lt;br&gt;&amp;nbsp;@CustomAnnotation3는 values()로 value()를 사용하지 않았기 때문에 하나만 입력해도 변수명을 적어야하네요.&lt;br&gt;&amp;nbsp;d 변수를 보면 @CustomAnnotation3의 values에 배열을 담았습니다. String[] values()가 배열을 입력받을 수 있기 때문인데요. 배열일 경우 하나만 입력할 때는 {} 중괄호를 제외하여 표시하여도 관계없습니다.&lt;br&gt;&lt;br&gt;&lt;br&gt;5. MainClass&lt;br&gt;&lt;br&gt;&lt;br&gt;&amp;nbsp;어노테이션이 컴파일 시 어떻게 되는지&amp;nbsp;알아볼 메인 클래스입니다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.tistory.sunimohs.study;  import java.lang.reflect.Field;  /**  * 어노테이션 테스트 메인 클래스  *   * @author sunimohs  * @since 2017. 10. 10.  */ @CustomAnnotation3(values = &quot;&quot;) public class MainClass {   public static void main(String[] args) {     AnnotationClass ac = new AnnotationClass();          try {       Class&amp;lt;? extends AnnotationClass&amp;gt; acCls = ac.getClass();              Field a = acCls.getDeclaredField(&quot;a&quot;);       Field b = acCls.getDeclaredField(&quot;b&quot;);       Field c = acCls.getDeclaredField(&quot;c&quot;);       Field d = acCls.getDeclaredField(&quot;d&quot;);              if (a.isAnnotationPresent(CustomAnnotation.class)) {         System.out.println(&quot;a 필드 어노테이션 확인&quot;);       } else if (b.isAnnotationPresent(CustomAnnotation2.class)) {         System.out.println(&quot;b 필드 어노테이션 확인&quot;);       } else if (c.isAnnotationPresent(CustomAnnotation3.class) || d.isAnnotationPresent(CustomAnnotation3.class)) {         System.out.println(&quot;c 혹은 d 필드 어노테이션 확인&quot;);       }     } catch (Exception e) {       e.printStackTrace();     }   } }&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br&gt;&lt;br&gt;&amp;nbsp;AnnotationClass객체에서 class를 가져와 a, b, c, d 필드 객체를 가져옵니다. 각각 a, b, c, d 필드 객체에 CustomAnnotation, CustomAnnotation2, CustomAnnotation3이 적용되어있는지 확인하고 System.out.println() 메소드를 통해 표시합니다. 결과는 아래와 같습니다. 위에서 말한 바와 같이 Retention이 RUNTIME으로 지정된 CustomAnnotation2만 확인이 되네요.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;pre style=&quot;background-color: rgb(40, 44, 40);&quot;&gt;&lt;p&gt;&lt;font color=&quot;#ffffff&quot;&gt;b 필드 어노테이션 확인 &lt;/font&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;/p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://t1.daumcdn.net/cfile/tistory/994C9C3359E042C110?original&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;첨부파일&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.00MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;/p&gt;&lt;div class=&quot;footnotes&quot;&gt; 
 &lt;ol class=&quot;footnotes&quot;&gt; 
  &lt;li id=&quot;footnote_12_1&quot;&gt;https://ko.wikipedia.org/wiki/%EB%A9%94%ED%83%80%EB%8D%B0%EC%9D%B4%ED%84%B0 &lt;a href=&quot;#footnote_link_12_1&quot;&gt;[본문으로]&lt;/a&gt;&lt;/li&gt; 
  &lt;li id=&quot;footnote_12_2&quot;&gt;http://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html, 자바에서 지정한 형태의 주석들을 인식하여 html로 된 api 문서 형태로 만들어주는 도구입니다. &lt;a href=&quot;#footnote_link_12_2&quot;&gt;[본문으로]&lt;/a&gt;&lt;/li&gt; 
  &lt;li id=&quot;footnote_12_3&quot;&gt;https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html, 같은 의미로 사용되는 상수를 여러개 묶어서 사용할 수 있도록 정의하는 형식. 예) 요일은 월, 화, 수, 목, 금, 토, 일로 일정하기 때문에 고정 값인 상수로 지정해서 사용하면 편하다. &lt;a href=&quot;#footnote_link_12_3&quot;&gt;[본문으로]&lt;/a&gt;&lt;/li&gt; 
 &lt;/ol&gt; 
&lt;/div&gt;&lt;script type=&quot;text/javascript&quot;&gt; tistoryFootnote.add(12,1,&quot;https:\/\/ko.wikipedia.org\/wiki\/%EB%A9%94%ED%83%80%EB%8D%B0%EC%9D%B4%ED%84%B0&quot;); tistoryFootnote.add(12,2,&quot;http:\/\/docs.oracle.com\/javase\/8\/docs\/technotes\/tools\/windows\/javadoc.html,\n\uc790\ubc14\uc5d0\uc11c \uc9c0\uc815\ud55c \ud615\ud0dc\uc758 \uc8fc\uc11d\ub4e4\uc744 \uc778\uc2dd\ud558\uc5ec html\ub85c \ub41c api \ubb38\uc11c \ud615\ud0dc\ub85c \ub9cc\ub4e4\uc5b4\uc8fc\ub294 \ub3c4\uad6c\uc785\ub2c8\ub2e4.&quot;); tistoryFootnote.add(12,3,&quot;https:\/\/docs.oracle.com\/javase\/tutorial\/java\/javaOO\/enum.html,\n\uac19\uc740 \uc758\ubbf8\ub85c \uc0ac\uc6a9\ub418\ub294 \uc0c1\uc218\ub97c \uc5ec\ub7ec\uac1c \ubb36\uc5b4\uc11c \uc0ac\uc6a9\ud560 \uc218 \uc788\ub3c4\ub85d \uc815\uc758\ud558\ub294 \ud615\uc2dd. \uc608) \uc694\uc77c\uc740 \uc6d4,\n \ud654, \uc218, \ubaa9, \uae08, \ud1a0, \uc77c\ub85c \uc77c\uc815\ud558\uae30 \ub54c\ubb38\uc5d0 \uace0\uc815 \uac12\uc778 \uc0c1\uc218\ub85c \uc9c0\uc815\ud574\uc11c \uc0ac\uc6a9\ud558\uba74 \ud3b8\ud558\ub2e4.&quot;); &lt;/script&gt;</description>
      <category>프로그래밍/Java 기초</category>
      <category>Annotation</category>
      <category>Interface</category>
      <category>Java</category>
      <category>어노테이션</category>
      <category>인터페이스</category>
      <category>자바</category>
      <author>수니모스</author>
      <guid isPermaLink="true">https://sunimohs.tistory.com/12</guid>
      <comments>https://sunimohs.tistory.com/12#entry12comment</comments>
      <pubDate>Fri, 13 Oct 2017 13:37:39 +0900</pubDate>
    </item>
  </channel>
</rss>