spring framework - 자바 기반 오픈소스 프레임워크, java ee에서 요구하는 수준의 기능을 java ee 사용하지 않고 구현위해 시작
특징
- 경량 컨테이너 : 객체 생성, 소멸 생명주기 관리하며 필요한 객체 얻어옴
- 제어의 역행 지원 : 메서드나 객체의 흐름 제어권을 필요에 따라 사용자 코드 호출
- 의존성 주입 지원 : 각각의 계층이나 서비스 간 의존성이 존재할 경우 연결해 줌
- 관점 지향 프로그래밍 지원 : 트랜잭션이나 로깅, 보안과 같이 여러 모듈에서 공통적으로 사용하는 기능의 경우 분리해 관리
스프링은 여러 서브 프로젝트로 구성 > 프로젝트는 여러 모듈로 구성됨 > 세부 프로젝트의 모듈 조합해 개발함
대표적 스프링 프로젝트 - 스프링 부트, 스프링 데이터, 스프링 배치, 스프링 시큐리티, 스프링 hateoas
스프링 부트 - 빠르고 간편하게 시작 가능 (특정 목적의 개발환결과 프로젝트 구조 자동 생성)
톰캣 같은 was 내장, 별도 웹 서버 설치 및 실행 없이 웹 구동 가능
리액티브 스택 - 새로운 비동기 논블로킹 i/o 구조 사용해 멀티코어 시스템 장점 살리고 대규모 사용자 접속 처리에 유용함
톰캣 8.5이상 사용 가능, nosql db 연동 지원
서블릿 스택 - 서블릿 api에 기반한 동기 방식 블로킹 i/o 구조 사용 > 하나의 요청은 하나의 스레드로 처리
Spring MVC 기반의 서버 프로그램 개발과 JPA, JDBC, NoSQL 데이터베이스 지원을 포함함
리액티브 프로그래밍 - 변화에 반응하는 모델 기본적으로 비동기i/o 기반 데이터 흐름과 변화 전파에 중점둠
데이터 흐름 먼저 정의 후 변경되었을 시 연관된 함수나 수식 업데이트 됨 (개발 효율 상승 및 적은 스레드로 많은 부하 처리 가능)
IoC - 제어의 역행
기존 - 프로그램 시작하는 곳에(main()) 필요한 객체 생성 및 메서드 호출하는 흐름
ioc- 작업 수행하는 쪽에서 객체 생성 > 프로그램 제어를 자신이 아닌 다른곳에 위임함
특징
객체가 자신이 사용할 객체 생성하거나 선택하지 않음
객체 자신이 어떻게 생성되고 사용되는지 알 수 없음
모든 객체는 제어 권한을 위임 받은 특별한 객체(컨테이너)에 의해 만들어지고 사용됨
스프링의 경우 - 컨테이너에서 객체 생성 및 공급 담당 / 빈 - 컨테이너에서 관리되는 스프링 객체
DI - 의존성 주입
ioc를 시스템적으로 구현하는 방법 (di, dl)
di - 클래스 간 의존관계를 bean 설정 기바능로 컨테이너가 자동으로 연결해주는 방식
dl - 저장소에 저장되어 있는 bean에 접근하기 위해 개발자가 컨테이너에 제공하는 api 이용해 빈 찾음
dl은 컨테이너에 대한 의존성 커짐(불필요한 코드 증가) > di 방식 선호됨
AOP - 관점 지향 프로그래밍
횡단 관심사(cross cutting concern) 분리 허용해 모듈성 증가가 목적
> 코드 자체를 수정하지 않는 대신 기본 코드에 추가 동작(어드바이스) 정의해 추가된 기능 실행됨
스프링 webMVC
mvc 기반 웹 개발과 구조적으로 거의 동일함
컨트롤러 직접 구현할 필요 없이 특정 요청에 대한 메서드 작성하면 됨
> 불필요한 중복 구현 없음, 구조 리펙토링 고민 필요 없음
모델 - 기존 코드 그대로 사용 가능 > 간결하게 개선 가능 + db 관련 작업 처리 가능
뷰 - jsp 그대로 사용 가능 > 독립적 개발 및 테스트 어려워짐
jsp 종속은 없음, 타임리프,jsp, 프리마커 등 다양한 템플릿 연동 지원
컨트롤러 구현
클래스 생성 후 선언부 위에 애너테이션 추가
@Controller - mvc 컨트롤러로 동작할 클래스 의미함
@RequestMapping() - 서블릿의 @WebServlet의 request mapping과 같음
기본적으로 jsx-rs와 유사한 구조로 서블릿과 달리 하나의 컨트롤러 클래스에서 여러 메서드와 요청 다양하게 처리 가능
모델 구현
기존 코드 완전히 재사용 가능
dao 객체 불러와 필요한 메서드 호출함 > jdbc 구조 사용 가능 / jdbc나 jpa 사용해 개선 가능
do(entity) 클래스 경우 재사용 가능, jpa 도입할 경우 추가로 애너테이션 필요
컨트롤러에서 dao 사용 간략화 public void addProduct(Product p) {
@Autowired String sql = "insert into product(name, price) value(?,?)";
ProductDAO dao; jdbcTemplateObject.update(sql, p.name, p.price);
}
restController - 스프링에서 rest api 구현에 사용하는 모듈
기본적인 개발 구조나 원리는 동일함 > 사용하는 애너테이션 등에서 차이
메서드에 따른 요청 구분하며 요청 또는 경로 파라미터에 따라 제공받을 수 있음
json 규격으로 전달되는 데이터를 자바 객체로 매핑 가능 > 리턴타입으로 사용하면 자동으로 json으로 변환됨
hello() - /api/hello?msg=welcome 형식으로 요청 이루어짐
hello2() - /api/hello/welcome 형식의 요청에 동작함
스프링 빈 - 스프링 컨테이너에 관리되는 자바 객체 > 등록은 설정 xml, 애너테이션, 설정 클래스 등으로 등록
> 오토와이러잉을 통해 공급됨 > 스프링 부트의 경우 애너테이션을 통한 빈 등록을 기본으로 함
>>필요한 빈 등록 > 필요한 위치에서 @AutoWired 통해 주입 받아 사용함
@Component 관계
- @Service, @Controller, @Repository 모두 @Component 상속 받음
컴포넌트 애너테이션으로 등록한 클래스는 모두 자동으로 빈에 등록됨
빈 등록
- @Componenet 사용
- @Configuration ~ @Bean 사용
- 필드 주입 @Autowired사용 > 순환 의존성 문제 발생 가능 / 불변 객체 만들 수 없음
실습
실행될 index파일
package org.example.demo_test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoTestApplication {
public static void main(String[] args) {
SpringApplication.run(DemoTestApplication.class, args);
}
}
package org.example.demo_test;
import org.springframework.ui.Model;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@Controller
@RestController("/test")
public class DemoTestController {
@GetMapping("/hello")
public String hello(Model model) {
model.addAttribute("title","hello test");
model.addAttribute("msg","msg");
return "hello";
}
@GetMapping("/hello2")
@ResponseBody
public String hello2(Model model) {
model.addAttribute("title","title");
model.addAttribute("msg","msg");
return "hello2";
}
}
뉴스 앱
package org.example.news;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class News {
private int aid;
private String title;
private String img;
private String date;
private String content;
@Override
public String toString() {
return "News [aid=" + aid + ", title=" + title + ", date=" + date + "]";
}
}
newsDAO는 그대로 가져옴
package org.example.news;
import org.springframework.stereotype.Component;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
@Component
public class NewsDAO {
final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
final String JDBC_URL = "jdbc:mysql://127.0.0.1:3306/newsdb?serverTimezone=Asia/Seoul";
public Connection open() {
Connection conn = null;
try {
Class.forName(JDBC_DRIVER);
conn = DriverManager.getConnection(JDBC_URL, "root", "1234");
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
public void addNews(News n) throws Exception {
Connection conn = open();
String sql = "insert into news (title, img, date, content) values(?,?,CURRENT_TIMESTAMP(),?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
try(conn; pstmt) {
pstmt.setString(1, n.getTitle());
pstmt.setString(2, n.getImg());
pstmt.setString(3, n.getContent());
pstmt.executeUpdate();
}
}
public List<News> getAll() throws Exception {
Connection conn = open();
List<News> newsList = new ArrayList<>();
String sql = "select aid, title, date_format(date, '%Y-%m-%d %H:%i:%s') as cdate from news";
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
try(conn; pstmt; rs) {
while(rs.next()) {
News n = new News();
n.setAid(rs.getInt("aid"));
n.setTitle(rs.getString("title"));
n.setDate(rs.getString("cdate"));
newsList.add(n);
}
}
return newsList;
}
public News getNews(int aid) throws Exception {
// 1) Connection 을 얻어온다.
Connection conn = open();
// 2) sql 을 작성한다.
String sql = "select aid, title, img, date_format(date, '%Y-%m-%d %H:%i:%s') as cdate, content from news"
+ " where aid=?";
// 3) pstmt 에 sql 을 적용한다. 매개변수가 있다면 argument를 설정한다.
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, aid);
News n = new News();
// 4) pstmt sql 문을 실행한다. ==> rs 에 담는다.
ResultSet rs = pstmt.executeQuery();
try(conn; pstmt; rs) {
// 5) 가져온 데이터를 처리한다.
if(rs.next()) {
n.setAid(rs.getInt("aid"));
n.setTitle(rs.getString("title"));
n.setImg(rs.getString("img"));
n.setDate(rs.getString("cdate"));
n.setContent(rs.getString("content"));
};
// 6) 리소스를 닫는다. try with resources 에 의해 자동으로 닫힘
}
// 7) 데이터베이스로부터 가져온 데이터를 반환한다.
return n;
}
public void delNews(int aid) throws Exception {
Connection conn = open();
String sql = "delete from news where aid = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, aid);
try(conn; pstmt) {
if(pstmt.executeUpdate() == 0) {
throw new SQLException("DB - news 삭제 에러");
};
}
}
}
NewsApiController
package org.example.news;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/news")
public class NewsApiController {
final NewsDAO newsDAO;
@Autowired
public NewsApiController(NewsDAO newsDAO) {
this.newsDAO = newsDAO;
}
@GetMapping
public List<News> getAllNews() {
List<News> newsList = null;
try {
newsList = newsDAO.getAll();
} catch(Exception e) {
throw new RuntimeException(e);
}
return newsList;
}
@GetMapping("/{aid}")
public News getNewsById(@PathVariable int aid) {
News news = null;
try {
news = newsDAO.getNews(aid);
} catch (Exception e) {
throw new RuntimeException(e);
}
return news;
}
@PostMapping
public String addNews(@RequestBody News news) {
try {
newsDAO.addNews(news);
} catch (Exception e) {
e.printStackTrace();
return "news api 등록 실패";
}
return "news api 등록 성공";
}
@DeleteMapping("/{aid}")
public String delNews(@PathVariable int aid) {
try {
newsDAO.delNews(aid);
} catch (Exception e) {
e.printStackTrace();
return "news api 삭제 실패";
}
return "news api 삭제 성공";
}
}
NewsWebController
package org.example.news;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/news")
public class NewsWebController {
final NewsDAO newsDAO;
@Autowired
public NewsWebController(NewsDAO newsDAO) {
this.newsDAO = newsDAO;
}
//application.properties에 입력한 저장경로
@Value("${news.imgdir}")
String fdir;
@GetMapping("/list")
public String news(Model m) throws Exception {
m.addAttribute("title", "News");
return "news/newsList";
}
@GetMapping("/{aid}")
public String news(@PathVariable int aid, Model model) {
News news = null;
try {
news = newsDAO.getNews(aid);
model.addAttribute("news", news);
} catch (Exception e) {
model.addAttribute("error","뉴스 조회 실패");
}
return "news/newsView";
}
}
'kosta_이론' 카테고리의 다른 글
25.05.22 스프링 테스트 코드 (2) | 2025.05.22 |
---|---|
25.05.21 spring framework (0) | 2025.05.22 |
25.05.12 jsp, 디자인패턴 (0) | 2025.05.12 |
25.05.09 JSP (eclipse) (1) | 2025.05.09 |
2025.04.19 게시판 crud 만들기 (1) | 2025.04.19 |