강의URL: https://www.inflearn.com/course/spring_revised_edition
공부 마친날: 2022년 2월 16일
복습여부: No
시작일: 2022년 2월 15일 오후 1:33
IntelliJ에서 Springboot 프로젝트를 항상 build+ run(어플리케이션 실행) 하지 말고 명령 프롬프트(cmd)에서 build하면 좀 더 빠르게 프로젝트 run 할 수 있다.
IntelliJ에서는 매번 build하고 run하지만 cmd에서는 한 번 build해두면 그 이후에는 run만 여러 번 할 수 있기 때문이다. (한 마디로 build와 run을 분리할 수 있다.)
먼저 cmd에서 cd
명령어로 프로젝트가 있는 위치까지 이동한 후 ./gradlew build
하면 gradle 빌드가 완료된다.
이후 생성된 jar
파일의 위치로 이동해서
java -jar ./*.jar
명령어를 입력해주면 된다.
🌏이 과정은 IntelliJ 안에 내장된 터미널에서도 유효하다!
ctrl + c
눌러서 종료할 수 있다.
Logging option을 Debug 모드로 주면 더 자세한 에러 메세지를 확인할 수 있기 때문에 편리하다.
[application.properties](http://application.properties)
에서
logging.level.org.springframework.web=DEBUG
로 설정하면 끝!
스프링 IoC에서 관리하는 객체를 Bean이라고 부른다. bean으로 등록된 객체는 스프링 IoC에서 관리하므로 따로 객체 생성을 해주지 않아도 자동으로 주입 받을 수 있다.
Bean은 기본적으로 위의 사진과 같이 Bean으로 등록된 클래스 옆에 녹색 콩 표시가 되어있다 (IntelliJ기준)
빈(bean)을 만들고 엮어주며 제공해준다.
IoC 컨테이너는 Application Context나 BeanFactory로 사용할 수 있다. ApplicationContext는 BeanFactory를 상속받고 있기 때문에 사실상 같은 일을 한다고도 볼 수 있다.
Spring IoC 컨테이너는 IoC 안에 있는 객체에다만 의존성을 주입해준다.
Spring IoC 컨테이너가 다루고 있는 Bean 객체는 SingleTon 법칙에 따라 하나의 객체이다. 따라서 아래의 코드의 getBean에서 받은 Repository 객체와 Controller 생성자에서 주입받는 repository 객체는 같은 객체이다.
@Controller
class Controller{
private final Repository repository;
private final ApplicationContext applicationcontext;
public Controller(Repository repository, ApplicationContext applicationContext){
this.repository = repository;
this.applicationcontext = applicationcontext;}
@GetMapping("/bean")
@ResponseBody
public String bean(){
return "Bean"+applicationContext.getBean(Repository.class) + "repository:"+this.repository;
}
물론 위에 코드처럼 applicationContext를 멤버변수로 가지고 쓸 일은 거의 없다. 그냥 기본 개념을 이해하기 위해 원리를 공부할 뿐이다.
⭐아무튼 위와 같이 ApplicationContext 안에는 Bean으로 등록된 객체가 있으며 각 객체는 클래스당 하나만 생성되기 때문에 getBean(클래스명)
으로 가져와도 하나의 객체만 정확히 가져와지며 이는 ApplicationContext를 굳이 위처럼 정의하지 않고 자동으로 주입받은 repository와 같은 객체라는 것만 알면 된다!
Bean 객체가 ApplicationContext에서 관리되려면 먼저 해당 클래스가 Bean으로 등록이 되어야 한다.
✅ Bean으로 등록하는 방법 1. `@Component` 어노테이션을 붙여서 사용하기 - `@Repository`, `@Service`, `@Controller` 는 상위에 @Component 어노테이션이 붙어져 있어서 자동으로 bean으로 등록될 수 있다. 2. `@Configuration`이 붙은 클래스에서 Bean으로 등록하기🌟 Configuration이란?
Java 설정 파일이라고 생각하면 쉽다.
@Configuration
어노테이션을 클래스에 붙이고
@Bean
public XXXController controller(){
return new XXXController();}
이런식으로 직접 수동으로 Bean을 등록할 수 있다.
@Autowired
를 붙이거나 생성자에서 주입을 받으면 된다.
그런데 이 때 @RequiredConstructor
를 클래스 설정에서 붙여주면 이는 필요한 필드를 모두 생성자에 자동으로 포함시킨다는 뜻이므로 생성자 코드 구현 없이 단순하게
public final Controller controller;
이런식으로 필드 정의만 final을 붙여 정의만 해줘도 자동으로 bean을 주입받을 수 있다. 결국 이 방법은 생성자를 통한 주입이라고 생각하면 된다.
왜 final인가?
생성자를 통해 주입받은 필드는 보통 고정되어 있으므로 그 값이 변하면 안된다. 따라서 final로 객체를 고정시켜주어야 한다.
Autowired로 받는 경우에는 final을 쓰면 안됨
왜냐하면 이때는 생성자에서 자동으로 파라미터에 Bean을 받는 구조가 아니라 필드에서 새로운 객체로 주입을 받는 경우이기 때문에 아직 주입받지도 않은 객체가 고정되어있어 버리면 객체를 주입해 줄 수가 없다.
컴파일이란 A.java 코드가 A.class 코드로 컴파일 되는 것을 말한다. 이 때 그냥 java 파일이 class 파일이 되기 전에 해당 java 파일의 필요한 부분에 AOP 부분을 삽입해주는 것이다.
class 파일까지 컴파일된 파일을 바이트로더가 로딩하는 시점에 메모리에서 AOP 기능을 올려주는 방법이다. 따라서 AOP가 컴파일이 완료된 class 파일에도 해당 AOP 관련 코드는 존재하지 않는다.
public interface Payment{
void pay(int amount);
}
Payment는 결제 수단에 대한 Interface이다. 기존의 결제 수단에는 Cash가 하나밖에 없었지만 CashPerf로 프록시를 만듦과 동시에 Payment라는 cash와 cashperf의 상위 인터페이스가 생성되어 클라이언트 단의 코드 변경 없이 cashperf가 cash를 대체하도록 한다.
public class Store{
Payment payment;
public Store(Payment payment){
this.payment = payment;
}
public void buySomething(int amount){
payment.pay(amount);
}
}
Store 클래스는 클라이언트라고 생각하면 편하다. 클라이언트에서 payment를 Bean으로 주입 받아 가져오는 형태이다.
public class Cash implements Payment{
@Override
public void pay(int amount){
System.out.println(amount+"현금");
}
}
원래 Store에서 Bean으로 등록해서 사용하고자 했던 Cash 클래스
public class CashPerf implements Payment{
Payment cash = new Cash();
@Override
public void pay(int amount){
StopWatch stopwatch = new StopWatch();
stopwatch.start();
cash.pay(amount);
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
}
}
CashPerf가 프록시패턴이라고 생각하면 된다. 이 클래스는 기존의 Cash 대신에 자신이 Bean으로 등록된다.
1. 먼저 대신 들어갈 클래스를 상위 인터페이스 객체로 받는다.
2. Payment 인터페이스의 pay를 오버라이드 해서 AOP 패턴에 구현할 코드를 작성한다.
3. joinPoint 코드가 실행될 시점에 상위 인터페이스 객체로 받았던 필드에서 원래 실행할 메서드(오버라이드한 메서드)를 실행한다.
새로운 코드가 추가되었지만 기존의 코드를 변경하지 않은 패턴
⭐ 스프링 AOP가 지원하는 것은 Bean이 만들어질 때 예시에서 보이는 CashPerf 같은 것이 자동으로 같이 만들어지는 기능이다.
그 때에는 cash 대신에 cashperf가 자동으로 bean으로 등록이 되게 되며 따라서 클라이언트가 원래 bean으로 등록되어야 했던 cash가 아닌 cashperf를 쓰게 된다.
AOP를 적용하고 싶은 곳에 @사용할AOP명
에 AOP명을 지어 넣는다.
quick fix ( IntelliJ Window 기준 alt+Enter
)기능을 활용하여 1에서 만든 어노테이션의 annotation interface를 만든다.
annotation interface에서 아래와 같이 @Target
과 @Retention
어노테이션을 붙여준다.
@Target(ElementType.Method) // 어노테이션을 붙일(실행할)장소
@Retention(RetentionPolicy.RUNTIME)// 언제동안 실행될 것인지 지정
public @interface LogExecutionTime{}
3에서 생성한 어노테이션이 달린 곳에 적용할 코드를 실제 Aspect class를 생성하여 작성한다.
@Component
@Aspect
public class LogAspect{
@Around("@annotation(위에서설정한어노테이션이름)")
public Object 어노테이션이름(ProceedingJoinPoint joinPoint) throws Trowable{
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object proceed = joinPoint.proceed();
stopWatch.stop();
return proceed;
}
}
@Component
, @Aspect
등록한다.Object proceed = joinPoint.proceed();
해주면 된다.PSA: 손쉽게 변경 가능한 서비스 인터페이스
예를 들어서 Springboot의 기본인 tomcat 서버를 네티로 변경할 때 코드를 크게 변경하지 않고도 PSA 기능 때문에 쉽게 변경 가능하다.
스프링 웹MVC도 여기에 해당 → Controller http Mapping을 손쉽게 할 수 있음 (HttpServelt을 개발자가 생성하지 않아도 가능)