2016년 12월 27일 화요일

redirect와 forward의 차이

redirect와 forward의 차이

  * Forward : Web Container 차원에서 페이지 이동만 있다. 실제로 웹 브라우저는 다른 페이지로 이동했음을 알 수 없다.
브라우져상에서 URL은 기존의 요청된 URL과 동일한 정보를 노출한다.
동일한  웹 컨테이너에 있는 페이지로만 이동할 수 있다.
현재 실행중인 페이지와 forwad에 의해 호출될 페이지는 request와 response 객체를 공유한다.

  * Redirect : Web Container는 Redirect 명령이 들어오면 웹 브라우저에게 다른 페이지로 이동하라고 명령을 내린다. 그러면 웹 브라우저는 URL을 지시된 주소로 바꾸고 그 주소로 이동한다. 다른 웹 컨테이너에있는 주소로 이동이 가능하다.
새로운 페이지에서는 request와 response객체가 새롭게 생성된다.
즉 새로운 페이지가 로딩된 것과 동일하다.

2016년 12월 12일 월요일

spring security를 이용한 oauth 2.0 구현


spring security를 이용한 대부분의 예제들이 spring boot를 이용하여 구현이 되어 있다.
하지만 꼭 boot로 구동을 해야 되는 건 아니니 프로젝트별 구성에 따라 맞게 구현을 하는게 좋을 것이다.

본인의 경우 boot를 지향하지 않기 때문에 tomcat 기반으로 구성을 진행했다.

기존에 spring security를 이용하여 maven 프로젝트를 구성하고 있다면 아래의 dependency만 추가해 주면 된다.
사실 oauth를 구성하는데 org.springframework.security.oauth 해당 라이브러리만 있으면 되는 것이나 관련 dendency추가해준다.
commons-lang3는 scope나 grant-types에 대해서 ,를 구분자로 리스트를 입력되어 있는 경에 대한 StringUtil이나 token발급에 필요한 util등을 사용한다.

================POM.xml===========================
                     <!-- spring security-->
                      <dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${springframework.security-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${springframework.security-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${springframework.security-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${springframework.security-version}</version>
</dependency>

       <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${commons-lang3.version}</version>
        </dependency>
        <!-- oauth -->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>${spring-security-oauth2-version}</version>
        </dependency>
        <dependency>
            <groupId>com.jayway.restassured</groupId>
            <artifactId>rest-assured</artifactId>
            <version>${rest-assured.version}</version>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <artifactId>commons-logging</artifactId>
                    <groupId>commons-logging</groupId>
                </exclusion>
            </exclusions>
        </dependency>
===================================================

실제 Oauth의 구현은 간단하다.
org.springframework.security.oauth라이브러리 내에 기능 구현이 거의 대부분이 되어 있다고해도 과언이 아니다.
customizing을 진행하기 위해서는 기능에 대한 구현체에 대한 확인이 필요하다.
하지만 그 일이 만만치가 않다. interface기반으로 실제 구현체들이 주입이 되는 방식으로 코드들이 숨겨져 있어서 일일이 찾아서 확인해야 한다.

https://projects.spring.io/spring-security-oauth/docs/oauth2.html에 설명된 아래의 3개의 configurer를 중심을 trace를 해 간다면 좀 더 쉽게 이해가 되었다.

  • ClientDetailsServiceConfigurer: a configurer that defines the client details service. Client details can be initialized, or you can just refer to an existing store.
  • AuthorizationServerSecurityConfigurer: defines the security constraints on the token endpoint.
  • AuthorizationServerEndpointsConfigurer: defines the authorization and token endpoints and the token services.

ClientDetailsServiceConfigurer
 - clientDetailsService에 대한 config를 설정
 - clientDetails에 대한 clientId나 grantType,scopes,TokenValiditySeconds등에 대해서 설정을 로직에 대해서 주입
 - inMemory/jdbc/withClientDetails 3가지 방식을 제공하는 것으로 보아도 무방하다.
 - inMemory source에서 clientDetails에 대해서 정의를 하고 말그대로 메모리에 올려서 처리하는 방식
 - jdbc jdbcTemplate(spring jdbcMapper) 방식의 구현체
 - withClientDetails bean로 ClientDetailsService구현체를 주입을 하던 custom ClientDetailsService의 구현체를 작성하여 주입이 가능/ 본인의 경우 해당 부분을 구현하여 처리

===================OAuth2AuthorizationServerConfig.java=============
@Override
    public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {// @formatter:off
// clients
// .jdbc(dataSource).withClient("sampleClientId").authorizedGrantTypes("implicit")
// .scopes("read", "write", "foo", "bar").autoApprove(false).accessTokenValiditySeconds(3600)
//
// .and().withClient("fooClientIdPassword").secret("secret")
// .authorizedGrantTypes("password", "authorization_code", "refresh_token","client_credentials").scopes("foo", "read", "write")
// .accessTokenValiditySeconds(3600) // 1 hour
// .refreshTokenValiditySeconds(2592000) // 30 days
//
// .and().withClient("barClientIdPassword").secret("secret")
// .authorizedGrantTypes("password", "authorization_code", "refresh_token").scopes("bar", "read", "write")
// .accessTokenValiditySeconds(3600) // 1 hour
// .refreshTokenValiditySeconds(2592000) // 30 days
// ;
        //clients.jdbc(dataSource).;
clients.withClientDetails(clientDetailsService);
}


AuthorizationServerSecurityConfigurer
- token endpoint에 대한 대한 제약을 config
- 접근제어 및 접근 제어 방식에 대한 정의

===================OAuth2AuthorizationServerConfig.java=============
 @Override
    public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        //oauthServer.passwordEncoder(passwordEncoder)
        oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
     
    }



AuthorizationServerEndpointsConfigurer
-

===================OAuth2AuthorizationServerConfig.java=============
@Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // @formatter:off
final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer()));
endpoints.approvalStore(approvalStore).tokenStore(tokenStore).authorizationCodeServices(authorizationCodeServices())
.tokenEnhancer(tokenEnhancerChain).authenticationManager(authenticationManager);
// @formatter:on
    }


