문제

https://programmers.co.kr/learn/courses/30/lessons/1829


코드

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
public int[] solution(int m, int n, int[][] picture) {
int numberOfArea = 0;
int maxSizeOfOneArea = 0;

boolean[][] isPassedArea = new boolean[m][n];

for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {

if (picture[i][j] == 0 || isPassedArea[i][j]) {
continue;
}

int areaRange = breadthFirstSearch(i, j, picture, isPassedArea);
maxSizeOfOneArea = (maxSizeOfOneArea < areaRange) ? areaRange : maxSizeOfOneArea;
++numberOfArea;
}
}

int[] answer = new int[2];
answer[0] = numberOfArea;
answer[1] = maxSizeOfOneArea;

return answer;
}

private int breadthFirstSearch(int x, int y, int[][] picture, boolean[][] isPassedArea) {
final int[] xAround = new int[]{1, -1, 0, 0};
final int[] yAround = new int[]{0, 0, 1, -1};

int areaRange = 1;

Queue<Position> queue = new LinkedList<Position>();

setPassedArea(isPassedArea, queue, x, y);

while (!queue.isEmpty()) {
Position currentPosition = queue.poll();

for (int i = 0; i < xAround.length; i++) {
int moveX = xAround[i] + currentPosition.x;
int moveY = yAround[i] + currentPosition.y;

if (!isSameAreaValidation(moveX, moveY, picture, isPassedArea, currentPosition)) {
continue;
}

setPassedArea(isPassedArea, queue, moveX, moveY);
++areaRange;
}
}

return areaRange;
}

private boolean isSameAreaValidation(int moveX, int moveY, int[][] picture, boolean[][] isPassedArea, Position currentPosition) {
if (isOutOfPicture(moveX, moveY, picture)) {
return false;
}

if (picture[moveX][moveY] == 0) {
return false;
}

if (isPassedArea[moveX][moveY]) {
return false;
}

if (picture[currentPosition.x][currentPosition.y] != picture[moveX][moveY]) {
return false;
}

return true;
}

private boolean isOutOfPicture(int moveX, int moveY, int[][] picture) {
if (moveX < 0 || moveY < 0) {
return true;
}

if (picture.length <= moveX || picture[0].length <= moveY) {
return true;
}

return false;
}

private void setPassedArea(boolean[][] isPassedArea, Queue<Position> queue, int x, int y) {
isPassedArea[x][y] = true;
queue.offer(new Position(x, y));
}

class Position {
private int x = 0;
private int y = 0;

public Position(int x, int y) {
this.x = x;
this.y = y;
}
}

흐름

  1. 이미 지나온 배열인지 확인하기 위해 picture array와 똑같은 크기의 boolean array를 만듦
  2. 2차원 배열 크기만큼 돌면서 picture[i][j] value가 0이거나 이미 지나온 길이면 찾아갈 필요가 없으므로 continute 해서 길을 찾지 않음
  3. 아니면 좌표 x,y를 저장할 class를 담는 Queue를 생성함
  4. queue에 현재 좌표를 저장하고 boolean array[x][y]를 true로 할당
  5. queue에 있는 값을 하나씩 꺼내면서 queue가 전부 비워질 때까지 반복
  6. queue에서 꺼낸 현재 좌표에서 상하 좌우를 비교해서 같은지 판단해야 하므로 현재 좌표에서 x -1 , +1, y +1, -1 씩 돌아가면서 판단할 좌표를 구함
  7. 구한 좌표가 validation을 통과하면 4번과 마찬가지로 좌표를 queue에 저장하고 true로 변경
    1. 구한 좌표 x, y가 array 범위를 넘어가진 않는 지
    2. 구한 좌표의 value가 0이 아닌 값 인지
    3. 구한 좌표가 이미 지나간 길이 아닌지
    4. 구한 좌표의 value가 현재 좌표의 value와 같은 지
    5. 위 4가지를 모두 통과해야 validation 통과
  8. 현재 영역의 크기를 증가시킴
  9. queue가 전부 비워지면 영역의 크기를 return해서 이전에 구한 영역의 크기와 비교해서 큰 값을 저장
  10. 영역 개수를 증가시킴

결과

결과


테스트 케이스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int[][] picture = new int[][] {{1, 1, 1, 0}, {1, 2, 2, 0}, {1, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 3}, {0, 0, 0, 3}};
assertArrayEquals(new int[] {4 , 5}, test.solution(6, 4, picture));

