반응형
반응형

새로운 회사로 오자마자, 세미나를 하라그러길래 (....)

이것저것 문서를 만들고, 데모 샘플을 만들기 위해 Spring Boot AMQP를 써보던중 삽질했던 부분이 몇 가지 있어서 정리하게 됨.


데모 샘플의 목표는 RabbitMQ 공식 홈에 있는 Tutorial 중 Worker, Publish/Subscribe에 대한 구현이었다.


Java소스를 제공은 하지만, Spring Boot가 어마어마하게 숨겨놓아서..

삽질이 필요하게 됨.


Spring Boot AMQP가 기본적으로 해주는건, RabbitTemplate에 대해서 기본설정을 해주기 때문에,

그냥 @Autowired만 붙여서 끄집어와서 사용만 하면된다.


하지만, Host가 localhost 기본으로 잡혀있기 때문에, remote 환경에 있는 경우에는 변경을 해주어야 할 것이고,

Connection관련된 세부적인 튜닝도 해주어야 할 것이다.


Host, Port등 바꾸는것은 매우 간단하다!

application.properties에서 Ctrl + Space눌러가며 입맛에 맞는 설정을 하면 되기 때문이다. sts기준..

intelliJ는 지원안할지도 모르겠다. 그래서 정리해봄.


spring.rabbitmq.host=192.168.33.10       // host (어차피 가상머신이라서 그냥 ip공개)
spring.rabbitmq.port=5672              // RabbitMQ 기본 포트 (굳이 안적어도 될듯 하지만 혹시나 실패 떨어지는걸 보기싫어서...)
spring.rabbitmq.username=jss              // localhost접근이 아니라서, 인증 정보가 필요하더군. admin페이지와 마찬가지
spring.rabbitmq.password=1234            // 비번도 유저네임과 같은 이유

저기 없는 값들은, 공식홈 가면 정말 자세하게 나와있음. 영어라는게 문제..


Host랑 인증정보는 성공적으로 커스터마이징 했다.

이제 커넥션 관련된 여러가지 옵션들을 튜닝을 해줘야 하는데.

org.springframework.amqp.rabbit.listener패키지에 있는 SimpleMessageListenerContainer가 옵션을 오버라이드 가능하도록 지원을 해준다.

그냥 아래처럼 Bean으로 만들어서 등록해버리면, 옵션 값 지정이 가능하다. 

RabbitMQ 공식홈에서 Java Client받아서 해본사람들은 알겠지만, Spring Boot AMQP가 설정까지도 여러가지 바꿔놨다.

비권장 설정들을 빼고, 좀 더 편하게 바꿔놓은 듯 하니 믿고 사용하자.


@Bean
SimpleMessageListenerContainer container(ConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) {
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    container.setQueueNames("testQueue");
    container.setMessageListener(listenerAdapter);
    container.setMaxConcurrentConsumers(1);      // 최대 컨슈머 수 인데.. 어떤 기준인지 모르겠다.
    container.setReceiveTimeout(3000L);         // 메시지 받을 때 타임아웃 값 (ms)
    container.setRecoveryInterval(3000L);        // 연결이 끊어졌을 시 Recover 시도를 어느 주기로 할지에 대한 term (ms)
    return container;
}


여기까지 지정해주고, 바로 rabbitTemplate가지고 convertAndSend를 날리면,

지정한 queue이름에 해당하는 queue가 없다고 뭐라뭐라 에러가 떨어질 것이다.


이 문제는, Queue를 Bean으로 등록해주면 해결이 된다.


@Bean
Queue queue() {
     return new Queue("testQueue", false);      // 두번째 인자값은 디스크에 저장할지, Ram에 저장할지에 대한 flag (false : ram) 
}


위의 설정으로 인해 Queue가 존재하지 않으면, 알아서 생성을 하도록 구성이 되어, 정상적으로 Send가 될 수 있는 환경이 만들어졌다!


받는 부분은 RPC형식으로 구현을 하는 사람들은, rabbitTemplate으로 recv를 하면 되지만,

보통 Queue에 메시지가 있는지 없는지 체크하면서, 가져와서 뭔가를 하는 행위가 많기 때문에