여기서 중요한 포인트는 EndPoint라는 건데 결론적으로는 controller와 동일한 기능을 처리 한다.

FrameworkEndpointHandlerMapping라는 클래스가 RequestMappingHandlerMapping를 상속 받아서 @FrameworkEndpoint라는 SpringSecurity에서 생성한 인터페이스 기반으로 Endpoint를 조회해서 requestMapping을 등록하는 역활을 한다.
즉 @FrameworkEndpoint를 붙혀주면 해당 클래스는 FrameworkEndpointHandlerMapping를 통해서 controller와 같이 동작한다고 보면 된다.

기존에 라이브러리에서 제공되는 FrameworkEndpoint의 리스트 아래와 같다.
AuthorizationEndpoint
 - authorize_code 발급을 위한 로직
 - /oauth/authorize api에 대한 기능 구현
CheckTokenEndpoint
 - accessToken에 대한 체크 로직
 - /oauth/check_token api에 대한 기능 구현
TokenEndpoint
 - accessToken를 발급하는 로직
 - /oauth/token api에 대한 기능 구현
TokenKeyEndpoint
 - JWT관련된 tokenKey 관련 기능 (사용안함)
 - /oauth/token_key api에 대한 기능 구현
WhitelabelApprovalEndpoint
 - authorize_code 발급시 scope에 대한 approval 를 정의하는 UI페이지에 대한 처리
 - /oauth/confirm_access 페이지에 대한 기능 구현
WhitelabelErrorEndpoint
 - error페이지에 대한 기능
 - /oauth/error 페이지에 대한 기능 구현


아래는 필요한 api에 대하서 작성한 예제이다.
 - 물론 해당 API를 controller로 구현하여도 무방하다.
 - 하지만 기존 Oauth코드와 통일성을 유지하기 위하여 custom API에 대해서 동일하게 FrameworkEndpoint 형태로 개발 진행
===============ClientManageEndpoint .java=============
@FrameworkEndpoint
public class ClientManageEndpoint {
    @Autowired
    private ClientDetailsService clientDetailsService;
    @Autowired
    private ClientRegistrationService clientRegistrationService;
 
    protected final Log logger = LogFactory.getLog(getClass());

    private WebResponseExceptionTranslator exceptionTranslator = new DefaultWebResponseExceptionTranslator();

    public ClientManageEndpoint(ClientDetailsService clientDetailsService) {
        this.clientDetailsService = clientDetailsService;
    }
 
    /**
     * @param exceptionTranslator the exception translator to set
     */
    public void setExceptionTranslator(WebResponseExceptionTranslator exceptionTranslator) {
        this.exceptionTranslator = exceptionTranslator;
    }

    @RequestMapping(value = "/oauth/clients/{clientId}",  method = RequestMethod.GET)
    @ResponseBody
    public Object checkToken(@PathVariable("clientId") String clientId) {

        ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
        if (client == null) {
            throw new InvalidClientException("Client was not recognised");
        }
        return client;
    }
    ...중략

Oauth를 위한 Service를 구성하는 있어서 springSecurity에서 제공하는 jdbc 방식을 이용하여 구현한다면 개발 공수는 거의 없다고 할수있다. 하지만 customizing을 하는데 있어 제약이 많은 관계로 요구사항이 많은 프로젝트라고 한다면 Service를 구현하여  customizing하는 것을 추천한다.

저는 기존에 사용하던 Mybatis를 기반으로 구현을 진행하였다.

=================clientDetailMapper.groovy

@Mapper
public interface ClientDetailsMapper {

    @Results(id="ClientDetailsResult", value = [
        @Result(property = "clientId", column = "client_id"),
        @Result(property = "clientSecret", column = "client_secret"),
        @Result(property = "resourceIds", column = "resource_ids", typeHandler=CommaDelimitedListTypeHandler.class),
        @Result(property = "scope", column="scope", typeHandler=CommaDelimitedListTypeHandler.class),
        @Result(property = "authorizedGrantTypes", column = "authorized_grant_types", typeHandler=CommaDelimitedListTypeHandler.class),
        @Result(property = "registeredRedirectUris", column = "web_server_redirect_uri",typeHandler=CommaDelimitedListTypeHandler.class),
        @Result(property = "authorities", column = "authorities", jdbcType=JdbcType.VARCHAR, typeHandler=AuthorityListTypeHandler.class),
        @Result(property = "accessTokenValiditySeconds", column = "access_token_validity"),
        @Result(property = "refreshTokenValiditySeconds", column = "refresh_token_validity"),
        @Result(property = "additionalInformation", column = "additional_information", typeHandler=JsonMappedTypeHandler.class),

        @Result(property = "autoApproveScopes", column = "autoapprove",typeHandler=CommaDelimitedListTypeHandler.class)
    ])
    @Select("""<script>
        SELECT client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove
        FROM oauth_client_details
        WHERE client_id = #{clientId}
    </script>""")
    public BaseClientDetails selectClientDetails(String clientId);
 
