Whiteship's Note

'Spring In Action'에 해당되는 글 38건

  1. 2007.12.16 스프링 인 액션 8장 발표자료
  2. 2007.12.04 Spring사용해서 POJO로 JMS하기 (ActiveMQ)
  3. 2007.11.30 RMI + Spring
  4. 2007.11.19 스프링 스터디 소스코드 공유 (2)
  5. 2007.11.18 SIA 6장 트랜잭션 관리 발표자료
  6. 2007.11.11 Spring Modules 프로젝트의 Caching 사용하기
  7. 2007.11.08 Spring + Ajax with DWR (Revolution) (10)
  8. 2007.11.08 Spring + Ajax with DWR (Coding)
  9. 2007.11.08 Spring + Ajax with DWR (1)
  10. 2007.11.03 Spring In Action 5장 발표자료
  11. 2007.10.26 Acegi로 웹 애플리케이션 보안하기 7
  12. 2007.10.26 Acegi로 웹 애플리케이션 보안하기 6
  13. 2007.10.26 Acegi로 웹 애플리케이션 보안하기 5 (2)
  14. 2007.10.26 Acegi로 웹 애플리케이션 보안하기 4
  15. 2007.10.26 Acegi로 웹 애플리케이션 보안하기 3
  16. 2007.10.26 Acegi로 웹 애플리케이션 보안하기 2
  17. 2007.10.26 Acegi로 웹 애플리케이션 보안하기 1
  18. 2007.10.22 Access Decision Manager
  19. 2007.10.19 Spring Security 설정 분류 및 커스터마이징 (5)
  20. 2007.10.18 Authentication Manager
  21. 2007.10.18 Spring In Action 1장 ~ 4장 소스 코드 (2)
  22. 2007.10.15 Spring이 제공하는 AutoProxyCreator
  23. 2007.10.06 Spring In Action 3장 발표 자료
  24. 2007.09.25 Spring 2.5에 추가되는 bean() joinpoint
  25. 2007.09.16 2장 발표 자료 및 소스 코드
  26. 2007.09.14 Basic bean wiring 테스트 코드
  27. 2007.09.13 왜 Spring MVC 컨트롤러에 AOP가 적용되지 않을까?
  28. 2007.09.12 Great power comes great responsibility(Autowiring)
  29. 2007.09.06 AOP 관련 질문 :: Before Advice의 객체를 Target 클래스로 넘겨주고 싶다.
  30. 2007.09.05 휴.. Spring AOP와 AspectJ 연동 성공 (6)

스프링 인 액션 8장 발표자료



'Spring In Action > 8. Remoting' 카테고리의 다른 글

스프링 인 액션 8장 발표자료  (0) 2007.12.16
RMI + Spring  (0) 2007.11.30
top

Write a comment.


Spring사용해서 POJO로 JMS하기 (ActiveMQ)



1. ActiveMQ 프로젝트를 다운로드 받습니다.(브로커를 실행해야 하기 때문에...)
2. Writer 작성하기
3. Reader 작성하기
4. bean 설정하기
    4-1. MessageListenerAdapter 로 Reader 등록하기.
    4-2. Writer 등록하기.
    4-3. ActiveMQConnectionFactory 등록하기.
    4-4. ActiveMQQueue 등록하기.
    4-5. Message Converter 등록하기.
    4-6. JMS Template 등록하기.
    4-7. Message Listener Container 등록하기.
5. 테스트.
    5-1. ActiveMQ 브로커 실행.
    5-2. 테스트 클래스 실행.

등록할 bean들이 조금 많지만, 다들 몇 줄 안 되고 필요한 이유를 알게 된다면 그리 어렵지 않습니다.

1. ActiveMQ 다운로드
http://activemq.apache.org/activemq-411-release.html

2. Writer 작성하기.
public class MessageWriter {

    private JmsTemplate jmsTemplate;

    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public void write(DTO dto){
        jmsTemplate.convertAndSend(dto);
    }
}

3. Reader 작성하기.
public class MessageReader {

    public void readMessage(DTO dto){
        DTO recievedDto = (DTO)dto;
        System.out.println(recievedDto.getName());
    }
}

Writer의 경우 스프링 API(JmsTemplate)에 종속성이 생겨서 사알짝 POJO라고 할 수 있지만, Reader의 경우는 완전한 POJO입니다.

4. bean 설정하기.

4-1. MessageListenerAdapter 로 Reader 등록하기.
    <bean id="messageReader"
        class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
        <property name="delegate">
            <bean class="MessageReader" />
        </property>
        <property name="defaultListenerMethod" value="readMessage" />
        <property name="messageConverter" ref="dtoConverter" />
    </bean>

4-2. Writer 등록하기.
    <bean id="messageWriter" class="MessageWriter">
        <property name="jmsTemplate" ref="jmsTemplate" />
    </bean>

4-3. ActiveMQConnectionFactory 등록하기.
    <bean id="connectionFactory"
        class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://localhost:61616" />
    </bean>

4-4. ActiveMQQueue 등록하기.
    <bean id="queue"
        class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg index="0" value="whitehsip.test.queue1" />
    </bean>

4-5. Message Converter 등록하기.
    <bean id="dtoConverter" class="DtoConverter" />

4-6. JMS Template 등록하기.
    <bean id="jmsTemplate"
        class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="connectionFactory" />
        <property name="defaultDestination" ref="queue" />
        <property name="messageConverter" ref="dtoConverter" />
    </bean>

4-7. Message Listener Container 등록하기.
    <bean
        class="org.springframework.jms.listener.SimpleMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory" />
        <property name="destination" ref="queue" />
        <property name="messageListener" ref="messageReader" />
    </bean>

5. 테스트.

5-1. ActiveMQ 브로커 실행.
다운받은 ActiveMQ의 bin 폴더의 activemq.bat 파일을 실행합니다.

5-2. 테스트 클래스 실행.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"/springContext.xml"})
public class QueueTest {

    @Autowired
    private MessageWriter messageWriter;

    @Test
    public void testActiveMQJMS() throws Exception {
        DTO sendingDto = new DTO();
        sendingDto.setName("기선");
        messageWriter.write(sendingDto);
    }

}

콘솔창에 "기선" 이라고 찍히면 됩니다.


'Spring In Action > 10. JMS' 카테고리의 다른 글

Spring사용해서 POJO로 JMS하기 (ActiveMQ)  (0) 2007.12.04
top

Write a comment.


RMI + Spring



Remoting 기능을 지원해줍니다. 스프링을 사용하면 매우 간단하게 RMI를 사용할 수 있습니다.

서비스를 제공하는 쪽

1. POJO로 서비스 인터페이스 구현체 개발
2. RmiServiceExporter 등록하기

인터페이스는 더이상 Remote 인터페이스를 확장하지 않아도 됩니다.

package chapter8.client;

public interface EchoService {

    // word를 세 번 반복한 문자열을 반환합니다.
    String say(String word);
}

구현체의 메소드들은 더이상 RemoteException을 던지지 않아도 됩니다.

package chapter8.server;

import chapter8.client.EchoService;

public class EchoServiceImpl implements EchoService {

    public String say(String word) {
        StringBuilder builder = new StringBuilder(word);
        for (int i = 0; i < 2; i++) {
            builder.append(word);
        }
        return builder.toString();
    }

}

스프링 설정 파일에 서비스 구현체와 Exporter를 설정해줍니다.

    <bean id="echoService" class="chapter8.server.EchoServiceImpl" />

    <bean class="org.springframework.remoting.rmi.RmiServiceExporter">
        <property name="service" ref="echoService" />
        <property name="serviceName" value="EchoService" />
        <property name="serviceInterface"
            value="chapter8.client.EchoService" />
    </bean>

RmiServiceExporter가 기본으로 로컬에 1099 포트에 RMI 레지스트리가 있는지 확인하고 있으면 서비스를 등록하고, 없으면 RMI 레지스트리를 새로 만들어 실행한 다음 서비스를 추가합니다. 이 모든일을 알아서 해주기 때문에 개발자는 할 일이 없습니다.(하고 싶다면, 레지스트리 위치 설정과 포트 설정을 할 수 있겠죠. 그 때는 registryHost 속성과 registryPort 속성에 원하는 값을 설정해 주면 됩니다.)

서비스를 사용하는 쪽

1. 마치 서비스가 로컬에 존재하는 듯이 코딩합니다.
2. 스프링 설정 파일에서 RmiProxyFactoryBean을 사용하여 원하는 서비스를 bean으로 설정합니다.

테스트 코드입니다.

package chapter8;

import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
import chapter8.client.Keesun;

public class KeesunTest extends AbstractDependencyInjectionSpringContextTests {

    @Override
    protected String[] getConfigLocations() {
        return new String[] { "chapter8/client/springContext.xml" };
    }

    private Keesun keesun;

    public void setKeesun(Keesun keesun) {
        this.keesun = keesun;
    }

    public void testDI() throws Exception {
        assertNotNull(keesun);
    }

    public void testEcho() throws Exception {
        String echoResult = keesun.yaahoo("Spring");
        assertEquals("SpringSpringSpring", echoResult);
    }
}

Keesun 이란 클래스가 EchoService를 사용하고 있습니다.

package chapter8.client;

public class Keesun {

    private EchoService echoService;

    public void setEchoService(EchoService echoService) {
        this.echoService = echoService;
    }

    public String yaahoo(String word){
        return echoService.say(word);
    }
}

마지막으로 스프링 설정 파일에 Keesun과 EchoService를 bean으로 등록해 줍니다.

    <bean id="keesun" class="chapter8.client.Keesun" />

    <bean id="echoService"
        class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
        <property name="serviceUrl"
            value="rmi://localhost/EchoService" />
        <property name="serviceInterface"
            value="chapter8.client.EchoService" />
    </bean>

확인하기

1. Server 실행하기
2. Test 코드(Client) 실행하기.

레지스트리 서버를 동작시키려면 서비스를 제공하는 측 스프링 설정 파일을 읽어들이면서 Exporter bean을 생성하면되겠죠.

package chapter8;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class EchoServiceServer {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("chapter8/server/springContext.xml");
    }
}

다음은 위에서 작성했던 KeesunTest를 실행해 줍니다.

