redirect와 forward의 차이
* Forward : Web Container 차원에서 페이지 이동만 있다. 실제로 웹 브라우저는 다른 페이지로 이동했음을 알 수 없다.
브라우져상에서 URL은 기존의 요청된 URL과 동일한 정보를 노출한다.
동일한 웹 컨테이너에 있는 페이지로만 이동할 수 있다.
현재 실행중인 페이지와 forwad에 의해 호출될 페이지는 request와 response 객체를 공유한다.
* Redirect : Web Container는 Redirect 명령이 들어오면 웹 브라우저에게 다른 페이지로 이동하라고 명령을 내린다. 그러면 웹 브라우저는 URL을 지시된 주소로 바꾸고 그 주소로 이동한다. 다른 웹 컨테이너에있는 주소로 이동이 가능하다.
새로운 페이지에서는 request와 response객체가 새롭게 생성된다.
즉 새로운 페이지가 로딩된 것과 동일하다.
2016년 12월 27일 화요일
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 와 같다.
여러가지 방법이 있겠으나 제가 선호 하는 방식에 대한 설명입니다.
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 "";
}
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 {
}
라벨:
annotation,
java,
spring
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
- @NonNull
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
With Lombok
01 import lombok.Cleanup;
02 import java.io.*;
03
04 public class CleanupExample {
05 public static void main(String[] args) throws 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 == -1) break;
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[] args) throws 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 == -1) break;
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
생략...
- @ToString
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(5, 10);
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(5, 10);
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
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(5, 10);
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(5, 10);
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 == this) return true;
17 if (!(o instanceof EqualsAndHashCodeExample)) return false;
18 EqualsAndHashCodeExample other = (EqualsAndHashCodeExample) o;
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) != 0) return 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 == this) return true;
50 if (!(o instanceof Square)) return false;
51 Square other = (Square) o;
52 if (!other.canEqual((Object)this)) return false;
53 if (!super.equals(o)) return false;
54 if (this.width != other.width) return false;
55 if (this.height != other.height) return false;
56 return true;
57 }
58
59 @Override public int hashCode() {
60 final int PRIME = 59;
61 int result = 1;
62 result = (result*PRIME) + super.hashCode();
63 result = (result*PRIME) + this.width;
64 result = (result*PRIME) + this.height;
65 return result;
66 }
67
68 protected boolean canEqual(Object other) {
69 return other instanceof Square;
70 }
71 }
72 }
- @NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor
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 == null) throw 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 == null) throw 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
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.PACKAGE) private 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 == this) return true;
051 if (!(o instanceof DataExample)) return false;
052 DataExample other = (DataExample) o;
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()) != 0) return 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*PRIME) + this.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 == this) return true;
103 if (!(o 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
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 == this) return true;
036 if (!(o 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()) != 0) return 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 == this) return true;
093 if (!(o 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
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
- @Synchronized
- @Getter(lazy=true)
- @Log
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
피드 구독하기:
글 (Atom)