    @Insert("""<script>
        INSERT INTO oauthdb.oauth_client_details(
            client_id
            ,client_secret
            ,resource_ids
            ,scope
            ,authorized_grant_types
            ,web_server_redirect_uri
            ,authorities
            ,access_token_validity
            ,refresh_token_validity
            ,additional_information
            ,autoapprove
        ) VALUES (
            #{clientId}
            ,#{clientSecret}
            ,#{resourceIds,jdbcType=VARCHAR, typeHandler=com.oneplat.oap.oauth.common.mybatis.handler.CommaDelimitedListTypeHandler}
            ,#{scope,jdbcType=VARCHAR, typeHandler=com.oneplat.oap.oauth.common.mybatis.handler.CommaDelimitedListTypeHandler}
            ,#{authorizedGrantTypes,jdbcType=VARCHAR, typeHandler=com.oneplat.oap.oauth.common.mybatis.handler.CommaDelimitedListTypeHandler}
            ,#{registeredRedirectUri,jdbcType=VARCHAR, typeHandler=com.oneplat.oap.oauth.common.mybatis.handler.CommaDelimitedListTypeHandler}
            ,#{authorities,jdbcType=VARCHAR, typeHandler=com.oneplat.oap.oauth.common.mybatis.handler.AuthorityListTypeHandler}
            ,#{accessTokenValiditySeconds}
            ,#{refreshTokenValiditySeconds}
            ,#{additionalInformation,jdbcType=VARCHAR, typeHandler=com.oneplat.oap.oauth.common.mybatis.handler.JsonMappedTypeHandler}
            ,#{autoApproveScopes,jdbcType=VARCHAR, typeHandler=com.oneplat.oap.oauth.common.mybatis.handler.CommaDelimitedListTypeHandler}
        )
    </script>""")
    public int insertClientDetails(ClientDetails clientDetails);
 
    @ResultMap("ClientDetailsResult")
    @Select("""<script>
        SELECT client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove
        FROM oauth_client_details
    </script>""")
    public List<ClientDetails> selectClientDetailsList();
 
}


================ClientDetailServiceImpl.java===========
@Service("ClientDetailServiceImpl")
public class ClientDetailServiceImpl implements ClientDetailsService, ClientRegistrationService {
    @Autowired
    ClientDetailsMapper clientDetailsMapper;

    @Override
    public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
        // TODO Auto-generated method stub
        return clientDetailsMapper.selectClientDetails(clientId);
    }
 
    @Override
    public void addClientDetails(ClientDetails clientDetails) throws ClientAlreadyExistsException {
        // TODO Auto-generated method stub
        clientDetailsMapper.insertClientDetails(clientDetails);
     
    }

    @Override
    public void updateClientDetails(ClientDetails clientDetails) throws NoSuchClientException {
        // TODO Auto-generated method stub
     
    }

    @Override
    public void updateClientSecret(String clientId, String secret) throws NoSuchClientException {
        // TODO Auto-generated method stub
     
    }

    @Override
    public void removeClientDetails(String clientId) throws NoSuchClientException {
        // TODO Auto-generated method stub
     
    }

    @Override
    public List<ClientDetails> listClientDetails() {
        // TODO Auto-generated method stub
        return clientDetailsMapper.selectClientDetailsList();
    }
}


=====================OAuth2AuthorizationServerConfig.java

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private Environment env;
    
    @Autowired
    DataSource dataSource;

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Value("classpath:schema.sql")
    private Resource schemaScript;
    
    @Autowired PasswordEncoder passwordEncoder;
    
    @Autowired
    ApprovalStore approvalStore;
    
    @Autowired
    TokenStore tokenStore;
    
    @Autowired
    @Qualifier("ClientDetailServiceImpl")
    ClientDetailsService clientDetailsService;
    
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    
    @Override
    public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        //oauthServer.passwordEncoder(passwordEncoder)
        oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
        
    }

    @Override
    public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {// @formatter:off
// clients
// .jdbc(dataSource).withClient("sampleClientId").authorizedGrantTypes("implicit")
// .scopes("read", "write", "foo", "bar").autoApprove(false).accessTokenValiditySeconds(3600)
//
// .and().withClient("fooClientIdPassword").secret("secret")
// .authorizedGrantTypes("password", "authorization_code", "refresh_token","client_credentials").scopes("foo", "read", "write")
// .accessTokenValiditySeconds(3600) // 1 hour
// .refreshTokenValiditySeconds(2592000) // 30 days
//
// .and().withClient("barClientIdPassword").secret("secret")
// .authorizedGrantTypes("password", "authorization_code", "refresh_token").scopes("bar", "read", "write")
// .accessTokenValiditySeconds(3600) // 1 hour
// .refreshTokenValiditySeconds(2592000) // 30 days
// ;
        //clients.jdbc(dataSource).;
clients.withClientDetails(clientDetailsService);
} // @formatter:on
    @Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // @formatter:off
final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer()));
endpoints.approvalStore(approvalStore).tokenStore(tokenStore).authorizationCodeServices(authorizationCodeServices())
.tokenEnhancer(tokenEnhancerChain).authenticationManager(authenticationManager);
// @formatter:on
    }
//    @Bean
//    public ClientDetailsService clientDetailsService() {
//        return new JdbcClientDetailsService(dataSource);
//    }
//    @Bean
//    public ApprovalStore approvalStore() {
//        return new JdbcApprovalStore(dataSource);
//    }
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(dataSource);
    }
    
    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore);
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }
    
    @Bean
    public TokenEnhancer tokenEnhancer() {
        return new CustomTokenEnhancer();
    }
    
//    @Bean
//    public TokenStore tokenStore() {
//        return new JdbcTokenStore(dataSource);
//    }
    
    //신규 EndPoint 등록
    @Bean
    public ClientManageEndpoint clientManageEndpoint() {
        return new ClientManageEndpoint(clientDetailsService);
    }

}


mysql에서 공백과 null 동시 체크

mysql에서 공백과 null 동시 체크
여러가지 방법이 있겠으나 제가 선호 하는 방식에 대한 설명입니다.

NULLIF(컬럼명,'') IS NULL
NULLIF를 이용하여 해당 컬럼명이 '' 공백과 같다면 null 치환하여 해당 컬럼이 null인지 여부를 체크하는 방식입니다.

관련 참고 함수

1. CASE value WHEN [compare_value] THEN result [WHEN [compare_value] THEN result  ...] [ELSE result] END
switch 문과 유사항 방식

    CASE WHEN [condition] THEN result [WHEN [condition] THEN result ...] [ELSE result] END