사용자 삽입 이미지


'Spring In Action > 8. Remoting' 카테고리의 다른 글

스프링 인 액션 8장 발표자료  (0) 2007.12.16
RMI + Spring  (0) 2007.11.30
top

TAG rmi, Spring

Write a comment.


스프링 스터디 소스코드 공유

Spring In Action : 2007.11.19 11:35


스프링 스터디에서 사용할 소스코드를 구글코드로 공유합니다. 한 동안 프로젝트의 크기가 10메가를 넘지 않아서 그냥 압축파일로 올려드렸었는데요, 이제는 거의 20메가가 다 되가기 때문에 티스토리에 올리려면 분할압축을 해야합니다. 그게 너무 귀찮아서 그냥 svn에 올리도록 하겠습니다.

이제부터는 스프링 스터디 하루나 이틀전에 체크아웃 받으셔서 발표 때 사용할 소스코드도 미리 참조하고 오실 수 있습니다.

http://whiteship.googlecode.com/svn/

이 주소를 SVN Repositories 뷰에서 추가해 주시면 됩니다. 이클립스에서 subversive를 사용하여 프로젝트를 체크아웃 받는 방법은 다음 글을 참조하시면 됩니다.

2007/07/02 - [Good Tools] - Subversive 사용하기

'Spring In Action' 카테고리의 다른 글

스프링 스터디 소스코드 공유  (2) 2007.11.19
top

  1. Favicon of http://gerions.egloos.com BlogIcon 윤걸 2007.11.21 09:30 PERM. MOD/DEL REPLY

    http://whiteship.googlecode.com/svn/trunk/ <== 요기로 지정하는게 편할 듯...

    trunk 추가요~

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2007.11.21 10:14 신고 PERM MOD/DEL

    넹. 트렁크밖에 안 쓰기 때문에 그래도 상관없죠.ㅋㅋ

Write a comment.


SIA 6장 트랜잭션 관리 발표자료




오늘 오후 1시에 발표합니다.

'Spring In Action > 6. Managing Transactions' 카테고리의 다른 글

SIA 6장 트랜잭션 관리 발표자료  (0) 2007.11.18
top

Write a comment.


Spring Modules 프로젝트의 Caching 사용하기



XML과 애노테이션을 사용하여 간단하게 캐시를 사용할 수 있습니다.

물론 나름대로 캐시기능을 구현하셔도 좋겠지만, 이미 다양한 캐시 관련 프레임워크들이 존재하고, 그 것들을 편하게 사용할 수 있도록 스프링에서 제공해주는 기능들을 사용하는 쪽이 더 빠르고, 쉽게 캐시를 접할 수 있는 방법이라 생각됩니다. 그 후에, 직접 캐시 기능을 커스터마이징 한다던가, 스프링 소스 코드를 통해 어떻게 캐시 프레임워크를 쉽게 사용하게 만들어 줬는지 살펴보는 것도 좋은 공부가 될 수 있으리라 생각합니다.

1. 캐시 적용할 대상 물색
2. 캐시 프레임워크 선택
3. XML 또는 애노테이션으로 캐시 설정.

순서는 크게 상관이 없지만, 저는 위의 순서대로 작업을 하겠습니다.

1. 캐시 적용할 대상 물색
사용자 삽입 이미지
위와 같은 클래스가 있을 때 getAll()과 같은 메소드가 캐시를 사용할 유력한 후보가 될 수 있습니다. 모든 회원들에 대한 정보를 가져올 때 마다 DB를 다녀와야만 하는 번거로움이 있지만, 이 정보는 자주 바뀌는 정보도 아니기 때문에 캐시 적용에 매우 유력한 후보가 됩니다.

그리고 캐시를 비워야 하는 시점도 역시 생각을 해야하는데, 위에서 save와 같이 캐시 해둔 정보에 변경이 가해질 때 캐시를 비우도록 하면, 다음에 캐시에 들어있는 정보를 가져가는 녀석이 새로운 정보를 가져갈 수 있도록 할 수 있기 때문에, 이 역시 캐시를 비우기에 적절한 메소드로 보여집니다.

2. 캐시 프레임워크 선택

저는 캐시를 써본적이 거의 없기 때문에 어떠한 프레임워크가 좋은지 모르겠습니다. 다만 Acegi 발표를 할 때 잠깐 보적이 있었던, EHCache를 사용하도록 하겠습니다. 이 밖에도, GigaSpaces, JBoss Cache, JCS, OpenSymphony’s OSCache, Tangosol’s Coherence 과 같이 처음 들어보는 캐시 프레임워크들이 존재합니다.

3. 캐시 네임스페이스 등록(XML/애노테이션 공통)

XML을 사용하던, 애노테이션을 사용하던 어차피 최소한의 XML 코드를 필요로 합니다. 그 XML은 다름이 아니라, 위에서 선택한 캐시에 대한 설정을 담고 있는 별도의 XML을 나타내기 위한 <XXX:config> 엘리먼트를 등록하기 위함입니다.

위에서 EHCache를 선택했기때문에, 저는 다음과 같은 코드를 스프링 설정 파일 선언부에 추가해주었습니다.

xmlns:ehcache="http://www.springmodules.org/schema/ehcache"

http://www.springmodules.org/schema/ehcache http://www.springmodules.org/schema/cache/springmodules-ehcache.xsd

4. 캐시 설정파일 작성 및 알려주기.

캐시 자체에 대한 설정 파일을 작성하고, 그 위치를 스프링 설정파일에서 설정해줍니다. 이 때 바로 위에서 등록한 네임스페이스를 사용합니다.

<ehcache>
    <defaultCache maxElementsInMemory="500" eternal="true"
        overflowToDisk="false" memoryStoreEvictionPolicy="LFU" />
    <cache name="memberCache" maxElementsInMemory="500" eternal="true"
        overflowToDisk="false" memoryStoreEvictionPolicy="LFU" />
</ehcache>

이 것은 캐시 자체에 대한 설정 파일입니다.
maxElementsInMemory는 필수속성으로써, 캐시 안에 저장할 수 있는 최대 엘리먼트의 갯수를 나타냅니다.
eternal은 해당 엘리먼트에 영속성을 부여할까 말까 설정합니다.
overflowToDisk는 만약에 저장해야할 엘리먼트의 갯수가 최대 갯수를 넘었을 때 저장 공간을 더 늘릴지 말지 설정합니다.
memoryStoreEvictionPolicy는 엘리먼트의 갯수가 저장할 수 있는 최대 갯수에 도달했을 때 어떤 것을 먼저 빼버릴 것인지 선정하는 방법을 설정합니다. 기본 값은 LRU(least resently used)로 사용한지 가장 오래 된 것 부터 뺴는 방법이있고, FIFO로 먼저 들어왔던 것 부터 빼는 방법 그리고 마지막으로 LFU(less frequently
used)제일 자주 안 사용하는 엘리먼트부터 빼는 방법이 있습니다.(캐시에 있는 엘리먼트마다 타임스탬프를 관리하는 것인가. 하는 생각이 듭니다.)

이렇게 작성한 캐시 설정 파일을ehcache.xml로 저장을 해두었다고 했을 때, 이 파일을 스프링에서 참조할 수 있도록 다음과 같이 스프링 설정 파일에서 설정합니다.

    <ehcache:config
        configLocation="classpath:chapter5/dao/hibernate/ehcache.xml" />

5. 본격적인 캐시 설정하기.

5-1. XML로 설정하기

    <ehcache:proxy id="memberDao" refId="memberDaoHibernateDaoSupport">
        <ehcache:caching methodName="getAll" cacheName="memberCache" />
        <ehcache:flushing methodName="save" cacheNames="memberCache" />
    </ehcache:proxy>

설정이 매우 직관적이기 때문에 쉽게 이해하실 수 있을 것입니다. 먼저 캐시가 AOP를 사용하기 때문에, 프록시객체를 설정하고 있는 모습을 볼 수 있습니다. 문제는 이 프록시 객체의 이름인데, 이 이름을 캐시를 적용할 대상의 이름으로 변경해 주고, 원래 대상의 이름을 target이라고 접미어를 붙여주어야 한다는 것입니다. 이러한 불편함은 Classic Spring AOP에서 프록시 팩토리 빈으로 Aspect 역할을 할 빈을 설정할 때 발생하던 문제점과 동일한 문제 입니다.

5-2. 애노테이션으로 설정하기

먼저, 캐시를 사용할 메소드위에 다음과 같은 애노테이션을 붙여줍니다.
@Cacheable(modelId="memberCacheModel")

캐시를 날려버릴 메소드 위에는 다음과 같은 애노테이션을 붙여줍니다.
@CacheFlush(modelId="memberFlushModel")

그리고 XML에서 다음과 같이 설정해줍니다.

    <ehcache:annotations>
        <ehcache:caching id="memberCacheModel" cacheName="memberCache" />
        <ehcache:flushing id="memberFlushMode" cacheNames="memberCache" />
    </ehcache:annotations>

XML 코드량이 거의 줄지 않았는데, 왜 굳이 이걸 써야 하는가? 하는 의문이 바로 드시죠? 그렇습니다. 저도 처음엔 그런 생각이 들었습니다. 오히려 모델ID라는 것 때문에 설정이 조금 더 햇갈리기까지 하니까요. 그렇지만, 이 녀석은 이름을 신경쓰지 않아도 된다는 장점이 있습니다.

그리고 이제와서 말씀드리지만, 애노테이션 말고 Jakarta Commons Attributes로 설정하실 수도 있다고 합니다. 이 방법은 저도 안해봐서 포스팅에 포함하진 못하겠네요. 따라서 Java 5 미만의 환경에서 이 기능을 사용하고 싶으신 분들도 말성이실 이유가 없습니다.
top

Write a comment.


Spring + Ajax with DWR (Revolution)



이전 글의 예제 코드는 DWR을 그대로 사용했을 뿐, Spring은 사용하지 않았습니다. 다시말하면, Spring Container로 부터 bean을 가져온 것이 아니라, new라는 생성기를 사용하여 자바스크립트를 생성했습니다.

