Clean Code that Works.

정말 처음 봤던 내용.

=======================================================================================================================

Spring에서는 비동기 처리 및 스케쥴링 처리를 위해서 @Async, @Scheduled 를 지원하는데요.
아래처럼 간단한 설정으로 사용 하기도 합니다.

<task:annotation-driven />

위 설정으로 세팅하고 @Scheduled를 사용하는데, 사용하는 입장에서는 당연히 비동기로 동작할 것이라 생각합니다.
(특정 시간에 동작하도록 하거나, fixedDelpay를 사용할 때)

하지만! 아무 설정을 하지 않을 경우에는 실제 작업을 수행하는 bean이 ConcurrentTaskScheduler로 설정되어 concurrent 하게 수행됩니다!

해서 비동기로 수행하고 싶으면 아래 처럼 설정을 추가해주시면 됩니다.

<bean class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
    <property name="poolSize" value="10"/>
</bean>
// or
<task:scheduler id="scheduler" pool-size="10" />

그럼 비동기로 동작하는 것을 확인 할 수 있습니다.

참고로 디폴트로 ConcurrentTaskScheduler를 등록해 주는 부분은
ScheduledAnnotationBeanPostProcessor.onApplicationEvent 에서 맨 마지막에 this.registrar.afterPropertiesSet() 를 호출해주는데
여기 들어가 보면 taskScheduler 가 null 일때 ConcurrentTaskScheduler로 세팅해주는것을 확인할 수 있습니다.

public void afterPropertiesSet() {
        if (this.taskScheduler == null) {
            this.localExecutor = Executors.newSingleThreadScheduledExecutor();
            this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
        }
        if (this.triggerTasks != null) {
            for (Map.Entry<Runnable, Trigger> entry : this.triggerTasks.entrySet()) {
                this.scheduledFutures.add(this.taskScheduler.schedule(entry.getKey(), entry.getValue()));
            }
        }
        if (this.cronTasks != null) {
            for (Map.Entry<Runnable, String> entry : this.cronTasks.entrySet()) {
                this.scheduledFutures.add(this.taskScheduler.schedule(entry.getKey(), new CronTrigger(entry.getValue())));
            }
        }
        if (this.fixedRateTasks != null) {
            for (Map.Entry<Runnable, Long> entry : this.fixedRateTasks.entrySet()) {
                this.scheduledFutures.add(this.taskScheduler.scheduleAtFixedRate(entry.getKey(), entry.getValue()));
            }
        }
        if (this.fixedDelayTasks != null) {
            for (Map.Entry<Runnable, Long> entry : this.fixedDelayTasks.entrySet()) {
                this.scheduledFutures.add(this.taskScheduler.scheduleWithFixedDelay(entry.getKey(), entry.getValue()));
            }
        }
    }

감사합니다. :)

토비의 스프링3 9장에 나오는 애플리케이션 정보 아키텍처에 대해서 읽고 있다.
책에 있는 내용을 조금 정리해 보았다.

이전에 어렴풋이 알고 있던 내용에 대해서 확실하게 알 수 있었다.

여기서 몇몇 아키텍처에 대한 내용이 나오는데.
애플리케이션에서 서로 흘러다니는 정보를 어떤 식으로 다루는 방식에 대한 아키텍처이다.
간단하게 엔터프라이즈 애플리케이션에 존재하는 정보를 데이터로 다루는 경우(주로 맵) 오브젝트로 다루는 경우(도메인 오브젝트)를 기준으로 구분할 수 있다.

  • DB/SQL 중심의 로직 구현방식
  • 거대한 서비스 계층 방식
  • 빈약한 도메인 오브젝트 방식
  • 풍성한 도메인 오브젝트 방식
각각 방식에 대해서는 책에 자세히 설명이 되어 있다.

실무에서는 어떻게 구성이 되어있는지 이야기를 해 보면,
지금 내가 진행하고 있는 프로젝트가 DB/SQL 중심의 로직 구현방식(90%), 거대한 서비스 계층 방식(10%)을 사용한다.

기선이 형한테 프로젝트 구성을 보여주면서 어떻게 스프링을 쓰면서 이렇게 쓰지? 하면서 성토를 했던 적이 있는데,
이게 바로 위의 두가지 구현방식을 사용한 것이었다.