switch 문 - IF문 의 조합형

2. IF(expr1, expr2, expr3)
  : expr1이 TRUE 이면 expr2를 리턴하고, 그렇지 않은 경우 expr3를 리턴한다.

3. IFNULL(expr1, expr2)
  : expr1이 NULL이면 expr2를 리턴하고, NULL이 아니면 expr1을 리턴한다.

4. NULLIF(expr1, expr2)
  : expr1 = expr2가 TRUE이면 NULL을 리턴하고, 그렇지 않으면 expr1을 리턴한다.
    이것은 CASE WHEN expr1=expr2 THEN NULL ELSE expr1 END 와 같다.

2016년 4월 17일 일요일

Custom Annotation Scan만들기

1. Scan을 할 Annotation Interface를 생성
2. ImportBeanDefinitionRegistrard 구현체 생성
<- 해당 구현체는 Spring @Configration이 선언된 클래스에 대해서 실행된다.
3. 테스트를 진행할 Annotation Interface를 생성
4.TestContext객체생성

테스트 클래스들은 Mybatis mapperScan을 기반으로 생성하였음.

TestScan 클래스

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(TestSannerRegistarar.class)
public @interface TestScan {



  String[] value() default {};

  String[] basePackages() default {};

  Class<?>[] basePackageClasses() default {};

  Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

  Class<? extends Annotation> annotationClass() default Annotation.class;

  Class<?> markerInterface() default Class.class;

  String sqlSessionTemplateRef() default "";

  String sqlSessionFactoryRef() default "";

}
TestScannerRegistarar 클래스


public class TestSannerRegistarar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  private ResourceLoader resourceLoader;

  /**
   * {@inheritDoc}
   */
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(TestScan.class.getName()));
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    if (resourceLoader != null) { // this check is needed in Spring 3.1
      scanner.setResourceLoader(resourceLoader);
    }

    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      scanner.setAnnotationClass(annotationClass);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      scanner.setMarkerInterface(markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

    List<String> basePackages = new ArrayList<String>();
    for (String pkg : annoAttrs.getStringArray("value")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (String pkg : annoAttrs.getStringArray("basePackages")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
    }
    scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

  /**
   * {@inheritDoc}
   */
  public void setResourceLoader(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }

}
TestMapper 클래스

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestMapper {
}

Mybatis MapperScan에 대해서

UserMapper가 매퍼 인터페이스와 같은 경로의 클래스패스에 마이바티스 XML매퍼 파일을 가지고 있다면 MapperFactoryBean이 자동으로 파싱할것이다. 매퍼 XML파일을 다른 클래스패스에 두는게 아니라면 마이바티스 설정파일에 매퍼를 지정할 필요가 없다. 좀더 세부적인 정보는 SqlSessionFactoryBean의 configLocation 프로퍼티를 살펴보자.
MapperFactoryBean은 SqlSessionFactory 나 SqlSessionTemplate가 필요하다. sqlSessionFactory 와 sqlSessionTemplate 프로퍼티를 셋팅하면 된다. 둘다 셋팅하면 SqlSessionFactory가 무시된다. 세션 팩토리 셋은 SqlSessionTemplate이 필요하고 MapperFactoryBean는 팩토리를 사용할것이다.

자바설정 사용

    @Configuration
@EnableTransactionManagement
@MapperScan(annotationClass = Mapper.class, basePackages = "com.test", sqlSessionFactoryRef = "sqlSessionFactoryBean")
public class CustomMyBatisContext{

    /**
     * myBatis의 {@link org.apache.ibatis.session.SqlSessionFactory}을 생성하는 팩토리빈을 등록한다.
     */
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource, ApplicationContext applicationContext)
            throws IOException {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();

        // 마이바티스가 사용한 DataSource를 등록
        factoryBean.setDataSource(dataSource);
        // 마이바티스 프로퍼티 설정
        Properties mybatisProperties = new Properties();
        mybatisProperties.setProperty("lazyLoadingEnabled", "true");
        mybatisProperties.setProperty("aggressiveLazyLoading", "false");
        mybatisProperties.setProperty("lazyLoadTriggerMethods", "");
        mybatisProperties.setProperty("mapUnderscoreToCamelCase", "true");
        factoryBean.setConfigurationProperties(mybatisProperties);
        factoryBean.setConfigLocation(applicationContext.getResource("classpath:mybatis/configuration.xml"));
        factoryBean.setTypeAliasesPackage("com.test.mybatis.model");
        factoryBean.setTypeHandlersPackage("com.test.mybatis.handler");
        return factoryBean;
    }

}
MapperScan에서 can을 진행할 Annotation 클래스를 지정하고 basePacke를 설정해준다.

지정된 Annotation이 설정되어 있는 클래스는 scan을 진행하고 해당 클래스들은 아래의 SqlSessionFactoryBean을 주입 받는다.


여기서 중요한 포인트는 SqlSessionFactoryBean 해당 클래스라 할수 있다.
실제 Mapperscan을 통해서 주입된 Bean데이타를 실제 Mybatis의 필요한 object를 생성하여 mybatis의 configure에 주입하는 하는 역활을 한다.