스프링 컨테이너가 관리하는 bean으로 자바스크립트를 생성하는 방법은 두 가지가 있습니다.
1. spring 생성기 사용하기.
2. 스프링 설정 파일에서 dwr 네임스페이스 사용하기.(스프링 2.0 이상에서 사용가능)

1. spring 생성기 사용하기.

dwr.xml을 다음과 같이 수정합니다.
new 생성기 대신 spring 생성기 사용하도록 수정.
class 송성 대신 beanName이라는 속성에 bean의 이름을 설정합니다.

<dwr>
    <allow>
        <convert converter="bean" match="whiteship.domain.Member" />
        <create creator="spring" javascript="MemberService">
            <param name="beanName" value="memberService" />
        </create>
    </allow>
</dwr>

그리고 Spring 설정 파일에 memberService라는 bean을 등록해야겠죠. 이 전 예제에서는 등록하지 않았었습니다.

    <bean id="memberService" class="whiteship.service.MemberServiceImpl" />

마지막으로 DWR이 스프링의 설정 파일을 알 수 있도록 설정해야 하는데, 기본으로 ContextLoaderListener를 통해 읽어오려고 합니다. 따라서 web.xml에 다음과 같이 설정되어 있다면, dwr.xml에 별도로 설정해 주지 않아도 됩니다.

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/**Context.xml</param-value>
    </context-param>

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



2. 스프링 설정 파일에서 dwr 네임스페이스 사용하기.

이번에는 dwr.xml 파일 처럼 별도의 DWR 파일을 사용하지 않고, 스프링 설정 파일로만, DWR로 사용할 bean을 지정해 주는 방벙입니다.

먼저, web.xml에서 등록했던 DwrServlet을 DwrSpringServlet으로 변경합니다.(이제 dwr.xml은 삭제해도 됩니다.)
그럼 다음, 스프링 설정 파일에 dwr 네임스페이스를 추가합니다.
마지막으로, 자바스크립트로 노출시킬 bean 내부에 <dwr:remote> 엘리먼트를 사용하여 설정합니다. 그리고 기본 타입이 아닌 데이터를 변환하기 위해 사용했던 convertor를 등록합니다.

    <servlet>
        <servlet-name>dwr</servlet-name>
        <servlet-class>
            org.directwebremoting.spring.DwrSpringServlet
        </servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>true</param-value>
        </init-param>
    </servlet>

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:lang="http://www.springframework.org/schema/lang"
    xmlns:dwr="http://www.directwebremoting.org/schema/spring-dwr"
    xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
http://www.directwebremoting.org/schema/spring-dwr http://www.directwebremoting.org/schema/spring-dwr-2.0.xsd"
    default-autowire="byName">

    <bean id="memberService" class="whiteship.service.MemberServiceImpl">
        <dwr:remote javascript="MemberService" />
    </bean>

    <dwr:configuration>
        <dwr:convert type="bean" class="whiteship.domain.Member" />
    </dwr:configuration>

</beans>


클라이언트 코드는 수정할 것이 없습니다.

이렇게 하면, 스프링의 DI와 AOP를 적용한 자바 객체를 자바스크립트 형태로 변환해주고(DWR이), 이것을 클라이언트 자바스크립트에서 호출할 수 있게 됩니다.

2007/11/08 - [Spring In Action/16. Integrating with other web frameworks] - Spring + Ajax with DWR (Coding)
2007/11/08 - [Spring In Action/16. Integrating with other web frameworks] - Spring + Ajax with DWR


top

  1. 굴돌 2008.03.02 23:49 PERM. MOD/DEL REPLY

    생각보다 간단하군요!
    좋은자료 고맙습니다. :)
    링크 퍼갈께요~

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.03.03 12:20 PERM MOD/DEL

    넹. :)

  2. 초보 2008.03.11 18:02 PERM. MOD/DEL REPLY

    여기 예제를 보고 따라보았는데요..

    1번째 Spring+DWR 은 에러는 안나는데 URL 에서 /dwr 확인시 .js 가 생성이 안되어 정상적으로
    실행을 해볼수 없었구요.
    2, 3 번 소스 다운받아 해보았는데 제대로 안되네요.. tomcat 뜰때 에러 뜨네요..경로 문제인지.
    경로 문제면 1번할때도 문제가 생겨야 하는데..2,3번은 톰캣 구동시 오류가 나서 볼수조차 없구요.
    무엇이 문제인지 모르겠네요.

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.03.11 18:10 PERM MOD/DEL

    흠.. /dwr에 js는 런타임 시에 DwrServlet이 동적으로 생성해 줍니다. 만약.. 그게 생성이 안되었다면.. 무언가 설정이 잘 못 되었을텐데요.

    예제 코드를 실행해보시려면요. Run As Server 메뉴를 클릭하구요. Manually define a new server를 선택하신 다음에 돌려보세요.

    전 방금 확인해봤더니.. 잘 돌아가는데요.. 훔;;

  3. 초보 2008.03.11 19:05 PERM. MOD/DEL REPLY

    이거 어제부터 2틀재 돌려보는중인데요..방금 리플 달아주셨네요..
    원인을 알아보니..
    springContext.xml 에서

    <bean id="memberService"
    class="whiteship.service.MemberServiceImpl">
    <dwr:remote javascript="MemberService"/>
    </bean>

    이 부분만 들어가면 tomcat 로그에
    심각: Error listenerStart
    2008. 3. 11 오후 6:57:17 org.apache.catalina.core.StandardContext start
    심각: Context [/SpringAjax3] startup failed due to previous errors

    이렇게 찍히면서 안되네요..
    경로 문제일까요??
    참고로 저는 톰캣5.5 를 따로 두고 ant로 빌드로 톰캣에 war로 넣어줍니다.. 혹시 이거 때문에..

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2008.03.11 23:02 신고 PERM MOD/DEL

    에러로그 전부를 제 이메일로 보내주세요.

    저는 이클리스에서만 실행시켜봤지, WAR로 묶어서 배포해보진 않아서 어떤 상황인지 예측이 안 되네요.

    아마도 배포 문제인듯 싶은데요. 로그에 보니까 previous errors 때문에 톰캣을 못 띄웠다고 되어 있는데.. 에러 로그를 전부 보고 싶습니다. :)

  4. 초보 2008.03.12 08:56 PERM. MOD/DEL REPLY

    에러 내용은 제가 올렸던 저 내용이 전부입니다. 톰캣 구동시 카탈리라 로그에서 이 로그만 뿌려주고는
    해당 웹app가 뜨지 않습니다.

    에러내용

    2008. 3. 11 오후 7:11:38 org.apache.catalina.core.StandardContext start
    심각: Error listenerStart
    2008. 3. 11 오후 7:11:38 org.apache.catalina.core.StandardContext start
    심각: Context [/SpringAjax3] startup failed due to previous errors

    위와 같은 문제로 struts + Spring 연결 설정시에도 한번 났었는데...web.xml 에서 경로패스가
    조금 틀려서 그렇더군요..
    이번에도 그런듯싶어 경로를 이것저것 수정해보았는데...

    결론은

    <bean id="memberService"
    class="whiteship.service.MemberServiceImpl">
    <dwr:remote javascript="MemberService"/>
    </bean>

    web.xml 에서 이 부분만 만나면 에러를 떨어뜨립니다.

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.03.12 10:52 PERM MOD/DEL

    저는 잘 돌아가는데요;;

    의심스러워 하시는 부분에는 전~혀 문제가 없습니다. 그냥 일반적인 스프링 설정일 뿐입니다.

    그냥 이클립스에서 돌려보시죠; 이클립스 프로젝트를 그냥 압축한거니까 이클립스에선 잘 돌아갈겁니다. ^^;;

  5. 꿀냥 2011.08.25 20:37 PERM. MOD/DEL REPLY

    질문이 있습니다.

    DWR 관련하여 프로젝트를 만들고
    압축파일을 복사 했습니다..

    화면까지는 나오는데
    텍스트 입력창에 글을 쓰기만 하면

    session error라고 경고 창이 뜨네요..
    세션 설정은 안보이는거 같은데요...

    이상하네요;;

  6. 따뜻한호빵 2012.02.01 11:20 PERM. MOD/DEL REPLY

    꿀냥님. web.xml 에서 dwr servlet 설정에서

    <init-param>
    <param-name>crossDomainSessionSecurity</param-name>
    <param-value>false</param-value>
    </init-param>

    추가하시면 될 거 같네요.

Write a comment.


Spring + Ajax with DWR (Coding)



다음의 순서대로 코딩을 할 수 있습니다.(꼭 이 순서를 지켜야 하는 것은 아닙니다.)
0. POJO로 Ajax (자바스크립트로 노출시킬) 서비스 구현.
1. web.xml에 DWR Servlet 등록하기.
2. dwr.xml 작성하기.
3. 클라이언트 코드 작성하기.
    3-1. 자바스크립트 등록하기.
    3-2. 요청하기.
    3-3. 요청 처리하기.

0. POJO로 Ajax (자바스크립트로 노출시킬) 서비스 구현.
사용자 삽입 이미지
매우 단순하게 구현했으며, MemberService 인터페이스에서는 Member의 이름으로 회원을 검색하여 결과를 리스트 형태로 반환하는 메소드를 가지고 있습니다.

1. web.xml에 DWR Servlet 등록하기.

web.xml에 다음과 같은 코드를 추가합니다.
    <servlet>
        <servlet-name>dwr</servlet-name>
        <servlet-class>
            org.directwebremoting.servlet.DwrServlet
        </servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>true</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>dwr</servlet-name>
        <url-pattern>/dwr/*</url-pattern>
    </servlet-mapping>

/dwr/ 로 시작하는 모든 요청을 DwrServlet이 처리하도록 설정하였습니다.

2. dwr.xml 작성하기.

WEB-INF/ 폴더에 dwr.xml 파일을 다음과 같이 작성합니다.
<!DOCTYPE dwr PUBLIC
"-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN"
"http://www.getahead.ltd.uk/dwr/dwr10.dtd">

<dwr>
    <allow>
        <convert converter="bean" match="whiteship.domain.Member" />
        <create creator="new" javascript="MemberService">
            <param name="class"
                value="whiteship.service.MemberServiceImpl" />
        </create>
    </allow>
</dwr>

Member 타입으로 캐스팅 할 수 있도록, converter를 등록해줍니다.
new 생성기를 사용하여, MemberService라는 이름의 자바스크립트를 생성합니다. 이 자바스크립트는 MemberServiceImpl 자바 클래스를 가지고 작성합니다.

=================================================================================
이제 서버쪽에서 설정해야 할 내용은 끝입니다. 이것으로 클라이언트 쪽에서 자바 객체의 메소드를 자바스크립트에서 호출할 수 있습니다. 어떻게 호출하는지 살펴보겠습니다.

3. 클라이언트 코드 작성하기.

3-1. 자바스크립트 등록하기.

View에서 사용할 자바스크립트를 등록합니다.
<script type='text/javascript' src='dwr/engine.js'></script>
<script type='text/javascript' src='dwr/interface/MemberService.js'></script>
<script type='text/javascript' src='dwr/util.js'></script>

맨 아래 한 줄은 옵션입니다. 위의 두 줄은 필수이며, 위의 자바스크립트 파일들이 없다고 걱정하시지 않아도 됩니다. web.xml에서 설정한 dwr/ 경로를 통해 DwrServlet이 알아서 처리할 것입니다.

두 번째 자바스크립트 파일의 이름은 dwr.xml에서 자바 클래스를 자바스크립트로 변환할 때 사용하기로 한 이름으로 설정해주시면 됩니다.

3-2. 요청하기.

폼에서 다음과 같은 input 엘리먼트가 있고, 해당 엘리먼트에서 자바스크립트를 호출합니다.
<form name="memberForm">
    <input type="text" name="name" maxlength="10" onkeyup="inputChanged();" />
</form>

function inputChanged() {
    var name = document.memberForm.name.value;
    if(name.length > 1) {
        MemberService.get(name, updateTable);
    } else {
        DWRUtil.removeAllRows("memberTable");
    }
}


3-3. 요청 처리하기.

위의 자바스크립트에서 updateTable이 바로 요청의 결과를 처리할 자바스크립트 입니다. 다음과 같이 작성합니다.

function updateTable(results) {
    DWRUtil.removeAllRows("memberTable");
    DWRUtil.addRows("memberTable", results, cellFuncs);
}

var cellFuncs = [
    function(data) { return data.name; },
    function(data) { return data.email; }
];

요청의 결과를 가져와서 테이블을 채워줄 때 해당 데이터에서 어떤 필드로 테이블을 채워야 하는지 정의하기 위해 cellFuncs 라는 구조체(?)를 정의해 주었습니다.

참조 : Spring In Action 16장
top

Write a comment.


Spring + Ajax with DWR



DWR은 Direct Web Remoting의 약어로 자바 객체를 기반으로 자바스크립트 코드를 생성하여, 클라이언트 자바스크립트에서 (생성한 자바스크립트를 사용하여) 서버쪽 자바 객체의 메소드를 호출할 수 있도록 해주는 프레임워크입니다.

수많은 Ajax 프레임워크 중에서 DWR이 Spring와 밀접한 관계가 된 것은 DWR 자체에서 Spring을 지원하는 기능을 구현했기 때문입니다. 따라서 매우 간단하게 Spring와 연동하여 사용할 수 있습니다. 연동한다는 의미를 좀 더 구체적으로 서술하면, Spring Container가 관리하는 bean을 DWR을 사용하여 자바스크립트로 노출시킬 수 있다는 것입니다.

Spring MVC를 사용하든, Spring Web Flow를 사용하든 관계가 없습니다. 어차피 요청은 자바스크립트에서 할 것이고, 이 요청을 처리하는 녀석은 DWR Servlet이지, Spring의 Dispatcher Servlet이 아닙니다. 특정 요청들을 한 곳으로 집중한다는 측면에서 둘은 비슷하지만, View Resolving, Hadler Mapping 까지 관장하는 Spring의 Dispatcher Servlet과는 전혀 다른 일을 합니다. DWR Servlet에 등록되어 있는 자바 객체를 바탕으로 요청을 처리할 자바스크립 코드를 생성하고, 그것으로 처리한 결과를 다시 (클라이언트)웹브라우저에게 돌려주는 역할을 합니다.

사용자 삽입 이미지

클라이언트 자바스크립트에서 특정 요청을 호출하면, 해당 요청을 Dwr Servlet이 받아들이고, Dwr Servlet은 drm.xml에 설정된 정보를 바탕으로 해당 요청을 처리할 자바 객체의 자바스크립트(DWR이 알아서 생성합니다.)를 호출하고 그 결과를 다시 돌려주게 됩니다. 그럼 돌려받은 정보로 비동기식으로 화면에 보여주는 것은 클라이언트쪽에서 알아서 하겠죠.

위 그림에서 Spring이 낑겨들어가는 쪽은 바로 녹색 네모 둘을 연결한 빨간 선입니다. creator는 자바스크립트 생성기를 말하는데, 자바스크립트 코드로 생성할 자바 객체를 지정해 주는데, 이 객체를 Spring Container에 있는 bean을 가져와서 생성할 수 있도록 설정할 수 있습니다.

왜? 스프링 컨테이너가 관리하는 bean으로 설정하려고 할까요? 스프링이 제공하는 DI와 AOP와 같은 기능을 사용할 수 있기 때문입니다. 그런 기능들을 사용하지 못한다면, 자바스크립트로 노출하고 싶어하는 자바 객체 내부에 해당 객체가 필요로 하는 모든 것들이 들어있어야합니다.(new를 많이 사용해서 코딩했겠죠.) 스프링을 사용한다면, 그럴 필요가 없어집니다. 이 말은 더욱 유연하고 유지관리하기 편리한 객체를 자바스크립트로 노출 시킬 수 있다는 뜻입니다.

설명은 이만하고, 다음 글에서 코딩을 해보겠습니다.
top

  1. Favicon of http://blog.paran.com/devbada BlogIcon xsitherx 2011.06.15 16:15 PERM. MOD/DEL REPLY

    안녕하세요!

    DWR에 대해 검색하던 중 찾게 되었습니다.

    쉽게 잘 설명을 해주셨네요~ ^^;;

    다름이 아니라 이 글의 일부를 조금 퍼가도 될런지 해서~ 댓글 남깁니다 :D


    그럼 수고하세요! ^^

Write a comment.


Spring In Action 5장 발표자료



소스코드만 먼저 올려둡니다.


소스코드에 1장부터 5장까지 발표할 때 사용한 코드가 포함되어 있습니다.
티스토리에는 하나의 파일이 최대 10MB를 넘지 못하기 때문에 부득이하게 분할 압축을 했습니다.


top

Write a comment.


Acegi로 웹 애플리케이션 보안하기 7



지금까지 등록한 필터들을 나열 하면 다음과 같습니다.

1. httpSessionContextIntegrationFilter => 세션 문맥 통합 필터
2. logoutFilter => 로그 아웃 필터
3. authenticationProcessingFilter => 인증 처리 필터
4. exceptionTranslationFilter => 예외 처리 필터
5. filterInvocationInterceptor => 권한 처리 필터

FilterChainProxy의 filterInvocationDefinitionSource속성에 등록한 이 들의 순서는 매우 중요합니다.

일례로, exceptionTranslationFilter가 filterInvocationInterceptor보다 뒤에 있으면 어떤 일이 발생할까요? 예외 처리 필터의 존재 가치가 없어집니다. 예외 처리 필터가 예외를 확인하기 전에 권한 처리 필터에게 넘겨지기 때문에, 예외 처리 필터는 예외가 발생한지도 모른 상태이고, 웹 브라우저는 그냥 에러를 출력하게 됩니다.

또 다른 예로, 권한 처리가 인증 처리 필터보다 앞에 있으면 어떤 일이 발생할까요? 위의 상태에서, 1, 2, 4, 5, 3형태로 배열 했더니 웹 요청 처리가 무한루프에 빠지는 것을 볼 수 있었습니다.ㅎㅎ

그런 반면, 로그 아웃 필터의 위치는 어디에 두던지 별 영향을 미치지 않는 것을 확인할 수 있었습니다.

필터들의 순서를 가지고 여러 가지 실험을 해봤을 때, 가장 안전한 선택은 다음과 같습니다.

1. 세션 문맥 통합 필터는 가장 앞에 둔다.
2. 권한 처리 필터는 가장 마지막에 둔다.
3. 예외 처리 필터는 권한 처리 필터 바로 앞에 둔다.
4. 인증 처리 필터는 예외 처리 필터 앞에 둔다.
5. 기타 인증과 관련된 필터(리멤버미 필터, 익명 사용자 처리 필터)는 인증 처리 필터 바로 뒤에 연달아 둔다.

예를 들어 다음과 같은 순서대로 필터를 나열하면 안전 합니다.

httpSessionContextIntegrationFilter,
authenticationProcessingFilter,
anonymousProcessingFilter,
rememberMeProcessingFilter,
logoutFilter,
exceptionTranslationFilter,
filterInvocationInterceptor

top

Write a comment.


Acegi로 웹 애플리케이션 보안하기 6



6. 예외 다루는 필터 등록하기

사용자 삽입 이미지

org.acegisecurity.ui.ExceptionTranslationFilter 를 등록해 줍니다. 그리고 이 필터에 authenticationEntryPoint 속성과 accessDeniedHandleerImpl 속성을 설정해 줍니다. authenticationEntryPoint 는 인증 예외가 발생했을 때 인증을 요구하는 페이지로 이동하도록 설정했으며, accessDeniedHandleerImpl 는 해당 사용자가 권한이 없을 요청을 했을 때 보여질 페이지를 설정했습니다.

7. 로그아웃 필터 등록하기

사용자 삽입 이미지
이 필터는 생성자 인젝션을 사용했네요. 흠~ 독특합니다. 첫 번째 인자로는 로그아웃을 한 뒤 보여줄 URL을 설정하고, 두 번쨰 인자로는 로그아웃 핸들러를 등록해 주었습니다. 이 객체가 실제로 Session 객체에서 Security Context 객체를 제거하는 일을 담당할 것입니다.

위 두 개의 필터를 역시 FilterChainProxy에 등록해주면, 잘 동작하는 모습을 확인할 수 있습니다.


top

Write a comment.


Acegi로 웹 애플리케이션 보안하기 5



5. HttpSessionIntegrationFilter 사용하기

앞에서 인증과 보안에 관련된 필터과 bean들을 모두 등록했지만, 애플리케이션은 동작하지 않습니다. 그 이유는 보안 정보가 여러 요청들 간에 유지되지 않기 때문입니다. 이 문제를 해결하기 위해 보안 정보를 Session에서 관리하는 필터를 등록해 줍니다.

    <bean id="httpSessionContextIntegrationFilter"
        class="org.acegisecurity.context.HttpSessionContextIntegrationFilter" />

이 필터는 다른 객체를 사용하지 않고 있어서 설정이 매우 간단합니다. 이렇게 등록한 다음 역시 다른 필터들과 마찬가지로 FilterChainProxy에 등록해 줍니다.
사용자 삽입 이미지
이 필터를 제일 앞에 등록합니다. 그 이유는 이 녀석이 하는 일과 관련이 있습니다.

사용자 삽입 이미지
그림은 IBM 기사에서 가져왔습니다. 먼저 Filter Chain Proxy에서 SIF를 호출하고 이 필터에게 요청을 넘깁니다.(1) 그럼 SIF는 이 요청이 이전에 다뤘던 요청인지 아닌지 판단합니다.(2) 만약에 이전에 다뤘던 요청이면 일을 끝내고 다음 필터로 요청을 넘깁니다.(4) 처음 다루는 요청이면 플래그를 설정하고(이미 다룬 요청이라는 것을 기억하기 위해) 세션 객체가 있는지 그리고 그 안에 보안 문맥(Security Context)를 담고 있는지 확인합니다. 있으면, 해당 보안 객체를 Security Context Holder에 넣어 둡니다. 만약 세션 객체가 없으면 새로운 보안 문맥을 생성하고 그것을 Security Context Holder에 넣어 둡니다.(3) 그런 다음 요청을 다음 필터로 넘깁니다.(4)

다른 필터들이 이 보안 문맥을 수정할 수 있습니다.(5) 모든 필터의 처리가 완료된 다음 SIF가 제어권을 가지게 됩니다.(6) 필터 체인 특성 상 그렇게 되어있습니다.
사용자 삽입 이미지
만약 이 때 Security Context가 변경되어 있다면, 이 정보로 Session 객체있는 Security Context를 수정합니다.(7)

이제는 애플리케이션에 한 번 로그인 상태로 끝내더라도, 다음에 접속할 때 정보를 계속 유지하고 있게 됩니다. 즉, 서버를 꼈다 켜도 사용자 정보를 기억하고 있습니다. 신기하네요.

이제 사용자 정보를 유지하는 방법은 알아냈는데, 문제는 한 번 로그인 한 사용자의 정보를 Session에서 삭제하는 방법입니다. 계속해서 한 사용자로 로그인 되어 있으면 안 되겠죠. 로그아웃이 필요합니다.
그리고 사용자 정보가 없을 때 예외를 브라우저에 출력하지 말고 로그인 폼으로 이동하도록 하는 예외 처리가 필요합니다.

다음 글에서 예외 처리와 로그아웃 필터를 다루겠습니다.
top

  1. Favicon of http://www.truthiness.pe.kr BlogIcon ongs 2007.10.26 14:19 PERM. MOD/DEL REPLY

    으아 끝장나네요. ~OTL. 마치 Acegi Load map같군요. 많은 도움 됩니다..

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2007.10.26 16:22 신고 PERM MOD/DEL

    에구 시험 볼 때도 안 하던 벼락치기 중입니다.

Write a comment.


Acegi로 웹 애플리케이션 보안하기 4



4. 권한 확인하기.

앞에서 인증은 제대로 동작하지만, 인증 만으로는 보안을 할 수 없습니다. 사용자는 식별할 수 있지만, 해당 사용자가 요청에 대한 권한이 있는지는 별도로 확인을 해야 합니다. 이 일을 해줄 필터과 bean을 등록하겠습니다.

권한을 위해 Acegi에서 제공하는 필터는 FilterSecurityInterceptor입니다.
사용자 삽입 이미지
FilterSecurityInterceptor에서는 앞에서 설저한 AuthenticationManager를 사용하며, 그 외에도 AccessDecisionManager와 objectDefinitionSource라는 속성을 설정합니다.

AuthentiactionManager를 사용하여 사용자 정보를 가져오고, ObjectDefinitionSource에 있는 설정과, AccessDecisionManager를 사용하여, 해당 사용자가 요청에 대한 권한이 있는지 확인합니다.

어떤 요청에 대한 필요한 권한을 설정해 놓은 것이 바로 ObejctDefinitionSource입니다. 위 그림의 빨간색 박스에 해당하며, /protected/로 시작하는 모든 요청은 ROLE_HEAD_OF_ENGINEERING 의 role을 가진 사용자에게만 허가합니다. 그것을 제외한 모든 요청은 IS_AUTHENTICATED_ANONYMOUSLY 라는 role이 사용 하도록 했는데, 이 것은 anonymous Filter를 등록했을 때 익명 사용자에게 부여한 권한이라고 생각하시면 됩니다. 나중에 더 자세히 살펴보겠습니다.

이렇게 설정한 상태에서 FilterChainProxy에 여기서 등록한 필터의 이름을 추가해 줍니다.

사용자 삽입 이미지
자 이 상태에서 바라는 것은 다음과 같습니다.
1. /**로 접속 했을 때는 익명 사용자로써, 화면이 보여집니다. 따라서 첫 페이지에 접속할 수 있으며, 로그인 페이지에도 접속할 수가 있습니다.
2. 로그인을 하지 않은 상태에서 /protected/**로 접속했을 때 예외가 발생할 것입니다. 해당 요청에 대한 권한이 없기 때문이죠.
3. ROLE_HEAD_OF_ENGINEERING role을 가진 사용자로 로그인을 한 상태에서라면, /protected/**로 접근 했을 때 화면을 볼 수 있습니다.

발생하는 예외는 다음과 같습니다.
사용자 삽입 이미지
org.acegisecurity.AuthenticationCredentialsNotFoundException
필요한 사용자에 대한 정보가 Security Context 객체에 없기 때문입니다.

서버를 종료한 다음 다시 실행하면, 로그인 정보를 기억하지 못합니다. 사용자 정보를 계속해서 유지하려면 필터 하나만 등록하면 됩니다.
top

Write a comment.


Acegi로 웹 애플리케이션 보안하기 3



3. 인증하자.(보안된 정보에 접근할 때 로그인을 하도록...)

이제부터 이전 글에서 등록한 FilterChainPorxy에 필터를 하나씩 등록하면 됩니다. 그러려면 일단 필터를 bean으로 등록해야겠죠. 그리고 그 필터가 종속성을 가지는 객체들도 역시 bean으로 등록하면 됩니다.

인증을 하기 위해 Acegi에서 제공하고 있는 필터는 AuthenticationProcessingFilter입니다.
사용자 삽입 이미지
이 필터는 authenticationManager를 사용하여 인증 처리를 하고 있습니다. 동작하는 원리는 IBM에 올라온 기사를 참조하면 자세히 설명되어 있습니다.
사용자 삽입 이미지
간략히 설명을 하면, APF로 요청, 응답, 필터 체인 객체가 넘어옵니다.(1) 그 뒤에 APF에서 인증 토큰(username, password, 등 기타 요청 개체에 딸려온 정보)을 만들고(2), 이 것을 AuthenticationManager에 넘겨줍니다.(3)

그럼 이제, AuthenticationManager를 등록해야겠습니다.
사용자 삽입 이미지
authenticationManager에서는 하나 이상의 authenticationProvider를 사용하여, 인증 작업을 처리하고 있습니다. manager는 여러 provider를 사용하여 어떤 provider가 APF로 부터 받은 인증 토큰을 지원하는지 확인합니다.(4) 그러려면 인증 토큰을 provider에게 보내야겠죠.(5)

authentication Provider는 manager로 부터 받은 인증 토큰에서 username을 꺼낸 다음, userChache가 등록되어 있다면 이 정보를 user cache service라는 녀석에게 넘깁니다.(6) 하지만 위의 예제는 아직 userChache를 사용하고 있지 않기 때문에, (7), (8)에 대한 설명은 생략하겠습니다.

userChache가 없거나, userChache에서 username으로 확인(9) 결과가 null 이면, 다시 username을 이번에는 user detail service에게 넘깁니다. (10)userDetailService는 뒷단(위의 예제에서는 프로퍼티 파일을 사용했네요.)에서 사용자 정보를 찾습니다.(11) 해당 username에 대한 사용자 정보를 찾아서 가져오거나, 그런 사용자가 없다는 예외를 발생시킬 것 입니다.(12)

이렇게해서 찾은 정보와 인증 토큰의 정보를 가지고 비번 확인을 합니다. 일치하면, 해당 사용자 정보를 다시 Authentication Manager에게 넘겨주고, 일치하지 않으면 예외를 발생시킵니다.(13) Authentication Manager는 다시 이 정보를 APF에게 잔달합니다.(14) 그러면 APF는 이 정보를 Security Context에 저장하고(15) 다음 필터를 호출합니다.(16)

자 이제 거의 다 끝났습니다.

마지막으로 할 일은 APF를 "Acegi로 웹 애플리케이션 보안하기 2"에서 만들었던 FilterChainProxy에 등록하는 일입니다.
사용자 삽입 이미지
filterChainProxy의 filterInvocationDefinitionSource 속성에 위와 같이 설정해 줍니다. 모든 URL은 비교하기 전에 모두 소문자로 바꾸고, ANT 스타일의 표현식을 사용할 것이라는 설정과, 모든 url이 authenticationProcessingFilter를 거치도록 설정했습니다.

http://www.acegisecurity.org/acegi-security/apidocs/constant-values.html#org.acegisecurity.util.FilterChainProxy.TOKEN_NONE

위 링크에서 Acegi에서 사용할 수 있는 상수들을 참조할 수 있습니다.

자 이렇게 하면...
이제 인증(로그인)은 제대로 동작하지만, 여전히 인증을 하지 않고도 보안이 필요한 자원에 누구나(로그인을 하든 안하든 관계없이)접근할 수 있습니다.

top

Write a comment.


Acegi로 웹 애플리케이션 보안하기 2



2. acegi 컨텍스트 파일 작성하고, 스프링 컨텍스트 리스너 등록하기.

일반적인 스프링 컨텍스트 파일을 작성합니다. Acegi 관련 bean 설정을 따로 모아두는 것이 관리에 용이할 것입니다. 따라서 새로운 파일을 작성합니다.

사용자 삽입 이미지

그리고 web.xml에서 참조할 FilterChainProxy bean을 위에서 생성한 파일에 등록합니다.

    <bean id="filterChainProxy"
        class="org.acegisecurity.util.FilterChainProxy">
    </bean>

그리고 다시 web.xml로 돌아갑니다. 위에서 작성한 스프링 애플리케이션 컨텍스트 파일을 스프링이 사용할 수 있도록 설정해 주어야 합니다. 그렇게 하기 위해서 필요한 것이, ContextLoaderListener 이며, 이 녀석을 등록하면 <context-param> 엘리먼트로 등록되어 있는 정보를 바탕으로 스프링 애플리케이션 컨텍스트 파일을 읽어옵니다.

즉 web.xml 설정에 다음의 코드를 추가합니다.
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

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

이제 web.xml과는 영원히 안녕입니다. 지금까지 web.xml에 추가한 내용은 filter, filter-mapping, listener, context-param 이렇게 네 개입니다.


top

Write a comment.


Acegi로 웹 애플리케이션 보안하기 1



1. web.xml에 필터 등록하기
사용자 삽입 이미지
FilterToBeanProxy를 Acegi Filter Chain Proxy로 등록합니다. FilterToBeanProxy는 스프링 애플리케이션 컨텍스트에 위치한 bean을 참조할 수 있도록 만들어둔 클래스입니다. 이녀석이 없었다면 일일히 모든 Filter에서 다음과 같은 코드를 삽입하여, Spring에 종속적인 코드를 만들어 가면서까지, 스프링 애플리케이션 컨텍스트에 위치한 bean을 필터에서 사용했을 것입니다.

ApplicationContext ctx = WebApplicationContextUtils.
getWebApplicationContext(servletContext);
Bar bar = (Bar) ctx.getBean("bar");

FilterToBeanProxy는 실제로 자신이 할 일을 스프링 애플리케이션 컨텍스트에 위치한 bean에게 모두 위임을 하는데, 그 bean을 찾기 위한 파라미터가 바로 targetClass 입니다. 이 필터를 생성할 때 스프링 애플리케이션 컨텍스트에서 targetClass 파라미터의 값으로 설정된 타입의 bean을 찾습니다. 즉 위의 설정을 보면 org.acegisecurity.util.FilterChainProxy를 찾게 되는 것입니다. 그리고 찾은 FilterChainProxy에게 할 일을 위임하는 것입니다. 그래야 스프링의 장점 중 하나인 DI(Dependency Injection)를 백분 활용할 수 있을 것이며, web.xml이 복잡해지지 않을 것입니다.

targetClass 속성 말고 targetBean 속성을 사용해서 스프링 애플리케이션 컨텍스트에 위치한 bean의 이름을 사용하여 역할을 위임할 bean을 찾을 수도 있는데, 만약에 bean의 이름을 변경하면, web.xml에서도 targetBean 속성의 값을 변경해야하기 때문에, targetClass 속성의 사용을 권장하고 있습니다.

이렇게 filter 설정을 마치면.. 이 filter를 적용할 url을 설정해 줍니다. 그 부분이 filter-mapping이며, 설정 내용을 보시면 모든 url이 모두 이 필터를 거치도록 설정한 것을 보실 수 있습니다.

이제 해당 웹 애플리케이션으로 들어오는 모든 요청은 FilterToBeanProxy를 거치게 되며, 이 녀석은 FilterChainProxy에게 그 역할을 위임합니다. 결국 모든 요청은 스프링 애플리케이션 컨텍스트에 등록되어 있는 FilterChainProxy를 거치게 되는 것입니다.

그럼 다음 글에서 FilterChainProxy를 살펴보겠습니다.

참조 : SIA 2판 7장
top

Write a comment.


Access Decision Manager



AccessDecisionManager 인터페이스를 사용하여 보안이 필요한 자원에 접근하는 사용자가 접근 권한이 있는지 확인합니다. AccessDecisionManager는 여러개의 AccessDecisionVoter 에게 실제 권한 확인 역할을 위임하며, 그 결과를 가지고 자원의 공개 여부를 결정합니다.

Acegi는 AccessDecisionManager의 구현체로 여러 Voter들의 투표 결과를 가지고 판단하는 방법에 따라 세 개의 클래스를 제공합니다. AffirmativeBased(Voter들 중에 하나라도 허락 하면 공개), ConsensusBased(Voter들의 결과가 일치하면 공개), UnanimousBased(모든 Voter들이 허락하면 공개) 클래스입니다.

AccessDecisionVoter 인터페이스의 핵심 메소드는 vote() 메소드로, 실제 요청한 자원에 대한 접근의 허가 여부를 결정하는 메소드입니다. 이 메소드는 결과값으로 세 개의 값 중 하나를 반환합니다.

ACCESS_GRANTED : 보안이 필요한 자원에 접근을 허용.
ACCESS_DENIED : 보안이 필요한 자원에 접근을 거부.
ACCESS_ABSTAIN : 나는 모르겠다.

Acegi가 제공하는 AccessDecisionVoter 인터페이스 구현체 중에 하나인 RoleVoter는 vote() 메소드에 전달되는 인자 중에 하나인 ConfigAttribute를 바탕으로 해당 자원에 대한 접근 권한이 있는지 Role 정보를 통해서 판단합니다.

ConfigAttribute는 필터나 다른 곳에서 설정할 것이고, RoleVoter에서 최소로 설정할 것은 Role 정보를 가져오는 방법입니다. 기본으로 Role_ 접두어가 붙어있는 녀석들을 읽어오는데, 이 접두어를 변경할 수 있으며, 아예 입력하지 않을 수도 있습니다.(이 방법은 비추하고 있습니다.)[각주:1]

정리하자면, access decision manager를 등록할 때는 어떠한 방식으로 투표 결과를 처리할 것인지에 따라 AccessDecisionManager 구현체를 선택하고, RoleVoter에 Role을 나타낼 때 사용한 접두어를 설정해 줍니다.

사용자 삽입 이미지

bean 설정은 다음과 같이 간단한 편입니다.
사용자 삽입 이미지


  1. An empty role prefix means that the voter will vote for every ConfigAttribute. When there are different categories of ConfigAttributes used, this will not be optimal since the voter will be voting for attributes which do not represent roles. [본문으로]
top

Write a comment.


Spring Security 설정 분류 및 커스터마이징



현재 1.0.5까지 나온 Acegi(Spring Security)의 설정은 매~~~우 깁니다. 하지만 대부분이 비슷한 설정을 사용하실 것이기 때문에 필요한 필터에 대한 설정을 복사해서 붙여넣고 일부분만 변경하여 사용하시면 됩니다.

문제는 설정이 매~~~우 길기 때문에 어지럽다는 것입니다. 'XML 지옥'이 이런 것이구나 하는 것을 Spring 공부하고 나서 처음으로 느끼게 되었습니다. 사실 이전에는 이렇게 긴 Spring 설정을 해본적이 별로 없었거든요. 그냥 컨트롤러가 늘어나면 XML 설정이 좀 많아 지는 구나.. 하는 정도 였으니까요. 그런데 Acegi는 정말 파격적으로 XML 코드가 들어납니다. 따라서 분류가 필요하다고 생각되더군요.

사용자 삽입 이미지


제가 생각한 분류 방법은 이렇습니다.
1. 필터들만 모아 둔 XML => acegiFilter.xml
2. Authentication Manager 관련 XML -> authenticationManager.xml
3. Authorization Manager 관련 XML -> authorizationManager.xml

이렇게 나누면 필터들만 따로 정리해서 볼 수 있기 때문에 그나마 덜 어지럽습니다.

acegiFilter.xml에서 변경해야 할 부분은 먼저 자신이 사용할 필터들만 남기고 나머지는 제거하는 일입니다. 그 다음으로는 자신의 애플리케이션에 맞게 세부 설정을 수정하는 일입니다. httpSessionContextIntegrationFilter 이 녀석은 뭐 거의 변경할 일이 없어보이고, authenticationEntryPoint에는 loginFormUrl에 로그인 폼을 보여줄 URL을 적어 주시면 됩니다. authenticationProcessingFilter 이 녀석의 속성 중에서 authenticationFailureUrl 정도를 변경하시면 되겠습니다. 그리고 로그인 폼과 로그인 실패 화면 두 개를 만들어 줍니다. 이 때 로그인 폼의 형식은

<form method="POST" action="j_acegi_security_check">
    <b>Username: </b><input type="text" name="j_username"><br>
    <b>Password: </b><input type="password" name="j_password"><br>
    <input type="submit" value="Login">
</form>

이것을 그대로 사용하시면 됩니다.

그런 다음 authenticationManager.xml로 이동하여 authenticationManager에서 자신이 사용할 인증 방법들을 설정해 줍니다. 보통 daoAuthenticationProvider는 꼭 사용하실 것이라 생각이 됩니다. 따라서

    <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
        <property name="providers">
            <list>
                <ref bean="daoAuthenticationProvider" />
            </list>
        </property>
    </bean>

이 상태의 bean 설정을 그대로 사용하시면 됩니다. 그 다음 daoAuthenticationProvider 설정은 역시 그대~로 사용하시면 되며 실제로 변경해야 할 부분은 daoAuthenticationProvider 이녀석이 사용하는 userDetailsService 설정입니다.

userDetailsService로 보통은 JdbcDaoImpl을 사용할 것으로 생각됩니다. DB에 있는 사용자 정보를 가져올 때 사용할 수 있는 구현체로, 사용자 정보, 패스워드, enabled, Role 등을 가져오는 sql을 작성해 주셔야 합니다. DB 스키마를 Acegi가 기본으로 예상하고 있는 형태를 사용하고 있다면 그럴 필요가 없겠지만 말이죠.

그러면 authenticationManager.xml 설정은 끝이 압니다. 복잡해 보이지만 실제로 변경해야 할 부분은 userDetailsService밖에 없었습니다.

그럼 다시 acegiFilter.xml로 돌아가서 '인증'과 관련된 필터과 manager 설정은 끝났고, 이제 예외 처리를 하는 필터와 '권한'과 관련된 필터를 설정하면 최소한의 필터를 갖추게 됩니다.

exceptionTranslationFilter는 인증에러가 발생하면 authenticationEntryPoint 속성에 설정된(이미 위에서 설정했습니다.) 곳으로 다시 돌아갑니다. 다시 인증을 요구하는 거죠. 그리고 accessDeniedHandler속성에 설정된 bean을 사용하여 권한 에러가 발생하면 해당 페이지로 이동합니다. 따라서 accessDeniedHandler bean의 errorPage 속성에 권한 에러 발생시 이동할 URL을 적어주시면 됩니다.

마지막으로 권한 처리 필터는 filterSecurityInterceptor 이 녀석으로써, 여기서는 objectDefinitionSource 속성에 URL = 권한, 권한, 권한
URL = 권한
이런 형태로 권한이 필요한 URL과 해당 URL에 접근할 수 있는 권한을 설정해 주시면 됩니다. 물론 Ant 스타일의 정규식을 사용해서 여러 URL을 나타낼 수 있습니다.

그리고 진짜 마지막으로 filterSecurityInterceptor가 사용하는 authorizationManager를 authorizationContext.xml에서 설정해 줍니다. 이 부분은 권한을 나타내는 값이 ROLE_ 이라는 접두사로 시작한다면 변경할 것이 하나도 없이 다음의 설정을 그대로 사용하시면 됩니다.

    <!-- access decision관련 : 하나의 voter만이라도 있으면 인증 통과 -->
    <bean id="accessDecisionManager"
        class="org.acegisecurity.vote.AffirmativeBased">
        <property name="decisionVoters">
            <list>
                <ref bean="roleVoter" />
            </list>
        </property>
    </bean>

    <bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter">
        <property name="rolePrefix">
            <value>ROLE_</value>
        </property>
    </bean>

위의 코드는 전부 한수형이 작성한 secutiryContext.xml에 제가 약간의 수정을 가한 코드를 참고하였습니다.


top

  1. Favicon of https://springframework.tistory.com BlogIcon 영회 2007.10.19 11:16 신고 PERM. MOD/DEL REPLY

    이런 얘기는 결론을 앞쪽에서 그림으로 표현하면 좋을 것 같다.

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2007.10.19 11:19 PERM MOD/DEL

    아.. 네~ ㅎㅎ
    역시 글보다는 그림이 좋쵸.

    Favicon of https://springframework.tistory.com BlogIcon 영회 2007.10.21 23:30 신고 PERM MOD/DEL

    그림으로 그리니까 훨씬 좋구나

  2. Favicon of http://www.truthiness.pe.kr BlogIcon ongs 2007.10.19 13:05 PERM. MOD/DEL REPLY

    Acegi 준비하고 계시는군요. 열공~~!!^^

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2007.10.20 21:20 신고 PERM MOD/DEL

    넵~ 열공이요.

Write a comment.


Authentication Manager



사용자 삽입 이미지
Spring Security를 구성하는 다섯 가지 컴포넌트 중에 하나인 Authentication Manager는 인증을 담당하고 있습니다.

Authentication Manager는 인증을 시도(authenticate 메소드 호출)해서 성공하면, Authentication 객체를 반환하고, 실패하면, AuthenticationException 예외를 던지는 메소드를 가지고 있습니다.

이 인터페이스의 구현체로 Provider Manager를 제공하고 있으며, 이 클래스를 여러 인증 방식을 사용할 수 있는 관리자 역할을 하고 있습니다. Spring Security에서 제공하고 있는 인증 방식은 총 14가지 입니다. 그 중에서 가장 자주 사용할 것 같은 Provider는 DAO를 사용하여 인증 정보를 가져오는 DaoAuthenticationProvider입니다.

사용자 삽입 이미지
하늘색 선은 implements, 파란색 선은 extends, 빨간 선은 composition 입니다.
하늘색 네모는 인터페이스, 파란색 네모는 클래스입니다.

DaoAuthenticationProvider는 UserDetailsService 인터페이스를 통해서 사용자 정보(UserDetail)를 가져옵니다.

UserDetailsService 인터페이스를 직접 구현하여 설정해도 되지만, Spring Sercurity에서 제공하는 InMemoryDaoImpl 또는 JdbcDaoImpl을 사용할 수도 있습니다. DB에서 사용자 정보를 가져오려면, JdbcDaoImpl을 사용하는 것이 좋겠습니다.

JdbcDaoImpl에는 dataSource, usersByUsernameQuery, authoritiesByUsernameQuery 속성을 설정하여 DB로 부터 사용자 정보를 가져옵니다. usersByUsernameQuery에는 username, password, enabled를 SELECT하고, authoritiesByUsernameQuery에는 username과 authority를 SELECT 하는 쿼리를 설정해 줍니다.

사용자 삽입 이미지

좀 복잡해 보이지만, 철저하게 인터페이스 기반으로 작성되어 있으며, 역할을 분담해 놓은 모습이 매우 깔끔합니다.

bean 설정을 다음과 같이 할 수 있습니다.
사용자 삽입 이미지
설정 내용은 상당하지만, 자세히 살펴보시면 클레스 상속 구조와 일치하기 때문에, 쉽게 이해하실 수 있습니다.
사용자 삽입 이미지
특히 DaoAuthenticationProvider에는 passwordEncoder(비번 암호화)와 saltSource, userCache(사용자 정보 캐쉬 사용) 들을 설정할 수도 있습니다.

마지막으로 정리하자면, AuthenticationManager의 구현체로 ProviderManager가 있으며, 이 녀석은 여러 AuthenticationProvider를 사용할 수 있습니다.
그 중에서도 DaoAuthenticationProvider는 UserDetails를 사용하여 사용자 정보를 가져옵니다.
이 때 JdbcDaoImp 이라는 구현체를 사용하여 datasource를 사용하여 사용자 정보와 권한을 가져오는 SQL 쿼리를 설정해주면 끝.

참조 : Spring In Action 2nd
top

Write a comment.


Spring In Action 1장 ~ 4장 소스 코드





AJN에서 진행하고 있는 Spring In Action 스터디에서 사용할 소스코드 입니다. 발표 준비하면서 책에 나와있는 코드들을 돌려 본 코드 입니다.

1. @Configure 실패
2. AspectJ와의 연동 실패

두 개 모두 로드타임위빙 인가를 사용해야 하는데, 그 것을 어떻게 해야하는지 해메고 있어서 예제 코드 실습에 실패했습니다. 그 밖에 모든 코드는 책을 보시면서 따라해 보시거나 제가 만들어 둔 Test 코드를 실행하시면 모두 동작하는 것을 확인하실 수 있습니다.
top

  1. 양철근 2007.10.21 20:16 PERM. MOD/DEL REPLY

    감사합니다.

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2007.10.21 21:27 PERM MOD/DEL

    넹. 저야말로 매번 참석해 주셔서 감사합니다. :)
    다음 발표도 열심히 준비하겠습니다.

Write a comment.


Spring이 제공하는 AutoProxyCreator



classic Spring Aspect를 사용할 때 Target 하나 마다 일일히 ProxyFactoryBean을 만들어 주는 일은 완전 노가다 입니다. 이 노가다를 줄여주기 위해 Spring에서 제공하는 녀석들이 바로 AutoProxyCreator 입니다.

Spring이 제공하고 있는 AutoProxyCreator들을 살펴보겠습니다.
사용자 삽입 이미지
위와 같은 상속 구조를 가지고 있습니다.
interceptorNames 속성에 Proxy를 생성할 Target이 되는 bean의 이름들을 설정하여 여러 Proxy를 만들 수 있도록 하는 클래스입니다.
BeanNameAutoProxyCreator 사용 예
@Aspect 애노테이션이 붙어있는 클래스에 정의되어 있는 Adivce가 적용될 Pointcut을 가지고 있는 클래스들의 Proxy를 생성하는 클래스입니다. 이 클래스를 bean으로 등록해둬도 되지만 aop 네임스페이스를 사용하고 있다면 <aop:aspectj-autoproxy />만 추가하면 됩니다.
현재 BeanFactory에 등록되어 있는 Advisor들을 사용하여 그들의 대상이 되는 Target 들을 파악하여 Proxy를 만들어줍니다. 이 녀석 역시 그냥 bean으로 등록해두기만 하면 됩니다.
Auto-proxy creator that considers infrastructure Advisor beans only, ignoring any application-defined Advisors. 라고 써있는데.. 흠 Infra Advisor는 Spring에서 제공하는 인터셉터(DebugInterceptor, PerformanceMonitorInterceptor)를 말하는 것 같습니다. 이 녀석들이 적용될 Target의 Proxy만 만들는 것 같습니다. 흠.. 아마도 디버깅이나 테스트 용도로 사용하겠군요.
top

Write a comment.


Spring In Action 3장 발표 자료



내일 오전 10시에 발표할 자료 입니다.

2007/09/02 - [Spring In Action/1. Springing into action] - 1장 발표 자료 및 소스 코드
2007/09/16 - [Spring In Action/2. Wiring beans] - 2장 발표 자료 및 소스 코드

'Spring In Action > 3. Advanced bean wiring' 카테고리의 다른 글

Spring In Action 3장 발표 자료  (0) 2007.10.06
top

Write a comment.


Spring 2.5에 추가되는 bean() joinpoint



참조 : http://blog.interface21.com/main/2007/09/24/the-new-bean-pointcut/

Classic Spring AOP를 사용할 때 여러 Target에 대해 각각의 proxyFactoryBean을 정의하는 것은 매우 귀찮은 일이고 XML도 방대해 지기 때문에 AutoProxyCreator를 사용했었습니다.

그 중에서도 BeanNameAutoProxyCreator는 XML에 등록되어 있는 bean 이름을 설정해 주면 해당 bean의 Proxy를 만들어 주는 편리한 API였지만, 단점은 aop 네임스페이스 기반 이나 @AspectJ 애노테이션 기반과 같이 사용할 수 없다는 것이였습니다.

Spring 2.5에서는 bean() 이라는 joinpoint 표현식을 제공하여 다음과 같이 다수의 target bean을 편리하게 지칭할 수 있게 됩니다.
사용자 삽입 이미지

Pointcut Join points selected in
bean(accountRepository) The bean named "accountRepository"
!bean(accountRepository) Any bean except the "accountRepository" bean
bean(*) Any bean
bean(account*) Any bean with name starting in "account"
bean(*Repository) Any bean with name ending in "Repository"
bean(accounting/showaccount) The bean named accounting/showaccount (designating, say, a controller handling that URL)
bean(accounting/*) Any bean whose name starts with "accounting/" (designating, say, any controller handling accounting-related URLs)
bean(accounting/*/edit) Any bean whose name starts with "accounting/" and ends with "/edit" (designating, say, any controller handling the edit operation functionality related to accounting)
bean(*dataSource) || bean(*DataSource) Any bean whose name ends with either "dataSource" or "DataSource"
bean(service:name=monitoring) The bean named "service:name=monitoring"

