56일차(1)/Spring Boot(2) : Bean 생성하는 방법, AOP 활용 실습
- 새 Spring Boot 프로젝트 만들기
- Spring Boot 프로젝트가 나온 계기는?
spring은 설정해야 할 값이 많았는데, boot에서는 설정을 대신해주는 것이 많다.
spring project를 좀더 편리하게 사용하기 위해!
File-New-Spring Starter Project (boot 사용)
- 이렇게 설정하고 테스트하기! Maven, 8버전으로 생성
- 설정은 2.7.8로
- 용도에 따라서 의존 dependency를 선택하면 알아서 pom.xml 이 설정된다.
- 프로젝트 구조가 복잡하다.
- Maven으로 생성하면 Maven의 특징인 pom.xml이 존재한다.
pom.xml
- 기본 dependency는 하나만 들어가 있는 상태.
- 하나만으로도 기본 dependency 들이 많이 설치되어 있다.
- 이렇게 메인 메소드가 들어있는 클래스가 있다.
- spring boot 프로젝트의 run하는 방법: main 메소드가 들어있는 곳에서 run 버튼 누르기
- run할 때에는 둘중 뭘로 해도 상관없으나 보통 Spring Boot App으로 하면 된다.
- 실행되었다가 바로 종료된다.
- run 하는 순간만 실행될 뿐 지속적으로 돌아가는 것은 아니다.
- run 했을때의 순간 찰나에 spring에 관련한 테스트를 한다.
- 기본으로 @SpringBootApplication 이라는 Annotation이 붙어있다.
- Run을 하면 이것이 속해있는 패키지를 포함해서 하위 패키지까지 자동으로 component scan이 일어난다.
- 이전에 쓰던 spring의 경우 이 패키지에 스캔이 일어나고 HomeController 객체가 생성되는 것이었다.
- 이 객체를 Bean 컨테이너에서 관리했다.
- 마찬가지로 boot에서도 자동으로 comnponent scan이 일어난다.
- 스캔이 일어날 때 bean으로 만들어질 것들은 알아서 bean으로 만들어진다.
- boot 패키지 클래스의 run이라는 메소드를 실행하면서 뭔가를 전달함
- run 이라는 static 메소드를 활용한다.
- 들어가는 인자는 첫번째 타입은 클래스타입(클래스명.class 하면 클래스타입이다)
두번째 타입은 string 배열이다.
- ConfigurableApplicationContext 타입을 리턴하는데,
이것은 ApplicationContext 인터페이스를 구현하는 타입이다.
- 그래서 ApplicationContext 타입으로 변수를 선언하여 받을 수 있다.(ctx)
- 이 타입으로 받으면서 getBean() 이라는 메소드를 호출한다.
- 메소드가 다중정의(overloading) 되어 있으므로 용도에 따라서 저 5개중에서 사용하면 된다.
- 첫번째 메소드를 사용함!
- spring이 관리하는 컨테이너로부터 원하는 타입의 객체를 가져오는 메소드
- 원하는 타입을 리턴해달라! 는 의미로 작성하는 것
- 여기에 클래스 타입을 넣어주면 그 타입이 자동으로 리턴 타입이 된다.
- spring이 관리하는 객체를 만드는 것
- spring에서는 component scan하면 객체를 만들수 있었다.
- xml문서에다가 bean설정을 통해서 or 특정 패키지를 component scan 하도록 해서!
** boot에서 객체 생성하기 테스트
com.example.demo.auto 패키지생성
Car 클래스 생성
package com.example.demo.auto;
import org.springframework.stereotype.Component;
//component scan을 통해서 bean이 되도록 한다.
@Component
public class Car {
public void drive() {
System.out.println("달려요!");
}
}
- 이 클래스로 spring이 관리하는 객체를 만들려면? 3가지 방법이 있다.
1) 제일 쉬운 방법 : @Component 어노테이션 붙이기.
- scan만 일어나면 자동으로 bean이 된다.
2) 다른방식으로 bean만들기
MyCar
package com.example.demo.auto;
public class MyCar {
public void drive() {
System.out.println("내 차가 달려요!");
}
}
- demo 패키지(기본)에 새 클래스생성
JavaConfig
package com.example.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.example.demo.auto.MyCar;
@Configuration
public class JavaConfig {
/*
* @Bean 어노테이션과 함께 MyCar type을 리턴하는 메소드를 만들어놓으면
* MyCar type 객체가 spring이 관리하는 bean이된다.
*/
@Bean
public MyCar myCar() {
MyCar c1=new MyCar();
return c1;
}
}
- JavaConfig에 @Bean 어노테이션과 함께 MyCar type을 리턴하는 메소드를 만들어 놓으면
MyCar type 객체가 spring이 관리하는 bean이 된다.
- 그 타입을 리턴해주는 메소드를 만든다.
- 이것이 boot의 전형적인 방식이다. spring에는 없던 방식!
- @Configuration 해서 메소드를 활용해서 bean 을 만든다.
- 기존의 servlet-context.xml의 기능을 이 설정 클래스에서 대체하는 것이다.
- xml문서에 작성했다면 파란색 텍스트와 같이 작성된다.
- 메소드명이 bean의 이름, 리턴타입이 생성할 객체를 의미하는 것이다.
- boot에서 bean을 만드는 방법을 익히기
- configuration 문서에서 @bean과 함께 해당타입을 리턴하는 메소드를 만들어주면 된다.
메인메소드 수정
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.example.demo.auto.Car;
import com.example.demo.auto.LegacyCar;
import com.example.demo.auto.MyCar;
@SpringBootApplication
public class Boot01Hello2Application {
//Spring Boot Application이 시작되는 main 메소드
public static void main(String[] args) {
ApplicationContext ctx=SpringApplication.run(Boot01Hello2Application.class, args);
//getBean( 원하는 type )
Car c1=ctx.getBean(Car.class);
c1.drive();
MyCar c2=ctx.getBean(MyCar.class);
c2.drive();
//resources 폴더의 config.xml 문서를 로딩해서 bean을 생성해서 관리하기 (예전 방식)
ApplicationContext ctx2=new ClassPathXmlApplicationContext("config.xml");
LegacyCar c3=ctx2.getBean(LegacyCar.class);
c3.drive();
}
}
- bean으로 만든 후에는 main 클래스의 이 객체를 통해서 얻어낸다.
- bean을 저장되어 있는 컨테이너로부터 가져오는 것이다.
- 핵심의존객체를 직접 생성하지 않고, 스프링이 생성하게 하고 받아서 쓴다.
- 받아오려면 getBean하면 된다.
- 메인메소드의 Appplication context 가 바로 container 라고 생각하면 된다.
- 이렇게 작성하면 빈이 있다면 들어오고, 없다면 에러가 날 것이다.
- 실행해보면 drive 메소드가 잘 실행된다. Bean이 생성되어 있는 것을 알 수 있다!
3)xml 문서를 통해서 bean으로 만들기(이전과 같은 방식)
LegacyCar 클래스 생성
package com.example.demo.auto;
public class LegacyCar {
public void drive() {
System.out.println("구형차가 달러요!");
}
}
- Main에 resources 폴더가 있다. 이곳에는 java 클래스가 아닌 자원을 넣는다.
- xml 파일을 만들 것!
- others 에서 이것을 선택해서 생성하면 된다.
- 이것이 sevlet context 와 같은 파일이다.
config.xml
<bean class="com.example.demo.auto.LegacyCar" />
- 자동완성으로 작성하면 된다.
- xml를 spring이 로딩하면 이 객체를 생성해서 자기가 관리한다.
메인메소드에 추가
- ClassPathXmlApplicationContext 객체를 생성하고, xml문서를 로딩하라고 하기
- 예전 spring 방식이다. spring boot에서는 이렇게는 잘 하지 않는다.
- 위 이미지상으로는 1, 2는 기존의방식, 3은 스프링 boot에서 만드는 방식
- 설정을 하는 java클래스(JavaConfig)에서 bean 을 만드는 방식을 새롭게 익혀야 한다.
- bean 을 어떻게 만들고 참조값을 어떻게 얻어내는지 정리하기!
- bean을 만드는 세가지 방법 익혀두기!
- 이렇게 bean을 생성하면 개발자는 이 안에서 원하는 메소드를 생성하고 기능을 추가하거나 필요에따라 변경할 수 있다.
- 지난 주에 했던 AOP 예제
- 메소드의 수행결과는 위와 같다.
- write는 쓰는 코드밖에 없는데, 해당메소드가 수행되기 이전/이후에 새로운 내용이 실행되어 들어가 있다.
- 이것은 핵심 비즈니스 로직은 아니다. 상관이 없다.
- java로 프로그래밍하다 보면 핵심 비즈니스 로직과는 상관없는,
여러 메소드를 횡단하고, 여러 메소드에서 필요로 하는 공통적인 작업들이 필요할 때가 있다.
- 여기에 AOP 방식을 적용하면, 다른곳에 작성해 두고 설정만으로도 넣을 수 있다.
- 이것이 Aspect Oriented Programming (관점 지향 프로그래밍) 이다. 스프링의 핵심 개념!
WritingUtil
- demo를 포함한 demo의 하위패키지는 자동으로 component scan이 일어난다.
- write 1,2,3을 실행했을 뿐인데 앞뒤로 새로운 작업이 들어가 있다.
- aspect 패키지 안에 writing aspect 클래스가있다.
- bean도 되어야 하고 aspect역할도 해야 하기 때문에 @@ 두개!
- 이전, 이후에 실행되는 Aspect
- 이런 모양의 메소드를 수행하기 전/후에 이 작업을 수행하라! 라는 뜻
- spring이 관리하는 bean 중에서 write*() 라는 이름으로 시작하는 메소드를 가리킨다.
- spring이 관리하는 bean 인데 그 안에 있는 메소드의 수행시에 개입한다.
- 메소드 수행의 이전, 이후에 개입!!
JavaConfig
package com.example.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import com.example.demo.aspect.MessengerAspect;
import com.example.demo.util.Messenger;
@Configuration
@EnableAspectJAutoProxy
public class JavaConfig {
@Bean
public MessengerAspect msa() {
return new MessengerAspect();
}
//Messenger 객체가 spring이 관리하는 bean이 될 수 있도록!
@Bean
public Messenger myMessenger() {
return new Messenger();
}
}
Messenger
package com.example.demo.util;
public class Messenger {
public void sendGreeting(String msg) {
System.out.println("오늘의 인사:"+msg);
}
public String getMessage() {
System.out.println("getMessage() 메소드가 수행됩니다.");
return "열심히 공부 하자~~";
}
}
MessengerAspect
package com.example.demo.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MessengerAspect {
//return type은 void 이고 send로 시작하는 모든 메소드가 point cut이다.
//@around로 aspect를 적용하면 메소드의 인자로 ProceedingJoinPoint type이 전달된다.
@Around("execution(void send*(..))")
public void checkGreeting(ProceedingJoinPoint joinPoint) throws Throwable {
//메소드에 전달된 인자들 목록을 얻어내기
Object[] args=joinPoint.getArgs();
//반복문 돌면서 원하는 type의 값을 찾아서 작업한다.
for(Object tmp:args) {
//만일 String type이면
if(tmp instanceof String) {
//원래 type으로 casting
String msg=(String)tmp;
System.out.println("aspect에서 읽어낸 내용:"+msg);
if(msg.contains("바보")) {
System.out.println("바보는 금지된 단어입니다.");
return; //메소드를 끝내기
}
}
}
//aspect가 적용된 메소드가 호출되기 직전에 할 작업은 여기서 한다.
joinPoint.proceed(); //aspect가 적용된 메소드 수행하기
//aspect가 적용된 메소드가 리턴된 직후에 할 작업은 여기서 한다.
System.out.println("aspect가 적용된 메소드가 리턴했습니다.");
}
/*
* return type 은 String 이고
* get 으로 시작은 메소드 이고
* 메소드에 전달되는 인자는 없다
* java.lang 페키지에 있는 type 은 페키지명 생략 가능
*/
@Around("execution(String com.example.demo.util.*.get*())")
public Object checkReturn(ProceedingJoinPoint joinPoint) throws Throwable {
// aspect 가 적용된 메소드를 수행하고 리턴되는 데이터 받아오기
Object obj=joinPoint.proceed();
//원래 type 으로 casting 해서 조사해 볼수가 있다.
String a = (String)obj;
//조사후 아예 다른 값을 리턴해 줄수도 있다.
return "놀자놀자!";
}
}
- 메신저를 다른방식으로 bean으로 만들기(JavaConfig 사용 : spring boot의 전형적인 방식)
- messenger 클래스에는 어노테이션이 없다.
- component scan이 아닌 설정 파일에서 bean 만들기
- component annotation이 안 붙어있으면 component sca이 아닌 bean설정을 통해서 빈이 된다고 알면 된다.
- Bean 이 되면 aspect 역할을 할 수 있도록 @Aspect가 붙어있지만,
이것이 붙어있다고 bean이 되는 것은 아니다!!
- @Around 는? Before+After 인데 합친 것보다는 기능이 더 많다.
- 개입만 하는 것이 아니고 메소드가 리턴하는 데이터도 직접 조사할 수 있다. Around라는 aspect를 사용하면!
@Around("execution(String com.example.demo.util.*.get*())")
- Util 패키지에 있는 모든 get이라는 이름의 메소드에 적용될 aspect 라는 뜻이다.
- demo.util 패키지의 모든 클래스의 get으로 시작하는 메소드
- * 은 와일드카드 역할. 이 자리에 아무거나 와도 된다.
- 메소드의 인자는 아무것도 없어야 한다.
- 리턴타입은 반드시 String
→ 이렇게 Aspectj Expression을 해석할 수 있어야 한다.
- spring이 관리하는 bean이 이런 메소드를 갖고있어야 한다. bean이 아니면 적용이 안된다!!!
- 만약 bean 중에서 이런 모양의 메소드가 있다면, 메소드 실행 이전과 이후에 개입할 수 있다.
- 메소드 수행 이전, 이후란?
- joinPoint.proceed() 를 호출하면 이 메소드를 호출하는 것과 같다.
- 이전(초록) 이후(분홍) 위치에서 개입하면 된다.
- joinPoint.proceed() 를 호출하지 않으면 실행되지 않는다.
- 분명히 getMessage를 호출했음에도 불구하고... joinPoint가 실행되지 않으면 호출이 안된다.
- getMessage안에 있는 "getMessage() 메소드가 수행됩니다." 가 수행되지 않는다.
- 하지만 return이 있으므로... 해당 메소드는 호출이 되지 않았는데도 값을 리턴해주기도 한다.
- proceed 호출 이전, 이후에 실행값을 넣어주기
- proceed를 호출하면 getMessage 메소드가 실행된다.
- object type으로 리턴되므로 캐스팅해서 내용을 검사해볼 수 있다.
- 리턴값은 별개이다!
- 원래 리턴하려는 값을 받아서 리턴하면 이렇게 나타난다.(이게 일반적)
- 하지만 다른 값을 리턴해줄 수도 있다!
Messenger 에 메소드 추가
- 메신저의 sendGreeting() 메소드를 전달하면서 메시지를 전달할 때,
안에 어떤 값이 들어있는지 조사할 수가 있다.
- 조사해서 어떤 경우에는 return에서 끝나고 (1번)
어떤 경우에는 흐름이 쪽 진행될 것! (2번)
- 두 개의 차이는 aspect가 적용된 메소드에서 joinPoint를 호출했느냐 아니냐이다.
- 조사한 내용에 문제가 있으면 → joinPoint 를 호출하지 않는다.
- 조사한 내용에 문제가 없으면 → joinPoint 를 호출한다.
- 3개의 단어로 테스트
- 2번에는 금지된 단어가 있다. 이 경우 입력받은 값을 리턴해주지 않았다.
→ joinPoint.proceed() 까지 가지 못하므로 메소드를 호출했음에도 불구하고 messenger 메소드의 내용을 실행하지 않을 수도 있다.
- 이것을 잘 활용하면 게시물 삭제시 본인이 작성한 글이 아니면 삭제를 요청해도 삭제 기능을 수행하지 않을 수 있다.
send*(..)
- 인자가 없어도 되고, 여러개 있어도 된다는 의미.
ex)
public void sendxxx(){ }
public void sendxxx(int num){ }
public void sendxxx(int num, String name){ }
public void sendxxx(String name, boolean isMan, Car car){ }
- 단 조사하는 것이 어려울 수 있다.. 메소드가 이렇게 다양하게 생겼을 수 있으므로...
- send*(..) 라고 작성하면 인자의 개수, 인자의 타입이 다양해도 적용할 수 있다.
- joinpoint.getArgs() 는 object 배열로 리턴된다.
public void sendxxx(){ }
- 이렇게 작성되어 있으면 이 배열은 방이 0인 빈 배열이다 (인자로 전달된 것이 없으므로)
public void sendxxx(int num){ }
- 이렇게 작성된 경우, 0번 방에 정수가 들어있다.
- object 배열에 이렇게 들어가 있으면 반복문을 돌면서 값을 꺼내오면 된다.
- instanceof 연산자로 데이터의 타입을 확인할 수 있다.
- tmp가 string이면 찾아서 내보내주기
- object가 string이고 0번 방에 있는 것이 확실하면 이렇게만 작성해도 되지만,
이 메소드 말고 다른메소드에도 다양하게 적용하려면(여러 메소드에 적용할 것이라면)
반복문을 돌면서 확인하는 작업도 필요하므로 for문 코드가 들어갔다.
- AOP를 잘 배워두기! 다양하게 활용 가능하다.
- spring 프로젝트를 import해서 사용하려면 import는 Maven으로 선택하면 된다.
- file-import-maven-Existing Maven Projects
- pom.xml에 의존 dependency들이 모두 명시되어 있기 때문에 하면 된다.
- repository 설정하기
- 동일하게 stage에 올리고 commit message추가
'국비교육(22-23)' 카테고리의 다른 글
56일차(3)/Spring(19) : 게시판 댓글 기능 구현 (0) | 2022.12.27 |
---|---|
56일차(2)/Spring(18) : 예외 클래스, Exception Controller (0) | 2022.12.26 |
55일차(4)/Spring Boot(1) : Spring Boot 기초, AOP의 구조 익히기 (0) | 2022.12.26 |
55일차(3)/Spring(17) : 게시판 글 삭제, 글 수정 기능 구현 (0) | 2022.12.25 |
55일차(2)/Spring(16) : 글 상세보기, 이전글/다음글 불러오기 (0) | 2022.12.25 |