REST(Representational Safe Transfer)


데이터를 주고 받는 방식의 아키텍처 (디자인 패턴)

 


HTTP URI를 통한 자원의 명시


  • url?category=novel 와 같은 쿼리 스트링 방식이 아니라 url/category/novel 과 같은 방식 사용
  • 대문자 사용 지양
  •  _ 대신 - 사용
  •  URL 마지막에 / 사용하지 않음
  •  행위를 포함하지 않음(insert, update, delete)
  •  가급적 명사 사용(동사 사용 지양)

 

 

HTTP Method(Get, Post, Put, Delete)로 해당 자원을 제어하는 행위를 표현


  • GET - Read(Select) : URL/1
  • POST - Create(Insert) : URL
  • PUT - Update : URL/1
  • DELETE - Delete : URL/1

 

 


Restful 서비스(API)


  • REST 아키텍처를 준수하는 서비스
  • RESTful 서비스에서는 Controller에서 요청 받은 내용을 처리하고 데이터를 가공해서 처리 결과를 "특정 플랫폼"에 적합한 형태의 View로 만들어서 응답하지 않고 데이터만 처리하거나 응답 데이터가 있다면 JSON/XML로 응답한다. (View와는 무관하다)
  • 즉, 클라이언트는 규칙에 맞게 작업을 요청하면 되고 서버는 어떤 플랫폼에서 어떻게 사용할 것인지를 신경쓰지 않고 요청 받은 데이터만 처리하고 응답하면 된다.
  • 클라이언트는 받은 데이터를 알아서 가공해서 사용한다. 즉, 멀티 플랫폼에서 사용 가능하다.
  • 이러한 특성으로, 사용자 플랫폼과 view에 상관하지 않고 요청한 데이터만 응답해주는 오픈 API에서 Restful한 서비스 방식을 많이 사용한다.
  • 어플리케이션의 복잡도 증가로 인해 서비스별 독립적 구축과 운영이 필요해지면서 많이 활용되고 있다.

 

 

 

전체 프로젝트 파일 구조


 

 

 

 

프로젝트 코드


 

 

Chap03RestApiApplication

package com.greedy.rest.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Chap03RestApiApplication {

	public static void main(String[] args) {
		SpringApplication.run(Chap03RestApiApplication.class, args);
	}

}

 

 

 

ContextConfiguration

package com.greedy.rest.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.greedy.rest")
public class ContextConfiguration {

}

 

 

 

MyBatisConfiguration

package com.greedy.rest.config;


import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan(basePackages="com.greedy.rest", annotationClass = Mapper.class)
public class MyBatisConfiguration {

}

 

 

 

 

 

 

 

BookController

package com.greedy.rest.controller;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.greedy.rest.model.dto.BookDTO;
import com.greedy.rest.model.service.BookService;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
/* @Controller + @ResponseBody
 * => 모든 메소드에 @ResponseBody가 적용되므로 메소드별로 명시할 필요가 없음
 * => @RestController의 용도는 JSON/XML 등의 형식으로 데이터를 반환하는 것
 * */
public class BookController {
	
	private BookService bookService;
	
	@Autowired
	public BookController(BookService bookService) {
		this.bookService = bookService;
	}
	
//	@GetMapping("/book/category/{category}")
//	public List<BookDTO> selectBookByCategory(@PathVariable String category){
//		
//		log.debug("조회 요청 category : {}", category);
//		
//		return bookService.selectBookByCategory(category);
//	}
	
	/* ResponseEntity : 개발자가 직접 결과 데이터와 HTTP 상태 코드를 
    제어할 수 있는 클래스로
	 * 결과 데이터가 예외적인 상황에 대해서 세밀한 제어를 할 수 있다.
	 * */
	@GetMapping("/book/category/{category}")
	public ResponseEntity<Map<String, Object>> 
    selectBookByCategory(@PathVariable String category){
		
		log.debug("조회 요청 category : {}", category);
		
		List<BookDTO> bookList = bookService.selectBookByCategory(category);
		
		Map<String, Object> result = new HashMap<>();
		result.put("data", bookList);
		
		if(bookList.isEmpty()) {
			result.put("data", "no data");
			result.put("message", "check your category");
		}
		
		return ResponseEntity.ok(result);
	}
	
	@PostMapping("/book")
	public ResponseEntity<Map<String, String>> insertBook(@RequestBody BookDTO book){
		
		log.debug("입력 요청 book : {}", book);
		
		String message = bookService.insertBook(book)
        		> 0 ? "book registration success" : "book registration failed";
		
		Map<String, String> result = new HashMap<>();
		result.put("message", message);
		
		return ResponseEntity.ok(result);
	}
	
	@PutMapping("/book/{id}")
	public ResponseEntity<Map<String, String>> 
    			updateBook(@PathVariable int id, @RequestBody BookDTO book){
		
		log.debug("수정 요청 id : {}", id);
		log.debug("수정 요청 book : {}", book);
		
		book.setId(id);
		
		String message = bookService.updateBook(book)
        		> 0 ? "book modification success" : "check your book id";
		
		Map<String, String> result = new HashMap<>();
		result.put("message", message);
		
		return ResponseEntity.ok(result);
	}
	
	@DeleteMapping("/book/{id}")
	public ResponseEntity<Map<String, String>> deleteBook(@PathVariable int id) {
		
		log.debug("삭제 요청 id : {}", id);
		
		String message = bookService.deleteBook(id)
        			> 0 ? "book deletion success" : "check your book id";
		
		Map<String, String> result = new HashMap<>();
		result.put("message", message);
		
		return ResponseEntity.ok(result);
	}
	
	/* 도서 아이디 전달 받아 해당 도서 정보 응답하는 기능 구현하기 */
	@GetMapping("/book/{id}")
	public ResponseEntity<Map<String, Object>> selectBookByid(@PathVariable int id) {
		
		log.debug("조회 요청 id : {}", id);
		
		BookDTO book = bookService.selectBookByid(id);
		
		Map<String, Object> result = new HashMap<>();
		result.put("data", book);
		
		if(book == null) {
			result.put("data", "no data");
			result.put("message", "check your book id");
		}
		
		return ResponseEntity.ok(result);
	}
	
	
	
	
	
}

 

 

 

 

interface BookMapper

package com.greedy.rest.model.dao;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import com.greedy.rest.model.dto.BookDTO;

@Mapper
public interface BookMapper {

	List<BookDTO> selectBookByCategory(String category);

	int insertBook(BookDTO book);

	int updateBook(BookDTO book);

	int deleteBook(int id);

	BookDTO selectBookByid(int id);

}

 

 

 

BookDTO

package com.greedy.rest.model.dto;

import lombok.Data;

@Data
public class BookDTO {
	
	private int id;
	private String title;
	private String author;
	private String publisher;
	private String category;
	
}

 

 

interface BookService

package com.greedy.rest.model.service;

import java.util.List;

import com.greedy.rest.model.dto.BookDTO;

public interface BookService {

	List<BookDTO> selectBookByCategory(String category);

	int insertBook(BookDTO book);

	int updateBook(BookDTO book);

	int deleteBook(int id);

	BookDTO selectBookByid(int id);
	
	

}

 

 

 

BookServiceImpl implements BookService

package com.greedy.rest.model.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.greedy.rest.model.dao.BookMapper;
import com.greedy.rest.model.dto.BookDTO;

@Service
@Transactional
public class BookServiceImpl implements BookService {
	
	private BookMapper bookMapper;
	
	@Autowired
	public BookServiceImpl(BookMapper bookMapper) {
		this.bookMapper = bookMapper;
	}

	@Override
	public List<BookDTO> selectBookByCategory(String category) {
		return bookMapper.selectBookByCategory(category);
	}

	@Override
	public int insertBook(BookDTO book) {
		return bookMapper.insertBook(book);
	}

	@Override
	public int updateBook(BookDTO book) {
		return bookMapper.updateBook(book);
	}

	@Override
	public int deleteBook(int id) {
		return bookMapper.deleteBook(id);
	}

	@Override
	public BookDTO selectBookByid(int id) {
		return bookMapper.selectBookByid(id);
	}

}

 

 

 

 

 

 

BookMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.greedy.rest.model.dao.BookMapper">
	
	<resultMap id="bookResultMap" type="com.greedy.rest.model.dto.BookDTO">
		<id property="id" column="BOOK_ID"/>
		<result property="title" column="BOOK_TITLE"/>
		<result property="author" column="BOOK_AUTHOR"/>
		<result property="publisher" column="BOOK_PUBLISHER"/>
		<result property="category" column="BOOK_CATEGORY"/>
	</resultMap>
	
	<select id="selectBookByCategory" resultMap="bookResultMap">
		SELECT
		       BOOK_ID
		     , BOOK_TITLE
		     , BOOK_AUTHOR
		     , BOOK_PUBLISHER
		     , BOOK_CATEGORY
		  FROM TBL_BOOK
		 WHERE BOOK_CATEGORY = #{ category }
	</select>
	
	<insert id="insertBook">
		INSERT
		  INTO TBL_BOOK
		(
		  BOOK_ID
		, BOOK_TITLE
		, BOOK_AUTHOR
		, BOOK_PUBLISHER
		, BOOK_CATEGORY
		)
		VALUES
		(
		  SEQ_BOOK_ID.NEXTVAL
		, #{ title }
		, #{ author }
		, #{ publisher }
		, #{ category }
		)
	</insert>
	
	<update id="updateBook">
		UPDATE
	           TBL_BOOK
	       SET BOOK_TITLE = #{ title }
	       	 , BOOK_AUTHOR = #{ author }
	       	 , BOOK_PUBLISHER = #{ publisher }
	       	 , BOOK_CATEGORY = #{ category }
	     WHERE BOOK_ID = #{ id }
	</update>
	
	<delete id="deleteBook">
		DELETE
		  FROM TBL_BOOK
		 WHERE BOOK_ID = #{ id }
	</delete>
	
	<select id="selectBookByid" resultMap="bookResultMap">
		SELECT
		       BOOK_ID
		     , BOOK_TITLE
		     , BOOK_AUTHOR
		     , BOOK_PUBLISHER
		     , BOOK_CATEGORY
		  FROM TBL_BOOK
		 WHERE BOOK_ID = #{id}	
	</select>
	
	
	
	


</mapper>

 

 

 

index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>rest api</title>
</head>
<body>
	<h1>REST API</h1>

</body>
</html>

 

 

 

application.yml

# server port config
server:
  port: 8001
  
# datasource config
spring:
  datasource:
    driver-class-name: oracle.jdbc.driver.OracleDriver
    url : jdbc:oracle:thin:@localhost:1521:xe
    username :
    password :
    
# mybatis config
mybatis:
  mapper-locations: mappers/**/*.xml

# logging level
logging:
  level:
    '[com.greedy.rest]': debug

 

 

 

 

 

 

 

 

 

 

 

'Programming > Spring Framework' 카테고리의 다른 글

Spring-Boot-Crud (2) : Thymeleaf  (0) 2022.05.09
Spring-Boot-Crud (1)  (0) 2022.05.09
Spring Security (2)  (0) 2022.04.25
Spring Security (1)  (0) 2022.04.25
Spring Boot 초기 설정  (0) 2022.04.20

 

 

 

 

Thymeleaf


타임리프는 스프링 부트에서 공식적으로 지원하는 뷰 템플릿으로 JSP와 달리 html 확장자를 가지고 있어 JSP처럼 Servlet이 문서를 표현하는 방식이 아니므로 서버 없이 동작 가능하다.

 

 

 

템플릿 엔진(Template Engine)


데이터와 이 데이터를 표현할 템플릿을 결합해주는 도구로 스프링 부트가 지원하는 템플릿 엔진은 Thymeleaf, Freemarker, Mustache, Groovy가 있다.

 

 


타임리프의 문법 적용


스프링 부트의 templates 폴더 하위html 확장자를 쓰고 xmlns:th 네임스페이스를 추가하고 타임리프 문법을 사용할 수 있다.

 

 


타임리프의 장점


  • natural templates를 제공 : HTML의 기본 구조를 그대로 사용하며 HTML 파일을 직접 열어도 동작한다.
  • 개발 시 디자인과 개발이 분리 되어 작업 효율이 좋다.
  • WAS를 통하지 않고 파일을 웹 브라우저를 통해 열 수 있어서 퍼블리셔와 협업이 용이하다.

 

 


타임리프의 단점


  •  jsp 태그 라이브러리와 custom 태그 들을 사용할 수 없어 기존 JSP 코드를 재사용할 수 없다.
  • 기존 태그를 유지하며 속성으로 템플릿 구문을 넣는데 있어 어느 정도 한계가 있고 자바스크립트의 도움이 필요할 수 있다.

 

 

 

 

 

 

프로젝트 전체 파일구조


 

 

 

 

 

 

 

프로젝트 코드 


 

 

 

 

 

Chap02ThymeleafApplication

package com.greedy.thymeleaf.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Chap02ThymeleafApplication {

	public static void main(String[] args) {
		SpringApplication.run(Chap02ThymeleafApplication.class, args);
	}

}

 

 

ContextConfiguration

package com.greedy.thymeleaf.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "com.greedy.thymeleaf")
public class ContextConfiguration {

}

 

 

ModelAndView expression(ModelAndView mv)

package com.greedy.thymeleaf.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import com.greedy.thymeleaf.model.dto.MemberDTO;

@Controller
@RequestMapping("/lecture")
public class LectureController {

	@GetMapping("expression")
	public ModelAndView expression(ModelAndView mv) {
		
		mv.addObject("member", new MemberDTO("유관순", 16, '여', "서울시 서대문구"));
		
		mv.setViewName("/lecture/expression");
		
		return mv;
	}
	
	
}

 

 

MemberDTO

package com.greedy.thymeleaf.model.dto;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class MemberDTO {
	
	private String name;
	private int age;
	private char gender;
	private String address;

}

 

 

 

 

 

index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<h1 align="center">Thymeleaf</h1>

<button onclick="location.href='/lecture/expression?title=표현식&no=5&no=6'">
   표현식</button>
	
	
	
	
</body>
</html>

 

 

 

expression.html

<!DOCTYPE html>

<!-- 
xmlns:th
: 타임리프의 th 속성을 사용하기 위한 네임스페이스로 html 태그의 속성으로 작성한다.
 -->
 
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	
	<h1 align="center">표현식</h1>
    
	<h2>주석</h2>
	<!-- 
		주석의 종류
		parser-level 주석
		: 정적 페이지에서 주석으로 있다가 thymeleaf가 처리 될 때 제거되어
        클라이언트에게 노출되지 않는 주석
		
		prototype-only 주석
		: 정적 페이지에 주석으로 있다가 thymeleaf 처리 후에 
        화면에 보여지게 되는 주석
	 -->
     
	 <ul>
	 	<li>parser-level 주석</li>
	 	<!--/* 주석내용 */-->
	 	<li>prototype-only 주석</li>
	 	<!--/*/ 주석내용 /*/-->
	 </ul>
	 
	 <h2>표현식1 - 변수 표현식 ${...}</h2>
	 <p th:text="${ param.title }"></p>
	 <p th:text="${ param.no[0] }"></p>
	 <p th:text="${ param.no[1] }"></p>
     
<!-- <p th:text="${ param.no[2] }"></p>

 		parameter로 넘어온 경우는 param, session attribute일 경우는 session,
 		model에 담겨 온 경우는 따로 적지 않음(request라고 적으면 에러)
 		파라미터가 존재하지 않으면 무시하지 않고 에러 발생함
 -->	 
	 
     
     
	 <h2>표현식2 - 메세지 표현식 #{...}</h2>
	 <!-- resources 폴더 하위에 messages.properties 
     라는 외부 리소스를 읽어온다. -->
	 <p th:text="#{ message.first }"></p>
	 <p th:text="#{ message.second(everyone) }"></p>
	
	<h3>표현식3 - 링크 표현식 @{...}</h3>
	<a th:href="@{/}">메인으로</a>
	<a th:href="@{/(name=${member.name},age=${member.age})}">Test1</a>
	<a th:href="@{/{name}/{age}(name=${member.name},age=${member.age})}">Test2</a>
	<a th:href="@{/{name}(name=${member.name},age=${member.age})}">Test3</a>
	
	
	
	
</body>
</html>

 

 

application.yml

