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은 타입을 유추하기 때문에 타입을 따로 쓰지 않고 지역변수를 선언할 수 있습니다.