@mapperscan에 대해서 customizing을 진행할때 해당 SqlSessionFactoryBean 을 상속 받아서 구현을 하던지 해당 클래스를 구현하여 수정할수 있다.
  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;

    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); 
      configuration = xmlConfigBuilder.getConfiguration();<-- 해당 configurationProperties 통해서 configration객체 생성
    } else {
      if (this.logger.isDebugEnabled()) {
        this.logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
      }
      configuration = new Configuration();<-- 해당 configurationProperties 통해서 configration객체 생성
      configuration.setVariables(this.configurationProperties);
    }

    if (this.objectFactory != null) {
      configuration.setObjectFactory(this.objectFactory);
    }

    if (this.objectWrapperFactory != null) {
      configuration.setObjectWrapperFactory(this.objectWrapperFactory);
    }

    if (hasLength(this.typeAliasesPackage)) { <-- typeAliasPackage 등록
      String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeAliasPackageArray) {
        configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
        if (this.logger.isDebugEnabled()) {
          this.logger.debug("Scanned package: '" + packageToScan + "' for aliases");
        }
      }
    }

    if (!isEmpty(this.typeAliases)) {
      for (Class<?> typeAlias : this.typeAliases) {
        configuration.getTypeAliasRegistry().registerAlias(typeAlias);
        if (this.logger.isDebugEnabled()) {
          this.logger.debug("Registered type alias: '" + typeAlias + "'");
        }
      }
    }

    if (!isEmpty(this.plugins)) {
      for (Interceptor plugin : this.plugins) {
        configuration.addInterceptor(plugin);
        if (this.logger.isDebugEnabled()) {
          this.logger.debug("Registered plugin: '" + plugin + "'");
        }
      }
    }

    if (hasLength(this.typeHandlersPackage)) {<--typeHandler등록
      String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeHandlersPackageArray) {
        configuration.getTypeHandlerRegistry().register(packageToScan);
        if (this.logger.isDebugEnabled()) {
          this.logger.debug("Scanned package: '" + packageToScan + "' for type handlers");
        }
      }
    }

    if (!isEmpty(this.typeHandlers)) {
      for (TypeHandler<?> typeHandler : this.typeHandlers) {
        configuration.getTypeHandlerRegistry().register(typeHandler);
        if (this.logger.isDebugEnabled()) {
          this.logger.debug("Registered type handler: '" + typeHandler + "'");
        }
      }
    }

    if (xmlConfigBuilder != null) { <- xml config가 설정되어 있다면 parse처리 
      try {
        xmlConfigBuilder.parse();

        if (this.logger.isDebugEnabled()) {
          this.logger.debug("Parsed configuration file: '" + this.configLocation + "'");
        }
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }

    if (this.transactionFactory == null) {
      this.transactionFactory = new SpringManagedTransactionFactory();
    }

    Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
    configuration.setEnvironment(environment);

    if (this.databaseIdProvider != null) {
      try {
        configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }

    if (!isEmpty(this.mapperLocations)) { <--mapperlocations가 설정되어 있다면 불러들여 build처리 
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }

        if (this.logger.isDebugEnabled()) {
          this.logger.debug("Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      if (this.logger.isDebugEnabled()) {
        this.logger.debug("Property 'mapperLocations' was not specified or no matching resources found");
      }
    }

    return this.sqlSessionFactoryBuilder.build(configuration);
  }

2016년 4월 1일 금요일

lombok 설치 및 정보 관련 이런저런


설치 순서
1. http://projectlombok.org/download.html 에서 lombok.jar 를 다운로드
2. java -jar 경로/lombok.jar
3. lombok.jar 실행하여 IDE 에 등록
    • java -jar lombok.jar
    • IDE를 못 찾는경우 수동으로 등록을 해줘야 한다.
IDE를 못 찾는 경우
    • 명시적으로 설치
    • 추가할 eclipse 실행파일 선택하고 install/update 실행


3. maven 연계
pom.xml 추가
<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.12.6</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

4. 사용
  • val
Finally! Hassle-free final local variables.
  • @NonNull
or: How I learned to stop worrying and love the NullPointerException.

With Lombok

01 import lombok.NonNull;
02 
03 public class NonNullExample extends Something {
04   private String name;
05   
06   public NonNullExample(@NonNull Person person) {
07     super("Hello");
08     this.name = person.getName();
09   }
10 }

Vanilla Java

01 import lombok.NonNull;
02 
03 public class NonNullExample extends Something {
04   private String name;
05   
06   public NonNullExample(@NonNull Person person) {
07     super("Hello");
08     if (person == null) {
09       throw new NullPointerException("person");
10     }
11     this.name = person.getName();
12   }
13 }






















  • @Cleanup
Automatic resource management: Call your close() methods safely with no hassle.

With Lombok

01 import lombok.Cleanup;
02 import java.io.*;
03 
04 public class CleanupExample {
05   public static void main(String[] argsthrows IOException {
06     @Cleanup InputStream in = new FileInputStream(args[0]);
07     @Cleanup OutputStream out = new FileOutputStream(args[1]);
08     byte[] b = new byte[10000];
09     while (true) {
10       int r = in.read(b);
11       if (r == -1break;
12       out.write(b, 0, r);
13     }
14   }
15 }

Vanilla Java

01 import java.io.*;
02 
03 public class CleanupExample {
04   public static void main(String[] argsthrows IOException {
05     InputStream in = new FileInputStream(args[0]);
06     try {
07       OutputStream out = new FileOutputStream(args[1]);
08       try {
09         byte[] b = new byte[10000];
10         while (true) {
11           int r = in.read(b);
12           if (r == -1break;
13           out.write(b, 0, r);
14         }
15       finally {
16         if (out != null) {
17           out.close();
18         }
19       }
20     finally {
21       if (in != null) {
22         in.close();
23       }
24     }
25   }
26 }



































  • @Getter / @Setter
Never write public int getFoo() {return foo;} again.

생략...

  • @ToString
No need to start a debugger to see your fields: Just let lombok generate a toString for you!


With Lombok

01 import lombok.ToString;
02 
03 @ToString(exclude="id")
04 public class ToStringExample {
05   private static final int STATIC_VAR = 10;
06   private String name;
07   private Shape shape = new Square(510);
08   private String[] tags;
09   private int id;
10   
11   public String getName() {
12     return this.getName();
13   }
14   
15   @ToString(callSuper=true, includeFieldNames=true)
16   public static class Square extends Shape {
17     private final int width, height;
18     
19     public Square(int width, int height) {
20       this.width = width;
21       this.height = height;
22     }
23   }
24 }

Vanilla Java

01 import java.util.Arrays;
02 
03 public class ToStringExample {
04   private static final int STATIC_VAR = 10;
05   private String name;
06   private Shape shape = new Square(510);
07   private String[] tags;
08   private int id;
09   
10   public String getName() {
11     return this.getName();
12   }
13   
14   public static class Square extends Shape {
15     private final int width, height;
16     
17     public Square(int width, int height) {
18       this.width = width;
19       this.height = height;
20     }
21     
22     @Override public String toString() {
23       return "Square(super=" super.toString() ", width=" this.width + ", height=" this.height + ")";
24     }
25   }
26   
27   @Override public String toString() {
28     return "ToStringExample(" this.getName() ", " this.shape + ", " + Arrays.deepToString(this.tags")";
29   }
30 }











































  • @EqualsAndHashCode
Equality made easy: Generates hashCode and equals implementations from the fields of your object.

With Lombok

01 import lombok.EqualsAndHashCode;
02 
03 @EqualsAndHashCode(exclude={"id""shape"})
04 public class EqualsAndHashCodeExample {
05   private transient int transientVar = 10;
06   private String name;
07   private double score;
08   private Shape shape = new Square(510);
09   private String[] tags;
10   private int id;
11   
12   public String getName() {
13     return this.name;
14   }
15   
16   @EqualsAndHashCode(callSuper=true)
17   public static class Square extends Shape {
18     private final int width, height;
19     
20     public Square(int width, int height) {
21       this.width = width;
22       this.height = height;
23     }
24   }
25 }

Vanilla Java

01 import java.util.Arrays;
02 
03 public class EqualsAndHashCodeExample {
04   private transient int transientVar = 10;
05   private String name;
06   private double score;
07   private Shape shape = new Square(510);
08   private String[] tags;
09   private int id;
10   
11   public String getName() {
12     return this.name;
13   }
14   
15   @Override public boolean equals(Object o) {
16     if (o == thisreturn true;
17     if (!(instanceof EqualsAndHashCodeExample)) return false;
18     EqualsAndHashCodeExample other = (EqualsAndHashCodeExampleo;
19     if (!other.canEqual((Object)this)) return false;
20     if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
21     if (Double.compare(this.score, other.score!= 0return false;
22     if (!Arrays.deepEquals(this.tags, other.tags)) return false;
23     return true;
24   }
25   
26   @Override public int hashCode() {
27     final int PRIME = 59;
28     int result = 1;
29     final long temp1 = Double.doubleToLongBits(this.score);
30     result = (result*PRIME(this.name == null 43 this.name.hashCode());
31     result = (result*PRIME(int)(temp1 ^ (temp1 >>> 32));
32     result = (result*PRIME+ Arrays.deepHashCode(this.tags);
33     return result;
34   }
35   
36   protected boolean canEqual(Object other) {
37     return other instanceof EqualsAndHashCodeExample;
38   }
39   
40   public static class Square extends Shape {
41     private final int width, height;
42     
43     public Square(int width, int height) {
44       this.width = width;
45       this.height = height;
46     }
47     
48     @Override public boolean equals(Object o) {
49       if (o == thisreturn true;
50       if (!(instanceof Square)) return false;
51       Square other = (Squareo;
52       if (!other.canEqual((Object)this)) return false;
53       if (!super.equals(o)) return false;
54       if (this.width != other.widthreturn false;
55       if (this.height != other.heightreturn false;
56       return true;
57     }
58     
59     @Override public int hashCode() {
60       final int PRIME = 59;
61       int result = 1;
62       result = (result*PRIMEsuper.hashCode();
63       result = (result*PRIMEthis.width;
64       result = (result*PRIMEthis.height;
65       return result;
66     }
67     
68     protected boolean canEqual(Object other) {
69       return other instanceof Square;
70     }
71   }
72 }








































































  • @NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor
Constructors made to order: Generates constructors that take no arguments, one argument per final / non-null field, or one argument for every field.



With Lombok

01 import lombok.AccessLevel;
02 import lombok.RequiredArgsConstructor;
03 import lombok.AllArgsConstructor;
04 import lombok.NonNull;
05 
06 @RequiredArgsConstructor(staticName = "of")
07 @AllArgsConstructor(access = AccessLevel.PROTECTED)
08 public class ConstructorExample<T> {
09   private int x, y;
10   @NonNull private T description;
11   
12   @NoArgsConstructor
13   public static class NoArgsExample {
14     @NonNull private String field;
15   }
16 }

Vanilla Java

01 public class ConstructorExample<T> {
02   private int x, y;
03   @NonNull private T description;
04   
05   private ConstructorExample(T description) {
06     if (description == nullthrow new NullPointerException("description");
07     this.description = description;
08   }
09   
10   public static <T> ConstructorExample<T> of(T description) {
11     return new ConstructorExample<T>(description);
12   }
13   
14   @java.beans.ConstructorProperties({"x""y""description"})
15   protected ConstructorExample(int x, int y, T description) {
16     if (description == nullthrow new NullPointerException("description");
17     this.x = x;
18     this.y = y;
19     this.description = description;
20   }
21   
22   public static class NoArgsExample {
23     @NonNull private String field;
24     
25     public NoArgsExample() {
26     }
27   }
28 }



































  • @Data
All together now: A shortcut for @ToString, @EqualsAndHashCode, @Getter on all fields, and @Setter on all non-final fields, and @RequiredArgsConstructor!



With Lombok

01 import lombok.AccessLevel;
02 import lombok.Setter;
03 import lombok.Data;
04 import lombok.ToString;
05 
06 @Data public class DataExample {
07   private final String name;
08   @Setter(AccessLevel.PACKAGEprivate int age;
09   private double score;
10   private String[] tags;
11   
12   @ToString(includeFieldNames=true)
13   @Data(staticConstructor="of")
14   public static class Exercise<T> {
15     private final String name;
16     private final T value;
17   }
18 }

Vanilla Java

001 import java.util.Arrays;
002 
003 public class DataExample {
004   private final String name;
005   private int age;
006   private double score;
007   private String[] tags;
008   
009   public DataExample(String name) {
010     this.name = name;
011   }
012   
013   public String getName() {
014     return this.name;
015   }
016   
017   void setAge(int age) {
018     this.age = age;
019   }
020   
021   public int getAge() {
022     return this.age;
023   }
024   
025   public void setScore(double score) {
026     this.score = score;
027   }
028   
029   public double getScore() {
030     return this.score;
031   }
032   
033   public String[] getTags() {
034     return this.tags;
035   }
036   
037   public void setTags(String[] tags) {
038     this.tags = tags;
039   }
040   
041   @Override public String toString() {
042     return "DataExample(" this.getName() ", " this.getAge() ", " this.getScore() ", " + Arrays.deepToString(this.getTags()) ")";
043   }
044   
045   protected boolean canEqual(Object other) {
046     return other instanceof DataExample;
047   }
048   
049   @Override public boolean equals(Object o) {
050     if (o == thisreturn true;
051     if (!(instanceof DataExample)) return false;
052     DataExample other = (DataExampleo;
053     if (!other.canEqual((Object)this)) return false;
054     if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
055     if (this.getAge() != other.getAge()) return false;
056     if (Double.compare(this.getScore(), other.getScore()) != 0return false;
057     if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false;
058     return true;
059   }
060   
061   @Override public int hashCode() {
062     final int PRIME = 59;
063     int result = 1;
064     final long temp1 = Double.doubleToLongBits(this.getScore());
065     result = (result*PRIME(this.getName() == null 43 this.getName().hashCode());
066     result = (result*PRIMEthis.getAge();
067     result = (result*PRIME(int)(temp1 ^ (temp1 >>> 32));
068     result = (result*PRIME+ Arrays.deepHashCode(this.getTags());
069     return result;
070   }
071   
072   public static class Exercise<T> {
073     private final String name;
074     private final T value;
075     
076     private Exercise(String name, T value) {
077       this.name = name;
078       this.value = value;
079     }
080     
081     public static <T> Exercise<T> of(String name, T value) {
082       return new Exercise<T>(name, value);
083     }
084     
085     public String getName() {
086       return this.name;
087     }
088     
089     public T getValue() {
090       return this.value;
091     }
092     
093     @Override public String toString() {
094       return "Exercise(name=" this.getName() ", value=" this.getValue() ")";
095     }
096     
097     protected boolean canEqual(Object other) {
098       return other instanceof Exercise;
099     }
100     
101     @Override public boolean equals(Object o) {
102       if (o == thisreturn true;
103       if (!(instanceof Exercise)) return false;
104       Exercise<?> other = (Exercise<?>o;
105       if (!other.canEqual((Object)this)) return false;
106       if (this.getName() == null ? other.getValue() != null : !this.getName().equals(other.getName())) return false;
107       if (this.getValue() == null ? other.getValue() != null : !this.getValue().equals(other.getValue())) return false;
108       return true;
109     }
110     
111     @Override public int hashCode() {
112       final int PRIME = 59;
113       int result = 1;
114       result = (result*PRIME(this.getName() == null 43 this.getName().hashCode());
115       result = (result*PRIME(this.getValue() == null 43 this.getValue().hashCode());
116       return result;
117     }
118   }
119 }



































































































  • @Value
Immutable classes made very easy.

With Lombok

01 import lombok.AccessLevel;
02 import lombok.experimental.NonFinal;
03 import lombok.experimental.Value;
04 import lombok.experimental.Wither;
05 import lombok.ToString;
06 
07 @Value public class ValueExample {
08   String name;
09   @Wither(AccessLevel.PACKAGE@NonFinal int age;
10   double score;
11   protected String[] tags;
12   
13   @ToString(includeFieldNames=true)
14   @Value(staticConstructor="of")
15   public static class Exercise<T> {
16     String name;
17     T value;
18   }
19 }

Vanilla Java

001 import java.util.Arrays;
002 
003 public final class ValueExample {
004   private final String name;
005   private int age;
006   private final double score;
007   protected final String[] tags;
008   
009   @java.beans.ConstructorProperties({"name""age""score""tags"})
010   public ValueExample(String name, int age, double score, String[] tags) {
011     this.name = name;
012     this.age = age;
013     this.score = score;
014     this.tags = tags;
015   }
016   
017   public String getName() {
018     return this.name;
019   }
020   
021   public int getAge() {
022     return this.age;
023   }
024   
025   public double getScore() {
026     return this.score;
027   }
028   
029   public String[] getTags() {
030     return this.tags;
031   }
032   
033   @java.lang.Override
034   public boolean equals(Object o) {
035     if (o == thisreturn true;
036     if (!(instanceof ValueExample)) return false;
037     final ValueExample other = (ValueExample)o;
038     final Object this$name = this.getName();
039     final Object other$name = other.getName();
040     if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
041     if (this.getAge() != other.getAge()) return false;
042     if (Double.compare(this.getScore(), other.getScore()) != 0return false;
043     if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false;
044     return true;
045   }
046   
047   @java.lang.Override
048   public int hashCode() {
049     final int PRIME = 59;
050     int result = 1;
051     final Object $name = this.getName();
052     result = result * PRIME + ($name == null 43 : $name.hashCode());
053     result = result * PRIME + this.getAge();
054     final long $score = Double.doubleToLongBits(this.getScore());
055     result = result * PRIME + (int)($score >>> 32 ^ $score);
056     result = result * PRIME + Arrays.deepHashCode(this.getTags());
057     return result;
058   }
059   
060   @java.lang.Override
061   public String toString() {
062     return "ValueExample(name=" + getName() ", age=" + getAge() ", score=" + getScore() ", tags=" + Arrays.deepToString(getTags()) ")";
063   }
064   
065   ValueExample withAge(int age) {
066     return this.age == age ? this new ValueExample(name, age, score, tags);
067   }
068   
069   public static final class Exercise<T> {
070     private final String name;
071     private final T value;
072     
073     private Exercise(String name, T value) {
074       this.name = name;
075       this.value = value;
076     }
077     
078     public static <T> Exercise<T> of(String name, T value) {
079       return new Exercise<T>(name, value);
080     }
081     
082     public String getName() {
083       return this.name;
084     }
085     
086     public T getValue() {
087       return this.value;
088     }
089     
090     @java.lang.Override
091     public boolean equals(Object o) {
092       if (o == thisreturn true;
093       if (!(instanceof ValueExample.Exercise)) return false;
094       final Exercise<?> other = (Exercise<?>)o;
095       final Object this$name = this.getName();
096       final Object other$name = other.getName();
097       if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
098       final Object this$value = this.getValue();
099       final Object other$value = other.getValue();
100       if (this$value == null ? other$value != null : !this$value.equals(other$value)) return false;
101       return true;
102     }
103     
104     @java.lang.Override
105     public int hashCode() {
106       final int PRIME = 59;
107       int result = 1;
108       final Object $name = this.getName();
109       result = result * PRIME + ($name == null 43 : $name.hashCode());
110       final Object $value = this.getValue();
111       result = result * PRIME + ($value == null 43 : $value.hashCode());
112       return result;
113     }
114     
115     @java.lang.Override
116     public String toString() {
117       return "ValueExample.Exercise(name=" + getName() ", value=" + getValue() ")";
118     }
119   }
120 }



































































































  • @Builder
... and Bob's your uncle: No-hassle fancy-pants APIs for object creation!

With Lombok

01 import lombok.Builder;
02 import lombok.Singular;
03 import java.util.Set;
04 
05 @Builder
06 public class BuilderExample {
07   private String name;
08   private int age;
09   @Singular private Set<String> occupations;
10 }

Vanilla Java

01 import java.util.Set;
02 
03 public class BuilderExample {
04   private String name;
05   private int age;
06   private Set<String> occupations;
07   
08   BuilderExample(String name, int age, Set<String> occupations) {
09     this.name = name;
10     this.age = age;
11     this.occupations = occupations;
12   }
13   
14   public static BuilderExampleBuilder builder() {
15     return new BuilderExampleBuilder();
16   }
17   
18   public static class BuilderExampleBuilder {
19     private String name;
20     private int age;
21     private java.util.ArrayList<String> occupations;
22     
23     BuilderExampleBuilder() {
24     }
25     
26     public BuilderExampleBuilder name(String name) {
27       this.name = name;
28       return this;
29     }
30     
31     public BuilderExampleBuilder age(int age) {
32       this.age = age;
33       return this;
34     }
35     
36     public BuilderExampleBuilder occupation(String occupation) {
37       if (this.occupations == null) {
38         this.occupations = new java.util.ArrayList<String>();
39       }
40       
41       this.occupations.add(occupation);
42       return this;
43     }
44     
45     public BuilderExampleBuilder occupations(Collection<? extends String> occupations) {
46       if (this.occupations == null) {
47         this.occupations = new java.util.ArrayList<String>();
48       }
49 
50       this.occupations.addAll(occupations);
51       return this;
52     }
53     
54     public BuilderExampleBuilder clearOccupations() {
55       if (this.occupations != null) {
56         this.occupations.clear();
57       }
58       
59       return this;
60     }
61 
62     public BuilderExample build() {
63       // complicated switch statement to produce a compact properly sized immutable set omitted.
64       // go to https://projectlombok.org/features/Singular-snippet.html to see it.
65       Set<String> occupations = ...;
66       return new BuilderExample(name, age, occupations);
67     }
68     
69     @java.lang.Override
70     public String toString() {
71       return "BuilderExample.BuilderExampleBuilder(name = " this.name + ", age = " this.age + ", occupations = " this.occupations + ")";
72     }
73   }
74 }































































  • @SneakyThrows
To boldly throw checked exceptions where no one has thrown them before!



  • @Synchronized
synchronized done right: Don't expose your locks.



  • @Getter(lazy=true)
Laziness is a virtue!



  • @Log
Captain's Log, stardate 24435.7: "What was that line again?"

With Lombok

01 import lombok.extern.java.Log;
02 import lombok.extern.slf4j.Slf4j;
03 
04 @Log
05 public class LogExample {
06   
07   public static void main(String... args) {
08     log.error("Something's wrong here");
09   }
10 }
11 
12 @Slf4j
13 public class LogExampleOther {
14   
15   public static void main(String... args) {
16     log.error("Something else is wrong here");
17   }
18 }
19 
20 @CommonsLog(topic="CounterLog")
21 public class LogExampleCategory {
22 
23   public static void main(String... args) {
24     log.error("Calling the 'CounterLog' with a message");
25   }
26 }

Vanilla Java

01 public class LogExample {
02   private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
03   
04   public static void main(String... args) {
05     log.error("Something's wrong here");
06   }
07 }
08 
09 public class LogExampleOther {
10   private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExampleOther.class);
11   
12   public static void main(String... args) {
13     log.error("Something else is wrong here");
14   }
15 }
16 
17 public class LogExampleCategory {
18   private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog("CounterLog");
19 
20   public static void main(String... args) {
21     log.error("Calling the 'CounterLog' with a message");
22   }
23 }


  • val
불변의 지역변수를 만들 수 있습니다. 현재 val은 필드에는 적용되지 않고 지역 변수에만 적용됩니다. final를 붙인 것과 비슷한 효과를 갖어옵니다. 또한 val은 타입을 유추하기 때문에 타입을 따로 쓰지 않고 지역변수를 선언할 수 있습니다.