Map 순회

in collection

Map 순회

Map을 사용하다보면 Map에 저장된 모든 데이터를 순회해야할 경우가 발생합니다. 여러가지 방법 중에서 아래의 3가지에 대해서 알아보았습니다.

Map 순회 방법

  1. entrySet()
  2. keySet()
  3. values()

entrySet 사용하기

entrySet은 key와 value 모두 필요한 경우에 사용하면 좋습니다. 반환값으로 Set을 반환해주기 때문에 for문을 사용해서 순회할수 있습니다.

1
2
3
4
5
6
Map<String, Object> map = new HashMap();

for (Map.Entry<String, Object> entry: map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
}

keySet 사용하기

keySet은 key 값만 필요한 경우에 사용하면 좋습니다. 마찬가지로 반환값은 Set입니다.

1
2
3
4
5
Map<String, Object> map = new HashMap();

for (String key : map.keySet()) {

}

values 사용하기

values는 value 값만 필요한 경우에 사용하면 좋습니다. 반환값은 Collection 입니다. Collection 또한 for문으로 순회할수 있습니다.

1
2
3
4
5
Map<String, Object> map = new HashMap();

for (Object value : map.values) {

}

Comment and share

SpEL

in spring, IoC Container

Spring Expression language 란?

SpEL이란 런타임시에 객체 그래프를 조회하고 조작하는 표현언어입니다. 스프링 3.0이상부터 지원하여 EL과 비슷하지만 메소드 호출 지원, 문자열 템플릿 기능까지 제공해 줍니다.

SpEL에서 지원하는 기능

  1. Literal expressions
  2. Boolean and relational operators
  3. Regular expressions
  4. Class expressions
  5. Accessing properties, arrays, lists, and maps
  6. Method invocation
  7. Relational operators
  8. Assignment
  9. Calling constructors
  10. Bean references
  11. Array construction
  12. Inline lists
  13. Inline maps
  14. Ternary operator
  15. Variables
  16. User-defined functions
  17. Collection projection
  18. Collection selection
  19. Templated expressions

SpelExpressionParser

SpelExpressionParser 클래스를 사용해서 리터럴 문자열 표현식을 파싱할수 있습니다. 싱글 쿼테이션으로 둘러싸인 문자열을 파싱 해줍니다.

1
2
3
4
5
6
7
8
@Test
public void test1() {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String message = (String) exp.getValue();

System.out.println(message);
}
1
Hello World

SpEL은 메소드를 호출하거나 프로퍼티에 접근하거나 생성자를 호출할수 있습니다. 아래의 예제는 String 메소드 중 하나인 concat을 호출하고 있습니다.

1
2
3
4
5
6
7
8
@Test
public void test2() {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
String message = (String) exp.getValue();

System.out.println(message);
}
1
Hello World!