int[][] picture1 = new int[][] {{0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}};
assertArrayEquals(new int[] {0 , 0}, test.solution(5, 5, picture1));

int[][] picture2 = new int[][] {{1, 0, 0, 0, 0}, {0, 0, 1, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 1}};
assertArrayEquals(new int[] {3 , 1}, test.solution(5, 5, picture2));

int[][] picture3 = new int[][] {{1, 1, 1, 1, 1}, {1, 1, 1, 1, 1}, {1, 1, 1, 1, 1}, {1, 1, 1, 1, 1}, {1, 1, 1, 1, 1}};
assertArrayEquals(new int[] {1 , 25}, test.solution(5, 5, picture3));

int[][] picture4 = new int[][] {{1, 1, 1, 1, 1}, {1, 1, 1, 1, 1}, {1, 1, 100, 1, 1}, {1, 1, 1, 1, 1}, {1, 1, 1, 1, 1}};
assertArrayEquals(new int[] {2 , 24}, test.solution(5, 5, picture4));

int[][] picture5 = new int[][] {{1, 1, 1, 0}, {1, 1, 1, 0}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}};
assertArrayEquals(new int[] {2 , 6}, test.solution(6, 4, picture5));

int[][] picture6 = new int[][] {{0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0}, {0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0}, {0, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 0}, {0, 1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 2, 1, 1, 1, 0}, {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, {0, 1, 3, 3, 3, 1, 1, 1, 1, 1, 1, 3, 3, 3, 1, 0}, {0, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 0}, {0, 0, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 0, 0}, {0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0}};
assertArrayEquals(new int[] {12 , 120}, test.solution(13, 16, picture6));

참고 사이트

댓글 공유

문제

Test Code

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
@Test
void testCreateWordFail() throws Exception {
// gvien
BindingResult bindingResult = new MapBindingResult(new HashMap(), "");
bindingResult.rejectValue("test", "test", "haha");

WordDTO.AddWord addWord = new WordDTO.AddWord();
addWord.setWord("test");
addWord.setMeaning("테스트");

given(this.service.getCreateWordBindingResult(addWord, bindingResult)).willReturn(bindingResult);

String addWordToJson = getAddWordToJson();

//when
ResultActions action = mockMvc.perform(post("/word/add")
.param("save", "")
.content(addWordToJson)
.contentType(MediaType.APPLICATION_JSON)
.with(csrf()))
.andDo(print());

//then
action.andExpect(status().isOk())
.andExpect(view().name("thymeleaf/word/createWordForm"));
}

위 코드처럼 this.service.getCreateWordBindingResult(addWord, bindingResult) method가 호출될 때 willdReturn으로 지정한 bindingResult가 return되게 하고 하려했으나 given이 제대로 동작하지 않아서 인지 getCreateWordBindingResult method 안에서 result가 존재하지 않아 NullPointerException이 발생하여 테스트가 실패했다.

Test Target

1
2
3
4
5
6
7
8
9
10
11
@PostMapping(value="/word/add", params= {"save"})
public String createWord(@Valid AddWord word, BindingResult bindingResult) {
BindingResult result = service.getCreateWordBindingResult(word, bindingResult);

if (result.hasErrors()) { // NullPointerException 발생
return "thymeleaf/word/createWordForm";
}

service.insertWord(word);
return "thymeleaf/index";
}

해결

ArgumentMatchers class에 any()를 이용해서 파라미터를 넘겼더니 정상적으로 동작했다.

1
given(this.service.getCreateWordBindingResult(any(WordDTO.AddWord.class), any(BindingResult.class))).willReturn(bindingResult);
  • p.s : 기본 자료형 (int, char)이나 String 등은 any()를 사용하지 않고 그대로 파라미터로 받아도 정상적으로 동작하는 것 같은데 어째서 따로 정의한 클래스만 인식(?) 하지 못해서 willReturn이 먹히지 않고 null이 return 되는지 알 수가 없었다.

전체 코드

댓글 공유

문제

Spring Boot + thymeleaf으로 진행하던 프로젝트를 테스트 하던 중
Ajax로 호출하는 Methoed를 Test 하였더니 아래와 같이 403 error가 return 되면서 Test가 실패했다.


원인

thymeleaf를 사용하면 기본적으로 CSRF 토큰을 넘겨주기 때문에 따로 csrf 토큰을 생성하지 않았으나, ajax에선 직접 csrf 토큰을 생성해서 넘겨줘야 하기 때문에 csrf 토큰을 생성해서 넘겨주도록 되어 있는데 Test 시에는 그렇지 않아서 403 Forbidden가 발생한 것으로 보인다.

403

thymeleaf config.html

1
2
<meta id="_csrf" name="_csrf" th:content="${_csrf.token}"/>
<meta id="_csrf_header" name="_csrf_header" th:content="${_csrf.headerName}"/>

common.js

1
2
const token = $("meta[name='_csrf']").attr("content");
const header = $("meta[name='_csrf_header']").attr("content");

해결

mockMvc.perform에서 get, post 호출 시 SecurityMockMvcRequestPostProcessors 의 csrf()with()로 파라미터에 추가한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;

...

//when
ResultActions action = mockMvc.perform(post("/word/add")
.param("save", "")
.content(addwordToJson)
.contentType(MediaType.APPLICATION_JSON)
.with(csrf())) // 이 부분
.andDo(print());

...

추가 한 후 다시 테스트 해보면 파라미터에 csrf 토큰이 생성되어 정상적으로 동작하는 것을 확인 할 수 있다.

1
2
3
4
5
6
7
8
9
10
MockHttpServletRequest:
HTTP Method = POST
Request URI = /word/add
Parameters = {save=[], _csrf=[6757e021-c0a3-4390-81d9-9c917d89c8c1]} // 이 부분
Headers = [Content-Type:"application/json", Content-Length:"59"]
Body = <no character encoding set>
Session Attrs = {SPRING_SECURITY_CONTEXT=org.springframework.security.core.context.SecurityContextImpl@83a38fdf: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@83a38fdf: Principal: org.springframework.security.core.userdetails.User@31bf3c: Username: jgji; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER}

...

성공


참고 사이트


전체 코드

댓글 공유

Codedeploy에서 배포그룹 생성 시 인스턴스를 잘못 등록

1
2
The deployment failed because no instances were found for your deployment group.
Check your deployment group settings to make sure the tags for your Amazon EC2 instances or Auto Scaling groups correctly identify the instances you want to deploy to, and then try again.

CodeDeploy

배포 그룹에서 태그 그룹으로 인스턴스를 지정할 때 위 사진처럼 일치하는 인스턴스가 존재한다는 문구가 나타나는 걸 확인해야한다.


생성한 버킷 리전과 인스턴스의 리전이 틀려서 에러 발생

1
2
payload:"{\"error_code\":5,\"script_name\":\"\",\"message\":\"
The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint.\",\"log\":\"\"}"}