top

Write a comment.


2장 발표 자료 및 소스 코드



top

TAG SIA 2판

Write a comment.


Basic bean wiring 테스트 코드



사용자 삽입 이미지
엄청납니다. 휴~~ 발표 준비 끝!! 일요일에 뵙겠습니다.
top

Write a comment.


왜 Spring MVC 컨트롤러에 AOP가 적용되지 않을까?



왜그럴까요? 저도 궁금해서 관련 아티클도 읽고 소스코드도 좀 해봤는데요; 아직도 조금 긴가민가 합니다.

AOP / AJAX Enabled Controllers in Spring MVC

위 글을 참조하였습니다.

Controller 인터페이스를 직접 구현하여 컨트롤러를 만들면 handleRequest() 메소드에 AOP를 적용할 수 있습니다. 하지만 자주 사용하는 MultiActionController, SimpleFormController에는 AOP가 적용되지 않습니다.

왜냐면, AbstractController에서 Controller 인터페이스를 구현하고, handleRequest 메소드를 final 메소드로 구현했습니다. 그래서 AOP가 적용되지 않는다고 합니다.

왜 냐면, MultiActionController, SimpleFormController 들의 프록시 객체를 만들 때, 인터페이스 기반으로 Proxy를 만들지 않고 CGlib 라이브러리를 사용해서 하위클래스를 생성하여 Proxy를 만들려고 하는데 final 메소드를 가지고 있기 때문에 그것마저도 못하기 때문인듯 합니다.