SpEL을 사용해서 객체(rootObject라고 부름)의 필드의 값을 가져 올수 있습니다. 그리고 Expression의 getValue의 파라미터 중에 하나인 desiredResultType에 반환받은 타입을 입력하면 위의 예제 처럼 캐스팅을 하지 않아도 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SpELVO {
private String name;

public SpELVO(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

@Test
public void test3() {
SpELVO spELVO = new SpELVO("intelli");

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");
String message = (String) exp.getValue(spELVO, String.class);

System.out.println(message);
}
1
intelli

EvaluationContext

EvaluationContext 인터페이스는 프로퍼티, 메소드, 필드를 처리하고 타입변환을 수행하는 표현식을 평가할때 사용합니다. 스프링에서는 두개의 구현체를 제공합니다.


1. SimpleEvaluationContext : SpEL에서 필수적인 기능만 제공합니다.(4.3.15 버전부터 지원)
2. StandardEvaluationContext : SpEL의 모든 기능을 사용할수 있습니다.

지금까지의 예제에서도 내부에서는 EvaluationContext를 사용하고 있습니다. 아래처럼 EvaluationContext를 직접 생성 후에 사용할수도 있습니다.

1
2
3
4
5
6
7
8
9
10
11
@Test
public void test4() {
SpELVO spELVO = new SpELVO("intelli");

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");
EvaluationContext evaluationContext = new StandardEvaluationContext();
String message = exp.getValue(evaluationContext, spELVO, String.class);

System.out.println(message);
}

Array

SpEL을 이용해서 배열에 접근 할수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ArrayList {
private List<String> names;

public List<String> getNames() {
return names;
}

public void setNames(List<String> names) {
this.names = names;
}
}


@Test
public void test5() {
ArrayList arrayList = new ArrayList();
arrayList.setNames(Arrays.asList("woman", "man"));

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("names[0]");
EvaluationContext evaluationContext = new StandardEvaluationContext(arrayList); // rootObject를 여기에 바로 쓸수도 있다.
String message = exp.getValue(evaluationContext, String.class);

System.out.println(message);
}
1
woman

값을 가져오는 것 말고도 값을 변경할수도 있습니다. 유의 해야할 점은 값을 추가해주는게 아닌 변경해주는 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test6() {
ArrayLists arrayLists = new ArrayLists();
arrayLists.setNames(Arrays.asList("man"));

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("names[0]");
EvaluationContext evaluationContext = new StandardEvaluationContext(arrayLists);
exp.setValue(evaluationContext, "woman");

System.out.println(arrayLists.getNames().get(0));
}

값을 변경해주는 기능은 배열 뿐 아니라 일반 객체에서도 사용 가능합니다. 이때 필드는 public 이거나 setter 메소드가 구현되어 있어야 합니다.

1
2
3
4
5
6
7
8
9
10
11
@Test
public void test7() {
SpELVO spELVO = new SpELVO("jesi");

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");
EvaluationContext evaluationContext = new StandardEvaluationContext(spELVO);
exp.setValue(evaluationContext, "sara");

System.out.println(spELVO.getName());
}

SpelParserConfiguration

SpelParserConfiguration를 사용 하면 Collection에서 index가 null일 경우에 자동으로 생성해주는 기능을 활성화 할수 있습니다.

아래의 예제에서는 배열에서 index가 null인 요소에 접근하려고 할때 자동으로 배열에 빈값이 추가된것을 확인 할수 있습니다. SpelParserConfiguration 생성자의 두번째 파라미터를 true로 전달하면 기능을 활성화 할수 있습니다(기본값은 두개 모두 false).

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test8() {
ArrayLists arrayLists = new ArrayLists();

SpelParserConfiguration configuration = new SpelParserConfiguration(false, true);
ExpressionParser parser = new SpelExpressionParser(configuration);
Expression expression = parser.parseExpression("names[4]");
String name = expression.getValue(arrayLists, String.class);

System.out.println("result : " + name);
System.out.println(arrayLists.getNames().size());
}
1
2
result : 
5

String 뿐 아니라 일반 객체도 자동으로 생성해 줍니다. 이때 자동으로 생성 될 객체는 디폴트 생성자가 존재하여야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class SpELVO {
private String name;

public SpELVO() {
}
}

public class ArrayRefrence {
private List<SpELVO> spELVOs = new ArrayList<>();

public List<SpELVO> getSpELVOs() {
return spELVOs;
}

public void setSpELVOs(List<SpELVO> spELVOs) {
this.spELVOs = spELVOs;
}
}

@Test
public void test9() {
ArrayRefrence arrayLists = new ArrayRefrence();

SpelParserConfiguration configuration = new SpelParserConfiguration(false, true);
ExpressionParser parser = new SpelExpressionParser(configuration);
Expression expression = parser.parseExpression("spELVOs[4]");
String name = expression.getValue(arrayLists, String.class);

System.out.println("result : " + name);
System.out.println(arrayLists.getSpELVOs().size());
}

빈 정의를 정의하는 표현식

XML와 어노테이션 기반의 설정 메타데이터와 SpEL 표현식을 함께 사용할수 있습니다. 표현식을 정의 하기 위한 문법은 #{ <expression string> }입니다.

XML Configuration

표현식을 사용해서 프로퍼티나 생성자의 전달인자에 값을 할당할수 있습니다.

1
2
3
<bean id="spelVo" class="kr.co.spring.SpELVO">
<constructor-arg name="name" value="hihi"/>
</bean>
1
2
3
4
5
6
@Test
public void xml() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring/spring-spel.xml");
SpELVO spelVo= (SpELVO) ctx.getBean("spelVo");
System.out.println(spelVo.getName());
}
1
hihi

어노테이션

@Value 어노테이션을 이용해서 SpEL을 사용할수 있습니다. 어노테이션이 위치할수 있는 곳은 필드, 메소드, 메소드나 생성자의 파라미터입니다.

필드에서의 사용

아래의 클래스는 Value 어노테이션을 사용하는 예제로 사용이 됩니다. 아래에서 보듯이 일반 문자열 및 빈으로 등록 된 객체의 메소드도 사용이 가능합니다(빈으로 등록되지 않으면 사용이 안됨).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ValueSpEL {
@Value("hi")
private String hello;

@Value("#{seplVo.getName()}")
private String name;

public String getName() {
return name;
}

public String getHello() {
return hello;
}
}

SpELVO와 ValueSpEL 클래스를 빈으로 등록합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class WebConfiguration {

@Bean
public SpELVO seplVo() {
return new SpELVO("sara");
}

@Bean
public ValueSpEL valueSpEL() {
return new ValueSpEL();
}
}
1
2
3
4
5
6
7
@Test
public void annotationField() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(WebConfiguration.class);
ValueSpEL valueSpEL = (ValueSpEL) ctx.getBean("valueSpEL");
System.out.println(valueSpEL.getHello());
System.out.println(valueSpEL.getName());
}
1
2
hi
sara