위 에러 로그는 아래 디렉토리에 위치한 파일을 열어서 확인 할 수 있다.
shell script에서 작성한 echo도 아래 파일에서 함께 확인 할 수 있다.

1
vim /opt/codedeploy-agent/deployment-root/deployment-logs/codedeploy-agent-deployments.log

CodeDeploy Log File

댓글 공유

에러

Travis 권한 에러

1
./mvnw: Permission denied

Spring Boot + Maven 프로젝트 Travis CI 연동 중 발생한 에러


해결

1
2
before_install:
- chmod +x mvnw

설정으로 권한 부여하여 해결

전체 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
language: java
jdk:
- openjdk8

branches:
only:
- master

# Travis CI 서버의 Home
cache:
directories:
- $HOME/.m2

before_install:
- chmod +x mvnw

# MAVEN 프로젝트는 필요 없음
#script: "./mvnw package"

# CI 실행 완료시 메일로 알람
notifications:
email:
recipients:
- your@gmail.com

참고 사이트

댓글 공유

평소 Junit의 assertEquals로 알고리즘 코드만 test 하다가
토이 프로젝트에서도 Junit을 사용해보기 위해 Spring에서의 Junit Test 방법을 정리 해보자 한다


Test할 Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Controller
public class HomeController {

@GetMapping("/")
public String home(Locale locale, Model model) {
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);

String formattedDate = dateFormat.format(date);

model.addAttribute("serverTime", formattedDate );

return "home";
}
}
  • index 페이지로 매핑되는 home 메서드만 존재하는 Controller

standaloneSetup() 메서드를 활용한 Test

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

private MockMvc mockMvc;

@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new HomeController()).build();
}

@Test
public void homeTest() throws Exception {

mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(model().attributeExists("serverTime"))
.andExpect(view().name("home"));
}
}

MockMvc 클래스를 통해 HTTP GET, POST 등에 대한 테스트를 할 수 있게 한다

  • perform() : get 방식으로 url을 호출
  • status() : http status에 대해 테스트
  • model() : model에 “serverTime” attribute가 존재하는 지 확인
  • view() : return하는 view의 name이 “home”인지 확인