사용자 삽입 이미지
위와 같은 상속구조에서 SampleController의 handleRequest() 메소드에 BeforeAdvice를 적용하는 Aspect를 다음과 같이 정의했습니다.

@Aspect
public class SampleAspect {

    @Pointcut("execution(* finalClassProxying.SampleController.handleRequest())")
    public void samplePointcut(){}

    @Before("samplePointcut()")
    public void sampleAdvice(){
        System.out.println("과연 될까?");
    }

}

테스트를 해보면 적용이 되지 않는 것을 확인할 수 있습니다.

public class FinalClassProxingTest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("finalClassProxying/applicationContext.xml");
        SampleController controller = (SampleController) context.getBean("sampleController");
        controller.handleRequest();
    }
}

흠냐... 로그 메시지에서 final 메소드라서 Proxy객체를 만들 수 없다는 메시지가 보고 싶었는데 보지를 못했습니다... 그래서 위에 제가 생각한 것이 맞는 건인지 아리까리 합니다.



top

Write a comment.


Great power comes great responsibility(Autowiring)



Autowiring이 매우 강력하기 때문에 그 만큼 강한 책임이 뒤따르게 된다는 표현입니다.[각주:1]
byName : bean의 이름과 bean이 설정되어야 할 속성이 이름이 같아야 합니다.
=> 만약에 속성의 이름을 변경하기로(리팩토링, 예를 들어 foo -> bar로) 결정했는데 bean의 이름을 변경하지 않았다면, ㄷㄷㄷ 거기에 더해서 혹시 bar라는 bean이 존재하고 있다면. 더욱 ㄷㄷㄷ;;