server:
  port: 8001

 

 

messages.properties

message.first=hello world
message.second=hello {0}

 

 

 

 


 

 

 

 

 

 

Chap02ThymeleafApplicationTests

package com.greedy.thymeleaf;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Chap02ThymeleafApplicationTests {

	@Test
	void contextLoads() {
	}

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'Programming > Spring Framework' 카테고리의 다른 글

Spring-Boot-Crud (3) : REST API  (0) 2022.05.09
Spring-Boot-Crud (1)  (0) 2022.05.09
Spring Security (2)  (0) 2022.04.25
Spring Security (1)  (0) 2022.04.25
Spring Boot 초기 설정  (0) 2022.04.20

 

 

 

 

 

 

 

Spring-Boot-Crud (1)


CRUD를 테스트 해 볼 프로젝트를 만든다. 파일 구조는 다음과 같다. 

 

 

 

 

 

 

Chap01BootCrudApplication


  • 프로그램 시작, 시동이 되는 파일로 생각한다.
  • @SpringBootApplication 은 통합적 처리가 가능한 어노테이션이다. 
package com.greedy.crud.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Chap01BootCrudApplication {

	public static void main(String[] args) {
		SpringApplication.run(Chap01BootCrudApplication.class, args);
	}

}

 

 

ContextConfiguration 

package com.greedy.crud.config;

public class ContextConfiguration {

}

 

 

 

MybatisConfig


  • 설정 파일을 넣을 config 패키지 하위에 작성하도록 한다. 
  • Chap01BootCrudApplication은 기본 시작되는 클래스이므로 이곳에 직접 작성하는 것보다 MybatisConfig 클래스를 따로 작성하여 필요한 설정 파일등을 추가하는 것이 좋다. 
  • MapperScan에 있어서 "com.greedy.crud"까지만 작성하게 될 경우 crud 하위의 모든 인터페이스를 대상으로 매퍼로 간주되고 구현체를 생성해달라는 의미가 된다. 매퍼가 아닌 인터페이스는 따로 건들지 않도록 세부 작성이 필요하다. 
  • 모든 인터페이스를 모두 매퍼로 설정할 것은 아니기 때문이다.  
package com.greedy.crud.config;

import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan(basePackages
= "com.greedy.crud", annotationClass = Mapper.class)
public class MybatisConfig {

}

 

 

 

MenuController 

package com.greedy.crud.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.greedy.crud.model.service.MenuService;

@Controller
@RequestMapping("/menu")
public class MenuController {

	
	private MenuService menuService;
	
	@Autowired
	public MenuController(MenuService menuservice) {
		this.menuService = menuService;
	}

	
	
}

 

 

 

interface MenuMapper


  • @Mapper는 인터페이스를 위한 mark이다. 
  • dao에 해당하는 것을 직접 만들지 않아도 된다. 마이바티스 스프링에서 매퍼 스캔기능 이용시 인터페이스만 만들어 놓고 mapper.xml을 만들었을때, 중간에 자동으로 호출해주는 구현체는 프록시를 이용해 자동생성 될 것이다. 
  • 그러므로 이와같이 @Mapper 를 통해 crud하위의 매퍼를 스캔 해 프록시로 MenuMapperImpl.java 를 생성하는 작업까지는 된 것이다. 
  • 여기서 추가로 -Impl이 호출할 대상이 어떤 경로에 있는지 매퍼를 등록하는 설정이 필요하다. 이를 application.yml에 작성하도록 한다. 
package com.greedy.crud.model.dao;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface MenuMapper {

}

 

 

 

 

Lombok


  • DTO/VP 클래스의 constructor, getter/setter, toString 등을 어노테이션을 통해 자동 작성해 줄 수 있는 기능을 가진다. 
  • 필드 수정 후 해당 코드에 대한 수정작업이 필요 없다는 장점이 있다.
  • 편리하지만 구조와 무관한게 남용될 수 있어 프로젝트에 따라 사용하지않는 경우도 있음에 유의한다.
  • 팀 프로젝트에서 활용 시 모든 팀원이 해당 라이브러리를 설치한 환경에서 사용해야 한다. 

 

 

Lombok 설치 방법 :


  1. 의존성 추가 후 라이브러리 설치
  2. 해당 jar 파일이 위치한 폴더에서 cmd 창으로 -java lombok-버전.jar 실행
  3. sts.exe 선택해서 install / update 후 sts 재부팅

 

 

 

MenuDTO


  • @Data는 위 주석처리된 모든 어노테이션을 한번에 생성할 수 있다. 
  • 단, @AllArgsConstructor는 제외이다. 
  • 편리하기는 하지만 최소한의 필요한 것들만 추가하는 방향으로 롬복을 사용하는 것이 바람직하다.
  • @Data를 만능키로 사용하지 않도록 한다.
package com.greedy.crud.model.dto;

import lombok.Data;
import lombok.NoArgsConstructor;


//@NoArgsConstructor
//@AllArgsConstructor
//@Getter
//@Setter
//@ToString
//@EqualsAndHashCode
@Data //매개변수 생성자를 제외하고 위의 내용을 한번에 처리할 수 있는 어노테이션
public class MenuDTO {

	
	private int code;
	private String name;
	private int place;
	private int categoryCode;
	private String orderableStatus;
	
	
	
}

 

 

 

MenuService

package com.greedy.crud.model.service;

public class MenuService {

}

 

 

 

MenuServiceImpl


원칙대로라면 인터페이스를 구현해야하지만 확인이 목적이므로 편의를 위해 빈 클래스를 만들어준다. 

 

package com.greedy.crud.model.service;

public class MenuServiceImpl {

}

 

 

 

 

 

 

 

 

MenuMapper.xml


  • dtd를 작성하기위해 다음과같은 경로를 참고할 수 있다.
  • 이클립스 메뉴의 help - marcket place - mybatis 검색 후 - mybatis 1.25 설치
  • mapper는 dtd를 필요로 하고 위 방법으로 좀 더 손쉽게 작성할 수 있다. 
  • namespace에는 인터페이스 지정이 필요하다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.greedy.crud.model.dao.MenuMapper"></mapper>

 

 

 

list.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

</body>
</html>

 

 

regist.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

</body>
</html>

 

 

 

application.yml


  • 매퍼 설정을 통해 MenuMapper.java 와 MenuMapper.xml 두가지가 인식되어 연결될 수 있다. 
  • /**/ : 하위에 경로가 얼마든지 있어도 상관없음을 나타냄 
  • /*.xml : *은 파일에 대한 패턴을 의미하며, 여기에서 패턴은 .xml에 해당한다. 
#server port config

server:
  port: 8001
  
 #oracle driver config
spring:
  datasource:
    url: jdbc:oracle:thin:@localhost:1521:xe
    driver-class-name: oracle.jdbc.driver.OracleDriver
    username:
    password: 
    
    
 # mybatis config
mybatis:
  mapper-locations: mappers/**/*.xml

 

 

 

 

 

 

 

 

 

'Programming > Spring Framework' 카테고리의 다른 글

Spring-Boot-Crud (3) : REST API  (0) 2022.05.09
Spring-Boot-Crud (2) : Thymeleaf  (0) 2022.05.09
Spring Security (2)  (0) 2022.04.25
Spring Security (1)  (0) 2022.04.25
Spring Boot 초기 설정  (0) 2022.04.20

 

 

 

 

 

 

폴더 구조는 다음과 같다. 

 

 

 

 

간단한 확인을 위해 list와 order의 html을 작성한다. 

 

 

 

 

list.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>menu list</title>
</head>
<body>
	<h1 align="center">메뉴 리스트</h1>
</body>
</html>

 

 

order.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>order</title>
</head>
<body>
	<h1 align="center">주문 페이지입니다.</h1>
</body>
</html>

 

 

 

 

 

 

 

menu와 order의 Controller를 작성한다. 

 

 

 

 

MenuController

package com.greedy.security.menu.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/menu")
public class MenuController {
	
	@GetMapping("/list")
	public String findMenuList() {
		
		return "menu/list";
	}
	
}

 

 

 

OrderController

package com.greedy.security.order.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class OrderController {
	
	@GetMapping("/order")
	public String orderPage() {
		
		return "order/order";
	}
}

 

 

 

 

 

 

 

 

 


잘못된 로그인 정보를 입력할시 보여질 화면도 구현한다. 

 

 

 

 

 

 

denied.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>denied</title>
</head>
<body>
	<h1 align="center">접근 권한이 없습니다. 관리자에게 문의해주세요.</h1>
</body>
</html>

 

 

 

 

 

 

 

 

실행


 

 

  • 실행 시 다음과같은 화면이 뜬다. 

 

 

  • 로그인 페이지는 다음과 같다. 아이디와 비밀번호를 입력한다. 

 

 

  • [000님 환영합니다] 라는 문구가 뜨도록 했는데 세가지 authentication 에 따라 다르게 뜨는 것을 확인 할 수 있다. 

 

 

 

 


principal


로그인 된 User 객체의 정보를 담고 있다.

 


principal.username


로그인 시 입력한 id 값




 

<h3><span sec:authentication="principal"></span>님 환영합니다</h3>

 

<h3><span sec:authentication="principal.username"></span>님 환영합니다</h3>

 

<h3><span sec:authentication="principal.name"></span>님 환영합니다</h3>

 

 

 

 

  • 관리자로 로그인 시에도 다음과같이 바뀌는 것을 확인할 수 있다. 
  • 즉, 회원 권한에 따라 로그인시 나타나는 화면이 달라진다. 

 

 

 

 

  • 로그아웃 시 다시 로그인이 가능해지며 메인화면으로 돌아간다. 

 

 

 

 

 

 

 

 

'Programming > Spring Framework' 카테고리의 다른 글

Spring-Boot-Crud (2) : Thymeleaf  (0) 2022.05.09
Spring-Boot-Crud (1)  (0) 2022.05.09
Spring Security (1)  (0) 2022.04.25
Spring Boot 초기 설정  (0) 2022.04.20
Spring Boot 초기 환경설정  (0) 2022.04.20

 

 

 

 

 

Spring Security (1)

 

 

 


 

새로운 DB를 만들어서 읽혀준다. 권한과 관련된 부분을 테스트 할 것이다. 

1번 권한을 가진 사람을 AUTHORITY_CODE 1을, 2번 권한을 가진 사람은  AUTHORITY_CODE 1, 2 코드를 모두 가지고 있다. 

 

 

 

 

 

 

 

  • 스프링 스타터 프로젝트로 새 프로젝트를 만들어 준다. 

 

 

추가한 의존성은 다음과 같다. 

 

 

 

 

 

 

프로젝트 전체 구조는 다음과 같다. 


 

 

 

 

 

 

 

 

 

 

 

 

Application.yml

오라클DB를 정보를 기입하여 연결한다. 

# server port config
server :
  port : 8001

# datasource config
spring :
  datasource : 
    driver-class-name : oracle.jdbc.driver.OracleDriver
    url : jdbc:oracle:thin:@localhost:1521:xe
    username :
    password :

# mybatis config
mybatis :
  mapper-locations : mappers/**/*.xml

 

 

 

 

 

 


  • 테스트를 위한 간단한 메인과 로그인 화면을 구현한다. 

 

 

 

main.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	  xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<title>main</title>
</head>
<body>
	<h1 align="center">Spring Security Project에 오신 것을 환영합니다.</h1>
	
	<div align="right">
		<!-- isAuthenticated() : 인증(로그인) 되어 있는지 확인 -->
		<th:block sec:authorize="isAuthenticated()">
			<!-- 
				principal : 로그인 된 User 객체의 정보를 담고 있다.
				principal.username : 로그인 시 입력한 id 값
				그 외의 정보가 필요할 경우 User 타입을 상속한 클래스를 
                만들어서 커스터마이징 할 수 있다.
			 -->
			<h3><span sec:authentication="principal"></span>님 환영합니다</h3>
			<h3><span sec:authentication="principal.username"></span>님 환영합니다</h3>
			<h3><span sec:authentication="principal.name"></span>님 환영합니다</h3>
			<button onclick="location.href='/member/logout'">로그아웃</button>
		</th:block>
		<!-- isAnonymous() : 인증(로그인) 되어 있지 않은 경우 -->
		<th:block sec:authorize="isAnonymous()">
			<h3>로그인이 필요한 서비스입니다.</h3>
			<button onclick="location.href='/member/login'">로그인</button>
			<button>회원가입</button>
		</th:block>
	</div>
	
	<button onclick="location.href='/menu/list'">메뉴 보기</button>
	<th:block sec:authorize="hasRole('MEMBER')">
		<button onclick="location.href='/order'">주문하기</button>
	</th:block>
	<th:block sec:authorize="hasRole('ADMIN')">
		<button onclick="location.href='/admin/dashboard'">관리자메뉴</button>
	</th:block>
	
</body>
</html>

 

login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>login page</title>
</head>
<body>


	<!-- 
		spring security의 formlogin을 할 경우 username, password로 꺼내게 되어 있으므로
        name 속성 설정에 유의한다.
		"/member/login" 요청은 시큐리티에 설정해두었으므로 해당 요청을 가로채서
		userDetailsService의 용도로 memberService를 사용하겠다고 전달 했기 
        때문에 loadUserbyUserName 메소드가 호출 되어
		User 객체를 리턴하게 되며 비밀번호 확인(설정 된 passwordEncoder 사용) 
        후 인증 되면 세션에 저장하는 기능이 자동으로 동작한다.
	 -->
     
     
	<h1 align="center">로그인 페이지</h1>
	<div align="center">
		<form action="/member/login" method="post">
			<div>
				<p style="color:red;">[[${errorMessage}]]</p>
			</div>
			<div>
				<span>아이디 : </span>
				<input type="text" name="username">
			</div>
			<div>
				<span>비밀번호 : </span>
				<input type="password" name="password">
			</div>
			<div>
				<button>로그인</button>
			</div>
		</form>
	</div>

</body>
</html>

 

 

 

 

 

 

 

config 폴더 내 클래스를 작성한다. 

 

 

 

 

Chap04SecuritySessionLoginApplication

package com.greedy.security.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Chap04SecuritySessionLoginApplication {

	public static void main(String[] args) {
		SpringApplication.run(Chap04SecuritySessionLoginApplication.class, args);
	}

}

 

 

SpringSecurityConfiguration

package com.greedy.security.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import com.greedy.security.handler.LoginFailHandler;
import com.greedy.security.member.model.service.MemberService;

/* 스프링 시큐리티 설정 활성화 + bean 등록 가능 */
@EnableWebSecurity
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
	
	private final MemberService memberService;
	private final PasswordEncoder passwordEncoder;
	
	@Autowired
	public SpringSecurityConfiguration(MemberService memberService,
    								PasswordEncoder passwordEncoder) {
		this.memberService = memberService;
		this.passwordEncoder = passwordEncoder;
	}

	
	/* 암호화에 사용할 객체 BCryptPasswordEncoder bean 등록 - ContextConfiguration */

	/* 정적 리소스는 권한이 없어도 요청 가능하게 무시할 경로를 작성한다 */
	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatchers("/css/**", "/js/**", "/images/**");
	}

	/* HTTP 요청에 대한 설정 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.csrf().disable()	/* csrf는 기본적으로 활성화 되어 있으므로 비활성화 처리 */
			/* 요청에 대한 권한 체크 */
			.authorizeHttpRequests()
				/* 요청 보안 수준의 세부적인 설정 */
				/* "/menu/**" 요청은 인증(로그인) 되어야 함을 명시 */
				.antMatchers("/menu/**").authenticated()
				/* "/menu/**" 의 GET 요청은 member 에게 허용 */
				.antMatchers(HttpMethod.GET, "/menu/**").hasRole("MEMBER")
				/* "/menu/**" 의 POST 요청은 admin 에게 허용 */
				/* hasRole 앞에 ROLE_가 자동으로 붙음 */
				.antMatchers(HttpMethod.POST, "/menu/**").hasRole("ADMIN")
				/* "/admin/**" 의 요청은 admin 에게 허용 */
				.antMatchers("/admin/**").hasRole("ADMIN")
				/* 그 외의 모든 요청은 허가함 - 인증(로그인) 되지 않은 사용자도 요청 가능 */
				.anyRequest().permitAll()
			.and()
				/* 로그인 설정 */
				.formLogin()
				/* 로그인 페이지 설정 */
				.loginPage("/member/login")
				/* 성공 시 랜딩 페이지 설정 */
				.successForwardUrl("/")
				/* 로그인 실패 시의 핸들러 설정 */
				.failureHandler(loginFailHandler())
			.and()
				/* 로그아웃 설정 */
				.logout()
				/* 로그아웃 주소 */
				.logoutRequestMatcher(new AntPathRequestMatcher("/member/logout"))
				/* JSESSIONID 쿠키 삭제 */
				.deleteCookies("JSESSIONID")
				/* 세션 만료 */
				.invalidateHttpSession(true)
				/* 성공 시 랜딩 페이지 */
				.logoutSuccessUrl("/")
			.and()
				/* 인증/인가 예외 처리 */
				.exceptionHandling()
				/* 인가 되지 않았을 때 - 권한이 없을 때 이동할 페이지 */
				.accessDeniedPage("/common/denied");
	}
    
    

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
		/* 로그인, 로그아웃은 MemberController에 작성하지 않고 
        스프링 시큐리티 모듈을 통해 처리 */
		/* 사용자 인증을 위해서 사용할 MemberService 등록, 
        사용하는 비밀번호 인코딩 방식 설정 */
        
		auth.userDetailsService(memberService).passwordEncoder(passwordEncoder);
	}
    
    
	
	/* 로그인 실패 핸들러 bean 등록 */
	@Bean
	public LoginFailHandler loginFailHandler() {
		return new LoginFailHandler();
	}
}

 

 

 