위 메서드들을 정상적으로 사용하기 위해선 아래 처럼 메서드들을 import 시켜야한다

1
2
3
4
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;

webAppContextSetup() 메서드를 활용한 Test

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
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"
, "file:src/main/webapp/WEB-INF/spring/root-context.xml"
, "file:src/main/webapp/WEB-INF/spring/spring-security.xml"})
@WebAppConfiguration
public class HomeControllerTest {

@Autowired
private WebApplicationContext context;

private MockMvc mockMvc;

@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}

@Test
public void homeTest() throws Exception {

mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(model().attributeExists("serverTime"))
.andExpect(view().name("home"));
}
}

@RunWith

  • Junit에 내장된 Runner외에 다른 실행자를 실행시킨다
  • 위 코드에선 SpringRunner 실행자로 Test를 실행하는데 이때 SpringRunner class는 SpringJUnit4ClassRunner class를 상속받고 있다
  • 테스트를 진행할 때 ApplicationContext를 만들고 관리하는데 이 때 싱글톤을 보장한다

@ContextConfiguration

  • 스프링 Bean 설정 파일의 위치를 지정한다
  • 위 코드에서는 Controller는 servlet-context.xml, 그 외 Service, Repository 등은 root-context.xml, 암호화를 위한 bcryptPasswordEncoder bean은 spring-security.xml에 지정되어 있어 3개를 지정했다

@WebAppConfiguration

  • applicationContext의 웹 버전을 작성하는데 사용된다

standalonesetup과 webAppContextSetup의 차이

standalonesetup() 메서드는 테스트할 컨트롤러를 수동으로 초기화해서 주입하고, webAppContextSetup() 메서드는 스프링의 ApplicationContext의 인스턴스로 동작한다

standalonesetup() 메서드는 컨트롤러에 집중하여 테스트할 때 사용되고
webAppContextSetup() 메서드는 스프링의 DI 컨테이너를 이용해 스프링 MVC 동작을 재현해서 테스트한다


번외 java.lang.NoClassDefFoundError: javax/servlet/SessionCookieConfig 에러

1
2
3
4
5
6
7
8
9
10
11
12
13
java.lang.NoClassDefFoundError: javax/servlet/SessionCookieConfig
at org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder.initWebAppContext(StandaloneMockMvcBuilder.java:339)
at org.springframework.test.web.servlet.setup.AbstractMockMvcBuilder.build(AbstractMockMvcBuilder.java:139)
at com.toon.controller.HomeControllerTest.setUp(HomeControllerTest.java:25)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
... 26 more
  • Spring 4.0 이상에서 Junit으로 테스트 시 servlet 3.0 API를 사용하기 때문에 발생
  • pom.xml에서 servlet 버전을 3.0.1 이상으로 올려주면 해결
  • artifactId가 javax.servlet-api 으로 변경 됐으므로 함께 변경해준다.
1
2
3
4
5
6
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>

참고 사이트

댓글 공유

문제

https://www.acmicpc.net/problem/11047


코드

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
38
39
40
41
42
43
44
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String coinAndMoney = br.readLine();

String[] coinAndMoneyArray = coinAndMoney.split(" ");

int coinAmount = Integer.parseInt(coinAndMoneyArray[0]);
long money = Long.parseLong(coinAndMoneyArray[1]);

long[] coinCategory = getCoincategory(coinAmount, br);

long answer = solution(coinAmount, money, coinCategory);

System.out.println(answer);
}

public static long[] getCoincategory(int coinAmount, BufferedReader br) throws IOException {
long[] result = new long[coinAmount];
for (int i = 0; i < coinAmount; i++) {

result[i] = Integer.parseInt(br.readLine());
}

return result;
}

public static long solution(int n, long money, long[] array) {
long answer = 0;

for (int i = n - 1; i >= 0; i--) {
if (array[i] > money) {
continue;
}

answer += (money / array[i]);
money %= array[i];

if (money == 0) {
break;
}
}

return answer;
}

흐름

  1. 돈의 종류 만큼 돌면서 큰 수 부터 가지고 있는 돈을 나눔
  2. 돈의 가치가 더 큰 경우엔 나눌 수 없으므로 continue
  3. 돈으로 갖고 있는 돈을 나눈 값을 더하고 남은 값은 다시 나눠야 하므로 money에 다시 저장함
  4. momeny가 0이 되면 나눌 돈이 없는 것이므로 사용한 돈 갯수 return

