Notice
Recent Posts
Recent Comments
Link
«   2025/07   »
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
Tags
more
Archives
Today
Total
관리 메뉴

dearbeany

[SpringBoot] Spring 프로젝트를 SpringBoot로 전환하기 (SpringBootBoard 실습) CORS Error해결부터 Swagger등록까지! 본문

Spring

[SpringBoot] Spring 프로젝트를 SpringBoot로 전환하기 (SpringBootBoard 실습) CORS Error해결부터 Swagger등록까지!

dearbeany 2022. 10. 27. 07:22

■ SpringBoot 의 특징 및 장점

- Spring 사용 시 개발자가 직접 해야만 했던 복잡한 설정을 해결

- 간편하고 자동화된 빌드 및 설정

- 프로젝트에 따라 자주 사용되는 라이브러리들 미리 조합(Best Practice)

- 복잡한 설정(XML) 하지 않아도 됨 (자동)

- 내장 서버를 제공해 WAS를 추가설치 안 해도 개발, 배포 손쉽게 가능 (독립실행)

- WAS 배포하지 않고도 실행할 수 있는 JAR파일로 개발 가능

 

 

지난 Spring 게시판 프로젝트를 SpringBoot를 이용한 게시판으로 만들어보자!

 

 

1. SpringBoot 프로젝트 생성 

- New > Spring Starter Project > SpringBootBoard 프로젝트 생성 (devtools, web, mysql, mybatis 추가)

- pom.xml에 다음을 추가

<!-- pom.xml -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-jasper</artifactId>
		</dependency>

 

 

2. 기존 Spring 게시판 코드 가져오기

- 다른 워크스페이스에 있는 프로젝트(SpringMVC_MyBatis_Board)의 파일 가져오는 법(복사대신)

import > General > File system > 등록할 프로젝트 선택 > 프로젝트 하위에서 가져올 부분만 선택 > src/main만 가져오겠다.

 

 


기존 Spring프로젝트를 SpringBoot로 바꿀 것이다.

그럼 Spring에서 설정했던 ① servlet-context.xml ② root-context.xml ③ web.xml의 차이점을 알아볼까?

참고:https://thiago6.tistory.com/70

더보기

xml 파일은 모두 객체(bean)을 정의한다.

 

(1) servlet-context.xml 

- servlet의 역할인 '요청' 과 관련된 객체를 정의한다.

- DispatcherServlet, url과 관련된 controller, @(어노테이션), ViewResolver, Interceptor, MultipartResolver 등에 대한 설정

<resources mapping="/resources/**" location="/resources/" />
    
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<beans:property name="prefix" value="/WEB-INF/views/" />
	<beans:property name="suffix" value=".jsp" />
</beans:bean>
	
<context:component-scan base-package="com.ssafy.board.controller" />

 

(2) root-context.xml

- Business Logic과 관련된 설정. Service, Repository(= DAO), DB

- (1)과 반대로 View와 관련되지 않은 객체를 정의한다.

 

<!-- Root Context: defines shared resources visible to all other web components -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
	<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
	<property name="url" value="jdbc:mysql://localhost:3306/ssafy_board?serverTimezone=UTC"/>
	<property name="username" value="root"/>
	<property name="password" value="ssafy"/>
</bean>
	
<!-- Mybatis를 사용하기 위한 SqlSessionFactory를 등록한다. -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	<property name="dataSource" ref="dataSource"/>
	<property name="mapperLocations" value="classpath*:mapper/**/*.xml"/>
	<property name="typeAliasesPackage" value="com.ssafy.board.model.dto"/>
</bean>
	
<!-- Mybatis에서 제공하는 scan 태그를 통해 dao interface들의 위치를 지정한다. -->
<mybatis-spring:scan base-package="com.ssafy.board.model.dao"/>
	
<context:component-scan base-package="com.ssafy.board.model.service"/>

 

(3) web.xml

- 설정을 위한 설정파일

- 최초로 WAS 구동 시, 각종 설정을 정의해준다.

- 여러 xml파일을 인식하도록 각 파일을 가리켜준다.

	<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/spring/root-context.xml</param-value>
	</context-param>
	
	<!-- Creates the Spring Container shared by all Servlets and Filters -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- Processes application requests -->
	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
		
	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
	<filter>
		<filter-name>encodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
	</filter>
	
	<filter-mapping>
		<filter-name>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

 

 