byType : 다들 아시다시피 같은 타입의 bean이 여러개 일 때 사용할 수가 없습니다.

constructor: byType과 마찬가지 입니다.

autodetect : 일단 byConstruct를 시도해보고, 그 다음에 byType을 시도 하기 때문에 위와 같은 상황에서UnsatisfiedDependencyException을 볼 수 있습니다.

무엇보다 가장 큰 단점은
"대체 뭐가 설정 된건지 알 수가 없다"
입니다.

그럼 사용하지 말아야 할까요??
글쎄요. 규약(Convention)이 잘 정의되어 있고, 잘 따르고 있는 집단에서는 사용하면 좋을 것 같습니다.
그런 집단이 아니더라도 자주 변동되지 않는 부분에 대한 bean 설정에서 byName으로 설정해 두면 XML도 간단해 지고 좋치 않을까 생각해 봅니다.
  1. 저 문장 혹시 스파이더맨1에 나오는 대사 아닌가요? 할머니가 스파이더맨한테.. [본문으로]
top

Write a comment.


AOP 관련 질문 :: Before Advice의 객체를 Target 클래스로 넘겨주고 싶다.



메일을 받았습니다.

"XML을 사용하는 초간단 예제에 대한 질문인데요.
만약에 before메서드에서 생성한 객체를 target클래스에 넘겨야할땐 어떻게 해야되는지요.
방법이 있나요?"