정보 흐름을 살펴보면 DB/SQL을 통해서 맵으로 데이터를 가져오고(거의 가공하지 않고) 바로 맵을 통해서 뷰에 보여준다.
거대한 서비스 계층 방식은 DB/SQL을 통해서 뷰에 보여줄 데이터를 보여주기 어려운 경우(리포팅 같은 경우에는 리포팅에 맞게 데이터를 재 가공 해줘야 한다)에 사용을 한다. 일단 데이터를 가져온 다음 서비스 계층에서 이를 가공한다.( 토스3에 나온 내용처럼 서비스계층이 거대해 지기 때문에 거대한 서비스 계층 방식이라고 한다)

이 개발 방식의 장점은 업무별로 독립적인 개발이 가능하므로 초기 개발 속도가 빠르고, 개발자 사이에 간섭 없이 독립적인 개발이 가능하다. 하지만 객체지향적인 설계가 없고(적용하기 힘들고), 개발자 개개인의 코딩습관이나 실력에 따라서 서비스 로직의 구현 방식이 달라진다.(유지보수가 힘들어진다.)
가장 큰 단점으로는 처음에는 개발하기 편하지만 중복이 많아지기 쉽고 장기적으로 코드를 관리하고 발전시키기 힘들다.
(토스 3)

정말 공감하는 내용이다.
지금 하고 있는 프로젝트가 화면 하나당 각각 컨트롤러, 서비스가 존재한다.(엄청난 수의 컨트롤러와 서비스들) 이를 어떻게 관리할지 난감하다. 결국엔 나중에 가면 이전 서비스들은 버리고 다시 재 개발 해야 한다.

빈약한 도메인 오브젝트 방식은 이전에 내가 스터디 하면서 개발을 하던 방식이었다.
도메인 오브젝트가 존재 하긴 하지만 거의 VO 로만(getter/setter)로 만 구성되어 있고, 도메인 오브젝트에 다른 비즈니스로직이 들어 가 있지 않다. 이 방식은 이전 방식보다 낳기는 하지만 비즈니스 로직이 서비스에 집중되어 있기 때문에 서비스의 크기가 커지고 서비스에 비즈니스 로직이 들어가 있기 때문에 이를 다른 서비스에서 사용하기 위해서는 사용하려는 서비스에 DI 해 주어야 한다.
사실 이 방식밖에 몰랐다.(스프링 샘플 소스도 이 방식이다)

하지만 더 낳은 방식인 풍성한 도메인 오브젝트 방식이 있다!!
사실 이 방식은 봄싹에서 봤다. 처음 봤을 땐, 응? 왜 도메인 오브젝트에 비즈니스 로직이 섞여 있지?
라는 느낌이었는데, 이번에 책 보면이 이 방식의 장점을 알 수 있었다.
이 방식은 도메인 오브젝트안에 로직을 담아 두면 이 로직을 서비스 계층의 메소드에 따로 만드는 것 보다 응집도가 높다.
빈약한 도메인 오브젝트 방식에 비해서 서비스 계층의 코드가 간결해 지고 비즈니스 로직 코드도 훨씬 이해하기 쉬어진다.

더 발전된 방식으로 도메인 계층 방식이 있는데
이 부분은 토스 3를 참조 하세요 @_@

토비의 스프링 3
이일민 저
예스24 | 애드온2

봄싹 서버가 PostgreSql을 사용하기 때문에 기존에 만들어 두었던 시큐리티를 좀 바꾸어야 했다.
테스트 돌리는 중간에 프로퍼티 참조를 잘 못해서 삽질을 완전 많이 했다. -_-;;;

프로퍼티 홀더에서 프로퍼티를 찾는 순서는 어떻게 되고, 동일 키 값을 가진 프로퍼티를 나중에 찾은 값이 덮어 씌우는 것인거 같은데, 이것 좀 확인해 봐야겠다.

아직 하이버네이트 공부를 제대로 하지 않아서, 도메인 클래스 만드는데도 약간 고생을..
시큐리티 문서에 있는 ACL 스키마를 가지고 도메인 클래스를 만드는데(쿼리가지고 테이블 생성하기 싫어서, 무엇보다 봄싹에선 도메인 클래스를 가지고 하이버네이트가 자동 생성하는 형식을 사용하고 있다.)

처음에는 그냥 자바 기본 데이터 타입으로 도메인 클래스를 생성하였는데, 그러다가 문제에 봉착 하였다.
identity가 생성이 안되는 것이었다.

You will have to set the classIdentityQuery and sidIdentityQuery properties of JdbcMutableAclService to the following values, respectively:

  • select currval(pg_get_serial_sequence('acl_class', 'id'))

  • select currval(pg_get_serial_sequence('acl_sid', 'id'))