주의를 할 점은 xml이나 어노테이션 등에서 사용 되는 $와 #의 차이점을 구분할줄 알아야 한다. $는 프로퍼티 문법이고,
#는 SpEL 문법이다.

Comment and share

데이터 바인딩 추사황 : Converter와 Formatter

초기 스프링에서는 PropertyEditor를 사용하였습니다. 이후 이를 대체할 Converter, Formatter 인터페이스가 등장하였습니다.

Converter

Converter는 S타입을 T타입으로 변환할수 있습니다. PropertyEditor와는 다르게 상태정보가 없기 때문에 쓰레드 세이프합니다. 이로 인하여 빈으로 등록 후 사용하여도 문제가 없습니다.

xml을 이용한 Custom Converter 등록하기

Converter 만들기

Conterver를 만드는 방법은 간단합니다. Converter<>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class EventConverter {

public static class StringToEventConverter implements Converter<String, Event> {

public Event convert(String source) {
Event event = new Event();
event.setId(Integer.parseInt(source));
return event;
}
}

public static class EventToStringConverter implements Converter<Event, String> {

public String convert(Event source) {
return "convert : " + source.getId();
}
}
}

Comment and share

프로퍼티 파일 가져오기

스프링을 이용해서 프러퍼티 가져오기

스프링에서 제공하는 Resource와 PropertySource를 통해서 쉽게 프로퍼티 파일을 가져 올수 있습니다.

1
2
3
4
5
6
7
8
9
10
public class PropertiesTest {

@Test
public void getProperties() {
Resource resource = new ClassPathResource("클래스패스 경로"); // prefix로 classpath:를 붙이지 않는다.
PropertySource propertySource = new ResourcePropertySource(resource);

propertySource.getProperty("");
}
}

Comment and share

Validation 추상화

Validator 인터페이스는 어플리케이션에서 사용하는 객체 검증용 인터페이스 입니다. 어떠한 계층과도 관계가 없이 사용할수 있습니다. 주로 웹에서 많이 사용되지만 서비스, 데이터 어디에서도 사용해도 좋습니다.

구현해야 하는 메소드

Validator을 구현하는 클래스는 두개의 메소드를 오버라이딩 해야 합니다.

1. supports

객체를 검증 할 때 Validator가 검증 할수 있는 클래스인지를 판단하는 로직을 구현하는 메소드입니다. 반환값이 true 이면 검증할수 있다고 판단합니다.

2. validate

실제 검증 로직이 이루어지는 메소드입니다.


Validator 클래스 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class EventValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Event.class.equals(clazz);
}

@Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "title", "not empty", "Title must not be null");
}
}

public class Event {
private String title;
private String name;

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

ValidationUtils를 사용해서 검증 로직 구현

스프링에서 제공하는 검증 유틸인 ValidationUtils를 사용해서 검증을 할수 있습니다.

아래 코드는 값이 null 이거나 빈값이거나 길이값이 0인 경우 에러로 처리해줍니다.

1
ValidationUtils.rejectIfEmptyOrWhitespace(Errors인스턴스, 필드명, 에러코드, 에러발생시 출력할 메시지)

ValidationUtils를 사용하지 않고 검증 로직 직접 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
public class EventValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Event.class.equals(clazz);
}

@Override
public void validate(Object target, Errors errors) {
if (target == null || "".equals(target.getTitle())) {
errors.reject(필드명, 에러코드명, 에러발생시 출력할 메시지);
}
}
}