결과

결과


테스트 케이스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
long[] array = new long[] {1,5,10,50,100,500,1000,5000,10000,50000};
assertEquals(6, test.solution(10, 4200, array));
assertEquals(12, test.solution(10, 4790, array));
assertEquals(1, test.solution(10, 50000, array));
assertEquals(2000, test.solution(10, 100000000, array));

array = new long[] {1};
assertEquals(2, test.solution(1, 2, array));
assertEquals(1, test.solution(1, 1, array));
assertEquals(100000000, test.solution(1, 100000000, array));

array = new long[] {1, 5};
assertEquals(1, test.solution(2, 5, array));

array = new long[] {1, 3};
assertEquals(2, test.solution(2, 4, array));

array = new long[] {1, 100};
assertEquals(1, test.solution(2, 100, array));

array = new long[] {5000};
assertEquals(1, test.solution(1, 5000, array));
  • 나머지는 입력 값 처리이고 solution 메서드가 주요 로직이므로 solution 메서드를 테스트
  • 이렇게 테스트 할 경우 입력처리 때문에 에러가 발생 할 수 있으니 꼭 java application으로 함께 테스트 해볼 것

댓글 공유

@SpringBootApplication의 동작 방식

Spring-Boot의 Main Application 코드에는 @SpringBootApplication이라는 Annotation이 존재한다.

1
2
3
4
5
6
@SpringBootApplication
public class Demo1Application {
public static void main(String[] args) {
SpringApplication.run(Demo1Application.class, args);
}
}
  • @SpringBootApplication
1
2
3
4
5
6
7
8
9
10
11
12
13
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

...

}

이 Annotation의 주된 내용은 아래 3가지 이다.

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

@SpringBootConfiguration

  • @SpringBootConfiguration은 기존 @Configuration과 마찬가지로 해당 클래스가 @Bean 메서드를 정의되어 있음을 Spring 컨테이너에 알려주는 역할을 한다.
  • @Configuration 어노테이션의 대안으로 둘의 차이점은 아래와 같이 설명하고 있다.

@SpringBootConfiguration is an alternative to the @Configuration annotation. The main difference is that @SpringBootConfiguration allows configuration to be automatically located. This can be especially useful for unit or integration tests.

@EnableAutoConfiguration

  • @ComponentScan에서 먼저 스캔해서 Bean으로 등록하고 tomcat등 스프링이 정의한 외부 의존성을 갖는 class들을 스캔해서 Bean으로 등록한다.
  • 이 때 정의된 class들은 spring-boot-autoconfigure안에 있는 MATE-INF 폴더에 spring.factories라는 파일안에 정의되어 있다.
  • 이 중에서 spring.boot.enableautoconfiguration을 key로하는 외부 의존성 class들이 존재한다.
  • 여기에 정의된 모든 class를 가져오는 게 아니라 class에 내부에 정의된 어노테이션에 따라 그 조건에 부합하는 class들만 생성된다.
    • @ConditionalOnProperty, @ConditionalOnClass, @ConditionalOnBean 등등
1
2
3
4
5
6
public @interface EnableAutoConfiguration {

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

...
}

spring.factories 위치

spring-boot-autoconfigure

META-INF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\

...

번외. 내장 Tomcat Class

spring.factories 파일 안에 보면

1
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\

가 있고 이 class의 코드를 보면 아래와 같이 tomcatServletWebServerFactoryCustomizer() 메서드를 통해 tomcat을 실행한다는 것을 알 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
return new ServletWebServerFactoryCustomizer(serverProperties);
}

@Bean
@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
ServerProperties serverProperties) {
return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}

...

@ComponentScan

  • @ComponentScan은 해당 어노테이션 하위에 있는 객체들 중 @Component가 선언된 클래스들을 찾아 Bean으로 등록하는 역할을 한다.
  • 이 때 꼭 @Component가 아니여도 @Component가 선언되어 있는 어노테이션인 @Service, @Repository, @Controller 등등도 포함된다.
  • @EnableAutoConfiguration이 스캔하기 전에 먼저 @ComponentScan이 진행된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
...
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
...
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
...
}

참고 사이트

댓글 공유

문제

https://programmers.co.kr/learn/courses/30/lessons/42839


코드

  • 순열을 이용한 완전탐색으로 해결
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public int solution(String numbers) {
char[] tochar = numbers.toCharArray();

int numbersLength = tochar.length;

Set<Integer> numberSet = new HashSet<Integer>();
for (int i = 1; i <= numbersLength; i++) {
perm(tochar, 0, i, numberSet);
}

int answer = getPrimeCount(numberSet);

return answer;
}