답변은 다음과 같이 해드렸습니다.

"before메서드에서 생성한 객체를 target클래스에 넘겨야할땐"가 구체적으로 어떤 경우인지를 설명해주시면 더 좋겠습니다.

일 단은 Before Advice에서 target이 되는 클래스의 대상 메소드로 값을 넘기려면, Target이 되는 클래스에서 Before Advice를 가지고 있는 Aspect를 알아야 합니다. 그 말은 Aspect에 종속성이 생기게 된다는 것입니다.

제 생각에는 그런 경우가 발생한다면, Before Adivce보다는 Around Adivce를 사용하는 것을 추천하고 싶습니다. Around Advice를 사용하시면 대상이 되는 메소드에 넘겨줄 인자 들이나, 메소드의 반환 값등을 마음대로 바꿀 수 있으며, 심지어 대상이 되는 메소드를 실행하지 않을 수도 있습니다.

제 답변이 도움이 되셨으면 좋겠습니다."


top

Write a comment.


휴.. Spring AOP와 AspectJ 연동 성공



이전 글의 예상이 맞았습니다.

AspectJ 파일(aj 확장자)을 ajc라는 것으로 컴파일을 해야 하는데 이 녀석을 Eclipse에서는 어떻게 컴파일 할 지 모르기 때문에 컴파일을 못해서 class 파일이 없으니까 결국 Spring 컨테이너는 class 파일이 없어서 AspectJ로 작성한 Aspect를 못 읽어 들이고 에러를 내고 만 것이였습니다.