테스트 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class ValidationTest {

@Test
public void testValidation() {
Event event = new Event(); // 타겟 객체
EventValidator eventValidator = new EventValidator(); // 검증 Validator

// BeanPropertyBindingResult는 Erros와 BindingResult의 구현체로써 보통은 웹에서는 MVC가 해당 객체를 생성하기 때문에 직접 생성할 일은 적다.
Errors errors = new BeanPropertyBindingResult(event, "event");

eventValidator.validate(event, errors); // 타겟 객체를 검증

for (ObjectError error : errors.getAllErrors()) { // 타겟 객체에서 Validation을 통과 못한 모든 에러를 가져옴(errors.getAllErrors)
System.out.println("=== error code ===");
System.out.println(Arrays.toString(error.getCodes()));
System.out.println(error.getDefaultMessage());
}
}
}

class EventValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Event.class.equals(clazz);
}

@Override
public void validate(Object target, Errors errors) {
Event event = (Event) target;

ValidationUtils.rejectIfEmptyOrWhitespace(errors, "title", "not empty", "Title must not be null");

if (event.getName() == null || "".equals(event.getName())) {
errors.rejectValue("name", "not empty", "Name must not be null");
}
}
}

어노테이션을 이용한 검증

위의 방식은 복잡한 검증 로직을 구현할 때는 사용하나 빈값체크, Max, Min 값 체크 등 간단한 Validation은 어노테이션을 통해서 검증을 할수 있습니다.

타겟 객체
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Event {

Integer id;

@NotEmpty
String title;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}
}
테스트 코드

직접 Validator를 구현하지 않고 어노테이션을 사용해서 간편한 검증 작업을 진행 할수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ValidationTest {

@Test
public void testValidation() {
Event event = new Event();
event.setEmail("aaa2");

Errors errors = new BeanPropertyBindingResult(event, "event");

eventValidator.validate(event, errors);

for (ObjectError error : errors.getAllErrors()) {
System.out.println("=== error code ===");
System.out.println(Arrays.toString(error.getCodes()));
System.out.println(error.getDefaultMessage());
}
}
}
pom.xml

Validation 어노테이션을 사용하기 위해서는 아래의 두개의 dependency를 추가해 주어야 합니다.

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.3.11.Final</version>
</dependency>

Comment and share

톰캣 원격 서버 디버깅

로컬에서 IDE를 통해서 서버에 접속하면 편하게 디버깅 작업을 진행 할수 있습니다. 하지만 원격 서버에 설치 된 서버를 실행했을 때는 디버깅이 되지 않아 오류가 발생 했을 때 어떤 문제가 발생했는지 정확히 파악하기가 쉽지 않습니다.


그래서 톰캣 서버를 사용 하는 경우에 원격 서버 디버깅 하는 방법에 대해서 알아보겠습니다.

윈도우 서버를 기준으로 작성되었습니다.


톰캣 설정

catalina.bat 파일을 열어서 아래의 코드를 추가 해줍니다. 아래에서 address는 디버깅 때 사용 할 포트로써 사용가능한 포트 번호를 입력해줍니다. 그리고 접속할수 있도록 인바운드/아웃바운드 규칙에 포트를 추가해줍니다.

1
set JPDA_OPTS=-agentlib:jdwp=transport=dt_socket,address=8888,server=y,suspend=n

톰캣 서버 시작

디버깅을 하기 위해서는 jpda를 이용해야 하기 때문에 톰캣 서버를 시작 할때 아래의 명령어로 시작 합니다.

1
catalina.bat jpda start

인텔리제이 설정

단축키는 Alt + Shift + F10 후에 0번 입력 또는 Run/Debug Configurations 팝업을 오픈합니다.


아래 이미지와 같이 remote를 추가해 줍니다.


아래 이미지와 같이 원격 IP와 위에서 톰캣에 작성했었던 디버깅 포트 번호를 입력해 줍니다.


사용

디버깅 적용은 로컬에 저장되어 있는 소스코드에 디비깅을 사용합니다. 그렇기 때문에 원격 서버의 소스와 로컬의 소스코드가 일치해야 합니다.

Comment and share

Response 웹서버 정보 노출

서버로 요청을 보낸 후에 받은 Response에서 웹서버 정보가 노출이 됩니다.

1
2
3
4
5
Server: Apache-Coyote/1.1
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
...

노출 방지