private void perm(char[] array, int depth, int length, Set<Integer> numberSet) {
if (depth == length) {

StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append(array[i]);
}

int number = Integer.parseInt(sb.toString());
if (number > 1) {
numberSet.add(number);
}

return;
}

for (int i = depth; i < array.length; i++) {
swap(array, i, depth);
perm(array, depth + 1, length, numberSet);
swap(array, i, depth);
}
}

private void swap(char[] arrary, int i, int j) {
char temp = arrary[i];
arrary[i] = arrary[j];
arrary[j] = temp;
}

private int getPrimeCount(Set<Integer> numberSet) {
int result = 0;
for (int i : numberSet) {
boolean isPrime = true;

for (int j = 2; j * j <= i; j++) {
if (i % j == 0) {
isPrime = false;
break;
}
}

if (isPrime) {
++result;
}
}

return result;
}

흐름

  1. [1,2,3] 배열일 경우 3 자리 수 까지 나타날 수 있으므로, numbers의 길이 만큼 반복하면서 순열을 만듬
  2. perm() 메서드를 재귀로 돌면서 깊이 만큼 index를 스위칭하면서 순열을 만듬
    • ex) [1,2,3] 인 경우, 0번째와 0번째를 스왑하여 123이 되고 그 이후엔 0번째와 1번째를 스왑해서 213, 그 이후엔 0과 2를 스왑해서 321 … 이런 식으로 진행된다
  3. 돌다가 깊이랑 이번에 만들 숫자의 길이가 같으면 중복되지 않게 Set에 저장하는데 1 이하인 경우엔 소수가 아니므로 저장하지 않음
  4. 다시 swap() 해서 배열 index를 원상복귀 시킴
  5. 반복해서 모든 순열을 만든 후 해당 Set에 저장된 수들이 소수 인지 판별해서 소수면 카운트 증가시킴

결과

번호 속도
테스트 1 통과 (1.06ms, 50.3MB)
테스트 2 통과 (9.99ms, 53MB)
테스트 3 통과 (0.83ms, 52.1MB)
테스트 4 통과 (7.08ms, 52.6MB)
테스트 5 통과 (23.43ms, 56MB)
테스트 6 통과 (0.91ms, 52.6MB)
테스트 7 통과 (1.13ms, 51.9MB)
테스트 8 통과 (18.16ms, 55.9MB)
테스트 9 통과 (0.96ms, 49.9MB)
테스트 10 통과 (10.37ms, 52.9MB)
테스트 11 통과 (2.51ms, 51.5MB)
테스트 12 통과 (2.16ms, 52MB)

테스트 케이스

1
2
3
4
5
6
assertEquals(3, test.solution("17"));
assertEquals(2, test.solution("011"));
assertEquals(1, test.solution("2"));
assertEquals(12, test.solution("7843"));
assertEquals(0, test.solution("9999999"));
assertEquals(1336, test.solution("1276543"));

참고 사이트

댓글 공유

문제

https://www.acmicpc.net/problem/1152


코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static long solution(String args) {
char[] tochar = args.trim().toCharArray();

long answer = 0;
if (tochar.length == 0) {
return answer;
}

for (char ch : tochar) {
if (ch == ' ') {
++answer;
}
}

return answer + 1;
}

흐름

  1. 문자열 앞 뒤로 공백이 존재 할 수 있으므로 trim()
  2. 공백만 있는 문자열인 경우 0 return
  3. char array로 변환된 문자열을 돌면서 공백인 경우 count 증가
  4. 공백을 기준으로 단어가 만들어졌으니 공백 개수 + 1 return
    • ex) hello, wolrd! = 공백 1개, 문자 2개

결과

결과


테스트 케이스

1
2
3
4
5
assertEquals(6, test.solution("The Curious Case of Benjamin Button"));
assertEquals(3, test.solution(" Mazatneunde Wae Teullyeoyo"));
assertEquals(2, test.solution("Teullinika Teullyeotzi "));
assertEquals(7, test.solution(" a b c d e f g "));
assertEquals(0, test.solution(" "));

전체 코드

https://github.com/jungguji/algorithm_training/blob/master/src/main/java/algorithm/baekjoon/string/%EB%8B%A8%EC%96%B4%EC%9D%98_%EA%B0%9C%EC%88%98.java

댓글 공유

Junggu Ji

author.bio


author.job