230427 (Spring Framework)
Spring Framework
자바 플랫폼을 위한 오픈 소스 애플리케이션 프레임 워크
동적인 웹 사이트 개발을 위한 여러 서비스를 제공
// Spring도 Framework 중 하나
★ 특징
꼭 무조건 알아야 할 특징 2가지 (누군가 물어볼 가능성 100%)
① DI (Dependency Injection) 의존성 주입
설정 파일 / 어노테이션을 통해 프레임워크가 만들어놓은 빈(객체)을 내가 활용할 변수에 자동으로 주입해주는 것
→ 내가 주입하는 게 아니라 프레임워크에게 기대야 함 / 개발자가 직접 의존하는 객체를 생성할 필요 없음
ex. IoC에 의해 프레임워크가 만들어놓은 객체를
내가 활용할 변수에 프레임워크가 직접 담을 수 있도록 하는 것
ex. '어떤 변수에 어떤 객체 담게 해줘' 라고 하는 방법으로 프레임워크에게 부탁
② IoC (inversion of Control) 제어 반전
개발자가 아닌 프레임워크에 주도권이 있음
(프레임워크가 짜 놓은 형식, 틀이 있기 때문에 내 마음대로 움직일 수 없다)
-객체 생성부터 모든 생명주기 관리까지 트랜잭션 관리, 로그 관리 등 전부 프레임워크가 주도
→ new ~ 이런 식으로 직접 객체를 생성할 일이 현저히 줄어듦
→ 스프링 컨테이너가 알아서 관리하기 때문에 메모리 관리에도 좋음
≫ 위 두 가지는 스프링에서만 보이는 특징은 아님,
프레임워크라 하면 무조건 보이는 특징이니 외울 것.
≫ 또한 서로가 유기적인 특징이며 뗄레야 뗄 수 없는 관계
Spring 구성 모듈
가장 중요한 것 : Core Container (이 컨테이너 때문에 IoC와 DI가 나타남)
✔ Data 접근 계층
-영속성 프레임워크(MyBatis)와 연결하는 것은 모듈이 담당
-스프링을 통해 DB(MyBatis)에 접근할 수 있게 다리 역할을 함
pom.xml
┌ 스프링은 기능을 담당하는 역할
└ MyBatis는 DB를 담당하는 역할
≫ 서로를 연결하기 위해 모듈이 존재 - 라이브러리 사용
✔ MVC 계층(MVC /Remoting)
≫ 서버에서 통신할 수 있도록!
✔ AOP 계층
≫ 메소드마다 공통적으로 들어갈 코드를 원하는 곳에 쓸 수 있게끔 함
-장점
1) 코드를 분리시킬 수 있다
2) 실행 시점을 정할 수 있음(디테일)
✔ Core Container
★ Spring의 동작 구조
✔ Spring 웹
1. 요청 들어옴
≫ web.xml 에서 정의된 DispatcherServlet 생성
2. Servlet은 이전과 다르게 요청된 url 받아주는 역할만 함 (일은 컨트롤러가 함)
≫ 내가 직접 만들지 않고 DispatcherServlet이 url 요청 받도록 하여 알아서 처리
3. HandlerMapping이 서블릿이 받은 url 요청에 맞는 컨트롤러를 찾음
4. view와 model과의 연결하여 요청은 Controller 객체가 처리(일)할 수 있게 넘김
5. ModelAndView 를 통해 값과 View 경로를 받아 함께 담는다 (필수로 쓰는 건 아님)
6. ViewResolver가 넘어가고자 하는 view를 찾아 해당 페이지로 넘겨줌 (결과를 화면에)
≫ 컨트롤러가 일 한 것 전달 받아서 ViewResolver로 갈 수 있도록 Servlet이 사이에서 도움
*요청 → DispatcherServlet → HandlerMapping → Controller → ModelAndView → ViewResolver → 화면 출력
≫ test나 target은 열어볼 필요 없음
≫ 여기의 resources는 생성 시 자동으로 생김 (프레임워크가 관리)
수업 중 약속..
main 안의 큰 resources
webapp 안의 resources
★ 상위 레벨 패키지 세 개가 있어야 하고, 마지막 3번째가 contextPath가 됨
ex) 위 같은 경우 com.mycompany.myapp → contextPath : myapp
-JSP는 자바 서버 기반으로 동작해 뷰에 보여지기 때문에, 서버에 연결이 안 되어있으면 실행 불가
-properties - Project facet과 pom.xml도 해당 자바 버전으로 (현재는 11) 다 바꿔주어야 함
+ pom.xml 밑에서 두 번째 플러그인에 소스, 타겟도 11로 변경해주기
-스프링은 백 단 수정 시 서버를 무조건 재실행 한 후 run 해야 함 (오류 발생 가능성 有)
실행 과정
<welcome-file-list>를 찾아보면,
1. 스프링은 서버가 실행할 때 Servers 안에 있는 web.xml을 먼저 확인한다. (<welcome-file-list>가 있음)
2. 서버가 실행되면 그제서야 프로젝트의 web.xml로 감 (servlet과 servlet-mapping을 통해 해당 클래스 연결)
-DispatcherServlet 서블릿의 역할을 전부 처리 :
1) 컨트롤러 찾기 (핸들러매핑 @RequestMapping),
2) 기본적으로 값을 String으로 넘김 (view의 이름을 가지고 ViewResolver)
3. 만약 프로젝트의 web.xml에 <welcome-file-list>가 없으면 서버에 있는 <welcome-file-list>를 따라감
4. 이후 프로젝트 web.xml에 <welcome-file-list>를 추가하거나 수정하면 기존의 서버에도 덮어씌워짐
근데 index.jsp가 아니라 왜 home.jsp가 뜰까?
왜?
▼
-요청이 오면 web.xml에서 DispatcherServlet이 요청을 받고
DispatcherServlet는 일 할 컨트롤러를 찾음 *HandlerMapping이 필요
▼
-핸들러 역할을 하는 @RequestMapping 어노테이션을 통해 요청 값을 처리할 컨트롤러를 확인
(web.xml 처럼 xml로 만들 수 있지만, 어노테이션으로 만들어 받는 것임)
-Model이라는 객체를 사용한 model.addAttribute는 request.setAttribute와 같은 역할을 함
-컨트롤러가 로직을 수행한 후, return "문자열" 로 값을 반환
-다시 DispatcherServlet가 값을 받는데 현재 상태로 혼자 처리할 수 없기 때문에
<init-param> 안의 <param-value> 를 통해 servlet-context.xml으로 향하게 된다
-servlet-context.xml 안에 ViewResolver가 뷰로 향할 경로를 찾는데 도움을 준다
(받아온 "문자열" 값 앞 뒤에 첨자를 달아주는 prefitx, suffix로 경로 / 확장자를 가공)
위의 ViewResolver를 통해 경로와 확장자를 가공하면
→ /WEB-INF/views/home.jsp
그렇기 때문에 index.jsp가 아닌 home.jsp가 view에 노출되는 것!
* DispatcherServlet는 handlerMapping과 ViewResolver와 함께 같이 일을 함
Spring에서는 앞으로 메소드를 만들 때, 반환 타입 String!
@ annotation
// @ 어노테이션은 클래스로 되어있기 때문에 맨 앞 글자는 대문자
@annotation 은 프로그램이 시작될 때 Static처럼 맨 처음 쭉 읽고 시작된다.
≫ 어떤 세팅, 뭘 만들어 놓을지 알기 때문에
@Controller 는 빈(bean, 객체)를 생성해주는 annotation
≫ 컨트롤러의 역할을 하는 빈(객체)을 생성 (Spring이 인지)
@Service는 서비스 역할을 하는 빈을 생성
@Repository는 DAO 역할을 하는 빈을 생성
@Component 는 아무 역할이 없는 객체를 생성해주는 annotation
≫ 예를 들면 vo
≫ @Component가 상위, 그 안에 @Controller가 있음
@Autowired로 의존성 주입
@Controller 로 컨트롤러 역할을 하는 빈 생성, view로 부터 값을 받아오는 방법
파라미터 전송받는 방법
1. HttpServletRequest 이용
2. @RequestParam 이용
속성 : value / defaultValue / required
≫ @RequestParam을 이용해 name이 id인 파라미터를 String id에 담는 것 (pwd도 마찬가지)
≫ 장점 : 무조건 String으로 받아오지 않아도 된다. (어떤 타입이든 알아서 형변환 해 줌)
↔ request.getParameter()는 String이 반환 타입이라 Integer.ParseInt로 파싱해 형변환을 해 주어야 함
≫ 현재는 value만 받아오고 있기 때문에 생략 가능
≫ 비밀번호 값을 입력하지 않고 제출하였더니, defaultValue 출력
≫ 없는 파라미터 test에 required를 false로 만들어 출력했더니 test의 기본 값인 null이 출력됨
*만약에 false로 하지 않으면 파라미터가 없을 경우 400 에러가 뜰 수 있다
≫ 만일 test가 String이 아닌 다른 자료형일 경우 해당 타입의 기본 값을 반환하겠다.
3. @RequestParam 생략
≫ 이왕이면 생략하지 않고 쓰는 것이 좋겠다.
4. @ModelAttribute 사용
// 해당 어노테이션 사용을 위해서는 모델 객체를 먼저 생성해주어야 한다.
// model.vo.Member 클래스 생성한 후 아래 Lombok 라이브러리를 사용해 구성
≫ 파라미터 명과 Setter 명만 일치시켜 준다면 간단히 객체에 담아올 수 있어 좋음 알아서 세팅돼서 들어감
≫ 만약 model.vo.Member 필드에는 없는데 값을 가져와야 한다면 request.getParameter()를 써도 됨
꼭 한 가지만 사용해야 하는 것은 아니니 필요에 따라 섞어서 사용해도 된다.
*회원가입 / 게시판에서 여러 개를 받아와야 하면 @ModelAttribute로 쉽게 받아오는 게 편리
5. @ModelAttribute 생략
≫ @RequestParam 처럼 생략 가능하나 가독성 떨어져 권장하지 않음
Back 단에서 View 단으로 보낼 수 있는 방법 두 가지
파라미터 전달하는 방법
1. Model 객체 : 맵 형식(key, value)
→ Model 객체는 request가 scope
// 원래 request.setAttribute()를 썼지만, 그 대신 model에 담아 보내면 됨!
≫ model은 setAttribuer가 아닌 addAttribute를 사용
// 로그인이 잘 됐을 경우는 굳이 request에 담을 필요가 없어서
loginUser라는 이름으로 받아온 loginUser를 session에 담아둠
2. ModelAndView 객체 : Model(data) + View(어디로 넘어갈 것인지)
// 반환 타입은 String이 아닌 ModelAndView!
≫ 값 / view 경로 를 맵 형식으로 mv에 담음
≫ 반환 타입을 String으로 하려면 데이터를 Model에 담으면 되고,
반환 타입을 ModelAndView로 하려면 Model(데이터)에 값과 View를 담으면 됨
≫ 세션 무효화로 로그아웃
@SessionAttribute
Model에 키를 넣고 세션에 등록하기 위해서 쓰는 어노테이션
// 물론 HttpSession를 써도 상관 없음
☆ 하지만 기존의 방법으로는 로그아웃이 안 됨
≫ HttpSession이 아니기 때문에 invalidate를 이용해 세션 무효화가 불가능하다
그럴 때는 SessionStatus 이용
그 안의 setComplete()를 사용해 주면 로그아웃이 가능하다.
스프링을 사용해 DB에 접근하기 위해 새로운 계정을 만들고, 테이블을 생성해주었다.
위에서 Controller 객체를 만들었으면, DB에 접근하기 위해서 Service(와 DAO가) 필요!
IoC와 DI
MemberService 객체를 만들어서 sysout으로 출력을 해 보았음
≫ mService의 주소값이 나오는데, 계속해서 새로운 주소값이 생성됨
이를 바로 유기적으로 결합도가 높다고 함.
→ 호출할 때마다 코드가 실행되면서 계속 영향을 받음
사실상, 결합도가 높은 코드는 좋지 못 한 코드.
결국 다른 코드들에 의해 영향을 너무 많이 받기 때문
(수정, 추가 시에 다른 코드에 의해 영향을 많이 받음)
코드를 그대로 나열하는 게 아닌, 덩어리 째로 만들어서
내가 필요할 때마다 가져다 쓸 수 있게끔 만들어야 한다. → 객체 지향!
목표는 최대한 결합도를 낮추는 것
방법 1) 실행 횟수에 상관 없이 같은 주소값이 나오도록 만듦
≫ 객체 생성 코드를 필드로 올리기
컨트롤러 객체를 만들 때 필드에서 서비스 객체를 한 번만 생성하니까
이후에 메소드가 실행될 때마다 반복적으로 생성되지 않을 수 있겠다
≫ 하지만 현재 서비스 객체 생성을 프레임워크가 아닌 개발자가 주도하는 상태
프레임워크는 객체 생성 또한 스스로가 주체가 되어 개발자가 따라야 한다.
≫ @Service를 통해 프레임워크가 주체가 되어 서비스 객체를 생성토록 함
그러나 서버를 껐다가 재실행 후 페이지 새로고침 하면? null
왜 객체의 주소값이 반환되지 않는 것일까?
★
1) 개발 주도권이 프레임워크에게 있는 것을 IoC라 한다
≫ 객체 생성, 생명주기 관리 등을 전부 프레임워크가 함
2) 그래서 나타나는 특징이 DI (객체를 변수에 담아달라는 의미의 의존성 주입)
≫ 만든 MemberService 객체를 mService라는 변수에 의존성 주입
≫ MemberService 타입의 mService 객체를 생성했는데,
@Autowired를 통해 의존성 주입 하겠다고 알리는 것!
≫ 아까는 null이 반환되었으나 현재는 주소값이 잘 반환됨♥
∴ 결론적으로 @Autowired가 DI 한 것
방법 2) 이름을 바꿔도 다른 코드에 영향을 끼치지 않게 함(ex.클래스명)
컨트롤러와 서비스 사이에 인터페이스를 끼워 넣으면 결합도가 낮아짐
(바로 연결되는 것이 아닌 거쳐서 거쳐서 연결되는 것이기 때문)
≫ 인터페이스 컨트롤러 - 서비스 - DAO 사이에 넣으면 되는데,
컨트롤러와 서비스 사이 / 서비스와 DAO 사이 / 혹은 둘 다 껴넣어도 된다
원래 있던 MemberService를 MemberServiceImpl로 클래스명 변경 후,
인터페이스 MemberService를 MemberServiceImpl이 가져다 구현할 것임!
(인터페이스는 추상 클래스이기 때문에 꼭 완성시켜주어야 함)
≫ MemberServiceImpl이 인터페이스 MemeberService를 구현할 것
≫ 여기서 MemberServiceImpl의 이름을 변경한다고 해도,
컨트롤러 안의 MemberService의 이름이 변경되지 않음 (인터페이스가 껴 있어서)
≫ MemberService 인터페이스가 중간에 껴 있기 때문에, 컨트롤러 안의 MemberService는 그대로.
로그인 구현
≫ mService 안에 login 메소드를 만들고 나서 MemberService를 살펴보면 빨간 줄.
인터페이스는 추상 클래스이기 때문에 오버라이딩을 통해 완성시켜주어야 한다.
≫ MemberServiceImpl에서 구현하고 완성을 시켜주어야 한다
mybatis-config와 member-mapper를 등록한 후
≫ @Autowired를 통해 SqlSession과 mDAO를 의존성 주입
≫ 그리고 mDAO login 메소드에 sqlSession과 m을 보내줌
≫ member-mapper.xml과 mybatis-config.xml은 이미 만들어둔 상태
▼
위에서 작성한 쿼리를 토대로 컨트롤러에 받아온 loginUser를 출력해 보았음
DB에 저장되어 있는 아이디, 패스워드로 로그인 하였더니
아래처럼 로그인 된 한 행의 정보를 받아올 수 있게 되었다.
≫ null은 없는 사용자 로그인일 경우
에러 페이지 대신 사용자 정의 예외 만들어보기
// 예외 클래스가 되기 위해서는 예외를 상속 받아야 하지만
굳이 try/catch 해 주고 싶지 않다 → UncheckedException (RuntimeException)
// UncheckedException은 굳이 예외 처리를 해 주지 않아도 된다
≫ 사용자 정의 예외 클래스에서는 기본 생성자와 매개변수 생성자 두 개만 있으면 됨
≫ throw는 예외 강제 발생 / throws는 예외 처리 위임 시 사용
≫ web.xml에서 에러페이지 등록해서 경로 마련해주기
Member-Context.xml로 멤버 관련된 부분 빼기
보라색 : mvc 기반 사용
노란색 : beans 기반 사용
Source 옆의 Namespaces에 가서 mvc 체크 후 아래와 같이 변경
≫ 어노테이션을 활성화시켜주는 것이 component-scan
▼ member 제외 가능해짐
Lombok
알아서 생성자, Getter, Setter, toString 만들어주는 라이브러리
https://mvnrepository.com/artifact/org.projectlombok/lombok/1.18.24
≫ pom.xml 에 <dependency> 가장 아래에 붙여넣기
完
model.vo.Member 객체 내의 모든 것들을 완성해주었다.
하지만 확인해보니 제대로 생성되지 않음 → 롬복을 설치*해주어야 함.
롬복 jar 파일 경로를 찾아 간 다음, 실행하면 아래와 같은 창이 나온다. (고추 뭐임)
현재 쓰는 STS를 선택한 후 Install / Update를 한 후 Quit Installer 하면 설치 완료 (STS 껐다 켜기)
*롬복 사용 시 주의사항 : 변수명을 소문자 하나에 대문자로 지으면 안 됨
ex) mid, mPwd 를 게터 세터 시 getMId, getMPwd 이렇게 되기 때문!
하지만, 내부적으로 꼬일 경우나 에러가 날 수 있기 때문에
이클립스 단축키를 사용해서 가독성 있게 코드 쓰는 것을 권장
root-context.xml 에서 <bean> 태그로 객체 생성하기
// DB 연결 관련된 것은 root-context
(하지만 back 단에 관련된 것들을 집어넣어도 상관 없음)
이전에는 DB 연결을 mybatis-config(mybatis에 대한 설정 정보 담음)에 담고 있었음
DB 연결 → mybatis-config → Template 작성 → SqlSession → Service → - (Session) - → DAO
Session을 넘기기 위해서는 SqlSession을 받아와야 함
하지만 스프링에서는 DB 연결하는 부분을 밖으로 뺌
→ 바로 'root-context' ▼
1) DataSource 빈 등록
Setter 이용해서 객체에 값을 넣는 방법
매개변수 생성자를 이용하거나 Setter를 이용해 객체에 값을 넣을 수 있다.
그런데 현재 <property>는 Setter를 불러와서 객체에 값을 넣는 방법!
≫ maven에서 common dbcp 다운로드 후 pom.xml에 추가
(DataSource 등록에 BasicDataSource를 위한 라이브러리)
2) SqlSession 빈 등록
매개변수 생성자 이용해서 값을 넣는 방법
<constructor-arg ref="sqlSession"/> : 매개변수 생성자
3) 트랜잭션 매니저 빈 등록
≫ 위의 dataSource를 참조함
≫ transactionManager를 위한 라이브러리 pom.xml에 추가
4) mybatis-config 등록
5) mybatis-Mapper 등록
객체 생성하는 방법 3가지
new로 생성하기
annotationComponent
<bean> xml 태그
mybatis 라이브러리 넣기
// maven이 관장하는 파일 pom.xml을 통해 추가하기
▽ 먼저 ojdbc8
1. MyBatis 는 진짜 마이바티스
2. MyBatis Spring
데이터베이스 모듈은 영속성 프레임워크와 연결하기 위한 모듈
≫ 마이바티스와 Spring을 연결하기 위한 다리 역할
(스프링에서 마이바티스를 사용할 수 있도록 하는 것)
만약 라이브러리들을 넣는데 호환이 안 될 경우,
다른 버전을 넣어서 사용해보면 된다.
▼ pom.xml 안의 설정 내용들