MybatisConfiguration

package com.greedy.security.config;

import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan(basePackages = "com.greedy.security", annotationClass = Mapper.class)
public class MybatisConfiguration {

}

 

 

 

 

 

 

 

main 폴더 내 MainController를 작성한다. 

 

 

 

MainController

package com.greedy.security.main.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class MainController {
	
	@GetMapping(value = {"/", "/main"})
	public String main() {
		return "main/main";
	}
	
	@PostMapping(value="/")
	public String redirectMain() {
		return "redirect:/";
	}
	
}

 

 

 

 

 

 

 

 

회원에 관련돼 구현될 클래스를 작성한다.

 

 

 

 

MemberController

package com.greedy.security.member.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("/member")
public class MemberController {

	@GetMapping("/login")
	public void loginForm() {}
	
	@PostMapping("/login")
	public void loginForm(@RequestParam(required=false) String errorMessage, Model model) {
		model.addAttribute("errorMessage", errorMessage);
	}
}

 

 

MemberMapper

package com.greedy.security.member.model.dao;

import org.apache.ibatis.annotations.Mapper;

import com.greedy.security.member.model.dto.MemberDTO;

@Mapper
public interface MemberMapper {

	MemberDTO findMemberById(String username);

}

 

 

 

AuthorityDTO