MessageListenerAdapter라는 리스너를 지원한다. (org.springframework.amqp.rabbit.listener 패키지에 있음.)


@Bean MessageListenerAdapter listenerAdapter(Receiver receiver) { return new MessageListenerAdapter(receiver, "receiveMessage"); }



그냥 복붙하면, Receiver클래스가 없다고 나올 것인데, 만들어주면 되고

Bean생성 시 인자값에는 방금 만든 POJO를 넣어주면 되고,

MessageListenerAdapter생성자의 두번째 인자값에 있는 method를 만들어주고, 

Queue로부터, 메시지가 도착했을 경우에 할 일을 구현해주면 된다.


여기까지 구현하고, 받는 부분 2개이상 띄우고

보내는 부분으로 메시지를 쭉 보내보면, 보내는 부분에서 bind되어 있는 채널들에다가 나눠서 뿌려주게 되고,

받는 부분에서는 그에따라 나눠서 받을 수 있게 된다. 이렇게 쓸 일이 있을지는 잘 모르겠다.. -_-;;

(Worker 구현 완료)


Pub/Sub 패턴 같은 경우에는 비교적 간단한 Worker와는 다르게 Fanout Exchange라는 것을 추가로 이용해야 한다.


일단 받는 쪽에서는 고칠 게 Queue이름밖에 없다. (Queue가 여러개 있을 때 제대로 BroadCasting을 하는지 보기위해)


보내는 쪽에서는 FanoutExchange를 등록을 해주어야 하는데, FanoutExchange를 Bean으로 만들어주고, 

만들어진 Bean을 Binding해주는 작업을 해주면 된다. 

테스트를 위해 2개의 Queue를 바인딩한다.


@Bean
FanoutExchange exchange() {
      return new FanoutExchange("test-exchange");
}

@Bean
Binding binding() {  
      return BindingBuilder.bind(queue1()).to(exchange());
}

@Bean
Binding binding_2() {
      return BindingBuilder.bind(queue2()).to(exchange());
}



그리고 가장 중요한.. rabbitTemplate에서 setExchange("test-exchange")를 해줘야 정상적으로 동작을 한다.


서로 다른 Queue를 바라보고 있는 Receiver Application을 띄우고, (보내는 쪽에서 exchange로 바인딩 된 Queue여야 한다.)

메시지를 보내보면, 메시지가 각 Queue에 BroadCasting되는 것을 확인할 수 있다.

반응형
,
반응형

Spring Framework는 4.1.7, jQuery는 2.1.4 버전을 사용하여 테스트함.


[Front-End]


<head>
<script>
function checkboxArr() {
    var checkArr = [];     // 배열 초기화
    $("input[name='test_check']:checked").each(function(i)) {
        checkArr.push($(this).val());     // 체크된 것만 값을 뽑아서 배열에 push
    }

    $.ajax({
        url: 'test_check'
        , type: 'post'
        , dataType: 'text'
        , data: {
            valueArrTest: checkArr
        }
    });
}
</script>
</head>


    <input type="checkbox" name="test_check" value="1" />
    <input type="checkbox" name="test_check" value="2" />
    <input type="checkbox" name="test_check" value="3" />
    <input type="checkbox" name="test_check" value="4" />


[Back-End]


@RequestMapping(value = "/test_check", method = RequestMethod.POST)
@ResponserBody
public void testCheck(@RequestParam(value = "valueArrTest[]") List<String> valueArr) {
    // TODO
}


Back-End단에서 받을땐 String[] 형식으로 하면 안되고, List<String> 형태로 사용해야 된다.

 

예전에는 for loop돌면서 delimiter를 정해서 String하나로 만들어 넘기거나, checkbox의 이름을 각각 다르게 정해서 조합해서 억지로 넘겼던 기억이 있는데, 위 방법으로 하면 깔끔하게 정리가 된다.

반응형
,
반응형

jdbcTemplate을 이용해서 select쿼리를 날리는 경우 .query 메소드를 사용하게 된다.


그런데 .query 메소드의 경우 List형태로 리턴을 해주기 때문에,

