dearbeany
[SpringBoot] Spring 프로젝트를 SpringBoot로 전환하기 (SpringBootBoard 실습) CORS Error해결부터 Swagger등록까지! 본문
[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의 차이점을 알아볼까?
● 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 알아보기
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()
@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);
}
}
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();
}
}
'Spring' 카테고리의 다른 글
[SpringBoot] KeyProperty과 useGeneratedKeys | resultType과 resultMap (Product 실습 에러 해결) (0) | 2022.10.28 |
---|---|
[SpringBoot] Spring과의 차이 | SpringBoot 프로젝트 Generate-Import-Run-View (0) | 2022.10.26 |
[Spring] Spring ~ REST 과목평가 예상문제 (1) | 2022.10.25 |
[REST] REST API @Annotation별 실습 (0) | 2022.10.24 |
[MyBatis] Spring 과 MyBatis 연동 및 세팅 | 게시판 CRUD 실습 (0) | 2022.10.21 |