package com.greedy.security.member.model.dto;

import lombok.Data;

/* TBL_AUTHORITY */
@Data
public class AuthorityDTO {
	
	private int code;
	private String name;
	private String desc;
	
}

 

authorityDTO 내의 필드. 권한을 확인에는 code 와 name이 필요하도록 한다.  

 

 

 

MemberDTO

package com.greedy.security.member.model.dto;

import java.util.Date;
import java.util.List;

import lombok.Data;

/* TBL_MEMBER */
@Data
public class MemberDTO {
	
	private int no;									//회원번호
	private String id;								//회원아이디
	private String pwd;								//회원비밀번호
	private String tempPwdYn;						//임시비밀번호여부
	private Date pwdChangedDatetime;				//회원비밀번호변경일자
	private String pwdExpDate;						//회원비밀번호만료일자
	private String name;							//회원이름
	private Date registDatetime;					//회원가입일시
	private int accumLoginCount;					//누적로그인횟수
	private int loginFailedCount;					//로그인연속실패횟수
	private String accLockYn;						//계정잠금여부
	private String accInactiveYn;					//계정비활성화여부
	private String accExpDate;						//계정만료일자
	private String accExpYn;						//계정만료여부
	private Date accSecessionDatetime;				//계정탈퇴일시
	private String accSecessionYn;					//계정탈퇴여부
	
	/* TBL_MEMBER_ROLE - 한 멤버는 여러 권한을 가질 수 있다 */
	private List<MemberRoleDTO> memberRoleList;		//권한 목록
	
}

 

 

 

MemberRoleDTO

package com.greedy.security.member.model.dto;

import lombok.Data;

/* TBL_MEMBER_ROLE */
@Data
public class MemberRoleDTO {
	
	private int memberNo;
	private int authorityCode;
	
	/* TBL_AUTHORITY - 권한 코드별로 가지는 권한을 나타냄 */
	private AuthorityDTO authority;
}

 

 

 

 

UserImpl

package com.greedy.security.member.model.dto;

import java.util.Collection;
import java.util.Date;
import java.util.List;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

import lombok.Getter;
import lombok.ToString;

@Getter
@ToString
public class UserImpl extends User {
	
	private int no;									//회원번호
	private String id;								//회원아이디
	private String pwd;								//회원비밀번호
	private String tempPwdYn;						//임시비밀번호여부
	private Date pwdChangedDatetime;				//회원비밀번호변경일자
	private String pwdExpDate;						//회원비밀번호만료일자
	private String name;							//회원이름
	private Date registDatetime;					//회원가입일시
	private int accumLoginCount;					//누적로그인횟수
	private int loginFailedCount;					//로그인연속실패횟수
	private String accLockYn;						//계정잠금여부
	private String accInactiveYn;					//계정비활성화여부
	private String accExpDate;						//계정만료일자
	private String accExpYn;						//계정만료여부
	private Date accSecessionDatetime;				//계정탈퇴일시
	private String accSecessionYn;					//계정탈퇴여부
	
	/* TBL_MEMBER_ROLE - 한 멤버는 여러 권한을 가질 수 있다 */
	private List<MemberRoleDTO> memberRoleList;		//권한 목록

	public UserImpl(String username, String password, 
    			Collection<? extends GrantedAuthority> authorities) {
		super(username, password, authorities);
	}
	
	public void setDetails(MemberDTO member) {
		this.no = member.getNo();
		this.id = member.getId();
		this.pwd = member.getPwd();
		this.tempPwdYn = member.getTempPwdYn();
		this.pwdChangedDatetime = member.getPwdChangedDatetime();
		this.pwdExpDate = member.getPwdExpDate();
		this.name = member.getName();
		this.registDatetime = member.getRegistDatetime();
		this.accumLoginCount = member.getAccumLoginCount();
		this.loginFailedCount = member.getLoginFailedCount();
		this.accLockYn = member.getAccLockYn();
		this.accExpDate = member.getAccExpDate();
		this.accSecessionDatetime = member.getAccSecessionDatetime();
		this.accSecessionYn = member.getAccSecessionYn();
		this.memberRoleList = member.getMemberRoleList();
	}

}

 

 

 

MemberService

package com.greedy.security.member.model.service;

import org.springframework.security.core.userdetails.UserDetailsService;

/* 스프링 시큐리티에서 제공하는 기능을 이용해 로그인 로직을 작성해야 함 */
public interface MemberService extends UserDetailsService{

}

 

 

 

MemberServiceImpl

package com.greedy.security.member.model.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.greedy.security.member.model.dao.MemberMapper;
import com.greedy.security.member.model.dto.AuthorityDTO;
import com.greedy.security.member.model.dto.MemberDTO;
import com.greedy.security.member.model.dto.MemberRoleDTO;
import com.greedy.security.member.model.dto.UserImpl;

@Service
public class MemberServiceImpl implements MemberService {
	
	private MemberMapper memberMapper;
	
	@Autowired
	public MemberServiceImpl(MemberMapper memberMapper) {
		this.memberMapper = memberMapper;
	}

