최근 개발되는 서비스들은 마이크로서비스 아키텍처(MSA)를 주로 채택
MSA란 애플리케이션이 가지고 있는 기능(서비스)이 하나의 비지니스 범위만 가지는 형태
12.1 RestTemplate이란?
✅ RestTemplate 특징
▪ HTTP 프로토콜의 메서드에 맞는 여러 메서드를 제공
▪ RESTful 형식을 갖춘 템플릿
▪ HTTP 요청 후 JSON, XML, 문자열 등의 다양한 형식으로 응답 받을 수 있다.
▪ 블로킹(blocking) I/O 기반의 동기 방식을 사용
▪ 다른 API를 호출할 때 HTTP 헤더에 다양한 값을 설정할 수 있다.
12.1.1 RestTemplate의 동작 원리
12.1.2 RestTemplate의 대표적인 메서드
12.2 RestTemplate 사용하기
12.2.1 서버 프로젝트 생성하기
1. RestTemplate의 동작을 확인하기 위한 서버 용도의 프로젝트를 생성
2. 실습을 위해 한 컴퓨터 안에서 두 개의 프로젝트를 가동시켜야 하기 때문에 톰캣의 포트 변경
3. 프로젝트에는 spring-boot-starter-web 모듈만 의존성으로 추가
4. 애플리케이션이 가동되는 톰캣의 포트를 변경하기 위해 'application.properties' 파일에 server.port 속성 추가
server.port = 9090
컨트롤러에는 GET과 POST 메서드 형식의 요청을 받기 위한 코드 구성
@RestController
@RequestMapping("/api/v1/crud-api")
public class CrudController {
@GetMapping
public String getName() {
return "Flature";
}
@GetMapping(value = "/{variable}")
public String getVariable(@PathVariable String variable) {
return variable;
}
@GetMapping("/param")
public String getNameWithParam(@RequestParam String name) {
return "Hello. " + name + "!";
}
@PostMapping
public ResponseEntity<MemberDto> getMember(
@RequestBody MemberDto request,
@RequestParam String name,
@RequestParam String email,
@RequestParam String organization
) {
System.out.println(request.getName());
System.out.println(request.getEmail());
System.out.println(request.getOrganization());
MemberDto memberDto = new MemberDto();
memberDto.setName(name);
memberDto.setEmail(email);
memberDto.setOrganization(organization);
return ResponseEntity.status(HttpStatus.OK).body(memberDto);
}
@PostMapping(value = "/add-header")
public ResponseEntity<MemberDto> addHeader(@RequestHeader("my-header") String header,
@RequestBody MemberDto memberDTO) {
System.out.println(header);
return ResponseEntity.status(HttpStatus.OK).body(memberDTO);
}
}
public class MemberDto {
private String name;
private String email;
private String organization;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getOrganization() {
return organization;
}
public void setOrganization(String organization) {
this.organization = organization;
}
@Override
public String toString() {
return "MemberDTO{" +
"name='" + name + '\'' +
", email='" + email + '\'' +
", organization='" + organization + '\'' +
'}';
}
}
12.2.2 RestTemplate 구현하기
1. RestTemplate을 포함하는 프로젝트 생성
▪ groupId : com.springboot
▪ artifactId : rest
▪ name : rest
▪ Developer Tools : Lombok, Spring Configuration Processor
▪ Web : Spring Web
2. SwaggerConfiguration 클래스 파일과 의존성 추가
✔️ RestTemplate은 이미 spring-boot-starter-web 모듈에 포함된 기능 -> pom.xml에 별도로 의존성을 추가 X
✔️ 프로젝트 구조로는 클라이언트로부터 요청을 받는 컨트롤러와 RestTemplate을 활용해 다른 서버에 통신 요청을 하는 서비스 계층으로 작성
✅ GET 형식의 RestTemplate 작성하기
@Service
public class RestTemplateService {
public String getName() {
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:9090")
.path("/api/v1/crud-api")
.encode()
.build()
.toUri();
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);
return responseEntity.getBody();
}
public String getNameWithPathVariable() {
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:9090")
.path("/api/v1/crud-api/{name}")
.encode()
.build()
.expand("Flature") // 복수의 값을 넣어야할 경우 , 를 추가하여 구분
.toUri();
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);
return responseEntity.getBody();
}
public String getNameWithParameter() {
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:9090")
.path("/api/v1/crud-api/param")
.queryParam("name", "Flature")
.encode()
.build()
.toUri();
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);
return responseEntity.getBody();
}
public final UriComponentsBuilder encode() {
return encode(StandardCharsets.UTF-8);
}
✅ POST 형식의 RestTemplate 작성
public ResponseEntity<MemberDto> postWithParamAndBody() {
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:9090")
.path("/api/v1/crud-api")
.queryParam("name", "Flature")
.queryParam("email", "flature@wikibooks.co.kr")
.queryParam("organization", "Wikibooks")
.encode()
.build()
.toUri();
MemberDto memberDto = new MemberDto();
memberDto.setName("flature!!");
memberDto.setEmail("flature@gmail.com");
memberDto.setOrganization("Around Hub Studio");
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<MemberDto> responseEntity = restTemplate.postForEntity(uri, memberDto,
MemberDto.class);
return responseEntity;
}
public ResponseEntity<MemberDto> postWithHeader() {
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:9090")
.path("/api/v1/crud-api/add-header")
.encode()
.build()
.toUri();
MemberDto memberDTO = new MemberDto();
memberDTO.setName("flature");
memberDTO.setEmail("flature@wikibooks.co.kr");
memberDTO.setOrganization("Around Hub Studio");
RequestEntity<MemberDto> requestEntity = RequestEntity
.post(uri)
.header("my-header", "Wikibooks API")
.body(memberDTO);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<MemberDto> responseEntity = restTemplate.exchange(requestEntity,
MemberDto.class);
return responseEntity;
}
✅ 프로젝트에서 쉽게 API를 호출할 수 있도록 Swagger 설정
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
//.apis(RequestHandlerSelectors.any())
.apis(RequestHandlerSelectors.basePackage("com.springboot.rest"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Spring Boot Open API Test with Swagger")
.description("설명 부분")
.version("1.0.0")
.build();
}
}
✅ 위에서 작성한 서비스 코드를 연결하는 컨트롤러 코드 작성
@RestController
@RequestMapping("/rest-template")
public class RestTemplateController {
private final RestTemplateService restTemplateService;
public RestTemplateController(RestTemplateService restTemplateService) {
this.restTemplateService = restTemplateService;
}
@GetMapping
public String getName() {
return restTemplateService.getName();
}
@GetMapping("/path-variable")
public String getNameWithPathVariable(){
return restTemplateService.getNameWithPathVariable();
}
@GetMapping("/parameter")
public String getNameWithParameter(){
return restTemplateService.getNameWithParameter();
}
@PostMapping
public ResponseEntity<MemberDto> postDto(){
return restTemplateService.postWithParamAndBody();
}
@PostMapping("/header")
public ResponseEntity<MemberDto> postWithHeader(){
return restTemplateService.postWithHeader();
}
}
➡️ 여기까지 작성했다면 애플리케이션을 실행하고 postDto() 메서드에 해당하는 POST API를 호출하면 아래 결과 출력
* 앞서 생성한 2개의 프로젝트가 모두 가동돼 있는 상태에서 진행되어야 함!
flature!!
flature@gmail.com
Around Hub Studio
public ResponseEntity<MemberDto> postWithHeader() {
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:9090")
.path("/api/v1/crud-api/add-header")
.encode()
.build()
.toUri();
MemberDto memberDTO = new MemberDto();
memberDTO.setName("flature");
memberDTO.setEmail("flature@wikibooks.co.kr");
memberDTO.setOrganization("Around Hub Studio");
RequestEntity<MemberDto> requestEntity = RequestEntity
.post(uri)
.header("my-header", "Wikibooks API")
.body(memberDTO);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<MemberDto> responseEntity = restTemplate.exchange(requestEntity,
MemberDto.class);
return responseEntity;
}
12.2.3 RestTemplate 커스텀 설정
✅ 아파치의 HttpClient를 사용하기 위해 아래와 같이 의존성 추가
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpClient</artifactId>
</dependency>
✅ RestTemplate 생성자
public RestTemplate restTemplate() {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
HttpClient client = HttpClientBuilder.create()
.setMaxConnTotal(500)
.setMaxConnPerRoute(500)
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setMaxConnTotal(500)
.setMaxConnPerRoute(500)
.build();
factory.setHttpClient(httpClient);
factory.setConnectTimeout(2000);
factory.setReadTimeout(5000);
RestTemplate restTemplate = new RestTemplate(factory);
return restTemplate;
}
12.3 WebClient란?
webClient의 특징
▪ 논블로킹(Non-Blocking) I/O를 지원
▪ 리액티브 스트림(Reactive Streams)의 백 프레셔(Back Pressure)를 지원
▪ 적은 하드웨어 리소스로 동시성을 지원
▪ 함수형 API를 지원
▪ 동기, 비동기 상호작용을 지원
▪ 스트리밍을 지원
12.3.1 WebClient 구성
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
12.4 WebClient 사용하기
✅ WebClient 구현
✅ 서버 프로젝트의 GET 메서드 컨트롤러에 접근할 수 있는 WebClient 생성
@Service
public class WebClientService {
public String getName() {
WebClient webClient = WebClient.builder()
.baseUrl("http://localhost:9090")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
return webClient.get()
.uri("/api/v1/crud-api")
.retrieve()
.bodyToMono(String.class)
.block();
}
public String getNameWithPathVariable() {
WebClient webClient = WebClient.create("http://localhost:9090");
ResponseEntity<String> responseEntity = webClient.get()
.uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api/{name}")
.build("Flature"))
.retrieve().toEntity(String.class).block();
ResponseEntity<String> responseEntity1 = webClient.get()
.uri("/api/v1/crud-api/{name}", "Flature")
.retrieve()
.toEntity(String.class)
.block();
return responseEntity.getBody();
}
public String getNameWithParameter() {
WebClient webClient = WebClient.create("http://localhost:9090");
return webClient.get().uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api")
.queryParam("name", "Flature")
.build())
.exchangeToMono(clientResponse -> {
if (clientResponse.statusCode().equals(HttpStatus.OK)) {
return clientResponse.bodyToMono(String.class);
} else {
return clientResponse.createException().flatMap(Mono::error);
}
})
.block();
}
예제에서 소개된 메서드 외 builder()를 사용할 경우 확장할 수 있는 메서드
▪ defaultHeader() : WebClient의 기본 헤더 설정
▪ defaultCookie() : WebClient의 기본 쿠키 설정
▪ defaultUriVariable() : WebClient의 기본 URI 확장값 설정
▪ filter() : WebClient에서 발생하는 요청에 대한 필터 설정
WebClient webClient = WebClient.create("http://localhost:9090");
WebClient clone = webClient.mutate().build();
✅ POST 요청 예제
public ResponseEntity<MemberDto> postWithParamAndBody() {
WebClient webClient = WebClient.builder()
.baseUrl("http://localhost:9090")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
MemberDto memberDTO = new MemberDto();
memberDTO.setName("flature!!");
memberDTO.setEmail("flature@gmail.com");
memberDTO.setOrganization("Around Hub Studio");
return webClient.post().uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api")
.queryParam("name", "Flature")
.queryParam("email", "flature@wikibooks.co.kr")
.queryParam("organization", "Wikibooks")
.build())
.bodyValue(memberDTO)
.retrieve()
.toEntity(MemberDto.class)
.block();
}
public ResponseEntity<MemberDto> postWithHeader() {
WebClient webClient = WebClient.builder()
.baseUrl("http://localhost:9090")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
MemberDto memberDTO = new MemberDto();
memberDTO.setName("flature!!");
memberDTO.setEmail("flature@gmail.com");
memberDTO.setOrganization("Around Hub Studio");
return webClient
.post()
.uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api/add-header")
.build())
.bodyValue(memberDTO)
.header("my-header", "Wikibooks API")
.retrieve()
.toEntity(MemberDto.class)
.block();
}
[섹션 0. 시큐리티]
스프링부트 시큐리티 1강- 환경설정
1. MySQL DB 및 사용자 생성
create user 'cos'@'%' identified by 'cos1234';
GRANT ALL PRIVILEGES ON *.* TO 'cos'@'%';
create database security;
use security;
2. 프로젝트 생성
▪ Lombok
▪ Spring Boot DevTools
▪ Spring Data JPA
▪ MySQL Driver
▪ Spring Security
▪ Mustache
▪ Spring Web
3. application.yml 작성
server:
port: 8080
servlet:
context-path: /
encoding:
charset: UTF-8
enabled: true
force: true
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/security?serverTimezone=Asia/Seoul
username: cos
password: cos1234
mvc:
view:
prefix: /templates/
suffix: .mustache
jpa:
hibernate:
ddl-auto: create #create update none
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
show-sql: true
4. controller 패키지 생성 후 IndexController 생성
@Controller // View를 리턴
public class IndexController {
// localhost:8080/
// localhost:8080
@GetMapping({"", "/"})
public String index() {
// 머스테치(Mustache)
return "index"; // index 파일은 View
}
}
5. templates에 index 파일 생성
package com.cos.securityex01.config;
import org.springframework.boot.web.servlet.view.MustacheViewResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer{
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
MustacheViewResolver resolver = new MustacheViewResolver();
resolver.setCharset("UTF-8");
resolver.setContentType("text/html;charset=UTF-8");
resolver.setPrefix("classpath:/templates/");
resolver.setSuffix(".html");
registry.viewResolver(resolver);
}
}
스프링부트 시큐리티 2강- 시큐리티 설정
@Controller
public class IndexController {
@Autowired
private UserRepository userRepository;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@GetMapping({ "", "/" })
public @ResponseBody String index() {
return "인덱스 페이지입니다.";
}
@GetMapping("/user")
public @ResponseBody String user(@AuthenticationPrincipal PrincipalDetails principal) {
System.out.println("Principal : " + principal);
// iterator 순차 출력 해보기
Iterator<? extends GrantedAuthority> iter = principal.getAuthorities().iterator();
while (iter.hasNext()) {
GrantedAuthority auth = iter.next();
System.out.println(auth.getAuthority());
}
return "유저 페이지입니다.";
}
@GetMapping("/admin")
public @ResponseBody String admin() {
return "어드민 페이지입니다.";
}
//@PostAuthorize("hasRole('ROLE_MANAGER')")
//@PreAuthorize("hasRole('ROLE_MANAGER')")
@Secured("ROLE_MANAGER")
@GetMapping("/manager")
public @ResponseBody String manager() {
return "매니저 페이지입니다.";
}
@GetMapping("/login")
public String login() {
return "login";
}
@GetMapping("/joinForm")
public String login() {
return "joinForm";
}
@GetMapping("/loginForm")
public String login() {
return "loginForm";
}
@GetMapping("/join")
public String join() {
return "join";
}
@PostMapping("/joinProc")
public String joinProc(User user) {
System.out.println("회원가입 진행 : " + user);
String rawPassword = user.getPassword();
String encPassword = bCryptPasswordEncoder.encode(rawPassword);
user.setPassword(encPassword);
user.setRole("ROLE_USER");
userRepository.save(user);
return "redirect:/";
}
@Secured("ROLE_ADMIN")
@GetMapping("/info")
public @ResponseBody String info() {
return "개인정보";
}
@PreAuthorize("hasRole('ROLE_MANAGER' or hasRole('ROLE_ADMIN)")
@GetMapping("/data")
public @ResponseBody String data() {
return "데이터정보";
}
@Configuration // IoC 빈(bean)을 등록
@EnableWebSecurity // 필터 체인 관리 시작 어노테이션 (스프링 시큐리티 필터가 스프링 필터체인에 등록됨)
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) // 특정 주소 접근시 권한 및 인증을 위한 어노테이션 활성화
public class SecurityConfig extends WebSecurityConfigurerAdapter{
// @Bean : 해당 메서드의 리턴되는 오브젝트를 IoC로 등록해줌
@Bean
public BCryptPasswordEncoder encodePwd() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/user/**").authenticated()
//.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')")
//.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN') and hasRole('ROLE_USER')")
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/loginProc")
.defaultSuccessUrl("/");
}
}
스프링부트 시큐리티 3강 - 시큐리티 회원가입
스프링부트 시큐리티 4강 - 시큐리티 로그인
스프링부트 시큐리티 5강 - 시큐리티 권한처리
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 페이지</title>
</head>
<body>
<h1>로그인 페이지</h1>
<hr/>
<!-- 시큐리티는 x-www-form-url-encoded 타입만 인식 -->
<form action="/loginProc" method="post">
<input type="text" name="username" />
<input type="password" name="password" />
<button>로그인</button>
</form>
<a href="/joinForm"> 회원가입을 아직 하지 않으셨나요?</a>
</body>
</html>
@Data
@Entity
public class User {
@Id // primary key
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
private String email;
private String role; //ROLE_USER, ROLE_ADMIN
@CreationTimestamp
private Timestamp createDate;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원가입 페이지</title>
</head>
<body>
<h1>회원가입 페이지</h1>
<hr/>
<form action="/joinProc" method="post">
<input type="text" name="username" placeholder="Username"/> <br/>
<input type="password" name="password" placeholder="Password"/> <br/>
<input type="email" name="email" placeholder="Email"/> <br/>
<button>회원가입</button>
</form>
</body>
</html>
// JpaRepository 를 상속하면 자동 컴포넌트 스캔됨.
// CRUD 함수를 JpaRepository가 들고 있음
// @Repository 어노테이션이 없어도 IoC됨 (자동으로 Bean으로 등록됨)
public interface UserRepository extends JpaRepository<User, Integer>{
// findBy 규칙 -> Username 문법
// select * from user where username = 1?
// Jpa Naming 전략
// SELECT * FROM user WHERE username = 1?
User findByUsername(String username);
// SELECT * FROM user WHERE username = 1? AND password = 2?
// User findByUsernameAndPassword(String username, String password);
// @Query(value = "select * from user", nativeQuery = true)
// User find마음대로();
}
// 시큐리티가 /login 주소 요청이 오면 낚아채서 로그인을 진행시킴
// 로그인을 진행이 완료가 되면 시큐리티 session을 만들어줌 (Security ContextHolder)
// 오브젝트 => Authentication 타입의 객체이여야 함
// Authentication 안에는 User 정보가 있어야 함
// User 오브젝트의 타입 => UserDetails 타입 객체이여야 함
//Sercurity Session => Authentication => UserDetails
// Authentication 객체에 저장할 수 있는 유일한 타입
@Data
public class PrincipalDetails implements UserDetails{
private User user;
public PrincipalDetails(User user) {
super();
this.user = user;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collet = new ArrayList<GrantedAuthority>();
collet.add(()->{ return user.getRole();});
return collet;
}
}
@Service
public class PrincipalDetailsService implements UserDetailsService{
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if(user == null) {
return null;
}
return new PrincipalDetails(user);
}
}
Quiz
1. ( RestTemplate )은 스프링에서 HTTP 통신 기능을 손쉽게 사용하도록 설계된 템플릿이다.
2. RestTemplate 동작 원리 중에서 외부 API로 요청을 보내게 되면 RestTemplate에서는 ( HttpMessageConverter )를 통해 ( RequestEntity )를 요청 메세지로 변환한다.
3. RestTemplate을 생성하고 사용하는 방법 중 가장 보편적인 방법으로, ( UriComponentsBuilder )는 스프링 프레임워크에서 제공하는 클래스로서 여러 파라미터를 연결해서 URI 형식으로 만드는 기능을 수행한다.
4. RestTemplate에서는 기본적으로 커넥션 풀을 제공하지 않지만, 이 기능을 활성화해서 사용하기 위해 가장 대표적으로 아파치에서 제공하는 ( HttpClient )로 대체해서 사용한다.
5. 현업에서는 RestTemplate을 많이 사용하지만, 최근 지원이 중단되어 ( WebClient )를 사용할 것을 권고하고 있다.
6. WebClient를 생성하는 방법은 ( create() ) 메서드를 이용한 생성과 ( builder() )를 이용한 생성으로 크게 두 가지가 존재한다.
7. WebClient의 특징 중 하나로 WebClient는 기본적으로 ( 논블로킹(Non-Blocking ) 방식으로 동작하기 때문에 기존에 사용하던 코드의 구조를 블로킹 구조로 바꿔줄 필요가 있다.
Code Quiz
1. GET 형식의 RestTemplate 작성해보기
@Service
public class RestTemplateService {
public String getName() {
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:9090")
.path("/api/v1/crud-api")
.encode()
.build()
.toUri();
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);
return responseEntity.getBody();
}
2. 서버 프로젝트의 GET 메서드 컨트롤러에 접근할 수 있는 WebClient 생성 코드 작성해보기
public String getName() {
WebClient webClient = WebClient.builder()
.baseUrl("http://localhost:9090")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
return webClient.get()
.uri("/api/v1/crud-api")
.retrieve()
.bodyToMono(String.class)
.block();
}
[출처] 장정우, 『스프링 부트 핵심가이드 스프링 부트를 활용한 애플리케이션 개발 실무』, 위키북스(2022), p349-372
ⓒ 다다
[스프링 3팀] 13장. 서비스 인증과 권한 부여 (1) | 2024.01.05 |
---|---|
[스프링 3팀] 10장. 유효성 검사와 예외 처리 (2) | 2023.12.22 |
[스프링3] 9장 연관관계 매핑 (1) | 2023.12.01 |
[스프링3] 8장. Spring Data JPA 활용 (0) | 2023.11.24 |
[스프링 3] 07 테스트 코드 작성하기 (0) | 2023.11.17 |