PostgreSQL에서는 위같은 방법으로 시퀀스를 자동 생성한다.

왜 안될까. 하고 찾아 보다가 PGSQL 문서를 찾아 보았다.
pg_get_serial_sequence(table_name, column_name)
get name of the sequence that a serial or bigserial column uses
이런 내용이...
serial, bigserial 타입의 컬럼만 시퀀스를 생성해 준단다.
어우 치사해 -_-;;

아무튼 자바에는 저런 타입이 없다.
그럼 어떻게 해야할 까..

도메인 클래스의 @Column에 속성중에 데이터 타입을 명시해 줄 방법이 있었다.
하이버네이트 어노테이션 설명
columnDefinition (optional): override the sql DDL fragment for this particular column (non portable)
이 속성을 명시 해 주면 된다. 아래와 같이..

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @Column(columnDefinition="bigserial")
    private int id;

스프링 속성 설정은 아래와 같이 해주면 된다. ACL은 DB별로 속성을 타지 않게 하려고 전부 표준 SQL로 동작을 하는데, identity 가져 오는 것은 DB마다 다 다르므로 각각 DB 타입에 따라 명시해 주어야 한다. (기본은 Call Identity())


이제 시큐리티 설정도 어느정도 마무리 되어 가고,
9월 즈음에 스터디 게시판 설계 마무리 하고 테스트 작성하면서 ACL에 대해서 더 포스팅 하도록 해야겠다.


나왔다 @_@

http://www.springsource.org/node/2740

뭐가 바뀐거지...

그중에 눈이 가는거 두개가 있는데...
support for Hibernate Core 3.6, Hibernate Validator 4.1, EclipseLink 2.1, EHCache 2.2
요즘에 하이버네이트 좀 보고 있어서 하이버네이트 관련 부분과,

JSR-303 Pattern message resolvable through Spring MessageSource (despite special characters)
JSR-303 Validation 쪽 보면서 메시지들 처리 아마 클래스명.변수명 형식으로 한다고 하는걸 어디선가 본거 같은데..
맞을꺼야 아마도. -ㄴ-..




스프링 MVC ShowCase!!!

Spring2010. 7. 23. 18:45
스프링 팀 블로그에서 spring mvc 3 showcase라는 멋진 포스팅을 해줬다.
http://blog.springsource.com/2010/07/22/spring-mvc-3-showcase/

블로그에있는 샘플 svn 주로를 체크 아웃 받으면 샘플 프로젝트를 다운 받을 수 있다.
screencast 도 제공하고 다운 받아 보면 pdf로 문서도 있다.

어디 강의용으로 써도 될만한 퀄리티 @_@

좋구만.

시큐리티 ACL이 동작을 하면서 acl 관련 정보들을 insert한 후 이 데이터들에 대한 id 값들을 가져와야 하는데.
기본적으로 identity 하는 방식이 아래와 같이 사용하고 있다.

private String classIdentityQuery = "call identity()";
private String sidIdentityQuery = "call identity()";

하지만 내가 사용하고 있는 MYSQL에는 저런게 없기 때문에.. -ㅁ-.. 어디서 사용하는 거지.. postrege sql??
아무튼 저것들을 변경해 주어야 한다.

구글링 해서 http://forum.springsource.org/showthread.php?t=67461 이 스레드에서 해결책을 발견.

뭐 그냥 쿼리만 mysql로 변경해 주면 된다

그래서 바꾸면 아래와 같이 MYSQL에서 사용하는 방식으로 쿼리를 변경 해 주면 된다. ㅋ
http://blog.sqlauthority.com/2007/03/25/sql-server-identity-vs-scope_identity-vs-ident_current-retrieve-last-inserted-identity-of-record/
여기도 참고 하면 괜찮은 URL(스레드에 있는 url이다)
<bean id="aclService" class="org.springframework.security.acls.jdbc.JdbcMutableAclService">
        <constructor-arg ref="dataSource"/>
        <constructor-arg ref="lookupStrategy"/>
        <constructor-arg ref="aclCache"/>
        <property name="classIdentityQuery" value="SELECT @@IDENTITY"/>
       <property name="sidIdentityQuery" value="SELECT @@IDENTITY"/>
</bean>

이제 거의 다 진행 되간다 ~_~

JdbcMutableAclService 클래스에 acl과 관련된 쿼리들이 다 담겨 있고, 이것들은 위와 같은 방식으로 각각 DB환경에 맞는 쿼리로 수정하여 사용하면 된다.