	/* 사용자 아이디를 통해 사용자 정보 조회하는 기능 - 로그인 시 호출 될 메소드 */
	@Override
	public UserDetails loadUserByUsername(String username) 
    								throws UsernameNotFoundException {
		
		/* 우리가 정의한 타입으로 유저 조회 */
		MemberDTO member = memberMapper.findMemberById(username);
		
		/* null 값이 없게 하기 위해 조회 된 값이 없을 시 빈 객체 */
		if(member == null) member = new MemberDTO();
		
		/* 권한 리스트 */
		List<GrantedAuthority> authorities = new ArrayList<>();
		
		if(member != null && member.getMemberRoleList() != null) {
			
			for(MemberRoleDTO role : member.getMemberRoleList()) {
				AuthorityDTO authority = role.getAuthority();
				
				if(authority != null) {
					authorities.add(new SimpleGrantedAuthority(authority.getName()));
				}
			}
		}
		
		// return new User(member.getId(), member.getPwd(), authorities);
	
    
		
		/* User 객체에 담기지 않는 추가정보를 User객체를 extends한 
        UserImpl에 담아서 리턴한다. */
        
		UserImpl user = new UserImpl(member.getId(), member.getPwd(), authorities);
		user.setDetails(member);
		
		return user;
	
	}

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'Programming > Spring Framework' 카테고리의 다른 글

Spring-Boot-Crud (1)  (0) 2022.05.09
Spring Security (2)  (0) 2022.04.25
Spring Boot 초기 설정  (0) 2022.04.20
Spring Boot 초기 환경설정  (0) 2022.04.20
5. Spring AOP (2)  (0) 2022.04.11

 

 

 

 

 

 

 

 

박스안에 들어가있는 3개는 기존에 사용했던 것이고 db연동과 관련해서 새롭게 의존성을 추가해준다. 

 

오라클과 

 

마이바티스와

 

Lombok 을 추가한 후 finish를 눌러준다. 

 

 

 

 

pom.xml에서 좀 더 많은 의존성이 추가된 것을 확인 해 볼 수 있다. 

 

 

 

 

 

 

에러로그를 보면 url이없다고 나온다. 이는 데이터 베이스 의존성을 추가했기 때문이다. 

db 연결정보가 없기 때문이며 접속 정보를 추가해주어야한다. 

 

 

 

 

설정 정ㅂ보는 여기서 작성 

사용해야하는 키값은 다음문서에서 참조할 수 있다. 

 

 

 

 

 

설정 파일은 다음과같이 작성해준다. 패스워드만 가렸다. 

 

 

 

위와같은 파일은 알아보기 어렵기 때문에 좀더 편의성을위해 

파일을 만들어 yml파일을 생성해준다.

 

: 와 tab을 이용해 계층을 형성해주어가며 작성해준다. 

 

 

문제가 없다면 콘솔창에 다음과같이 뜬다. 

 

 

 

 

 

 

Lombok 라이브러리 추가 

 

패키지 익스플로러 > Maven Dependencies > lombok 을 찾는다. 해당파일의 내 컴퓨터 경로명을 찾아준다. 

 

내 로컬 저장소에서의 경로는 다음과 같았다. 

 

 

여기서 cmd 창을 불러오기위해 주소창에 cmd. 을 입력해준다. 

 

 

 

 

cmd 창에 다음과 같이 입력한다. 

java -jar lombok-1.18.22.jar

1.18.22은 버전이다. 

 

입력후엔 경고창이 뜨며 찾을 수 없다고 나오는데 이때 침착하게 select location창을 누른다. 

 

 

버전에 유의하며 선택해준다. 

단, 여기서 경로 중 한글폴더명 등 경로에 한글이 포함되면 오류가 발생한다. 

경로에 한글값이 포함돼있지않은지 한번 더 확인후 설치해주도록 한다. 

 

 

아래 창이 떴다면 정상적으로 설치된것이다. 

 

 

컴파일 에러가 사라진걸 확인할 수 있다. 

 

 

 

 

각각의 클래스 별로 수행해야할 로직은 다음과 같다. 

 

 

 

 

 

dtd 필요 

 

 

 

 

 

 

 

 

 

 

 

 

아까 만든 dao 파일을 네임스페이스로 지정해준다. 

 

 

 

 

yml 파일쪽에 매퍼를 등록시켜주어야 인식할 수 있다. 

 

 

 

 

 

같은 패키지 아래 있지않으면 동작시 실패한다. 

 

 

 

 

 

기본동작파일을 똑같이 config 아래에 넣어준뒤 패키지명을 수정해주고 동작시키면 오류가 발생하지않는다. 

 

정상적으로 실행되는 화면

 

 

 

결국 test를 하고싶다면 파일구조가 동일해야함을 알 수 있다. 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'Programming > Spring Framework' 카테고리의 다른 글

Spring Security (2)  (0) 2022.04.25
Spring Security (1)  (0) 2022.04.25
Spring Boot 초기 환경설정  (0) 2022.04.20
5. Spring AOP (2)  (0) 2022.04.11
5. Spring AOP (1)  (0) 2022.04.11

 

 

 

 

 

Features

  • Create stand-alone Spring applications
  • Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
  • Provide opinionated 'starter' dependencies to simplify your build configuration
  • Automatically configure Spring and 3rd party libraries whenever possible
  • Provide production-ready features such as metrics, health checks, and externalized configuration
  • Absolutely no code generation and no requirement for XML configuration

 

 

스프링 프레임워크와 별도의 기능은 아님

독립형, 톰캣 내장, 스타터 제공, 라이브러리 유동성 관리(버전), 스프링 외 다른 회사의 라이브러리도 가능, XML 을 어노테이션 형식으로 적용

 

 

 

 

 

 

spring.io 홈페이지에 방문해 Project > Spring Tools

 

 

https://spring.io/

 

Spring makes Java simple.

Level up your Java code and explore what Spring can do for you.

spring.io

 

 

 

 

 

 

 

 

 

Maven 과 Gradle 둘 중 하나 선택가능. 결국 MVN repository에서 다음과같이 가져다 사용된다. 

 

 

 

 

 

 

 

 언어는 자바언어를 사용

 

 

SNAPSHOT 이나 M2 같은경우 아직 준비중이니 안정화가 떨어진다.

 

 

 

 현재쓰기 가장 적합한 버전은 2.6.6버전이다.

 

 

 참고로 부트는 2.6.6 을 스프링 버전은 5버전대를 사용했다. 

 

 

 

 Project Metadata 같은 경우는 다음과같이 설정했다. 

 

 

자바의 여러버전이있지만 8, 11, 17 버전이 지원이 오래동안 되는걸 확인할 수 있다. (LTS, Long Term Support)

즉 안정적으로 사용하기위해 앞선 세버전을 많이 사용한다.

 

 

 

 

 

ADD를 눌러 다음과같이 추가한다.  

 추가한 STARTER에 관한 설명

https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters

 

 

 

해당버튼을 누르면 압축 파일로 다운 받을 수 있다. 압축을 푼 후 작업환경에  임포트 시켜준다. 

 

 

 

 

 

처음엔 시간이 좀 걸린다.  

 

 

 

 

 파일구조를 간략히 보면 각각의 파일이 들어가야 할 곳은 이러하다. 

 

 

 

 

 Test 는 잘 적용 및 실행 되는지 테스트 해보기위한 도구이다. 

 

 

테스트임을 나타내기 위한 어노테이션이 붙는다. 

 

 

 

 

 

 

 

 

 

 

 

포트번호를 바꾸고 실행을 하면 다음과같이 404 오류 페이지가 뜬다. 

 

 

 

 기본적인 커스터마이징은 다음과같다. 포트번호 8080이 기본이지만 덮어쓰는 형식으로 다르게 적용할 수도 있다. 

 

https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties

 

 

순서대로 자바파일, 정적파일, 뷰 등 작성

 

 

 

 

 

프로젝트 만들기위해선 위의 3버전이 아닌 4버전으로 적용한다. 

tools 탭으로 가서 이클립스용 spring tools4를 다운받는다. 

 

https://spring.io/tools

 

 

 

 

