@ModelAttribute, Controller 메소드에 붙였을때

JAVA/Spring|2019. 5. 20. 18:41

@ModelAttribute, Controller 메소드에 붙였을때 요청이 들어오면 들어온 요청 Model에 메소드 리턴값이

들어오는것을 확인할 수 있다.

 

Object dutchMap = model.asMap().get("dutchMap");

@ModelAttribute("dutchMap")
	public Map<String,DutchDao> getDutchMap(){
		Map<String,DutchDao> map = new HashMap<>();
		map.put("k11", new DutchDao());
		map.put("k12", new DutchDao());
		map.put("k13", new DutchDao());
		return map;
	}

그런데 하나 궁금점

 

위에 getDutchMap()의 결과인 Map<String,DutchDao>값을

Controller 생성 초기에 bean으로 등록해서 가지고(캐싱하고) 있을까?

 

아니면 매번 요청마다 getDutchMap()을 부르게 될까?

 

확인 방법은 간단하게 getDutchMap()을 break point를 찍고 확인해보니..

 

요청마다 getDutchMap() 를 부르고 있었다..;;;

 

실시간으로 매번 생성해서 model에 세팅하는 용도라면 적절할지 모르겠지만..

모든 요청마다 매번 메소드 불리기 때문에 주의해서 사용해야 겠다...

 

 

 

 

댓글()

[Spring] ApplicationEventPublisher, AbstractApplicationEventMulticaster

JAVA/Spring|2019. 4. 30. 19:00

지난번 글에서 Spring 에서 Event를 publish 할때

TaskExecutor가 설정되어있지 않은 경우 publish 하는 스레드에서 이벤트핸들러들을 부른다고 했다.

https://meteorkor.tistory.com/entry/ApplicationEventPublisher

 

Spring Event ApplicationEventPublisher

Spring에 Event 처리하는 부분이 있길래 의문점이 조금 생겨 코드를 살펴 봤다 물론 동작은 각 설정에 따라 변경될수 있겠지만 기본 설정(가능한 설정을 손 안댄 상태에서)에서 어떻게 동작하는가? "이벤트별 블럭..

meteorkor.tistory.com

TaskExecutor를 설정하면 당연히 설정된 TaskExecutor에 이벤트 수행을 위임하게 될것이다

그렇다면 TaskExecutor를 어디에 어떻게 설정 해야 할까?

 

일단 샘플로 해보려던건 이벤트를 publisher가 아니라 Event만 처리하는 단일 스레드를 만들어 처리하는 것!

 

역시나 코드를 까봐야 알수 있는데 

ApplicationEventPublisher 에 publishEvent(event)를 쭉 타고 가면

AbstractApplicationContext에 publishEvent(Object event, ResolvableType type) 쪽에 도달할 수 있다.

보면 결국 ApplicationEventMulticaster를 구해서 multicastEvent()를 부르는걸 볼수 있고

그안에서 결국 지난번 글에서 말한대로 Executor가 있으면 Executor를 통해 수행하고 없으면 invokeListener()를 통해 로컬 콜을 수행하는걸 볼수 있다.

 

위에 getTaskExecutor()를 보면  결국 ApplicationEventMulticaster가 들고 있는 TaskExecutor를 사용하는것을 확인할수 있었다

 

그럼 ApplicationEventMulticaster 는 어디서 TaskExecutor를 가져올까..?

빈으로 TaskExecutor를 등록하면 타입으로 가져와서 쓰려나..?

 

그런데 아무리 TaskExecutor 타입의 빈을 등록해도 ApplicationEventMulticaster 안에 TaskExecutor는 null이였다..

 

그럼 ApplicationEventMulticaster 타입의 빈을 등록해두면 ApplicationEventMulticaster을 등록한 빈으로 사용하려나? 했는데 마찬가지로 안됬다

 

결국 찾은 방법은

빈이름을 주고 등록하는것

"applicationEventMulticaster" 이름으로 고정되어있나보다..

 

 

그래서 위와같이 빈 이름을 주고 빈등록을 하면 위 구현체를 사용해서 Event를 Multicasting 하는것을 볼 수 있었다.

 

 

 

 