2010.08.27일에 추가
PostgreSQL에서는 다른 방법으로 identity를 확인한다.

You will have to set the classIdentityQuery and sidIdentityQuery properties of JdbcMutableAclService to the following values, respectively:

  • select currval(pg_get_serial_sequence('acl_class', 'id'))

  • select currval(pg_get_serial_sequence('acl_sid', 'id'))




private PermissionFactory permissionFactory = new DefaultPermissionFactory();이전 포스트에서 보았던 블로그 샘플을 보면, 커스텀 ACL을 등록해서 사용 하는 것을  볼 수 있다.

public class ExtendedPermission extends BasePermission {

    private static final long serialVersionUID = 1L;
    public static final Permission ACCEPT = new ExtendedPermission( 1 << 5, 'a'); // 32
    public static final Permission WRITE_CREATE = new ExtendedPermission( 1 << 6, 'E'); // 64
   
    protected ExtendedPermission( int mask, char code) {
        super( mask, code);
    }
}

이렇게 원하는 권한을 만든 후에, 이것을 스프링에게 알려줘야 하는데..
2.0 버전에는 아래의 BasePermission.registerPermissionFor() 메서드를 호출해서 새로 등록한 퍼미션 클래스를 등록하면 됬었다.

/**
* Registers the public static permissions defined on this class. This is
* mandatory so that the static methods will operate correctly. (copied from
* super class)
*/
static {
registerPermissionsFor(ExtendedPermission.class);
}

하지만 3.0 버전으로 넘어 오면서 패키지 구조가 많이 바뀌고, 이로 인해 메서드들도 바뀌게 되었다.(햇갈려!!!)

하여, 위의 registerPermissionFor 는 사라지고(다른 클래스로 옴겨가고)
이를 해결 하기 위하여,
PermissionFactory를 구현한 Concrete Class 인 DefaultPermissionFactory의 registerPublicPermissions() 메서드가 있다.

바로 registerPublicPermissions() 메서드가 내부적으로 registerPermission을 호출 해서 스프링에 등록이 되는 것이다.(이 표현이 맞나.. 아무튼 레지스트리에)
하여 이를 사용해서 등록을 해야 하는데, DefaultPermissionFactory의 생성자 중 하나인
/**
     * Registers the <tt>Permission</tt> fields from the supplied class.
     */
    public DefaultPermissionFactory(Class<? extends Permission> permissionClass) {
        registerPublicPermissions(permissionClass);
    }

요것을 통해서 Permission 클래스를 상속 받은(ExtendedPermission) 클래스를 파라미터로 넘겨 주면 이를 등록하게 된다.

이렇게 해서 ExtenedPermission을 등록 할 수 있는데, 그럼 과연 DefaultPermissionFactory는 어디서 등록을 해야 하는가.
이거 찾느라 시간을 너무 허비..덕분에 acl에 대해 좀 더 알 수 있었다.

바로 BasicLookupStrategy에 permissionFactory에다가 DefaultPermissionFactory를 등록 하면 된다.
위 클래스의 소스를 보면 알겠지만 기본적으로
private PermissionFactory permissionFactory = new DefaultPermissionFactory();
이렇게 해서 사용 하고 있다. 이걸 new DefaultPermissionFactory(ExtendedPermissionFactory.class); 로 바꿔주어야 하는데,
XML 설정 파일을 통해서 변경 할 수 있다.

시큐리티 설정을 위한 XML 설정 역시 머리가 아팠는데... 영어 문서의 압박과 구버전 샘플의 압박.
요리 저리 디버깅 해가면서 조금은 더 알게 되었다.

대충 구조를 살펴 보면 아래와 같은 구조를 가지게 된다.

XML 설정 구조


그럼 lookupStrategy를 어디에다가 등록을 하는가 하면
aclService의 구현클래스의 변수인 lookupStragegy에다가 등록을 해 주면 된다.

 <bean id="lookupStrategy" class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
        <constructor-arg ref="dataSource"/>
        <constructor-arg ref="aclCache"/>
        <constructor-arg ref="aclAuthorizationStrategy"/>
        <constructor-arg>
            <bean class="org.springframework.security.acls.domain.ConsoleAuditLogger"/>
        </constructor-arg>
        <property name="permissionFactory">
            <bean class="org.springframework.security.acls.domain.DefaultPermissionFactory">
                <constructor-arg value="net.study.spring.test.security.ExtendedPermission"/>
            </bean>
        </property>
    </bean>

