기본으로 HttpSession 값 바로 가져오는 방식에서
어노테이션 기반 Session 으로 개선하기
패키지 구조
build.gradle
buildscript {
ext {
springBootVersion = '2.1.9.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
plugins {
id 'java'
id 'eclipse'
id 'org.springframework.boot' version '2.3.2.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
}
group 'com.practice.springboot'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
implementation('org.springframework.boot:spring-boot-starter-web')
implementation('org.projectlombok:lombok')
}
test {
useJUnitPlatform()
}
HttpSession 값 바로 가져오기
resources/application.yml
spring:
mvc:
view:
suffix: .html
suffix 지정
resources/templates/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>INDEX</title>
</head>
<body>
<h1>INDEX</h1>
<input type="text" id="name" value="김씨">
<button type="button" id="btn-post">post</button>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="/js/app/index.js"></script>
</body>
</html>
resources/templates/home.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>HOME</title>
</head>
<body>
<h1>HOME</h1>
</body>
</html>
resources/static/js/app/index.js
var main = {
init: function() {
var _this = this;
$('#btn-post').on('click', function () {
_this.post();
});
},
post : function () {
var data = {
name: $('#name').val(),
}
$.ajax({
type: 'POST',
url: '/home',
dataType: 'json',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(data),
}).done(function() {
window.location.href = '/home';
}).fail(function (error) {
alert(JSON.stringify(error));
});
},
};
main.init();
javascript
com/practice/springboot/Application.java
package com.practice.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
com/practice/springboot/config/WebConfig.java
package com.practice.springboot.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/templates/", "classpath:/static/");
}
}
WebMvcConfigurer
- 자식이 되려는 클래스가 웹과 관련된 처리를 하게됩니다.
- @EnableWebMvc 어노테이션을 사용하는것보다 코드량을 간단히 쓸 수 있습니다.
- @EnableWebMvc 어노테이션이 자동적으로 세팅해주는 @Override를 할 수 있게 됩니다.
addResourceHandlers
- 오버라이드 메서드로 경로 설정할 수 있습니다.
- Cache-control 가능합니다.
- 정적 캐시(/static/...)를 지우고 싶은 경우에 캐시컨트롤로 삭제가 안되는 경우에 mac인 경우 Shift + commend + R 로 강력 새로고침으로 지우는것을 시도해 볼 수 있습니다.
com/practice/springboot/web/dto/RequestUserDto.java
package com.practice.springboot.web.dto;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
public class RequestUserDto {
private String name;
@Builder
public RequestUserDto(String name){
this.name = name;
}
}
페이로드로 넘어올 Dto, VO를 생성해서 두개로 사용하는게 좋지만 간단하게 DTO 하나로 테스트
com/practice/springboot/config/dto/SessionUser.java
package com.practice.springboot.config.dto;
import lombok.Builder;
import lombok.Getter;
import java.io.Serializable;
@Getter
public class SessionUser implements Serializable {
private String name;
@Builder
public SessionUser(String name){
this.name = name;
}
}
인증쪽에서 관리할 SessionUser Dto입니다.
com/practice/springboot/web/HomeController.java
package com.practice.springboot.web;
import com.practice.springboot.web.dto.RequestUserDto;
import lombok.RequiredArgsConstructor;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
@RequiredArgsConstructor
@RestController
public class HomeController {
private final HttpSession httpSession;
@PostMapping("/home")
public RequestUserDto home(Model model, @RequestBody RequestUserDto requestUserDto) {
httpSession.setAttribute("user", requestUserDto);
return requestUserDto;
}
}
Controller에서 httpSession의 속성 값을 사용할 수 있도록 RestController에서 httpSession에 속성 값을 지정하기 위하여 생성
com/practice/springboot/web/IndexController.java
package com.practice.springboot.web;
import com.practice.springboot.web.dto.RequestUserDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.http.HttpSession;
@RequiredArgsConstructor
@Controller
public class IndexController {
private final HttpSession httpSession;
@GetMapping("/")
public String index() {
return "index";
}
@GetMapping("/home")
public String home() {
SessionUser user = (SessionUser) httpSession.getAttribute("user");
System.out.println("name : " + user.getName());
return "home";
}
}
HomeController에서 지정한 세션값을 home() 에서 확인할 수 있습니다.
여기까지 작성하였을때 session 값을 사용하는것에 문제는 없습니다.
하지만 2개 이상의 엔드포인트에서 httpSession.getAttribute(?) 가 반복적으로 작성될 경우에 코드가 굉장히 많아지기 때문에
어노테이션 기반으로 세션값을 가져오도록 설정하도록 합니다.
post 버튼 클릭시
name 출력 후 이동
어노테이션 기반 Session 으로 개선하기
패키지, 클래스 추가
com/practice/springboot/config/auth/LoginUser.java
package com.practice.springboot.config.auth;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
}
어노테이션 기반으로 Session 값을 받도록 어노테이션 클래스 생성
@Target(ElementType.PARAMETER)
- 이 어노테이션이 생성될 수 있는 위치를 지정합니다.
- PARAMETER로 지정했으니 메소드의 파라미터로 선언된 객체에서만 사용할 수 있습니다.
- 이 외에도 클래스 선언문에 쓸 수 있는 TYPE 등이 있습니다.
@interface
- 이 파일을 어노테이션 클래스로 지정합니다.
- LoginUser라는 이름을 가진 어노테이션이 생성되었다고 보면 됩니다.
@Retention(RetentionPolicy.RUNTIME)
- 어노테이션의 라이프 사이클 설정
- RetentionPolicy.SOURCE
- 컴파일러에 의해 삭제됩니다.
- RetentionPolicy.CLASS
- 컴파일러에 의해 클래스 파일에 기록되지만 런타임에 VM에 의해 유지될 필요는 없습니다.
- RetentionPolicy.RUNTIME
- 컴파일러에 의해 클래스 파일에 기록되고 런타임에 VM에 의해 유지되므로 반사적으로 읽을 수 있습니다.
- RetentionPolicy.SOURCE
com/practice/springboot/config/auth/LoginUserArgumentResolver.java
package com.practice.springboot.config.auth;
import com.practice.springboot.config.dto.SessionUser;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpSession;
@RequiredArgsConstructor
@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {
private final HttpSession httpSession;
@Override
public boolean supportsParameter(MethodParameter parameter) {
boolean isLoginUserAnnotation = parameter.getParameterAnnotation(LoginUser.class) != null;
boolean isUserClass = SessionUser.class.equals(parameter.getParameterType());
return isLoginUserAnnotation && isUserClass;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return httpSession.getAttribute("user");
}
}
HandlerMethodArgumentResolver
- 조건에 맞는 경우 메소드가 있다면 HandlerMethodArgumentResolver의 구현체가 지정한 값으로 해당 메소드의 파라미터로 넘길 수 있습니다.
supportsParameter()
- 컨트롤러 메서드의 특정 파라미터를 지원하는지 판단합니다.
- 여기서는 파라미터에 @LoginUser 어노테이션이 붙어 있고, 파라미터 클래스 타입이 SessionUser.class인 경우 true를 반환합니다.
resolveArgument()
- 파라미터에 전달할 객체를 생성합니다.
- 여기서는 세션에서 객체를 가져옵니다.
com/practice/springboot/config/WebConfig.java
package com.practice.springboot.config;
import com.practice.springboot.config.auth.LoginUserArgumentResolver;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final LoginUserArgumentResolver loginUserArgumentResolver;
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/templates/", "classpath:/static/");
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(loginUserArgumentResolver);
}
}
addArgumentResolvers()
- 사용자 지정 컨트롤러 메서드 인수 유형을 지원하는 확인자를 추가합니다.
/com/practice/springboot/web/IndexController.java
package com.practice.springboot.web;
import com.practice.springboot.config.auth.LoginUser;
import com.practice.springboot.config.dto.SessionUser;
import com.practice.springboot.web.dto.RequestUserDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.http.HttpSession;
@RequiredArgsConstructor
@Controller
public class IndexController {
private final HttpSession httpSession;
@GetMapping("/")
public String index() {
return "index";
}
@GetMapping("/home")
public String home(@LoginUser SessionUser user) {
System.out.println("name : " + user.getName());
return "home";
}
}
@LoginUser SessionUser user
- 위의 작업으로 어노테이션 클래스가 생성되었습니다.
- httpSession.getAttribute("user")를 반복적으로 적을 필요가 없어졌습니다.
- 이제는 어느 컨트롤러이든지 @LoginUser를 사용하여 세션 정보를 가져올 수 있습니다.
post 버튼 클릭시 동일하게
name 출력 후 이동