클래스 간 결합도 낮추기
interface를 구현하여 사용하고자 하는 클래스에 implements 시키고
>> 추상화 작업
다형성을 이용하여 결합도를 낮춘다 (결합도를 없앨 수는 없음, 낮출뿐)
개발자가 아니라 Spring Framework가 객체를 제어한다 IOC(제어의 역전)
DI(의존성 주입)
@Autowired
(Spring Bean에 등록되어 관리하고 있는 Data만 @Autowired로 주입 가능)
1. @Bean 사용
Spring이 Autowired 애너테이션을 사용하려면 Bean에 객체가 등록이
되어 있어야 한다
따라서 Application.java 파일에 @Bean을 사용하여
강제로 스프링빈에 객체를 추가해주면 사용할 수 있다
2. @Component 사용
Application.java 파일의 @ComponentScan 어노테이션이
프로젝트 파일 안의 @Component 어노테이션이 붙은
모든 클래스를 찾아서 검색하고 찾게되면 Bean에 등록하여 사용할 수 있게함
단, @Component 어노테이션을 사용하려면 어노테이션을 붙일
클래스 파일과 코드가 필요한데 그것이 없는 경우가 있기 때문에
1번처럼 @Bean을 사용하여 강제로 Bean에 등록해서 사용하는 방식이 필요하다
다른 패키지에 있는 클래스를 @Component를 사용하여 찾도록 하려면
Application.java 파일의 클래스에 @ComponentScan(basePackages = {패키지명}
을 사용하여 접근할 수 있도록 해야한다.
두 개 이상의 클래스에 @Component를 사용하는 경우
Bean으로 뭘 사용할지 몰라 충돌이 일어나기 때문에 이 경우에는
Bean으로 사용할 Data의 이름을 @Autowired에 따로 명시해주어야 한다.
Spring Framework - DI, AOP
+
Spring @mvc Framework - MVC
+
MyBatis Framework - JDBC(ORM Framework 아님)
+
Log4J Framework - 디버깅
# AOP (관점지향프로그래밍) - Spring API 사용
@Component 어노테이션 필요
특정 Method의 실행 순서를 가로채서 먼저실행(@Before) 또는 나중실행(@After)
AOP class에 @Aspect 어노테이션 적용후 @Before, @After 사용
# Interceptor - Spring MVC API 사용
Interceptor : @Controller의 실행 순서를 가로채기
Interceptor로 사용할 class 생성 후
HandlerInterceptor Interface를 상속받아 구현하여 사용
Default method 인 preHandle()와 postHandle() 메서드 오버라이딩 추가
Application.java 클래스가 WebMvcCOnfigurer interface를 상속받도록 하고
Default method인 addInterceptors method 오버라이딩 추가하여
어떤 Controller를 가로채어 작업할 것인지에 대한 코드 작성
# Filter - 서블릿 요청 - Servlet API 사용
Filter : 웹 요청 URL을 가로채기
Question. Filter, Interceptor, AOP가 모두 적용된다면?
Filter > Interceptor > AOP > method > AOP > Interceptor > Filter 순서
출력결과 >>
===== Before Filter =====
===== before Intercept =====
===== before AOP =====
member b
===== after AOP =====
===== after Intercept =====
===== After Filter =====
# 웹 요청 (HttpServletRequest)이 들어오면 Interceptor를 사용하면 됨
HttpServletRequest 의 부모격인 servletRequest 가 들어오면 Filter 사용
# Transaction
Service 단에서 conn을 설정하고
DAO에서 2개의 sql 실행
트랜잭션 실패시 Service 단에서 rollback
Controller 단에서 트랜잭션을 설정하게 되면 쿼리가 실패했을 때
Controller 의 요청분석과 view 처리마저 모두 취소되는 문제가 발생한다.
그래서 Service단을 만들어 Controller와 Mapper(DAO)사이에
중계역할을 하도록 만들어 Service에서 트랜잭션이 수행되도록 한다.
이 때 Service와 DAO를 class name으로 접근하는 방식은 결합도가 높기 때문에
interface를 사용하여 결합도를 줄이는것이 좋은 방법이다.
단, 프로젝트 때는 interface 하지 말것..
Unit.java (main 함수)
package Unit;
public class Unit {
Weapon x = new Knife();
public void fight() {
System.out.println(x.getName() + "(으)로 싸우다");
}
public static void main(String[] args) {
Unit unit = new Unit();
unit.fight();
}
}
Weapon.java (Interface)
package Unit;
public interface Weapon {
public String getName();
}
Gun.java (Weapon Interface 상속)
package Unit;
public class Gun implements Weapon{
@Override
public String getName() {
return "총";
}
}
Knife.java (Weapon Interface 상속)
package Unit;
public class Knife implements Weapon{
@Override
public String getName() {
return "칼";
}
}
Unit.java 실행결과 (Console)
칼(으)로 싸우다.
HelloController.java (Controller)
package com.gd.AOPTest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController // view 가 없는 컨트롤러
public class HelloController {
@GetMapping("/a")
public void a() {
System.out.println("a");
}
// Filter + Interceptor + AOP
@GetMapping("/member/b")
public void b() {
System.out.println("member b");
}
/*
@GetMapping("/admin/c")
public void c() {
System.out.println("admin c");
}
*/
}
AopTestApplication.java (main 함수)
package com.gd.AOPTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@ServletComponentScan // Servlet 에서 사용하는 어노테이션도 Spring 이 작업하겠다
@SpringBootApplication
public class AopTestApplication implements WebMvcConfigurer{
@Autowired
MyInterceptor myInterceptor;
public static void main(String[] args) {
SpringApplication.run(AopTestApplication.class, args);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// Intercept 할 Controller 의 Mapping 주소(url)
registry.addInterceptor(myInterceptor).addPathPatterns("/member/*");
WebMvcConfigurer.super.addInterceptors(registry);
}
}
MyFilter.java (Filter)
package com.gd.AOPTest;
import java.io.IOException;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;
@WebFilter("/member/*") // member 으로 시작하는 모든 요청에 대해서 Filter 를 적용하겠다
// @WebFilter 어노테이션은 servlet(Tomcat) 것임
// Spring 이 처리할 수 있도록 Application.java 파일에 추가 어노테이션(@ServletComponentScan) 필요
public class MyFilter implements Filter{
// 아무 내용이 없으면 클래스명 아래 에러 발생
// interface 를 상속 받았기 때문에
// 1. 추상클래스가 되던가 (abstract)
// 2. 추상메서드를 작성하던가
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
request.setCharacterEncoding("UTF-8");
System.out.println("===== Before Filter =====");
// this.doFilter(request, response, chain); --> 재귀호출 (자신이 자신을 호출 --> 무한루프)
chain.doFilter(request, response);
System.out.println("===== After Filter =====");
}
}
MyInterceptor.java (Interceptor)
package com.gd.AOPTest;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Component
public class MyInterceptor implements HandlerInterceptor{
@Override // 특정 Controller 의 실행을 가로채어 preHandler method 가 먼저 실행되도록 하는 어노테이션
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
request.setCharacterEncoding("UTF-8");
System.out.println("===== before Intercept =====");
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override // 특정 Controller 의 실행이 끝나면 postHandler method 가 실행되도록 하는 어노테이션
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("===== after Intercept =====");
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
}
MyAOP.java (AOP)
package com.gd.AOPTest;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyAOP {
// 특정 method 의 실행을 가로채어 @Before method 가 먼저 실행되도록 하는 어노테이션
// method 전체 경로 작성, a(..)는 'a 라는 method 안에 어떤 인자가 오던지 전부' 라는 뜻
@Before("execution(* com.gd.AOPTest.HelloController.b(..))")
public void beforePrint() {
System.out.println("===== before AOP =====");
}
// 특정 method 의 실행이 끝나면 @After method 가 실행되도록 하는 어노테이션
@After("execution(* com.gd.AOPTest.HelloController.b(..))")
public void afterPrint() {
System.out.println("===== after AOP =====");
}
/*
출력결과 :
===== before AOP =====
a
===== after AOP =====
*/
}
Filter(before) > Interceptor(preHandler) > AOP(before) >
method > AOP(after) > Interceptor(postHandler > Filter(after) 순서로 적용
출력결과 >>
===== Before Filter =====
===== before Intercept =====
===== before AOP =====
member b
===== after AOP =====
===== after Intercept =====
===== After Filter =====
MemberController.java (Controller)
package com.gd.transaction.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.gd.transaction.service.IMemberService;
@RestController
public class MemberController {
@Autowired
IMemberService memberService;
@GetMapping("changePw")
public void changePw() {
// 요청처리
memberService.addPw();
// 뷰 응답
}
}
MemberService.java (Service)
package com.gd.transaction.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.gd.transaction.repository.IMemberDAO;
@Service
@Transactional
public class MemberService implements IMemberService {
@Autowired
IMemberDAO memberDAO;
public void addPw() {
memberDAO.insertPw();
memberDAO.updatePw();
}
}
IMemberService.java (Service.Interface)
package com.gd.transaction.service;
public interface IMemberService {
public void addPw();
}
MemberDAO.java (DAO)
package com.gd.transaction.repository;
import org.springframework.stereotype.Repository;
@Repository
public class MemberDAO implements IMemberDAO{
public int insertPw() {
System.out.println("변경된 PW 입력 메서드");
return 0;
}
public int updatePw() {
System.out.println("사용자 테이블 PW 변경 메서드");
return 0;
}
}
IMemberDAO.java (DAO.Interface)
package com.gd.transaction.repository;
public interface IMemberDAO {
public int insertPw();
public int updatePw();
}