
static class SubLogicInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
log.info("[INTERCEPTOR] 전처리!");
Object result = invocation.proceed();
log.info("[INTERCEPTOR] 후처리!");
return result;
}
}
@Test
@DisplayName("스프링 프록시 팩토리에서는 프록시 대상 객체가 인터페이스를 구현한 구체 클래스라면, JDK를 사용한다.")
void proxy_test_1() throws Exception {
MockService target = new MockServiceImpl();
// 인터페이스가 있나? -> 있다 -> JDK 써야지 ~
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.addAdvice(new SubLogicInterceptor());
MockService proxy = (MockService) proxyFactory.getProxy();
log.info("target.getClass() = {}", target.getClass());
log.info("proxy.getClass() = {}", proxy.getClass());
proxy.logic();
}
ProxyFactory 라는 클래스의 객체를 생성하고, 대상 객체인 target을 넣어준다.target은 MockService라는 인터페이스 타입의 인터페이스 구현 클래스 생성자를 호출한 인스턴스이다. 즉, 인터페이스를 기반이라는 것!addAdvice를 통해 관심사의 코드를 추가해주고, getProxy()로 프록시를 만들어준다.logic() 메서드 호출 시 invoke()가 호출된다.proxy는 MockService로 캐스팅했지만, .getClass()를 했을 때 class jdk.proxy3.$Proxy13가 나온다.@Test
@DisplayName("구상 클래스(Concrete Class)라면, 스프링 프록시 팩토리는 CGLIB 방식으로 프록시 객체를 생성한다.")
void proxy_test_2() throws Exception {
ConcreteMockService target = new ConcreteMockService();
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.addAdvice(new SubLogicInterceptor());
ConcreteMockService proxy = (ConcreteMockService) proxyFactory.getProxy();
log.info("target.getClass() = {}", target.getClass());
log.info("proxy.getClass() = {}", proxy.getClass());
proxy.logic1();
}
ConcreteMockService는 구현 클래스이다. proxy.getClass()를 호출했을 때 ConcreteMockService$$SpringCGLIB$$0 이러한 결과가 나오는 것을 알 수 있다.proxyFactory.setProxyTargetClass(true); // 대상 클래스를 상속받는 방식으로 CGLIB 방식으로 만들어준다.
@Slf4j
@Aspect
@Component
public class LoggingAspect {
// execution(접근제어자 반환타입 메서드이름(파라미터) [예외])
// @Around("execution(* * io.silver.greppaop.app.AopService.logic(String, String))")
// @Around("@annotation(io.silver.greppaop.config.Logging)")
@Around("execution(public void io.silver.greppaop.app.AopService.logic())")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[LoggingAspect] 횡단 관심사 로깅 시작");
Object result = joinPoint.proceed();
log.info("[LoggingAspect] 횡단 관심사 로깅 종료");
return result;
}
}
@Aspect로 등록해줄 수 있다.@Around
실행 전, 후에 Advice를 실행하겠다는 것이다.
ProceedingJoinPoint를 인자로 받을 수 있다는 것이 큰 특징이다.
Object를 반환해준다.
execution
point cut을 통해 필터링을 해줄 수 있는데, 그 중 하나가 execution이다.
execution(접근제어자 반환타입 메서드이름(파라미터) [예외]) 로 어디에 적용될지 필터링해줄 수 있다.
// http://localhost:8080/hello-world
@RequestMapping(method = RequestMethod.GET, path = "/hello-world")
public void receiveRequestFromBrowser() {
log.info("Hello World!");
}
end-point를 맵핑해주는 어노테이션은 @RequestMapping 라고 한다.RequestMethod라는 enum에 Http 메서드가 담겨있다.@GetMapping("/print-spring")
public void printSpring1(HttpServletResponse resp) throws Exception{
resp.setContentType("text/html;charset=UTF-8");
PrintWriter writer = resp.getWriter();
writer.println("안녕하세요!");
}
HttpServletResponse 를 받아올 수 있다. text/html;charset=UTF-8 세팅을 해두어야 응답 본문에 한국어가 담겨있어도 잘 보일 수 있다.setContentType를 통해 먼저 세팅한 후 그걸로 writer를 불러와야 오류가 안 난다. (순서 지켜야 한다)Controller는 View를 반환하는데, 여기서는 응답 본문에 값이 담겨있기 때문에 View가 필요없고, 오류가 나지 않는다.@ResponseBody //
@GetMapping("/print-spring3")
public String printSpring3() {
return "안녕하세요! 반가워요!"; // 반환하는 문자열을 바디에 넣어준다.
}
@ResponseBody를 이용하면, 더이상 view를 찾지 않고, 인코딩도 자동으로 해준다.HttpMessageConverter의 구현체 (스프링이 만드는)가 본문 응답을 반환값을 가지고 만들어준다.StringHttpMessageConverter : 반환 타입이 문자열일 때MappingJackson2HttpMessageConverter 문자열이 아닌 객체일 때Content-Type: text/html;charset=UTF-8@ResponseBody // 더이상 view를 찾지 않는다. 인코딩도 해준다.
@GetMapping("/print-integer") // "/print-spring3"를 end-point라고 부른다.
public Integer printInteger() {
return 1;
}
Content-Type: application/json 이라고 나온다.Integer 타입을 반환하므로, MappingJackson2HttpMessageConverter가 사용되어 JSON 형식(application/json)으로 변환된다.@Getter
class SomeType {
private final String data = "데이터!!";
}
@ResponseBody
@GetMapping("/print-obj")
public SomeType printObj() {
return new SomeType();
}
{
"data": "데이터!!"
}
@GetMapping("/page-2")
public String showPage2() {
// /templates/txt_page.txt
return "txt_page"; //
}
꼭 view는 html파일이어야 할까? -> 그렇지 않다 !
# application.yml 설정법
spring:
thymeleaf:
# prefix : classpath:/custom/
suffix : .txt
# application.properties 설정법
spring.thymeleaf.prefix = classpath:/templates/
spring.thymeleaf.suffix = .txt
키 = 값 형태로 나타낸다.@GetMapping("/params1")
public String showParams1(HttpServletRequest req) {
String name = req.getParameter("name");
log.info("name = {}", name);
Map<String, String[]> parameterMap = req.getParameterMap();
// parameter 얻는 방법 1번
Set<Entry<String, String[]>> entries = parameterMap.entrySet();// key value가 묶여서 셋으로 나옴
// parameter 얻는 방법 2번
for (Map.Entry<String, String[]> entry : entries) {
log.info("{} = {}", entry.getKey(), Arrays.toString(entry.getValue()));
}
// parameter 얻는 방법 3번
parameterMap.forEach((key, value) -> log.info("key = {}, value = {}", key, value));
return "index";
}
HttpServletRequest 를 이용하여 파라미터를 가져올 수 있다. index.html 파일이 있을 경우, 경로를 지정해주지 않은 localhost:8080에 index 뷰가 자동으로 뜬다.@GetMapping("/params3")
public String showParams3(@RequestParam(name = "name", required = false) String name
, @RequestParam(name = "age", required = false, defaultValue = "1") Integer age)
{
// default value는 문자열로 줘야 한다.
log.info("name = {}", name);
log.info("age = {}", age);
return "index";
}
@RequestParam을 이용해서 파라미터를 받아올 수 있다. name = "name" 에서 앞에 name은 고정이고, 파라미터의 이름이 ""에 들어간다. String name 에 해당한다.required=false를 설정해두면, 파라미터가 없을 때 에러가 뜨지 않는다. defaultValue 기본 값을 설정해둘 수 있고, 문자열로 값을 넣어줘야 한다.@GetMapping("/params4")
public String showParams4(String name){
// 이렇게 해도 name으로 파라미터 하면 들어온다.
// nickname=~~ 이렇게 하면 안 들어온다.
log.info("name = {}", name);
return "index";
}
RequestParam이 없더라도, name에 매칭이 된다면, 파라미터로 받아올 수 있다.nickname=santa 라고 하면, name=null이 되는 것이다.@Getter
@Setter
@ToString
@EqualsAndHashCode
@Data // getter, setter, toString, equalsAndHashCode 다 들어있다.
class SignInRequest {
private String username;
private String password;
}
@GetMapping("/params5")
public String showParams5(@ModelAttribute SignInRequest signInRequest){
log.info("signInRequest = {}", signInRequest);
log.info("signInRequest.getUsername() = {}", signInRequest.getUsername());
log.info("signInRequest.getPassword() = {}", signInRequest.getPassword());
return "index";
}
mutable 해야만 값을 넣어줄 수 있다.setter 나 생성자가 있어야한다.@ModelAttribute 를 예전에는 필수로 붙여줘야 했었는데, 이젠 바껴서 생략 가능하다.@Data 어노테이션을 붙여줄 경우, getter, setter, ToString, EqualsAndHashCode를 다 포함할 수 있다.@GetMapping("/params7")
public String showParams7(String[] favorites){
// List<String> 으로 보내면 안된다.
for (String favorite : favorites) {
log.info("favorite = {}", favorite);
}
return "index";
}
favorites라는 파라미터 여러 개를 받아오고 싶을 때는 String[] 배열로 받아올 수 있다. 단, List<String>는 객체이기 때문에 사용할 수 없다.@GetMapping("/path1/{name}") // 경로 변수 만들 수 있다.
public String showPage1(@PathVariable(name="name") String name) {
log.info("name = {}", name);
return "index";
}
@GetMapping("/path1/admin") // 구체적인 게 우선이다.
public String showPage2() {
log.info("ADMIN");
return "index";
}
http://ej.blog/posts/1, http://ej.blog/posts/2,... 이러한 경로가 필요할 때, 경로에 맞는 메서드를 다 만들어주느냐? -> NO!{ } 안에 변수를 넣어줄 수 있다.@PathVariable 을 사용하여 uri에서 변수를 받아올 수 있다.name="name"에서 앞의 name은 고정이고, ""이 경로 변수가 되는 것이다. String name이 이 메서드에서 사용할 변수localhost:8080/path1/admin을 실행시킬 때, 어떤 메서드가 실행될까?showPage2가 실행된다. @GetMapping({"/path2", "/path2/{name}"})
public String showPage3(@PathVariable(name="name", required = false) Optional<String> name) {
// Optional<String> 을 넣어서 null도 처리할 수 있다.
// required=false 여야 null이 들어갈 수 있다.
log.info("name = {}", name);
return "index";
}
required = false 를 설정해두면 null도 처리할 수 있다.Optional<String>을 통해 null을 처리할 수 있다./path2/를 실행시키면 name = null 이 아니라, 에러가 난다.@GetMapping("/boards/{boardName}/posts/{postId}")
public String showPage4(@PathVariable String boardName, @PathVariable String postId) {
log.info("boardName = {}", boardName);
log.info("postId = {}", postId);
return "index";
}
@PathVariable 파라미터를 주지 않아도, boardName, postId와 이름을 같게 사용하면 경로 변수를 잘 받아올 수 있다.오늘 내용은 다행히 잘 따라갈 수 있었다 ! 오늘은 스프링에게 미움을 느끼지 않았다. ㅎㅎ 어제와 다른 그녀 스프링.. 암튼 다행이다. 오늘은 재밌게 들었던 것 같다 ! 그리고 너무 신기했다.. ㅎ 웹에 막 뭐가 뜨고,, 주소 바꾸니까 다른 거 뜨고.. 아 재밌어
(이렇게만 가주라 응응)
아~~~ Friday!!!! 금요일 ! Freitag !! 아, 행복하다. 근데 약속은 없다. 허허 집 최고.. 근데 좀 밖에 나가고 싶기도 해 이젠 ㅎ..
오늘 아침에 일어나서 밖을 봤는데 에..? 언제 벚꽃이 이렇게 폈어..? 진짜 신기하다.. 내가 모르는 사이에 열심히도 폈구나.. 예쁘다 팝콘 같아 ! 벚꽃 구경 가고 싶다 ~ 이번 주 주말도 알차게 잘 보내야지 ㅎㅎ 복습하고,, 스터디 하고 헤헤
다음주 금요일만 기다려요 ~ :)