해결책은 ajc 사용해서 컴파일 해도 되겠지만.. 커맨드 라인에서 무언가를 하는것이 상당히 귀찮은 저 같은 분들은 AJDT 플러그인을 설치하시면 됩니다.

AJDT 홈페이지에 가시면 Eclipse 버전에 따라 release 정보가 표시되어 있습니다.

저는 Eclipse 3.3을 사용하기 때문에 다음의 URL로 업데이트 사이트를 등록하고 플러그인을 설치했습니다.
http://download.eclipse.org/tools/ajdt/33/update

설치한 뒤 해당 프로젝트에 "Convert to AspectJ Project"를 실행해주면 aj 파일은 ajc 사용해서 자동으로 컴파일 해주고, aspecjrt.jar 파일을 클래스패스에 추가해 줍니다.

사용자 삽입 이미지

그럼 Advice가 적용되는 지점에 해당 Advice가 몇 개 적용되는지 보여 줍니다.
사용자 삽입 이미지

멋진 툴입니다.

Class를 못찾던 문제도 해결 됐고 Spring AOP와 AspectJ를 연동도 간단하게 해결되었습니다.
top

  1. Favicon of http://secondMemory.kr BlogIcon 봉기환 2009.06.02 21:55 PERM. MOD/DEL REPLY

    헤에. 툴을 잘 쓰는 것도 중요한 문제인 듯 싶네요!

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2009.06.07 09:12 PERM MOD/DEL

    넹ㅋㅋ잘쓰면 편하죠.

  2. Favicon of http://blog.jidolstar.com BlogIcon 지돌스타 2010.05.12 22:02 PERM. MOD/DEL REPLY

    안녕하세요. 글 정말 잘보고 있습니다. 주신 예제로 Classic, Annotation, Namespace 기반의 모든 테스트는 문제없이 완료했는데... AspectJ를 직접 쓴 방법은 어찌해야하는지 모르다가 AJDT를 이용해 에러는 없앴습니다. 근데 아쉽게도...
    여전히

    Cannot find class [aspectj.Manner] for bean with name 'mannerAspect' defined in class path resource [aspectj/aspectjContext.xml]

    이런 에러가 나네요 흑흑... Aspect Visualiser로 보면 별 문제가 없던데.. 왜 그런지 모르겠더군요.

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2010.05.13 13:04 신고 PERM MOD/DEL

    댓글을 이제서야 봤네요. ajc 컴파일이 잘 안됐었나 보군요.

  3. Favicon of http://blog.jidolstar.com BlogIcon 지돌스타 2010.05.13 12:02 PERM. MOD/DEL REPLY

    ㅎㅎㅎ 알고봤더니... aspectj에서도 package를 넣어야 하더군요. 소책자 예제에는package 설정이 없었습니다. 그런데도 AJDT는 아무런 에러를 내벳지 않더군요. 툴을 처음 다뤄보는거라 에로가 많네요. ㅋ

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2010.05.13 13:04 신고 PERM MOD/DEL

    AJDT가 영.. 구린가 보군요.
    인텔리J로 넘어오세요.ㅋㅋ

Write a comment.