 jar 파일이므로 압축은 따로 풀지않도록 주의한다. 

 

 

해당 파일의 경로안에서 cmd.를 입력하면 해당 파일의 경로로 동작시킬 수 있다. 

 

 

 

 

 

 

 

 

 

 

스타터 프로젝트를 열어 

 

 

 

 

 

 

 

 

html파일을 만들어보고싶으나 현재 상태에서는 기본적인 html을 생성할 수 없다. 해결방법으론 

 

 

 

 

설치가 완료되면 다시 시작할수있는 창이 뜨는데 다시 시작해주면 된다. 

 

기본적으로 html 파일을 사용할 수 없었으나 마켓 플레이스를 통해 웹을 설치해준 후에는 web에서 html 파일을 확인해볼 수 있다.

 

static 영역에 welcome 파일격인 html파일을 다음과 같이 작성한 후 다시 실행해준다. 

 

 

8001에 다음과같은 화면을 확인해볼 수 있다. 

 

 

 

 

 

 

 

 

 

 

 

db연동과 관련해서 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'Programming > Spring Framework' 카테고리의 다른 글

Spring Security (1)  (0) 2022.04.25
Spring Boot 초기 설정  (0) 2022.04.20
5. Spring AOP (2)  (0) 2022.04.11
5. Spring AOP (1)  (0) 2022.04.11
4. Spring DI 관리  (0) 2022.04.07

 

 

 

 

 

 

 

 

 

Advice 작성하기

 

 

 

 

 

 

 

 

 

Advice의 종류


 

 

 

 

 

 

 

 

JoinPoint Interface


  • JoinPoint는 Spring AOP 혹은 AspectJ에서 AOP의 부가 기능을 지닌 코드가 적용되는 지점을 뜻하며, 모든 어드바이스는 org.aspectj.lang.JoinPoint 타입의 파라미터를 어드바이스 메소드에 첫 번째 매개변수로 선언해야 한다.
  • 단, Around 어드바이스는 JoinPoint의 하위 클래스인 ProceedingJoinPoint 타입의 파라미터를 필수적으로 선언해야 한다.

 

 

 

 

 

 

 

 

 

 

JoinPoint Interface 메소드


 

 

 

 

 

 

 

 

 

Advice 예시


 

 

 

 

 

 

 

 

 

 

 

 

 

 

Spring AOP 사용하기

 

 

 

 

 

 

 

maven : pom.xml에 라이브러리 추가


AOP 사용을 위한 라이브러리를 pom.xml에 등록한다.

 

 

 

 

 

 

 

 

servlet-context.xml 에 xmlns : aop 등록


스프링 설정 파일(servlet-context.xml)을 클릭 후 하단의 Namespaces 탭을 클릭하여 aop을 선택하여 해당 요소를 사용하기 위한 설정을 추가한다.

 

 

 

 

 

 

 

 

 

 

XML : <aop: ??? >


  • <aop:config> : AOP 설정 정보임을 나타낸다.
  • <aop:aspect> : 아스펙트를 설정한다.
  • <aop:around pointcut=“execution()”> : Around 어드바이스와 포인트컷을 설정한다

 

 

 

 

 

 

 

 

 

 

Advice를 정의하는 태그


 

 

 

 

 

 

 

 

 

 

 

Annotation : @Aspect


  • 1. 클래스 선언부에 @Aspect 어노테이션을 정의한다.
  • 2. 이 클래스를 아스펙트로 사용하려면 Bean으로 등록해야 하므로 @Component 어노테이션도 함께 정의한다.

 

 

 

 

 

 

 

 

 

 

Annotation : <aop:aspect-autoproxy>


3. aop 탭으로 가서 beans를 우클릭하고 ‘Insert <aop:aspect-autoproxy> element’를 클릭하여 <aop:aspect-autoproxy> 요소를 추가한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

Advice를 정의하는 어노테이션


 

 

 

 

 

 

 

 

 

 

Annotation Advice 예시


 

 

 

 

 

 

 

 

 

@Pointcut 어노테이션 표현식


  • Poincut 어노테이션을 사용할 때 execution 속성에 표현식을 설정해 주어야 하는데, 다음과 같은 형식으로 지정해 주어야 한다.
  • 예시의 표현식은 리턴 타입과 매개변수를 무시하고, com.greedy.biz.  패키지로 시작하는 클래스 중 이름이 Impl로 끝나는 클래스의 모든 메소드를 뜻한다.

 

 

 

 

 

 

 

 

 

 

@Pointcut 어노테이션 주요 표현식


 

 

 

 

 

 

 

@Pointcut 어노테이션 주요 표현식


 

 

 

 

 

 

 

 

'Programming > Spring Framework' 카테고리의 다른 글

Spring Boot 초기 설정  (0) 2022.04.20
Spring Boot 초기 환경설정  (0) 2022.04.20
5. Spring AOP (1)  (0) 2022.04.11
4. Spring DI 관리  (0) 2022.04.07
3. Spring IOC (2)  (0) 2022.04.06

 

 

 

 

 

 

 

 

 

Spring AOP

 

 

 

 

 

 

 

Spring AOP란


Spring AOP 란, 관점 지향 프로그래밍의 약자로 일반적으로 사용하는 클래스(Service, Dao 등) 에서 중복되는 공통 코드 부분(commit, rollback, log 처리)을 별도의 영역으로 분리해 내고, 코드가 실행 되기 전이나 이 후의 시점에 해당 코드를 붙여 넣음으로써 소스 코드의 중복을 줄이고, 필요할 때마다 가져다 쓸 수 있게 객체화하는 기술을 말한다.

 

 

 

 

 

 

 

 

 

Spring AOP 개요


아래 이미지와 같이 공통되는 부분을 따로 빼내어 필요한 시점에 해당 코드를 추가 해주는 기술을 AOP라고 말한다.

 

 

 

 

 

 

 

 

 

 

 

Spring AOP의 구조


공통되는 부분을 따로 빼내어 작성하는 클래스를 Advice라고 이야기 하며, 해당 시점을 Joinpoint, 그리고 그 시점에 공통 코드를 끼워 넣는 작업을 Weaving 이라고 말한다.

 

 

 

 

 

 

 

 

 

 

 

Aspect 란?


  • 어스펙트(Aspect)는 부가기능을 정의한 코드인 어드바이스(Advice)와 어드바이스를 어디에 적용하지를 결정하는 포인트컷(PointCut)을 합친 개념이다.
  • AOP 개념을 적용하면 핵심기능 코드 사이에 끼어있는 부가기능을 독립적인 요소로 구분해 낼 수 있으며, 이렇게 구분된 부가기능 아스펙트는 런타임 시에 필요한 위치에 동적으로 참여하게 할 수 있다.

 

 

 

 

 

 

 

 

 

 

 

Spring AOP 의 핵심 용어


 

 

 

 

 

 

 

 

 

 

 

 

 

AOP 구조 정리


 

 

 

 

 

 

 

 

 

 

 

 

 

Spring AOP의 특징

 

 

 

 

 

1) Spring은 프록시(Proxy) 기반 AOP를 지원한다.


  • Spring은 대상 객체(Target Object)에 대한 프록시를 만들어 제공하며, 타겟을 감싸는 프록시는 서버 Runtime 시에 생성된다.
  • 이렇게 생성된 프록시는 대상 객체를 호출 할 때 먼저 호출되어 어드바이스의 로직을 처리 후 대상 객체를 호출한다.
  • Proxy : 대상 객체를 직접 접근하지 못하게 '대리인'으로써 요청을 대신 받는 기술

 

 

 

 

 

 

2) Proxy는 대상 객체의 호출을 가로챈다(Intercept).


  • Proxy는 그 역할에 따라 타겟 객체에 대한 호출을 가로챈 다음 어드바이스의 부가기능 로직을 수행하고 난 후에 타겟의 핵심기능 로직을 호출 (전처리 어드바이스) 
  • 혹은 타겟의 핵심기능 로직 메서드를호출한 후에 부가기능(어드바이스)을 수행한다. (후처리 어드바이스)

 

 

 

 

 

 

 

 

 

3) Spring AOP는 메소드 조인 포인트만 지원한다.


  • Spring은 동적 프록시를 기반으로 AOP를 구현하기 때문에 메소드 조인 포인트만 지원한다. 
  • 즉, 핵심기능(대상 객체)의 메소드가 호출되는 런타임 시점에만 부가기능(어드바이스)을 적용할 수 있다.
  • 하지만, AspectJ 같은 고급 AOP 프레임워크를 사용하면 객체의 생성, 필드값의 조회와 조작, static 메서드 호출 및 초기화 등의 다양한 작업에 부가기능을 적용할 수 있다.

 

 

 

 

 

 

 

 

 