댓글()

Spring Event ApplicationEventPublisher

JAVA/Spring|2019. 4. 24. 19:00

Spring에 Event 처리하는 부분이 있길래 의문점이 조금 생겨 코드를 살펴 봤다

물론 동작은 각 설정에 따라 변경될수 있겠지만 기본 설정(가능한 설정을 손 안댄 상태에서)에서 어떻게 동작하는가?

 

"이벤트별 블럭킹 큐에다 넣고 뽑아 가려나?" 그럼 딱히 락처리 할 부분은 없을것 같은데..?

 

코드를 살펴보니 

 

ApplicationEventPublisher에 event들을 발행시, SimpleApplicationEventMulticaster 인경우

TaskExecutor가 지정되어있지 않으면 invokeListener()를 통해 등록된 Listener들을 각각 호출하고 있었다.

 

즉 Listener가 싱글톤인경우 동일 Listener객체에 onApplicationEvent()가 불릴꺼고

싱글톤일 경우 당연히 고려해야 하긴 하겠지만, 여러 스레드가 불릴것은 고려해야 한다.

 

프로토타입의 Publisher 일때는 Listener객체가 싱글톤인 경우 동일 Listener객체가 불리고

 

Listener객체가 프로토타입일 경우 테스트 해보니 invokeListener()시 매번 생성된 Listener객체에 onApplicationEvent() 불리고 있었다.

 

정리

이벤트 발행시에는 붙어있는 이벤트리스너(객체를 재활용하든 매번 만들던)를 직접 부름

 

Publisher : 싱글톤, Listener : 싱글톤

: 여러 스레드가 한 pub 객체를 호출시 동일한 listener의 onApplicationEvent()를 부름

Publisher : 싱글톤, Listener : 프로토타입

: 여러 스레드가 한 pub 객체를 호출시 매번 새로 생성된 listener의 onApplicationEvent()를 부름

Publisher : 프로토타입, Listener : 싱글톤

: 여러 스레드가 여러 pub 객체를 호출시 동일한 listener의 onApplicationEvent()를 부름

Publisher : 프로토타입, Listener : 프로토타입

: 여러 스레드가 여러 pub 객체를 호출시 매번 새로 생성된 listener의 onApplicationEvent()를 부름

 

 

 

 

댓글()

Spring AnnotationConfigApplicationContext

JAVA/Spring|2019. 4. 24. 13:47

이전에는 코드 살펴봤던 내용들을 정리해놓지 않아 어떻게 바뀐건지 파악하기 어려운감이 있어 정리..

 

5.1.6 기준

 

ComponentScan 경로의 클래스들을 조사해서

빈등록 혹은 스캔 대상이 되는 어노테이션이 붙어있는 대상들을 추려서 처리한다 정도겠지만

구체적으로 코드상에서 어떻게 대상들을 조사하는지를 조금 더 살펴보고자 했다.

 

 

 

일단 AnnotationConfigApplicationContext 생성자에서 

AnnotatedBeanDefinitionReader
ClassPathBeanDefinitionScanner

두가지를 생성하고 있다.

 

특히, ClassPathBeanDefinitionScanner  에서 동작을 살펴보니

spring.componentsMETA-INF/spring.components 파일이 컴포넌트 항목들을 캐시하고 있었고

componentsIndex 필드에 담고 있었다.

 

그후 생성자쪽 scan()에서 componentsIndex 필드에 캐시된 항목이 있으면

ClassPathScanningCandidateComponentProvider.addCandidateComponentsFromIndex()를 호출해서 파일에 있는 항목들을 넣고

 

캐시된 항목이 없으면 

ClassPathScanningCandidateComponentProvider.scanCandidateComponents()을 호출해서

컴포넌트 스캔 경로(classpath*:com/meteor/aaaa/**/*.class)에 있는 모든 클래스를 찾아가며

ScannedGenericBeanDefinition 객체를 빈으로 등록한다.

 

일단 빈 스캔과정은 간략하게 살펴봤고..

 

 

요약

1. spring.components 파일에 스캔 항목들 클래스와 붙어있는 어노테이션이 적혀있어 클래스로더를 찾지 않고 활용