이렇게 등록을 하면 스프링이 올라갈때, 커스텀된 권한까지 가지고 올라가기 때문에 acl_entry 테이블의 mask에 등록된 커스텀 acl 값이 잘 올라가고, Mask '32' does not have a corresponding static Permission(커스텀 acl 등록 안될경우 발생하는 오류) 이런 오류는 표시되지 않게 된다.


스프링에서 테스트를 진행할 시 트랜잭션은 오토 롤백 되게 구성되어 있다.

이걸 몰라서 -,.-;; 왜 롤백되지 하고 잠시 삽질을..

spring one 동영상 파일중에서 캡춰


테스트에 대한 문서를 보면 테스트 문서 보기 확인 할 수 있다.

@TransactionConfiguration(defaultRollback=true)
기본적으로 디폴트 롤백이 true로 설정되어 있어서 이다.
이것을 false 로 바꾸던지, 테스트를 실행하는 메서드에
@Rollback(false)이렇게 주면 된다.


스프링에서는 다양한 방법으로 스케쥴링을 지원 한다.
Task Execution and scheduling

프로젝트 진행을 하다 보면 스케쥴링을 해야 하는 경우가 많은데(조직도 라던지, 트리거 형식으로 라던지)
아무튼 이런 경우에, 현재 작동중인 Job들을 확인 하고 이를 제어 할 수 있는 것을 해보고 싶어서 해 보았다.

어차피 스케쥴링이라는게 쓰레드이기 때문에 쓰레드를 조회 해서 제어할 수도 있지만, (모든 쓰레드가 표시되서 제어가 힘들다)
기선님이 알려주신 쓰레드 리스트 보는 방법
스프링에서 Quartz 가 지원 되기 때문에 이것을 사용해서 하기로 했다.

하여 결과를 보면, 일단 아래와 같이 구현을 하였다.


구조를 살펴보면
스케쥴러 > 트리거 그룹 > 트리거 > 잡 그룹 > 잡
이런 형식으로 되어 있었다.

일단 잡 그룹으로만 나누어서 스케쥴링을 한다고 생각하고 위와같이 구현 하였다.
각 잡 들 마다 정지, 시작 기능이 있고,
이를 총괄하는 스케쥴러에 대한 정지, 시작 기능이 있다.

트리거 그룹이나 트리거 들에 대한 제어도 각각 추가할 수 있다.(세부 구현 내용이 많아진다.)
사이트 환경에 맞추어 하면 될듯 싶다.(보통 잡 그룹을 여러개 두고, 잡으로 나누어서 하면 될것 같다.)


설정 파일에서 보듯이 jobScheduler를 가지고 하위 잡 정보들을 가져와서 이를 가지고 보여줄 데이터를 만들던가, 잡 들을 조작 하면 된다.



개인적으로 하면서 햇갈리고 애 먹엇던 내용을 정리 하는 곳.

1. SimpleFormController클래스에서 onSubmit메서드를 override 했는데 submiit을 POST로 날렸는데 onSubmit 메서드를 타지 않는다.
 -> onSubmit 파라미터 중에 BindException 이 java의 것이 아니라 스프링의 import org.springframework.validation.BindException을 사용 하도록 한다.

2. SimpleFormController에서 submit한구 결과 페이지가 표시 되지 않고,
Neither BindingResult nor plain target object for bean name 'commandName' available as request attribute
에러를 뿜어낼 때, formView와 successView의 이름을 다르게 설정 한다. 이 부부은 스펙을 좀 읽어봐야 하겠는데.. -_-;; 너무 귀찮고.. 영어는 어려와~!~!

3. java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?
이런 오류가 나올 경우.
왜 나왔냐 하면 .jsp 파일에 직접 접근 할 때 스프링 form 태그를 사용할때 발생 하였다.
이것저것 찾아보고, 샘플 파일들에서 설정을 봐보니.

전에는 servlet에서 *-servlet.xml 파일을 로딩하도록 하고 있었으나,
이렇게 하면 안되고 web.xml에서 이런 형식으로 사용을 해야 된다고 한다.

<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/applicationContext.xml
        </param-value>
</context-param>

<listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener
        </listener-class>
</listener>

4. 이클립스 디버깅 안될때.
JDK 버전이 6u14~15는 이클립스에서 디버깅이 잘 안된다.(웹 프로젝트의 경우)
이럴땐 그냥 6u16으로 변경 고고싱 -_-