XML 기반의 aop 네임스페이스를 통한 AOP 구현


  • 부가기능을 제공하는 Advice 클래스를 작성한다.
  • XML 설정 파일에 <aop:config>를 이용해서 아스펙트를 설정한다. (즉, 어드바이스와 포인트컷을 설정함)

 

 

 

 

 

 

 

 

@Aspect 어노테이션을 이용한 AOP 구현


  • @Aspect 어노테이션을 이용해서 부가기능을 제공하는 Aspect 클래스를 작성한다. 
  • 이때 Aspect 클래스는 어드바이스를 구현하는 메서드와 포인트컷을 포함한다.
  • XML 설정 파일에 <aop:aspectj-autoproxy />를 설정한다.

 

 

 

 

 

 

 

 

 

 

 

 

'Programming > Spring Framework' 카테고리의 다른 글

Spring Boot 초기 환경설정  (0) 2022.04.20
5. Spring AOP (2)  (0) 2022.04.11
4. Spring DI 관리  (0) 2022.04.07
3. Spring IOC (2)  (0) 2022.04.06
3. Spring IOC (1)  (0) 2022.04.06

 

 

 

 

 

Spring DI 관리

 

  • DI는 IoC 구현의 핵심기술로, 컨테이너가 빈의 설정정보를 읽어봐 자동으로 해당 객체의 연결을 해준다. 
  • 이 DI 를 어떻게 실질적으로 구현할 수 있는지 코드를 통해 알아보도록 한다. 

 

 

  • 프로젝트를 생성해준다. 

 

 

 

  • 실행 할 main 메소드가 있는 클래스 Application1을 생성 해 준다. 

 

 

  • 테스트의 대상이 될 수 있는 인터페이스 Account를 먼저 생성해준다. 

 

 

  • Account를 통해 수행할 수 있는 기능을 추상적인 메소드로 작성해준다.  
  • 작성한 반환형으로 결과를 출력, 리턴 받을 수 있다. 
  • 이곳에서는 추상 메소드만이 정의됐으므로 실질적인 구현체가 필요하다. 작성해준다. 

public interface Account {
	
	/* 잔액 조회 */
	String getBalance();
	
	/* 입금 */
	String deposit(int money);
	
	/* 출금 */
	String withDraw(int money);
	
}

 

 

  • PersonalAccount 라는 클래스를 작성해준다. 이는 Account 라고하는 인터페이스를 구현할 것이다. 

 

 

implemets 사용시 클래스명에 에러가 뜰텐데 이를 클릭해 앞선 구현항목들을 작성해준다 

인터페이스를 구현한다는 것은 인터페이스에 정의된 내용들을 구현하도록 강제하는 것과 같다.

 

 

 

  • 필드에는 Account가 가져야 할 내용(은행코드, 계좌번호, 계좌비번, 잔액)들을 선언해준다. 

 

 

  • 필드를 설정한 뒤 마우스 우 클릭 > Generate Constructor using Field 선택 

 

  • 해당 되는 필드들을 확인 한 뒤 Generate 클릭

 

  • 전체 Constructor 를 만들어 주기 위해 위의 과정을 한번 더 반복한다. 
  • 다시 마우스 우 클릭 > Generate Constructor using Field
  • 유의 할 점은, 잔액 조회 없이 첫 조회를 위해 balance(잔액)을 제외한 상태로 Generate 시켜줘야 한다는 점이다. 

 

 

  • 다음과 같이 생성된 모습을 확인할 수 있다. 

 

 

  • 구현을 강제받은 메소드들의 내용을 구현한다. 
  • 먼저 잔액조회 시 리턴할 내용을 작성한다. 

 

 

3.DI - 11:11

 

 

 

 

 

 

 

public class PersonalAccount implements Account{
	
	private int bankCode;
	private String accNo;
	private String accPwd;
	private int balance;
	
	public PersonalAccount(int bankCode, String accNo, String accPwd) {
		super();
		this.bankCode = bankCode;
		this.accNo = accNo;
		this.accPwd = accPwd;
	}

	public PersonalAccount(int bankCode, String accNo, String accPwd, int balance) {
		super();
		this.bankCode = bankCode;
		this.accNo = accNo;
		this.accPwd = accPwd;
		this.balance = balance;
	}

	@Override
	public String getBalance() {
		
		return this.accNo + " 계좌의 현재 잔액은 " + this.balance + "원 입니다.";
	}

	@Override
	public String deposit(int money) {
		
		String str = "";
		
		if(money >= 0) {
			this.balance += money;
			str = money + "원이 입금되었습니다.";
		} else {
			str = "금액을 잘못 입력하셨습니다.";
		}
		
		return str;
	}

	@Override
	public String withDraw(int money) {
		
		String str = "";
		
		if(this.balance >= money) {
			this.balance -= money;
			str = money + "원이 출금되었습니다.";
		} else {
			str = "잔액이 부족합니다. 잔액을 확인해주세요.";
		}
		
		return str;
	}

	
	
	
	
	
	
	
	
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

XML 단독 사용


모든 Bean을 명시적으로 XML에 등록하는 방법

 

 

 

 

 

 

 

 

 

 

XML과 빈 스캐닝 (Bean Scanning)의 혼용


  • Bean으로 사용될 클래스에 특별한 어노테이션(Annotation)을 부여하고 Spring 컨테이너가 이를 통해 자동으로 Bean을 등록
  • 이 방식을 빈 스캐닝(Bean Scanning)을 통한 자동인식 Bean 등록기능이라고 한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Spring DI Annotation

 

 

 

 

 

Bean 등록 Annotation


@Repository, @Service, @Controller는 특정한 객체의 역할에 대한 @Component의 구체화된 형태이다.

 

 

 

 

 

 

 

Bean 의존관계 주입 Annotation


  • @Autowired 와 @Resource 어노테이션은 @Component 로 의존관계를 설정한 객체로부터 의존 관계를 자동으로 주입해주는 어노테이션이다.
  • @Autowired는 타입으로, @Resource는 이름으로 연결해주는 점이 다르다.

 

 

 

 

 

 

 

 

@Autowired


  • 정밀한 의존관계 주입 (Dependency Injection)이 필요한 경우에 유용하다.
  • @Autowired는 필드 변수, setter 메소드, 생성자, 일반메소드에 적용 가능하다.
  • 의존하는 객체를 주입할 때 주로 Type을 이용하게 된다.
  • @Autowired는 <property>, <constructor-arg> 태그와 동일한 역할을 한다.
  • @Qualifier : @Autowired 와 함께 쓰이며, 한 프로젝트 내에 @Autowired로 의존성을 주입하고자 하는 객체가 여러개 있을 경우, @Qualifier("name")를 통해 원하는 객체를 지정하여 주입할 수 있다.

 

 

 

 

 

 

 

 

@Resource


  • 어플리케이션에서 필요로 하는 자원을 자동 연결할 때 사용된다.
  • @Resource는 프로퍼티, setter 메서드에 적용 가능하다.
  • 의존하는 객체를 주입할 때 주로 Name을 이용하게 된다.

 

 

 

 

 

 


@Value


  • 단순한 값을 주입할 때 사용되는 어노테이션이다.
  • @Value(“Spring”)은 <property .. value=“Spring” /> 와 동일한 역할을 한다.

 

 

 

 

 

 

 

<context:component-scan> 태그


  • @Component를 통해 자동으로 Bean을 등록하고, @Autowired로 의존관계를 주입받는 어노테이션을 클래스에서 선언하여 사용했을 경우에는 해당 클래스가 위치한 특정 패키지를 Scan하기 위한 설정을 XML에 해주어야 한다.

 

 

 

 

 

 

<context:component-scan> 태그 예시


<context:include-filter>태그와 <context:exclude-filter>태그를 같이 사용하면 자동 스캔 대상에 포함시킬 클래스와 포함시키지 않을 클래스를 구체적으로 명시할 수 있다.

 

<context:component-scan base-package=“com.korea.firstSpring" />

 

 

 

 

 

 

 

 

 

'Programming > Spring Framework' 카테고리의 다른 글

5. Spring AOP (2)  (0) 2022.04.11
5. Spring AOP (1)  (0) 2022.04.11
3. Spring IOC (2)  (0) 2022.04.06
3. Spring IOC (1)  (0) 2022.04.06
2. Spring Framework  (0) 2022.04.06

+ Recent posts