웹 서버 정보 노출을 방지 하기 위해서는 server.xml의 Connector 설정을 통해 변경할수 있습니다. 태그에 server=””를 추가하고 값으로 노출을 원하는 문자열을 삽입하면 됩니다.

1
2
<Connector port="8080" server="원하는 문자열">
...

Comment and share

CORS란

CORS란 도메인 또는 포트가 다른 서버의 자원을 요청하는 매커니즘을 말합니다. 이때 요청을 할때는 corss-origin HTTP에 의해 요청됩니다.


이때 요청을 할때는 cross-origin HTTP에 의해 요청됩니다.


하지만 동일 출처 정책 때문에 CORS 상황이 발생시에 요청한 데이터를 브라우저에서 보안목적으로 차단합니다. 해당 문제를 해결하는 가장 쉬운 방법은 같은 도메인을 사용하는 것입니다. 하지만 요즘에는 각기 다른 용도로 사용되는 경우에는 개별 서버를 구축하기 때문에 이는 해결책이 될수 없습니다.


다른 해결방법은 서버에서 CORS 효청을 허용해주면 됩니다. 서버로 들어오는 모든 요청에 대해서 CORS 요청을 허용하기 위해서 Filter를 이용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class CORSFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;

response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
response.setHeader("Access-Control-Allow-Origin", "3600");
response.setHeader("Access-Control-Allow-Origin", "x-auth-token, x-requested-with, origin, content-type, accept, sid");

chain.doFilter(req, res);
}
}
1
2
3
4
5
6
7
8
<filter>
<filter-name>cors</filter-name>
<filter-class>kr.co.spring.filter.SimpleCORSFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>cors</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Access-Control-Allow-Methods

POST, PUT, DELETE 등 허용할 ACCESS METHOD를 설정할수 있습니다.

Access-Control-Max-Age

HTTP Request 요청에 앞서 Preflight Request 라는 요청이 발생되는데, 이는 해당 서버에 요청하는 메소드가 실행 가능한지(권한 여부) 확인을 위한 요청입니다. Preflight Reuqest는 OPTIONS 메소드를 통해 서버에 전달됩니다(Allow-Methods 설정에서 OPTIONS를 허용해야 합니다).


Access-Control-Max-Age는 Preflight Request를 캐시할 시간입니다. 단위는 초입니다. 캐시를 하게 된다면 해당 시간이 지난 뒤에 재 요청을 하게 됩니다.

Access-Control-Allow-Origin

허용할 도메인을 설정할수 있습니다. 값은 *로 설정하면 모든 도메인에서 허용하는 것이고, 특정 도메인만 설정할수 있습니다.

Comment and share

스프링 이벤트 처리

이벤트를 사용하는 이유는 비지니스 로직과 사이드 이벤트와의 결합도를 낮추기 위해서 사용됩니다. 사이드 로직이 구현된 클래스를 직접 호출하는게 아닌 이벤트 처리를 통해서 결합도를 낮추어 줍니다.

스프링 4.2 이전 버전

이벤트 객체 생성

이벤트를 전달하기 위한 객체로써 ApplicationEvent를 상속해서 구현한다. 이벤트 발생시에 전달할 데이터 값을 해당 이벤트 객체에 주입해준다. 이벤트 객체는 Bean으로 등록하지 않는다.

1
2
3
4
5
6
7
8
9
10
11
12
public class MyEvent extends ApplicationEvent {
private MyDomain myDomain;

public MyEvent(Object source, MyDomain myDomain) {
super(source);
this.myDomain = myDomain;
}

public MyDomain getMyDomain() {
return myDomain;
}
}

이벤트 핸들러 생성

이벤트가 발생했을 때 호출될 메소드를 정의하는 클래스입니다. ApplicationListener을 구현하고 이벤트 발생시 onApplicationEvent메소드가 호출됩니다. 이벤트 핸들러는 Bean으로 등록 합니다.

1
2
3
4
5
6
7
@Component
public class MyEventHandler implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent myEvent) {
System.out.println("이벤트 받았다. 데이터는 " + myEvent.getMyDomain());
}
}

이벤트 퍼블리싱

스프링에서 제공하는 ApplicationEventPublisher를 이용해서 이벤트를 퍼블리싱 할수 있습니다. ApplicationEventPublisher는 스프링에서 Bean으로 등록 되어 있어 @Autowired로 바로 사용할수 있습니다. publishEvent 메소드를 통해서 퍼블리싱 하면 등록 된 리스너 객체가 호출되게 됩니다.