.queryForObject 메소드를 사용하는 경우도 있다.


다만, .queryForObject 메소드의 경우에는, row가 정상적으로 1개 나왔을 경우에는 문제가 없는데,

row가 아예 나오지 않는 경우에 대해 exception을 떨구게 된다.


이곳저곳 구글링을 해보면, try catch 문으로 exception을 캐치하여 null을 리턴하도록 하라고는 하는데,

try catch 문의 경우 성능 저하의 원인이 될 수 있기 때문에, 왠만해선 사용을 권장하지 않는다.


해결방안은, .query메소드를 사용하되, 단일 행이 나올 것이라고 예상되는 구문에서는

DataAccessUtils.singleResult나 DataAccessUtils.uniqueResult를 이용하여 List형태를 Object형태로 리턴하게끔 하면 된다.


메소드 구현체 내부를 들어가보면 알겠지만, .query 메소드의 결과로 나온 List의 사이즈를 체크하여,

사이즈가 0이면 null을 리턴하고, 사이즈가 0이 아니면 첫번재 element를 끄집어와서 리턴을 해주는 방식으로 되어 있다.

반응형
,
반응형

Spring Boot의 경우 application.properties 파일로 온갖 설정을 다 할 수 있도록 되어 있다.


하지만, 개발환경, QA환경, 서비스 환경에 대해서 application.properties에 들어가는 내용이

달라지는 경우가 대부분이다.


Spring Boot는 application.properties 명을 아래와 같이 지정함으로써 profile기능을 제공한다.


application-{profile명}.properties


기존에 존재하는 application.properties는 기본적으로 모든 profile에 포함이 되게 된다. (super class 상속 개념이라고 보면 쉽다.)


저렇게 파일을 만들어주고, WAS설정에 JAVA_OPTS 에 아래와 같이 추가를 해준다.


아래는 Tomcat8 기준 Catalina.sh를 예시로 들어봤다.

ex) service profile에 대한 properties를 활성화 한다.

JAVA_OPTS="-Dspring.profiles.active=service"


Maven을 사용하는 경우, 플러으니 사용하고 profile별로 파일 복사하고 이것저것 해서 가능하지만,

어차피 JAVA_OPTS는 서비스 전에 튜닝을 해야하니, maven보다는 이게 개인적으로 더 단순해서 맘에든다.

반응형
,
반응형

@ModelAttribute에 들어갈 Class를 생성 후 별 다른 생성자를 만들지 않고,

그냥 사용하게 되면, 생성자를 찾을 수 없다는 오류가 발생한다.


이런 경우 비어 있는 기본 생성자를 하나 만들어 주면 해결이 된다.

반응형
,
반응형

freemarker같은 경우는 resources파일을 그냥 starter project구조에서

src/main/resources/templates 에만 넣어주면 알아서 설정을 다 해주는 구조였는데..


Boot에서는 가장 기본이 되는 jsp가 이리저리 해줄것이 많다.

이런걸 보면 Spring 에서도 jsp사용을 권장하고 있지는 않은 것 같다.


1. 의존성 주입 (pom.xml)


<dependency>
 <groupId>javax.servlet</groupId>
 <artifactId>jstl</artifactId>
</dependency>
<dependency>
 <groupId>org.apache.tomcat.embed</groupId>
 <artifactId>tomcat-embed-jasper</artifactId>
 <scope>provided</scope>
</dependency>



2. application.properties 변경 (javaConfig나 xml에서의 viewResolver 설정과 동일하니 별도로 설명은 안하겠음)

spring.view.prefix=/WEB-INF/views/   

spring.view.suffix=.jsp



위와 같은 설정이 되어 있어야, 컨트롤러가 jsp 페이지를 제대로 로딩한다.

jsp 쓸일은 없겠지만, Spring Security 예제 따라하다보니까 왜 안되는지 이해가 안되서 이틀동안 헤맨결과 이유가 저거였음...-_- 굳이 Boot에서는 jsp를 사용하지 않도록 하자.

반응형
,
반응형

Spring Security를 이용하는 페이지 작업 시 csrf 토큰을 넣기 위하여

