-
80일차 - RESTAPI, Properties 활용, Spring FW 특징, AOP 활용(로그인 처리)백엔드(웹 서버, WAS)/Spring Boot 2024. 5. 23. 14:38
- url 만으로도 원하는 정보를 얻어오거나 특정한 요청을 하는 것 : REST API
- 사용 설명서 포함(첫 번째, 두 번째, 세 번째가 무엇을 의미하는지)
- get/java/~
- 자바스크립트에서 웹 서버로 직접 요청하는 방식 (보안성 떨어짐)
- 실제로는 서버 대 서버로 요청 많이 한다.
<서버가 한 대 일 경우>
13_WebClient (서버 대 서버 요청)
프로젝트 생성 시 라이브러리 설정 - webflux
→ 서버 대 서버 통신이 가능하도록 해주는 라이브러리+
pom.xml
<!-- WebClient 라이브러리(서버 대 서버 통신이 가능하도록 해주는 라이브러리) --> <!-- 프로젝트 생성 시 webflux 로 검색해서 찾으면 된다. --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
SendController
@RestController public class SendController { Logger logger = LoggerFactory.getLogger(getClass()); private final APIService service; public SendController(APIService service) { // 생성자 주입 this.service = service; } @GetMapping(value="/get/send/{val}") public Map<String, String> getSend(@PathVariable String val){ logger.info("param : "+val); return service.getSend(val); } @PostMapping(value="/post/send/{val}") public List<Map<String, Object>> postSend(@PathVariable String val, @RequestHeader Map<String, String> header){ logger.info("val : "+val); logger.info("header : "+header); String key = header.get("authorization"); logger.info("key = "+key); return service.postSend(val,key); } @GetMapping(value="/get/fluxTest") public List<Map<String, Object>> fluxTest(){ return service.fluxTest(); } }
RecvController
@RestController public class RecvController { Logger logger = LoggerFactory.getLogger(getClass()); // map으로 보냈기 때문에 map 형식으로 받아줘야 함 @GetMapping(value="/return/{msg}") public Map<String, String> getReturn(@PathVariable String msg){ logger.info("저쪽 서버로 부터 보낸 내용 : "+msg); Map<String, String> map = new HashMap<String, String>(); map.put("msg","네가 보낸 메세지 : "+msg); return map; } @PostMapping(value="/listReturn") public List<Map<String, Object>> listReturn(String cnt, @RequestHeader Map<String, String> header){ logger.info("header : "+header); int count = Integer.parseInt(cnt); String key = header.get("authorization"); logger.info("cnt = "+cnt); logger.info("key = "+key); List<Map<String, Object>> list = new ArrayList<Map<String,Object>>(); Map<String, Object> map = null; for (int i = 1; i < count; i++) { map = new HashMap<String, Object>(); map.put("no", i); map.put("name", "Kim"); map.put("salary", i*100000); list.add(map); } return list; } @PostMapping(value="/fluxReturn") public List<Map<String, Object>> fluxReturn(@RequestBody Map<String, Object> param){ logger.info("json param : "+param); List<Map<String, Object>> list = new ArrayList<Map<String,Object>>(); Map<String, Object> map = null; for (int i = 1; i < 11; i++) { map = new HashMap<String, Object>(); map.put("no", i); map.put("name", "Lee"); map.put("salary", i*100000); list.add(map); } return list; } }
APIService
@Service public class APIService { Logger logger = LoggerFactory.getLogger(getClass()); // 다른 서버로 요청을 보내고, 응답을 받는 기능 만들기 // WebClient 는 Spring5.0 에서 부터 지원하는 라이브러리이다. // HttpConnection(Spring 기본 내장) -> RestTemplate 등을 사용 했었다. // Non-Blocking 방식을 지원하기 때문에 비동기 처리가 가능하여 속도 면에서 우월하다. (비동기는 기다리지 않는다. 먼저오면 먼저 수행)-> 통신 양이 많으면 유리하다. // get 방식 전송과 post 방식 전송이 조금 다르다. public Map<String, String> getSend(String msg){ //1. 어디로 보낼지 WebClient client = WebClient.create("http://localhost:8080"); //2. 전송 방식 지정 + 3. 상세 url 추가(있으면) + 4. 전송 // retrieve() : 전송 후 body 값을 받아온다. // exchange() : 전송 후 상태 값 헤더 값도 받아온다.(테스트 이외에는 잘 사용하지 않음) // bodyToMono() : 동기 통신 시 사용(한번에 1개 요청 처리 시) // bodyToFlux() : 비동기 통신 시 사용(한번에 여러 개 요청을 처리 시) Mono<Map> mono = client.get().uri("/return/"+msg).retrieve() .bodyToMono(Map.class); //5. 받을 방식 지정 // 6. 받아온 Mono 로 부터 자바 객체를 빼 온다. Map<String, String> resp = mono.block(); logger.info("response : "+resp); return resp; } public List<Map<String, Object>> postSend(String val, String key) { //1. 어디로 보낼지 WebClient client = WebClient.create("http://localhost:8080"); // 2. 파라메터 추가 FormInserter<String> form = BodyInserters.fromFormData("cnt", val); // 파라메터를 추가하고 싶다면 아래 방법도 있음 (MultiValueMap 에 담아서 한번에 보내는게 더 낫다.) // form.with("name", "value"); //3. 전송 방식 지정 + 4. 상세 url 추가(있으면) + 5. 헤더 값 + 6. 파라메터를 담은 form 추가 + 7. 전송 Mono<List> mono = client.post().uri("/listReturn").header("authorization", key).body(form).retrieve().bodyToMono(List.class); //List<Map<String, Object>> list = mono.block(); // 이 방식이 간단하긴 하지만 속도 면에서 다른 방식으로 추천한다. List<Map<String, Object>> list = mono.flux().toStream().findFirst().get(); logger.info("response : "+list); return list; } public List<Map<String, Object>> fluxTest() { //1. 어디로 보낼지 WebClient client = WebClient.create("http://localhost:8080"); // 2. 파라메터 추가 Map<String, Object> json = new HashMap<String, Object>(); json.put("age", 40); json.put("name", "kim"); json.put("married", false); json.put("score", new int[] {30,40,50,60,70,80,90}); // json 형태로 파라메터를 보내고 싶은 경우 // bodyValue() 를 사용한다. // 받는 곳에서는 @RequestBody 로 받아줘야 한다. //3. 전송 방식 지정 + 4. 상세 url 추가(있으면) + 5. bodyValue 값 + 6. 전송 Flux<Map> flux = client.post().uri("/fluxReturn").bodyValue(json).retrieve().bodyToFlux(Map.class); List<Map<String, Object>> list = flux.toStream().collect(Collectors.toList()); logger.info("reponse : "+list); return list; } }
index.html
<html> <head> <meta charset="UTF-8"> <title>Insert title here</title> <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script> <style> </style> </head> <body> <input type="text" id="msg"/> <button onclick="getSend()">GET SEND</button> <br/><br/> <input type="number" id="cnt"/> <button onclick="postSend()">POST SEND</button> <p><button onclick="fluxTest()">FLUX TEST</button></p> </body> <script> // get 방식으로 서버로 데이터를 보내면 -> 받은 데이터를 다른 서버로 보낸다. // 다른 서버에서는 받은 데이터를 -> 보낸 서버로 되돌려 보내고 -> 이 것을 클라이언트로 되돌려 준다. function getSend(){ var val = $('#msg').val(); console.log('get send : ', val); $.ajax({ type:'get', url:'/get/send/'+val, dataType:'JSON', success:function(data){ console.log(data); }, error:function(e){ console.log(e); } }) } // POST 방식 // 입력한 숫자만큼의 크기를 갖는 리스트를 받는다. function postSend(){ var val = $('#cnt').val(); console.log('post send : ', val); // 전송 : 헤더, 바디 (둘의 경계는 공백 한 줄) // 헤더 : 지금부터 보낼 문서에 대한 사전 정보 (헤더에 보통 인증 키 등을 보내는 경우가 많다.) // 바디 : 보낼 본문 $.ajax({ type:'post', url:'/post/send/'+val, dataType:'json', beforeSend:function(obj){ obj.setRequestHeader('Authorization','KEYNAME3636F') }, success:function(data){ console.log(data); }, error:function(e){ console.log(e); } }); } function fluxTest(){ console.log('flux send'); $.ajax({ type:'get', url:'/get/fluxTest/', dataType:'JSON', success:function(data){ console.log(data); }, error:function(e){ console.log(e); } }) } </script> </html>
proerties 에 슈퍼 어드민 계정을 생성하고 로그인해보는 예제
application.properties
# user setting user.id=superAdmin user.pw=pass@Goodee user.ip=127.0.0.1
controller
@PostMapping(value="/login") public ModelAndView login(String id, String pw, HttpServletRequest request) { String ip = request.getRemoteAddr(); // IPv6 를 IPv4 로 변환할 경우 // Run As > Run Configuration > Arguments > Vm 선택 후 아래 내용 추가 // -Djava.net.preferIPv4Stack = true logger.info("ip : {}", ip); logger.info("id : {}", id); logger.info("pw : {}", pw); return service.login(id, pw, ip); }
service
// application.properties 에 있는 값 가져오기 @Value("${user.id}") private String user_id; @Value("${user.pw}") private String user_pw; @Value("${user.ip}") private String user_ip; public ModelAndView login(String id, String pw, String ip) { ModelAndView mav = new ModelAndView(); logger.info("user_id : {}", user_id); logger.info("user_pw : {}", user_pw); logger.info("user_ip : {}", user_ip); String msg = "관리자 계정으로 접속할 수 없습니다"; String page = "login"; if (id.equals(user_id) && pw.equals(user_pw) && ip.equals(user_ip)) { msg="관리자님 안녕하세요"; page="result"; } mav.addObject("msg", msg); mav.setViewName(page); return mav; }
콘솔에 아이피 보이는 IPv6 형식을 IPv4 로 변경하기
반복되는 로그인 처리를 간편하게 구현
AOP (Aspect Oriented Programming, 관점 지향 프로그래밍)
AOP 관점 지향 프로그래밍 : 특정 시점에서 확인해준다
예시 ) AOP 같은 경우는 무언가를 감시하는 역할을 한다
성영이가 지하철을 타고 학원에 온다. <- 몇번 지하철 개찰구를 뛰어 넘는거 같아서 감시
내가 성영이가 집에서 나오면 따라가서 개찰구에서 교통카드를 태그하는지 확인함
하지만 너무 비효율적이므로 지하철 개찰구에서 태그하는지 안하는지 확인하면 효율적이다
IOC : 마이바티스가 알아서 데이터베이스와 연결 과 종료를 자기 알아서함
의존성 주입 : 싱글톤 패턴
효울적인 자원 관리
AOP : 하나 하나 흐름을 쫓아가지 않고 특정 시점에서 확인하는것
39~40일차 스프링의 주요 개념
IOC (Inversion Of Control, 제어 역행)
모든 프로그램은 사용자가 내가 사용할래 나 그만 사용할래
-> 커넥션을 맺어올때 필요할때만 쓰고, 커넥션 클로즈해서 내가(사용자) 닫았다
스프링이 알아서 커넥션을 맺어오고 닫아준다
DI(Dependency Injection, 의존성 주입)
느슨하게 해라
클래스 a가 클래스 b 를 사용하려면 일일히 객체화해야한다
클래스 a는 빈을 통해서 클래스 b 의 객체를 사용한다(느슨한 결합)
코드 내에서 결합이 낮다는건 수정할 곳이 적어진다
어디에다가 미리 등록해두면 프로그램에서 가져다가 쓰기 때문에 최소한의 소스 수정
특정한 설정에 의해서 최소한의 수정으로 소스를 변경할 수 있다
대표적인 예가
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory --> <!-- @Controllers 에서 반환 타입이 String 일 경우 이름만으로 해당 .jsp 를 찾도록 해주는 설정 --> <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>
스프링 프레임워크에서 객체 자원을 효율적으로 관리
싱글톤 패턴 : 원본에서 복사본을 하나만 놓고 다른 사람들도 사용할 수 있게 정적 영역에다가 옮겨둔다
전체적으로 정적 영역에 있다고 공지함
@Service 어노테이션을 붙인 MemberService 는
다른 클래스에서 @Autowire MemberService serivce 해주면 싱글톤 객체로 사용할 수 있다
단점으로는 누군가가 이 싱글톤 객체를 바꾸어버리면 큰일난다
@Autowired MemberDAO dao;
Interceptor
스프링 보안의 기본이다
예시 )
현재 : detail 요청이 들어옴 -> 세션에 로그인 정보를 확인함
인터셉터 : 세션 로그인 정보를 확인함 -> detail 요청이 처리된다
preHandler : 컨트롤러 요청 전에 확인하기
preHandler : 컨트롤러 요청 후에 확인하기
예시 ) preHandler, 지하철에 탑승 전에 표검사 preHandler, 놀이공원의 입장 팔찌
관련 참고 자료
'백엔드(웹 서버, WAS) > Spring Boot' 카테고리의 다른 글
82일차 - 톰캣, 빌드, 빌드 전 프로필 설정 (0) 2024.05.27 81일차 - 인터셉터, 파일 업로드, 다운로드, 스케쥴러 (2) 2024.05.24 79일차 - RESTful (0) 2024.05.23 77~78일차 - Transaction, RESTFUL 개념과 사용 방식 (0) 2024.05.21 76일차 - 로그 설정 및 동적 쿼리 예제 (0) 2024.05.17