1
2
3
4
5
6
7
8
9
10
@Component
public class EventService {
@Autowired
private ApplicationEventPublisher publisher;

public void run() {
MyDomain myDomain = new MyDomain();
publisher.publishEvent(new MyEvent(this, myDomain));
}
}

스프링 4.2 이후 버전(4.2 포함)

이벤트 객체 생성

4.2버전 부터는 이벤트 객체 생성시에 ApplicationEvent를 상속 받을 필요가 없습니다. 스프링 코드가 들어가지 않게 되면서 결합도를 낮춰줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyEvent {
private MyDomain myDomain;
private Object source;

public MyEvent(Object source, MyDomain myDomain) {
this.source = source;
this.myDomain = myDomain;
}

public MyDomain getMyDomain() {
return myDomain;
}
}

이벤트 핸들러 생성

이벤트 핸들러도 더 이상 ApplicationListener를 구현하지 않아도 됩니다. 이벤트 발생시 호출될 메소드에 @EventListener 어노테이션을 붙여 줍니다.

1
2
3
4
5
6
7
8
@Component
public class MyEventHandler {

@EventListener
public void onApplicationEvent(MyEvent myEvent) {
System.out.println("이벤트 받았다. 데이터는 " + myEvent.getMyDomain());
}
}

결론

이벤트 객체와 이벤트 핸들러에서 스프링 관련 코드가 사라지면서 결합도가 낮아지게 되었습니다.
이벤트 리스너가 2개 이상인 경우에는 모두 실행이 되게 됩니다. 이때 순서는 보장 되지 않습니다.

  • @Order을 사용해서 순서를 정할수있습니다.
  • @Order 사용시에 숫자가 작을수록 먼저 실행이 됩니다.
  • 비동기적으로 실행을 원하면 @Async와 함께 사용합니다. 이때 @Order를 사용하더라도 순서가 보장되지 않습니다.

스프링에서 제공되는 이벤트

  1. ContextRefreshedEvent : 컨텍스트가 리프레시 될떄 발동
  2. ContextClosedEvent: 컨텍스트가 종료될때 발동

Comment and share

함수형 컴포넌트

어떠한 상태도 없고 라이프사이클 관련 메소드도 사용하지 않을때 지금까지 사용해왔었던 컴포넌트 생성 방법을 사용한다면 사용하지도 않는 메소드를 추가하게 됩니다. 함수형 컴포넌트를 사용하면 심플하게 작성할수 있습니다.

사용방법

functional 속성으로 사용

functional 속성을 사용하는 경우 template 태그를 사용할수 없습니다. 이때 render 함수를 사용 합니다.

1
2
3
4
5
6
7
8
<script>
export default {
functional : true,
render(h, context) {
// ...
}
}
</script>

장점

함수형 컴포넌트는 라이프 사이클 메소드를 가지지 않습니다. 이를 통해서 앱 퍼포먼스 향상 효과를 가질수 있습니다.

FunctionalView
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<template>
<div>
<template v-for="item in list">
<functional-component :key="item"/>
</template>
</div>
</template>

<script>
import FunctionalComponent from '../components/functional/FunctionalComponent.vue';

export default {
components: {
FunctionalComponent
},
data() {
return {
list: []
}
},
created() {
for (let i = 0; i < 1000; i++) {
this.list.push(i);
}
}
}
</script>

<style scoped>

</style>
FunctionalComponent
1
2
3
4
5
6
7
8
9
10
11
12
<script>
export default {
functional: true,
render(h) {
return h('div', '함수형 컴포넌트');
}
}
</script>

<style scoped>

</style>

함수형 컴포넌트를 1000개 생성하고 있습니다. 하지만 count는 1이라는 것을 확인할수 있습니다.

NoneFunctionalComponent

functional만 지우고 나머지는 위의 코드와 동일 합니다.

1
2
3
4
5
6
7
8
9
10
11
<script>
export default {
render(h) {
return h('div', '함수형 컴포넌트');
}
}
</script>

<style scoped>

</style>


SPA에서 functional 사용하기

2.5.0+ 이후로는 템플릿 기반의 함수형 컴포넌트를 정의할수 있습니다. template 태그에 functional 속성을 추가하면 동일하게 함수형 컴포넌트를 사용할수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
<template functional>
<div>함수형입니다.</div>
</template>
<script>
export default {
}
</script>

<style scoped>

</style>

Comment and share

Moon Star

author.bio


author.job