1. IOC : Inversion Of Control
- 스프링에서는 일반적인 JAVA 객체를 생성하여 개발자가 관리하는 것이 아닌 Spring Container에게 모두 맡긴다
- 객체 관리의 제어 권한이 개발자 -> Spring Framework(의 Spring Container)에게 넘어감 : 제어의 역전
2. DI : Dependency Injection
- 외부로부터 사용할 객체를 주입 받는다. 즉, Spring Container가 주입한다.
- 의존 코드를 따로 두어, 의존성으로부터 격시킴으로써 코드 테스트에 용이
- 이에따라 안정적으로 테스트 가능(기대값 활용)
- 추상화, 순환참조방지 가능
- IOC, DI 예제 : Base64, URL Encoder
1) 적용전
public class Base64Encoder {
public String encode(String msg){
return Base64.getEncoder().encodeToString(msg.getBytes(StandardCharsets.UTF_8));
}
}
public class urlEncoder { ... }
public class SpringIocApplication {
public static void main(String[] args) {
String url = "...";
// base64, url Encoding code
Base64Encoder base64Encoder = new Base64Encoder();
// UrlEncoder urlEncoder = new UrlEncoder();
String result = base64Encoder.encode(url);
System.out.println(result);
}
}
: 원하는 동작에 따라 encoder를 생성해서 encoding
2) DI 적용
public class Encoder {
private IEncoder iEncoder;
// DI 주입(param 설정)
public Encoder(IEncoder iEncoder){
// this.iEncoder = new Base64Encoder();
// this.iEncoder = new UrlEncoder();
this.iEncoder = iEncoder;
}
public void setiEncoder(IEncoder iEncoder){
this.iEncoder = iEncoder;
}
public String encode(String msg){
return iEncoder.encode(msg);
}
}
public interface IEncoder {
String encode(String msg);
}
public class Base64Encoder implement IEncdoer {
public String encode(String msg){
return Base64.getEncoder().encodeToString(msg.getBytes(StandardCharsets.UTF_8));
}
}
public class urlEncoder implement IEncdoer { ... }
public class SpringIocApplication {
public static void main(String[] args) {
String url = "...";
// still created by programmer(new)
Encoder encoder = new Encoder(new Base64Encoder());
//Encoder encoder = new Encoder(new urlEncoder());
Strubg result = encoder.encode(url);
System.out.println(result);
}
}
: 코드 수정은 간결해졌지만 여전히 programmer에 의해 관리(new 생성)
3) IOC 적용
public interface IEncoder {
String encode(String msg);
}
@Component // spring container에게 "이 클래스를 bean으로 관리해라"라는 어노테이션 즉 IOC 발생
public class Base64Encoder implement IEncdoer {
public String encode(String msg){
return Base64.getEncoder().encodeToString(msg.getBytes(StandardCharsets.UTF_8));
}
}
@Component
public class urlEncoder implement IEncdoer { ... }
@Component
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException{
context = applicationContext;
}
public static ApplicationContext getContext(){
return context;
}
}
@SpringBootApplication
public class SpringIocApplication {
public static void main(String[] args) {
SpringApplication.run(SpringIocApplication.class, args);
ApplicationContext context = ApplicationContextProvider.getContext();
String url = "...";
Encoder encoder = context.getBean("base64Encode", Encoder.class); // Encoder class에 qualifier로 지정된 것으로 실행
String result = encoder.encode(url);
System.out.println(result);
}
}
@Configuration // 여러 개의 Bean 생성
class AppConfig{
@Bean("base64Encode")
public Encoder encoder(Base64Encoder base64Encoder){
return new Encoder(base64Encoder);
}
@Bean("urlEncode")
public Encoder encoder(UrlEncoder urlEncoder){
return new Encoder(urlEncoder);
}
}
: programmer가 new로 생성하는 것 없이 모두 Bean으로 Spring Container가 관리
cf) AppConfig 대신 Encoder에 직접 Qualifier 지정하는 것으로도 가능
@Component
public class Encoder {
private IEncoder iEncoder;
public Encoder(@Qualifier("urlEncoder") IEncoder iEncoder){ // qualifier로 설정(Component name)
this.iEncoder = iEncoder;
}
public void setiEncoder(IEncoder iEncoder){
this.iEncoder = iEncoder;
}
public String encode(String msg){
return iEncoder.encode(msg);
}
}
3. Bean
- Spring Container가 관리하는 단위
- @Component 어노테이션을 통해 Class를 Bean으로 등록 가능
cf) method는 @Bean으로 등록. 즉, 하나의 @Compnent 내에 여러 @Bean
- 어노테이션 등록한 이후 해당 객체는 Spring Container가 관리 : IOC
4. AOP : Aspect Oriented Programming : 관점지향 프로그래밍
- 대부분 Web, Business, Data Layer로 정의
Web | Business | Data | |
REST API 제공, Client 중심 로직 적용 | 내부 정책에 따른 로직 개발 | DB 및 외부 연동 처리 |
- Spring에서 어노테이션으로 실현 가능 ex) @Before, @After, @AfterReturning
예시)
// create custom annotation
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Decode {
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Timer {
}
@Aspect // aop로 동작하게 하는 어노테이션
@Component
public class ParameterAop {
@Pointcut("execution(* com.example.aop.controller..*.*(..))") // controller 하위 모든 것
private void cut(){
}
@Before("cut()")
public void before(JoinPoint joinPoint){
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
System.out.println(method.getName());
Object[] args = joinPoint.getArgs(); // 매개변수배열
for(Object obj : args){
System.out.println("type : "+obj.getClass().getSimpleName());
System.out.println("value : "+obj);
}
}
@AfterReturning(value = "cut()", returning = "returnObj")
public void afterReturn(JoinPoint joinPoint, Object returnObj){
System.out.println("return obj : " + returnObj);
}
}
@Aspect
@Component
public class TimerAop {
@Pointcut("execution(* com.example.aop.controller..*.*(..))") // controller 하위 모든 것
private void cut(){
}
@Pointcut("@annotation(com.example.aop.annotation.Timer)") // Timer annotation이 붙은 것
private void enableTimer(){
}
@Around("cut() && enableTimer()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object result = joinPoint.proceed();
stopWatch.stop();
System.out.println("total time : " + stopWatch.getTotalTimeSeconds());
}
}
@Aspect
@Component
public class DecodeAop {
@Pointcut("execution(* com.example.aop.controller..*.*(..))") // controller 하위 모든 것
private void cut(){
}
@Pointcut("@annotation(com.example.aop.annotation.Decode)") // Decode annotation이 붙은 것
private void enableDecode(){
}
@Before("cut() && enableDecode()")
public void before(JoinPoint joinPoint) throws UnsupportedEncodingException {
Object[] args = joinPoint.getArgs();
for(Object arg : args){
if(arg instanceof User){
User user = User.class.cast(arg);
String base64Email = user.getEmail();
String email = new String(Base64.getDecoder().decode(base64Email), "UTF-8");
user.setEmail(email);
}
}
}
@AfterReturning(value = "cut() && enableDecode()", returning = "returnObj")
public void afterReturning(JoinPoint joinPoint, Object returnObj){
if(returnObj instanceof User){
User user = User.class.cast(returnObj);
String email = user.getEmail();
String base64Email = Base64.getEncoder().encodeToString(email.getBytes());
user.setEmail(base64Email);
}
}
}
@RestController
@RequestMapping("/api")
public class RestApiController {
@GetMapping("/get/{id}")
public String get(@PathVariable Long id, @RequestParam String name){
// before aop applied
// System.out.println("get method");
// System.out.println("get method : " + id + " " + name);
return id + " " + name;
}
@PostMapping("/post")
public User post(@RequestBody User user){
// before aop applied
// System.out.println("post method : "+user);
return user;
}
@Timer // Timer aop만 반응할 수 있도록
@DeleteMapping("/delete")
public void delete() throws InterruptedException {
// db logic...
Thread.sleep(1000*2);
}
@Decode // Decode aop만 반응할 수 있도록
@PutMapping("/put")
public User put(@RequestBody User user){
System.out.println("put : " + user);
return user;
}
}
: 요청 -> Controller -> Annotation & Mapping -> AOP -> 설정된 관점에서 동작
'프로그래밍 > SPRING' 카테고리의 다른 글
한끝완2 (0) | 2022.05.23 |
---|---|
한끝완1 (0) | 2022.02.20 |
[SPRING] Response, ObjectMapper (0) | 2021.09.09 |
[SPRING] SPRING BOOT 특징, REST API, JSON (0) | 2021.09.08 |
[SPRING] 웹 개발 개론 (0) | 2021.08.16 |