2. web.xml은 필요 없으므로 삭제 

- 기존 web.xml(.xml들을 설정하는 파일)은 root-context.xml servlet-context.xml에 대한 세팅파일이나, 이미 dispatcherServlet은 bean으로 등록 돼있고 서블릿은 알아서 매핑돼있다. 또한 알아서 utf-8설정 돼있기에 필터도 필요 없다.

- 결과적으로 web.xml 파일은 필요 없다!

 

 

 

3. application.properties 설정

# (1)
# Spring version) servlet-context.xml
#	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
#		<beans:property name="prefix" value="/WEB-INF/views/" />
#		<beans:property name="suffix" value=".jsp" />
#	</beans:bean>

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

# (2)
# Spring version) root-context.xml
#	<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
#		<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
#		<property name="url" value="jdbc:mysql://localhost:3306/ssafy_board?serverTimezone=UTC"/>
#		<property name="username" value="root"/>
#		<property name="password" value="ssafy"/>
#	</bean>

# SpringBoot version) db connection
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/ssafy_board?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=ssafy


# (3)
# Spring version ) root-context.xml
#	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
#		<property name="dataSource" ref="dataSource"/>
#		<property name="mapperLocations" value="classpath*:mapper/**/*.xml"/>
#		<property name="typeAliasesPackage" value="com.ssafy.board.model.dto"/>
#	</bean>

# SpringBoot version ) mybatis
mybatis.mapper-locations=classpath*:mapper/**/*.xml
mybatis.type-aliases-package=com.ssafy.board.model.dto

- Spring 프로젝트 root-context.xml에서 설정해주었던 내용인 dataSource, mybatis와 관련된 설정해주자.

 

● Spring의 root-context.xml 중 <context:component-scan>이 SpringBoot에선 필요 없는 이유?

→ 최상단에 있는 SpringBootApplication 클래스가 @SpringBootApplication 했으므로 @ComponentScan 을 갖고있기 때문이다. (@Component만 달면 bean등록됨 )

더보기

@SpringBootApplication 알아보기

https://dearbeany.tistory.com/69

package com.ssafy.board;

@SpringBootApplication
public class SpringBootBoardApplication {
..

→ com.ssafy.board 하위로 @Component 달리면 모두 bean으로 등록된다.

<!-- root-context.xml -->
<context:component-scan base-package="com.ssafy.board.model.service"></context:component-scan>

 

 

 

 

 

4. DBConfig생성 - MyBatis 

- xml 쓰지않고 java파일을 설정파일로 쓰겠다(JavaConfig 방식) → @Configuration

- Mapper로 쓰기 위해 → @MapperScan(basePackges = {"여러 개의 패키지면 배열로"})

@Configuration
@MapperScan(basePackages = "com.ssafy.board.model.dao")
public class DBConfig {
}
<!-- root-context.xml 의 아래내용은 삭제해도 좋다 -->
<mybatis-spring:scan base-package="com.ssafy.board.model.dao"/>

 

 

결과적으로 root-context.xml의 설정내용을 모두 옮겨왔다. 파일을 삭제해도 무방.

이제부터는 servlet-context.xml의 설정을 옮겨와보자.

 

 

 

 

5. WebCofig 생성 - ViewResolver

- WebConfig를 JavaConfig방식으로 작성한다.

- @Bean : 어노테이션을 통해 component-scan 해서 bean으로 등록함

- @Value : application.properties의 설정(prefix, suffix값)을 가져오기 위해 사용

@Configuration
public class WebConfig {
	@Value("${spring.mvc.view.prefix}")
	private String prefix;

	@Value("${spring.mvc.view.suffix}")
	private String suffix;