아래와 같은 taglib을 사용해야 한다. (input hidden으로 박아넣겠다면 굳이 없어도 되겠지만..)


<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>



pom.xml에 의존성 주입을 jstl과 tomcat-embed-jasper만 넣어놓으면,

JasperException이 발생하며, tags를 가져오지 못했다고 뭐라뭐라 한다.


이런 경우 pom.xml에 아래와 같은 의존성을 주입해주면 해결이 된다.


<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
</dependency>



Spring Security가 제공해주는 Role의 신세계를 경험하기 위해 테스트를 하는데, 

이건 뭐 의존성이 장난이 아니다..;;

반응형
,
반응형

WebApplication.java 파일의 경우 Spring Boot의 Runner역할을 하고 있고,

@SpringBootApplication Annotation을 달게되면, ComponentScan등 기본적으로 되어야 하는 귀찮은 설정들이 해소가 된다.


그런데... 이 WebApplication.java 파일이 기본 package가 아닌 하위 package로 들어가게 되면

ComponentScan의 역할을 제대로 하지 않는 것 같다.


특히 JPA를 사용하는 경우 Repository를 제대로 찾지를 못해서 계속 bean 생성 오류를 뿜게 된다.


앞으로, WebApplication.java파일은 package root에 위치하도록 프로젝트를 구성해야 할 것 같다.

반응형
,
반응형

Spring Security는 기본적으로 UserDetailService를 구현한 인터페이스들을 이용해서 인증처리를 한다.


Default Jdbc인증 같은 경우 JdbcDaoImpl이라는 클래스가 인증의 역할을 담당하게 되는데,

Security를 통해 인증 후 principal이라는 객체에 인증 정보가 들어가는데, 이것도 사전에 정해진 객체인 org.springframework.security.core.userdetails.User라는 클래스에 맞게 들어가게 된다.


하지만, 서비스를 하다보면 저 객체에 포함되지 않는 정보들도 UI에 표시를 해줘야 하는 경우가 분명히 있다.


이런 경우 User클래스와 UserDetailService를 상속받아서 customizing을 하게 된다.


User클래스는 간단히 extends만 해서 새롭게 객체만 만들어주면 되고,

UserDetailService의 경우도 implements로 구현해야하는 메소드만 구현을 해주면 되는데,

보통은 그냥 이렇게 구현만 하면, Spring이 알아서 wrapping을 해주는게 정상인데, Spring Boot + Security의 경우 명시적으로 세팅을 해주어야 하는 부분이 있어서 공유함.


xml설정은 예전에 때려치웠기 때문에, java config기준으로 설명을 한다.


Security 설정을 위해서는 WebSecurityConfigurerAdapter라는 놈을 상속받아서 설정 파일을 만들고,

configureGlobal이라는 메소드를 bean으로 만들어주어야 한다. 


해당 메소드에 보면 AuthenticationManagerBuilder라는 객체가 Argument로 들어있는데, 이 값을 이용해서 아까 wrapping을 위해 만든

UserDetailService를 wrapping해줄 수가 있다.


주의할점 하나. User클래스 상속받아서 새거 만들 때, 추가되는 property들에 대해서 Getter, Setter를 만들어 주어야 함.


굳이 전체 소스 공개는 안하겠고, config 설정만 공개함.


//SecurityConfig.java
@Autowired
private UserDetailsService userDetailService;
 
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
 auth.userDetailsService(userDetailService);
}



UserDetailsService가 아무리해도 wrapping이 안되는 것 같길래... 이틀정도 삽질하다보니, config에 있었던 것임. 


반응형
,
반응형

pom.xml에 아래와 같이 boot-maven-plugin에 executable을 true로 설정해주면 된다.

war는 그냥 되지만, jar는 저 옵션이 있어야 데몬으로 뜨는듯


<build>
 <finalName>프로젝트명</finalName>
 <plugins>  
  <plugin>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-maven-plugin</artifactId>
   <configuration>
    <executable>true</executable>
   </configuration>
  </plugin>
 </plugins>
</build>



※ boot 1.3.3 부터는 true가 기본으로 붙는듯 하다. 따로 설정안해줘도 동작함;;
반응형
,
반응형