스프링에서는 일반적인 Java 객체를 new로 생성하여 개발자가 관리하는 것이 아닌 Spring Container에 모두 맡긴다.
즉, 개발자 → 프레임워크로 제어의 객체 관리 권한이 넘어 갔음으로 ‘제어의 역전’ 이라고 한다.
Url을 Encode하는 작업을 Ioc(제어의 역전)과 Di(의존성 주입)을 통하여 관리하는 예제
@Component
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContextcontext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context= applicationContext;
}
public static ApplicationContext getContext(){ returncontext;}
}
public interface IEncoder {
String encode(String message);
}
//@Component
public class Encoder {
private IEncoder iEncoder;
// public Encoder(@Qualifier("base64Encoder") IEncoder iEncoder) { this.iEncoder = iEncoder;}
public Encoder(IEncoder iEncoder) { this.iEncoder = iEncoder;}
public void setIEncoder(IEncoder iEncoder) { this.iEncoder = iEncoder;}
public String encode(String message){ return this.iEncoder.encode(message);}
}
@Component("base64Encoder")
public class Base64Encoder implements IEncoder{
@Override
public String encode(String message) {
return Base64.getEncoder().encodeToString(message.getBytes(StandardCharsets.UTF_8));
}
}
@Component
public class UrlEncoder implements IEncoder{
@Override
public String encode(String message) {
try{
return URLEncoder.encode(message, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}
// 여러가지 Bean 생성 => Configuration
@Configuration
class AppConfig{
@Bean("base64Encode")
public Encoder encoder(Base64Encoder base64Encoder){
return new Encoder(base64Encoder);
}
@Bean("urlEncode")
public Encoder encoder(UrlEncoder urlEncoder){
return new Encoder(urlEncoder);
}}
public class SpringIocApplication {
public static void main(String[] args) {
SpringApplication.run(SpringIocApplication.class, args);
ApplicationContext context = ApplicationContextProvider.getContext();
// Encoder 주입 방식
// Base64Encoder base64Encoder = context.getBean(Base64Encoder.class);
// UrlEncoder urlEncoder = context.getBean(UrlEncoder.class);
// Encoder encoder = new Encoder(base64Encoder);
// encoder.encode(msg);
Encoder encoder = context.getBean("base64Encode", Encoder.class);
String url = "www.naver.com/books/it?page=1&size=123&name=duckbillLvr";
String result = encoder.encode(url);
System.out.println(result);
}
}
관점 지향 프로그래밍
스프링 어플리케이션은 대부분 특별한 경우를 제외 하고는 MVC 웹 어플리케이션에서는 Web Layer, Business Layer, Data Layer로 정의
Annotation | 의미 |
---|---|
@Aspect | 자바에서 널리 사용하는 AOP 프레임워크에 포함되며, AOP를 정의하는 Class에 할당 |
@Pointcut | 기능을 어디에 적용시킬지(메소드, 어노테이션 등) AOP를 적용시킬 시점을 설정 |
@Before | 메소드를 실행하기 이전 |
@After | 메소드가 성공적으로 실행 후, 예외가 발생하더라도 실행 |
@AfterReturning | 메소드 호출 성공 실행 시 (Not Throws) |
@AfterThrowing | 메소드 호출 실패 예외 발생 (Throws) |
@Around | Before / After 모두 제어 |
Aop를 이용하여 Decode와 Timer 설계
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Decode {
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Timer {
}
@Aspect
@Component
public class DecodeAop {
// MEMO: Controller 하위 메소드를 Pointcut 시점으로 지정
@Pointcut("execution(* com.example.springaop.controller..*.*(..))")
private void cut(){}
// MEMO: Decode Annotation을 Pointcut 시점으로 지정
@Pointcut("@annotation(com.example.springaop.anotation.Decode)")
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 afterReturn(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(StandardCharsets.UTF_8));
user.setEmail(base64Email);
System.out.println("after returning :" + user);
}
}
}
Aop를 이용하여 Base64 request를 Decode하고 실행 완료후 afterReturn을 이용하여 다시 Encode하여 저장하는 AOP이다.@Aspect
@Component
public class TimerAop {
@Pointcut("execution(* com.example.springaop.controller..*.*(..))")
private void cut(){}
@Pointcut("@annotation(com.example.springaop.anotation.Timer)")
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.getTotalTimeMillis());
}
}
Around Aop를 사용하여 작업 시간을 측정하는 Aop@RestController
@RequestMapping("/api")
public class RestApiController{
@Timer
@DeleteMapping("/delete")
public void delete() throws InterruptedException {
Thread.sleep(1000 * 2);
}
@Decode
@PutMapping("/put")
public User put(@RequestBody User user){
System.out.println("put method" + user);
return user;
}
}
Aop를 사용할 Method에 Annotation을 적용하여 Aop를 사용한다는 것을 Spring에 알려준다.Object Mapper을 활용하면 Object → json → Object의 변환에 유용하다.
@Getter
@Setter
@ToString
public class User {
private String name;
private int age;
@JsonProperty("phone_number")
private String phoneNumber;
public User(){
this.name = null;
this.age = 0;
this.phoneNumber = null;
}
public User defaultUser(){
return new User("default", 0, "000-0000-0000");
}
public User(String name, int age, String phoneNumber) {
this.name = name;
this.age = age;
this.phoneNumber = phoneNumber;
}
}
@SpringBootTest
class SpringObjectMapperApplicationTests {
@Test
void contextLoads() throws JsonProcessingException {
/*INFO: Json -> Object
* Object -> Json
controller req json(text) -> object
response object -> json(text)
*/
var objectMapper = new ObjectMapper(); // ObjectMapper 생성
//MEMO: object -> text objectMapper가 get method활용
//TIP: objectMapper가 get method를 참고하므로 다른 method이름 앞에 get을 붙이면 안됨
var user = new User("duckbill", 27, "010-1234-1234");
var text = objectMapper.writeValueAsString(user);
System.out.println(text);
//MEMO: text -> object기본생성자로 초기화 필요
var objectUser = objectMapper.readValue(text, User.class);
System.out.println(objectUser);
}
}
System.in: User(name=duckbill, age=27, phoneNumber=010-1234-1234)
Json Text: {"name":"duckbill","age":27,"phone_number":"010-1234-1234"}
System.out: User(name=duckbill, age=27, phoneNumber=010-1234-1234)
User → Json User → User 로 변환되는 과정의 Object Mapping