	@Bean
	public ViewResolver internalResourceViewResolver() {
		InternalResourceViewResolver resolver = new InternalResourceViewResolver();
		resolver.setPrefix(prefix);
		resolver.setSuffix(suffix);
		resolver.setViewClass(JstlView.class);
		return resolver;
	}
    ..
<!-- servlet-context.xml의 아래 내용은 이제 삭제해도 좋다 -->
	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
	</beans:bean>

- 참고로 SpringBoot에서 jsp 사용을 위한 설정이므로 REST 사용 시엔 필요없다!

 

 

 

 

6. WebCofing 생성 - resources 

- /** : 모든 요청을 처리

- 더이상 resources 폴더 없으므로 classpath에서 static으로 들어오는 것을 처리

- @EnableWebMvc 추가, WebMvcConfigurer를 implements하자.

더보기

@EnableWebMvc

-  이를 @Configuration이 붙은 설정 클래스에 붙이면 스프링이 제공하는 웹과 관련된 최신 전략들을 기반으로 설정을 자동화한다.

@WebMvcConfigurer

- @EnableWebMvc를 통해 자동 설정되는 빈들의 설정자이다. 다음은 메소드의 의미이다.

add~: 기본 설정이 없는 빈들에 대하여 새로운 빈을 추가함

ex) intercepter도 추가할 거면 메소드 addIntercepters() 

출처: https://mangkyu.tistory.com/176

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
	}
}
<!-- servlet-context.xml의 아래 내용은 이제 삭제해도 좋다 -->
<resources mapping="/resources/**" location="/resources/" />

 

이제 servlet-context.xml 의 설정을 모두 옮겨왔으니 파일 삭제해도 좋다!

 

 

 

 

결과적으로
web.xml / root-context.xml / servlet-context.xml 은 모두 필요없다는 걸 확인했다!
SpringBoot는 Spring과 달리 xml설정이 별도로 필요없다.

 

 

 

 

 

 

7. REST도 해보자

- RESTController 생성 → @RestController @RequestMapping 추가

- swagger 혹은 talend로 확인해보기 

@RestController
@RequestMapping("/api")
public class BoardRestController {
	@Autowired
	private BoardService boardSevice;

	@GetMapping("/board")
	public ResponseEntity<List<Board>> list(){
		return new ResponseEntity<List<Board>>(boardSevice.getBoardList(), HttpStatus.OK);
	}
}

talend

 

 

8. AJAX 통신 

- Ajax요청을 보내서 값을 가져와서 보여준다

@Controller
public class BoardController {
	@Autowired
	private BoardService boardService;

	@RequestMapping("list2")
	public String list2(Model model) {
		return "/board/list2";
	}
	..

list2.jsp

- \${board.id} 앞에 '\(백슬래시)' 붙여줘야 JSP로 해석하는 걸 막고 JavaScript로 해석해준다!

	<script>
		const xhr = new XMLHttpRequest();
		// xhr이 요청을 보냈을 때 0~4까지 5개의 상태가 존재한다.
		// 각각의 상태가 변환될 때마다 이 메소드를 동작시킨다
		xhr.onreadystatechange = function(){
			if(xhr.readyState == 4){ // 서버에 요청한 데이터가 처리완료됐으면 (에러 or 정상)
				if(xhr.status == 200){ // HttpStatus를 OK로 보냈으므로 
//					console.log(xhr.response) // 데이터 잘 넘어오는지 찍어보자
					const list = JSON.parse(xhr.response)
					let html = "";
					
					for(let board if list){
						html += '
							<tr>
								<td>\${board.id }</td>				
								<td>\${board.title }</td>				
								<td>\${board.writer }</td>				
								<td>\${board.viewCnt }</td>				
								<td>\${board.regDate }</td>				
							</tr>
						'
					}
					document.getElementById("boardTbody").innerHTML = html;
				}
			}
		}
		xhr.open("GET", "/api/board"); // 어떤 페이지에 어떤 방식으로 처리를 할 지 한다
		xhr.send(); // 데이터 요청을 진행
	</script>

 

 

 

 

■ CORS Error

교차 출처 리소스 공유(Cross-Origin Resource Sharing), 추가 HTTP헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제. 웹 애플리케이션은 리소스가 자신의 출처(도메인, 프로토콜, 포트)와 다를 때 교차 출처 HTTP 요청을 실행한다.

- 하나의 페이지에선 똑같은 자료를 얻을 때 같은 출처를 요구한다.

- 같은 페이지의 A데이터는 Naver, B데이터는 Kakao에서 얻어오고, 자체적으로 돌리는 서버주소도 존재한다면? 내가 보내는 요청과 응답을 하는 주소의 출처가 다르다.

- 브라우저가 '동일 출처가 아니고 Cross된 출처이므로' 발생하는 이슈를 막는다.

- 단 talend, Post man의 경우 내 것에서 요청을 보내는 것이므로 CORS error가 발생하지 않음

- 그러나 똑같은 주소이지만 다른 포트를 이용해서(= 다른 컴퓨터) 통신할 땐 CORS error 발생함

- 5500번에서 동작시킨다 → 버튼 클릭 → 8080번으로 요청을 보내면 → CORS 에러발생

- 추후 Vue를 써도 같은 문제가 발생한다!

 

Cors Error 해결하려면?

(1) 프록시 서버 활용

(2) 헤더 추가

(3) @CrossOrigin 활용 → 이 방법을 사용해보자!

@RestController
@RequestMapping("/api")
@CrossOrigin("http://127.0.0.1:5500") // 5500 포트번호로 요청이 오는 것은 호출하겠다 
@CrossOrigin("*") // 일반적으로 사용자의 IP, 포트번호를 등록하는 것은 말이 안 되므로 모든 것을 허용한다 
public class BoardRestController {
..

- 단 이러한 설정(컨트롤러 별 설정)을 매번 해야할까? @CrossOrigin을 매번 적어주기 번거롭다.

- 전역설정을 해보자. 이전 WebConfig는 웹과 관련된 설정이 담긴 클래스였다. addCorsMappings()를 추가해보자.

public class WebConfig implements WebMvcConfigurer {
	..
	@Override
	public void addCorsMappings(CorsRegistry registry) {
		registry.addMapping("/**").allowedOrigins("*").allowedMethods("GET", "POST");
	}
    // '/**' : 하위 경로 모두, '*' : 모든 걸 허용 'GET, POST' 방식만 허용한다(UPDATE, DELETE.. 등은 허용하지X)
 }

 

 

 

- 아래는 완성된 WebConfig 클래스

더보기
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

	@Value("${spring.mvc.view.prefix}")
	private String prefix;

	@Value("${spring.mvc.view.suffix}")
	private String suffix;

	@Bean
	public ViewResolver internalResourceViewResolver() {
		InternalResourceViewResolver resolver = new InternalResourceViewResolver();
		resolver.setPrefix(prefix);
		resolver.setSuffix(suffix);
		resolver.setViewClass(JstlView.class);

		return resolver;
	}

	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		//넣지 않았을 때와 넣고 나서  (list2.jsp 에서 이미지를 읽을 수 있는지 없는지가 갈림 확인해볼것)
		registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");

		registry.addResourceHandler("/swagger-ui/**")
				.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/");
	}

	@Override
	public void addCorsMappings(CorsRegistry registry) {
		registry.addMapping("/**").allowedOrigins("*").allowedMethods("GET", "POST");
	}

}

 

■ SpringBoot에서 Swagger 등록하는 법?

(1) pom.xml에 springfox-boot-start 추가

		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-boot-starter</artifactId>
			<version>3.0.0</version>
		</dependency>

 

(2) WebConfig에서 메소드 추가

- Spring 2.6이상 버전으로 올라가며 swagger 3.0과 충돌하였다.

- 이전 프로젝트에서 swagger의 resources를 등록했었다.

- WebConfig 클래스의 addResourceHandlers에 추가해주자.

<!-- servlet-context.xml -->
<!-- swagger 3.0.0 -->
<resources location="classpath:/META-INF/resources/webjars/springfox-swagger-ui/" mapping="/swagger-ui/**"></resources>
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		registry.addResourceHandler("/swagger-ui/**")
				.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/");
	}
    ..

 

(3) SwaggerConfig 생성

@Configuration
public class SwaggerConfig {
	@Bean
	public Docket api() {
		return new Docket(DocumentationType.SWAGGER_2).select()
				.apis(RequestHandlerSelectors.basePackage("com.ssafy.board.controller"))
				.paths(PathSelectors.ant("/api/**")).build().apiInfo(apiInfo());
	}
	private ApiInfo apiInfo() {
		return new ApiInfoBuilder().title("Dearbeany Swaager").description("swagger 테스트 중이다!!!").version("v1").build();
	}
}

 

http://localhost:8080/swagger-ui/index.html