Closed JasonYoo1995 closed 2 years ago
DispatcherServlet이 처리할 요청 주소 패턴을 설정한다.
return new String[]{"/"}
는 모든 요청에 대한 처리를 의미함.
필터는 서블릿으로 들어오는 모든 요청뿐만 아니라, 나가는 응답에도 적용하는 로직이다.
요청의 문자열 인코딩을 설정한다. forceEncoding=true
로 주면 응답 문자열의 인코딩도 지정한다.
<form>
, <input>
태그 사용 시 GET, POST 제외한 다른 메서드를 지원하지 않는 브라우저가 있다.
스프링은 해당 정책을 우회하여 PUT, PATCH, DELETE 메서드도 사용할 수 있는 꼼수를 제공한다.
태그 작성 시,
hidden타입 +
_method` 이름을 갖도록하면
스프링이 이를 인식하여 PUT, PATCH, DELETE용 컨트롤러를 호출 해 준다.
<input type="hidden" name="_method" value="put"/>
HiddenHttpMethodFilter
를 추가하는 것으로 이 기능을 활성할 수 있다.
EntityManager는 트랜잭션 단위로 유지되므로 다음과 같은 문제를 경험할 수 있다.
Spring Security: DelegatingFilterProxy
로 시큐리티 필터 체인을 호출하여 유저 정보를 획득했을 때, 해당 트랜잭션이 종료된 상태에서 컨트롤러에 @CurrentUser
로 유저 정보를 주입할 경우 유저 정보는 준영속화 상태라 획득할 수 없다. (참고글)
시큐리티 필터 체인은 다른 필터보다 우선하여 호출되고 있다. 중요한 점은 콘트롤러 보다 앞서 호출된다는 점이며, 언급된 이슈를 경험하게 되는 원인이다.
연관관계 엔터티 지연 로딩: 엔터티가 다른 엔터티와 @OneToMany
, @ManyToMany
관계를 가질 때 개발자는 해당 어트리뷰트의 지연 로딩을 선호한다. Singer와 Album이 일대다 관계이고 singer.albums 어트리뷰트가 지연로딩 된다고 가정하자. 만약 트랜잭션 내에서 지연로딩을 끝마치지 못 했다면, 뷰에서 singer.albums를 이용할 때 문제를 야기한다.
OSIV 패턴 EntityManager를 요청 스레드에 바인딩하여 뷰에서도 영속성 컨텍스트가 살아있는 패턴이다. 영속성 컨텍스트는 트랜잭션 범위 밖에서 조회 기능만을 제공한다. 지연 로딩 기능도 조회 기능에 포함되어 트랜잭션 범위 밖에서 동작 가능하다.
OSIV 패턴을 적용하면 뷰에서도 영속성 컨텍스트가 살아 있기 때문에 지연 로딩을 동작시킬 수 있다.
Spring Boot는 옵션 spring.jpa.open-in-view=true
로 지정하여 OSIV가 적용된 상태로 어플리케이션을 구성해 준다.
OSIV 패턴의 단점 장시간 DB 커넥션 리소스를 사용한다. 실시간 및 대량 트래픽이 중요한 애플리케이션에서는 자원이 모자랄 수 있다. 이것은 결국 장애로 이어진다.
OSIV 패턴의 대안
스프링 MVC는 DispatcherServlet
이 /
에 매핑되는 것을 허용한다.
원래 "/"는 컨테이너의 Default Servlet이 사용하는 주소인데 그럼 DispatcherServlet
이 이것을 덮어 씌우게 된다.
이 경우 GET /styles/standard.css
같은 리소스 요청이 생기면, DispatchServelet
에 대응하는 컨트롤러가 없어 응답을 할 수 없다.
그러므로 여전히 Default Servelet이 리소스 요청을 처리하도록 만들 방법이 필요하다.
그 방법은, 가장 낮은 우선순위의 /**
-> Default Servlet 매핑을 설정하는 것이다.
이를 구현하는 간편한 코드가 아래와 같다.
// <=> <mvc:default-servlet-handler/>
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
참고글- spring docs 참고글 - tomcat what is the default servelet?
컨트롤러 클래스 작성 없이 간단하게 뷰를 내려주는 뷰 컨트롤러를 등록한다.
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//시작페이지 url을 "/"가 아닌 home으로
registry.addRedirectViewController("/", "home");
// "/login" 요청에 대하여 "loginPage" 뷰를 내려준다.
registry.addViewController("/login").setViewName("loginPage");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/")
.setCachePeriod(31556926);
}
국제화 메시지가 담긴 리소스 번들(프로퍼티 파일)을 메시지 소스로 사용한다.
@Bean
ReloadableResourceBundleMessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("WEB-INF/i18n/messages", "WEB-INF/i18n/application");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setFallbackToSystemLocale(false);
return messageSource;
}
리소스 번들(프로퍼티 파일)을 테마 소스로 사용한다.
/src/main/resources 하위에 테마명.properties
파일을 생성하면, 해당하는 테마에 대한 리소스 번들이 된다.
styleSheet=resources/styles/standard.css
@Bean ResourceBundleThemeSource themeSource() {
return new ResourceBundleThemeSource();
}
@Bean
UrlBasedViewResolver tilesViewResolver() {
UrlBasedViewResolver tilesViewResolver = new UrlBasedViewResolver();
tilesViewResolver.setViewClass(TilesView.class);
return tilesViewResolver;
}
@Bean TilesConfigurer tilesConfigurer() { TilesConfigurer tilesConfigurer = new TilesConfigurer(); tilesConfigurer.setDefinitions( "/WEB-INF/layouts/layouts.xml", "/WEB-INF/views/**/views.xml" ); tilesConfigurer.setCheckRefresh(true); return tilesConfigurer; }
### 국제화 지원 설정(쿠키 및 URL 패러미터)
- CookieLocaleResolver
쿠키를 사용하여 로케일 값을 브라우저에 저장하고. 나중에 로케일을 리졸브 할 때 사용할 수 있다. 기본적으로 세션 동안 쿠키 값이 유지된다.
```java
@Bean
CookieLocaleResolver localeResolver() {
CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver();
cookieLocaleResolver.setDefaultLocale(Locale.ENGLISH);
cookieLocaleResolver.setCookieMaxAge(3600);
cookieLocaleResolver.setCookieName("locale");
return cookieLocaleResolver;
}
lang
패러미터 이용.
@Bean
LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
interceptor.setParamName("lang");
return interceptor;
}
국제화 지원과 동일한 설정 패턴이다.
@Bean
CookieThemeResolver themeResolver() {
CookieThemeResolver cookieThemeResolver = new CookieThemeResolver();
cookieThemeResolver.setDefaultThemeName("standard");
cookieThemeResolver.setCookieMaxAge(3600);
cookieThemeResolver.setCookieName("theme");
return cookieThemeResolver;
}
@Bean
ThemeChangeInterceptor themeChangeInterceptor() {
return new ThemeChangeInterceptor();
}
getValidator()
와 addFormatters()
를 구현하여 검증 및 포맷터 빈 등록.
@Override
public Validator getValidator() {
return validator();
}
@Bean
public Validator validator() {
final LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(messageSource());
return validator;
}
// <=> replacement for 'typeConversionService' bean
@Override
public void addFormatters(FormatterRegistry formatterRegistry) {
formatterRegistry.addFormatter(dateFormatter());
}
@Bean
public DateFormatter dateFormatter() {
return new DateFormatter();
}
여태껏 사용한 구성설정에서
addResourceHandler()
나 configureDefaultServletHandling()
부분을 주석 처리했을 때
스태틱 리소스 파일을 위한 요청이 어떻게 달라지는 지 확인해 봅니다.
얻고자 하는 파일은 프로젝트 폴더 /src/main/resources/styles/stadard.css 에 위치하던 파일입니다.
리소스 핸들러 등록 | Default Servlet 핸들링 | 접근 주소 (/src/main/resources/styles/standard.css) |
---|---|---|
X | X | 접근할 수 있는 주소가 없음. |
X | O | (ROOT 배포)localhost:8080/styles/standard.css |
(일반 배포)localhost:8080/서블릿명/styles/standard.css |
||
O | * | (ROOT 배포)localhost:8080/resources/styles/standard.css |
(일반 배포)localhost:8080/서블릿명/resources/styles/standard.css |
위 표를 보았을 때, 리소스 핸들러가 등록되어 있다면, 스태틱 리소스에 매핑되는 URL이 꼭 존재하므로 Default Servlet 핸들링 설정은 신경쓰지 않아도 됩니다.
리소스 핸들러가 없다면, https://github.com/caffeine-library/pro-spring-5/issues/91#issuecomment-986165189 에서 언급한 대로 리소스 요청에 매핑해 줄 컨트롤러가 디스패처 서블릿에 없기 때문에 Default Servelet 핸들링을 활성하여야 합니다.
주제
16장 스프링 웹 애플리케이션을 읽고 중요✨ 하다고 생각하는 키워드와 선택한 이유에 대해서 코멘트로 달아주세요.
90