2. spring.components 파일(캐시파일)이 없으면 클래스로더에 컴포넌트 스캔 항목 클래스들을 하나씩 분석해서 ScannedGenericBeanDefinition 객체를 만듬

 

다음에 살펴볼 내용은...

"spring.components은 어떤 타이밍에 어떻게 캐시 파일을 생성할까?"

 

 

 

 

댓글()

Spring Boot 메일을 보내자(서비스 에러 알림)

JAVA/Spring|2018. 12. 19. 15:51

SpringBoot로 만든 toy 프로젝트를 라즈베리파이에 올려서

사용하고 있는데


가끔 에러가 발생하는 경우가 있다.

물론 가끔씩 접속해서 로그를 확인하면 되지만


에러가 언제 발생할지 모르기 때문에 알림 용도로 Mail전달받으면 어떨까 싶어 

SpringBoot에 Gmail Smtp를 활용하여 에러 스택과 url 그리고 param을 전달 받으면 좋겠다 싶어

최근에 적용한 내용을 남긴다.



1. 메이븐 pom.xml 추가

일단 메이븐 pom.xml 에 dependency를 추가


<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-mail</artifactId>

</dependency>


2. spring properties 에 smtp 설정 추가(application.properties)

gmail의 smtp 를 활용하기 위해 접속 정보가 필요하다.

application.properties에 하단 내용들을 설정한다.

#---------------------------------------

spring.mail.host=smtp.gmail.com

spring.mail.username=xxxx@gmail.com

spring.mail.password=xxxx


#그리고 보안 설정들

spring.mail.properties.mail.smtp.auth = true

spring.mail.properties.mail.smtp.socketFactory.port = 465

spring.mail.properties.mail.smtp.socketFactory.class = javax.net.ssl.SSLSocketFactory

spring.mail.properties.mail.smtp.socketFactory.fallback = false

#---------------------------------------

#--구글 계정의 보안 수준이 낮은 앱의  엑세스를 허용해야 보내진다...


이제 설정들은 됬고 Mail을 전송하는 서비스 클래스를 만들자


3. MailService 클래스 생성

@Service

public class MailService {

@Autowired

private JavaMailSender javaMailSender;

public void sendEmail(String emailAddr, String content) {

SimpleMailMessage mail = new SimpleMailMessage();

mail.setTo(emailAddr);

mail.setFrom("123@gmail.com");

mail.setSubject("[Service] Error Notify");

mail.setText(content);

javaMailSender.send(mail);

}

}



Mail 전송하는 서비스 클래스를 테스트를 마치고


4. 에러 처리를 위한 @ControllAdvise 활용 및 MailService 활용


Controller 단에서 발생하는(혹은 하위에서 발생후 throw되어 올라온) 에러들을 처리하기 위하여

@ControllerAdvise를 활용하여 ErrorHandler 를 생성했다.


@ControllerAdvice

public class ErrorHandler {

@Autowired

private MailService mailService;

@ExceptionHandler(value = Exception.class)  

    public String handleException(HttpServletRequest req, Exception e){

StringBuilder stb = new StringBuilder();

stb.append( "requestURI : " );

stb.append( req.getRequestURI() );

stb.append( "\n" );

stb.append( "req.getQueryString() : " );

stb.append( req.getQueryString() );

stb.append( "\n" );

stb.append( "e : " );

stb.append( "\n" );

stb.append( Throwables.getStackTraceAsString(e) );

mailService.sendEmail("xxxxx@gmail.com", stb.toString());

return e.getMessage();

    }  

}



물론.. 더 깔끔하게 만드는 방법들이 있겠지만


일단 기록을 위해 남기고 추가적으로 수정해 나가도록...






댓글()

Boot를 Docker로 뛰우자(Spring Boot in Container)

JAVA/Spring|2018. 11. 21. 14:02

포스팅 및 글들을 보며 삽질한 내용을 작성해나가려고 한다.


FROM openjdk:8-jdk-alpine

VOLUME /tmp

COPY target/*.war app.jar

ENTRYPOINT ["java","-jar","/app.jar"]


https://spring.io/blog/2018/11/08/spring-boot-in-a-container

댓글()