Spring boot 공작소

(1장-4장)
yarn
EJB Spring Spring boot
배보다 배꼽이 더 큰 스프링 설정
환경 설정 > 코딩
스프링으로 Hello world 구현 할려면
Web.xmlmaven dispatch
servelt
Controller
class
tomcat Hello world
Web Application
스프링 부트 핵심
• 자동구성
• 공통으로 필요한기능을 자동으로 구성
• 스타터 의존성
• 어떤 기능이 필요한지 알려주면 LIB를 빌드에 추가
• 명령줄 인터페이스(CLI)
• 액추에이터
스프링에서 jdbcTemplate 사용하기
• jdbcTemplate bean생성
• Datasource 의존성 주입
• 이외에 많은 구성들이 이런식으로 작성.
•일일이 작성할 필요가 있을까?
자동 구성
• Classpath에 H2라이브러리, jdbctemplate를 발견하면 spring
boot에서 자동으로 구성.
• Spring boot는 구성의 부담을 제공하는 여러가지 방법을 제공.
프로젝트 빌드에 의존성 추가하기
• 어떤 라이브러리가 필요한가?
• 라이브러리 그룹과 아티팩트는 ?
• 버전은 ?
• 다른 라이브러리와는 잘 동작할까?
스타터 의존성
• Spring boot는 스타터 의존성 수단으로 프로젝트 의존성을 쉽게 관리.
•org.springframework:spring-core
•org.springframework:srping-web
•org.springframework:spring-webmvc
•Com.fasterxml.jackson.core:jackson-databind
•Org.hibernate:hiber-validator
•org.apache.tomcat.embed:tomcat-embed-core
•org.apche.tomcat.embet:tomcat-embet-el
•org.apache.tomcat.embed:tomcat-embed-logging-juli
= org.srpingframework.boot:spring-boot-starter-web
Rest API Spring MVC 구성
CLI
• Spring boot는 spring application을 빠르게 작성 하기 위해서
새로운 방법을 제공.
• CLI는 개발자가 코드에만 집중하게 하여, 코드만 작성해도
application을 개발되어지게 한다.
Actuator
• 작동중인 application 내부를 살펴 볼 수 있는 기능을 제공.
• 컨텍스트에 구성된 bean
• 자동구성으로 구성된 것
• 환경 변수, 시스템 프로퍼티, 명령줄 인자
• 구동중인 현재 thread 상태
• 메모리 사용량, 가비지 컬렉션, 데이터 소스 사용량
Spring Initializr
• Spring initializr는 spring boot 프로젝트 구조를 만드는 web
application.
• 기본적인 프로젝트 구조와 코드를 빌드하는데 필요한 메이븐이나 그레이들
빌드 명세를 만든다.
• 제공
• 웹기반 인터페이스 (start.spring.io)
• Spring Tool Suite
• IntelliJ IDEA
• Spring boot CLI
Initializr 기본 구조
•build.gradle : gradle build 명세서, (=pom.xml)
•gradlew : gradle wrapper, 시스템에 gradle 설치 하지 않았으면
Gradle 명령대신에 이스크립트를 사용…
•Application.java : main class
•application.properties : 필요한 구성을 추가하는 프로퍼티.
•ApplicationTest.sjava : Junit Test class
Application.java
@SpringBootApplication //컴포넌트 구성 과 자동구성
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@SpringBootApplication
@Configuration
@ComponentScan
@EnableAutoConfiguration
application.properties
• 선택적인 파일 (사용 안 해도 됨)
• 명시적으로 요청하는 부분이없음
• Spring boot가 로드되면 자동으로 이 파일을 로드.
조건 별 자동구성
• 조건에 따라 자동 구성을 제어 할 수 있다.
• 특정 클래스가 classloader에 load 되었는지 확인 하여 자동 구성 사
용.
• @ 어노테이션으로도 조건 별 구성 가능.
자동구성에 사용하는 조건 annotation
• @conditionalOnBean
• @conditionalOnMissingBean
• @conditionalOnClass
• @conditionalOnMissingClass
• @conditionalOnExpresstion
• @conditionalOnJava
• @conditionalOnJndi
• @conditionalOnProperty
• @conditionalOnResource
• @conditionalOnWebApplication
• @conditionalOnNotWebApplication
스프링에서 자신만의 조건을 구성하는 방법.
Public class JdbcTemplateCondition implements Condition {
try{
context.getClassLoader().loadClass(“org.springframework.jd
bc.core.JdbcTemplate”);
return false;
} catch(Exception e) {
return false;
}
}
Condtion class 사용ㅇ하여 matches 함수를 override.
ClassLoader에 해당 class가 로드가 되었을 경우 처리.
Annotation을 사용하여 조건별 자동구성
@Conditional(JdbcTemplateCondition.class)
@Bean
public MyService myService() {
….
}
JdbcTemplateCondition가 class에 있을 경우 Bean 생성.
독서 목록 Application 만들어보자
Book.java
@Entity //JPA에서 관리하기위해 설정
public class Book {
@Id //id라는 변수에 유일성을 부여 (PK)
@GeneratedValue(strategy = GenerationType.AUTO) //기본키 자동생성
private Long id;
private String reader;
private String isbn;
private String title;
private String author;
private String description;
//getter setter 생략.
}
GenerationType.*
IDENTITY : 데이터베이스에 위임
SEQUENCE : 시퀀스를 사용
TABLE : 키 생성 테이블을 사용
AUTO : DB에 의존하지 않고 자동으로 생성
ReadingListRepository.java
public interface ReadingListRepository extends
JpaRepository<Book, Long> {
List<Book> findByReader(String reader);
//reader 변수기준으로 검색하는 메소드 추가.
}
interface JpaRepository<T, ID extends Serializable>
18개의 영속성 method 사용가능 (이미 만들어져있음)
T : Repository가 사용할 domain type
Class ID의 propertie type
ReadingListController.java
@Controller
@RequestMapping("/")
public class ReadingListController {
private static final String reader = "craig";
private ReadingListRepository readingListRepository;
@Autowired
public ReadingListController(ReadingListRepository readingListRepository) {
this.readingListRepository = readingListRepository;
}
@RequestMapping(method = RequestMethod.GET)
public String readersBooks(Model model) {
List<Book> readingList = readingListRepository.findByReader(reader);
if (readingList != null) model.addAttribute("books", readingList);
return "readingList";
}
@RequestMapping(method = RequestMethod.POST)
public String addToReadingList(Book book) {
book.setReader(reader);
readingListRepository.save(book); //JpaRepository에 이미 구현 된 method
return "redirect:/";
}
}
readingList.html #1
<!DOCTYPE html>
<html>
<head>
<title>Reading List</title>
<link rel="stylesheet" th:href="@{/css/style.css}"></link>
</head>
<body>
<h2>Your Reading List</h2>
<div th:unless="${#lists.isEmpty(books)}">
<dl th:each="book : ${books}">
<dt class="bookHeadline">
<span th:text="${book.title}">Title</span> by
<span th:text="${book.author}">Author</span>
(ISBN: <span th:text="${book.isbn}">ISBN</span>)
</dt>
<dd class="bookDescription">
<span th:if="${book.description}"
th:text="${book.description}">Description</span>
<span th:if="${book.description eq null}">
No description available</span>
</dd>
</dl>
</div>
readingList.html #2
<div th:if="${#lists.isEmpty(books)}">
<p>You have no books in your book list</p>
</div>
<hr/>
<h3>Add a book</h3>
<form method="POST" th:action="@{/}">
<label for="title">Title:</label>
<input type="text" name="title" size="50"></input><br />
<label for="author">Author:</label>
<input type="text" name="author" size="50"></input><br />
<label for="isbn">ISBN:</label>
<input type="text" name="isbn" size="15"></input><br />
<label for="description">Description:</label><br />
<textarea name="description" cols="80" rows="5"></textarea><br />
<input type="submit" value="Add Book" />
</form>
</body>
</html>
style.css
body {
background-color: #cccccc;
font-family: arial, helvetica, sans-serif;
}
.bookHeadline {
font-size: 12pt;
font-weight: bold;
}
.bookDescription {
font-size: 10pt;
}
label {
font-weight: bold;
}
3장. 구성을 사용자화하기
피자를 주문 할 때
VS
완전체 피자
자동구성 설정
입맛에 맞게 토핑 구성
사용자구성 설정
자동 구성 사용하지 않기
• 자동 구성에 영향을 줄 수 있는 2가지 방법
• 명시적으로 오버라이드 하는 방법
• 프로퍼티로 구성하는 방법
자동 구성 오버라이드
• Springboot이 제공하는 모든 자동 구성이 기능으로서 100% 만족 시켜주
지 않는다.
• 대표적으로 보안을 적용 할때 사용되는 자동구성은 적합하지 않다.
• Spring-boot-starter-security
애플리케이션 보안 설정하기 (자동 구성)
• gradle
compile('org.springframework.boot:spring-boot-
starter-security')
• maven
<dependency>
<groupId>org.springframework.boot</gropuId>
<artifactId>spring-boot-starter-security</
artifactId>
</dependency>
자동구성 되어지는 보안의 문제점
• HTTP 기본 인증
• 기본 password가 log에 노출 된다.
• Using default security password: c84eb182-978f-496b-
aa1b-14317b80c6a2
사용자 정의 보안 구성 하기
SecurityConfig.java
@Configuration //스프링 환경 설정을 java에서 한다는 의미
@EnableWebSecurity //웹 보안을 활성화 한다.
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private ReaderRepository readerRepository;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").access("hasRole('READER')“).antMatchers("/
**").permitAll()
.and().formLogin().loginPage("/login“).failureUrl("/login?error=true");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(new UserDetailsService() {
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
return readerRepository.findOne(username);
}
});
}
}
WebSecurityConfigurerAdapter.java
• confiure(WebSecurity)
• 스프링 시큐리티의 필터 연결설정 하기 위한 오버라이딩.
• configure(HttpSecurity)
• 인터셉터로 요청을 안전하게 보호하는 방법을 설정하기 위한 오버라이딩
• configure(AuthenticationManagerBuil
der)
• 사용자 세부 서비스를 설정 하기 위한 오버라이딩
login.html<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<link rel="stylesheet" th:href="@{/css/style.css}"></link>
</head>
<body onload='document.f.username.focus();'>
<div id="loginForm">
<h3>Login With Username and Password</h3>
<div class="error" th:if="${param.error}">
Incorrect username or password. Try again.
</div>
<form name='f' th:action="@{/login}" method='POST'>
<table>
<tr>
<td>User:</td>
<td><input type='text' name='username' value=''/></td>
</tr>
<tr>
<td>Password:</td>
<td><input type='password' name='password'/></td>
</tr>
<tr>
<td colspan='2'><input name="submit" type="submit" value="Login"/></td>
</tr>
</table>
</form>
</div>
</body>
</html>
ReadingListApplication.java
@SpringBootApplication
public class ReadingListApplication extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(ReadingListApplication.class, args);
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
//controller 없이 url path와 view name을 설정
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver>
argumentResolvers) {
argumentResolvers.add(new ReaderHandlerMethodArgumentResolver());
//resolver를 등록하여 controller method에 arguments type의 대한 mapping 을 도와준다.
// 사용하기 위해서는 RequstMappingHanlderAdapter 를 설정해줘야한다.
}
}
ReaderHandlerMethodAgrumentResolver.java
public class RederHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
//Resolver가 적용가능 한지 검사하는 역할
public boolean supportsParameter(MethodParameter parameter) {
return Reader.class.isAssignableFrom(parameter.getParameterType());
}
@Override
//들어오는 paramater을 이용해서 수정하거나 추가 할 수 있는부분
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer
mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Authentication auth = (Authentication) webRequest.getUserPrincipal();
return auth != null && auth.getPrincipal() instanceof Reader ? auth.getPrincipal() :
null;
}
}
HandlerMethodArgumentResolver class는 controller로 들어오는 paramater를 수정하거나
공통적으로 추가를 해 줘야 하는 경우 에 사용한다.
ReadingListController.java
@Controller
@RequestMapping("/")
public class ReadingListController {
private ReadingListRepository readingListRepository;
@Autowired
public ReadingListController(ReadingListRepository readingListRepository) {
this.readingListRepository = readingListRepository;
}
@RequestMapping(method=RequestMethod.GET)
public String readerBooks(Reader reader, Model model) { //Reader parameter추가
List<Book> readingList = readingListRepository.findByReader(reader); //reader 객체로 변경
if (readingList != null) {
model.addAttribute("books", readingList);
model.addAttribute("reader", reader);
}
return "readingList";
}
@RequestMapping(method=RequestMethod.POST)
public String addToReadingList(Reader reader, Book book){
book.setReader(reader);
readingListRepository.save(book);
return "redirect:/";
}
}
ReaderRepository.java
public interface ReaderRepository extends
JpaRepository<Reader, String> {
}
//Reader class의 대한 JpaRepository 설정
ReadingListRepository.java
public interface ReadingListRepository extends
JpaRepository<Book, Long> {
List<Book> findByReader(Reader reader);
//reader 변수기준으로 검색하는 메소드 추가.
}
//parameter type String -> Reader로 변경
Book.java
@Entity //JPA에서 관리하기위해 설정
public class Book {
private Reader reader; //Reader class 추가
//기존 변수 생략
//getter setter 생략.
}
Reader.java
@Entity
public class Reader implements UserDetails {
private static final long SerialVersionUID = -1L;
@Id
private String username;
private String fullname;
private String password;
//getter setter 생략…
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("ROLER_READER"));
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
/resource/data.sql
insert into Reader(username,
password, fullname) values('yarn',
'1234', 'choonghyun')
프로퍼티를 이용하여 외부적으로 구성
• 명령줄 인자
• java:comp/env
• JVM 시스템 프로퍼티
• 운영체제의 환경변수
• random.*로 시작 하는 프로퍼티 때문에 생성되는 무작위 값
• 외부에 있는 application.properties, application.yml
• 내부에 있는 application.properties, application.yml
• @PropertySource로 지정된 프로퍼티 소스
• 기본 프로퍼티
우
선
순
위
순
application.properties, application.yml
• application.properties
• xxx.xxx.xxx=xxxx
• application.yml
spring:
main:
show-banner: false
• application.yml > application.properties
프로퍼티 값 가져오기
@ConfigurationProperties(prefix=“amazon”)
public class xxxxController {
private String amazonId;
private void setAmazonId(String amazonId) {
this.amazonId = amazonId;
}
//생략
}
amazon.amazonId=yarn //applicatiopn.properties에 설정
@ConfigurationProperties(prefix=“amazon”) properties에 prefix가
amazon 인 속성을 가져옴
Class 하나에 프로퍼티 모으기
@Component
@ConfigurationProperties(“amazon”)
Public class AmazonProperties {
private String amazonId;
public void setAmazonId(String amazonId) {
this.amazonId = amazonId;
}
//생략
}
@ConfigurationProperties “xxx” 접두어가 붙은 프로퍼티 가져와서 주입
HTTP and HTTPS
• 그러면 HTTP와 HTTPS를 프로토콜을 동시에 사용 할려면 어떻게 해
야 할까?
• server.port를 하나 더 만들면 될까?
• Spring boot는 단 하나의 포트를 application.properties or
application.yml에 설정 할 수 있다.
How to configure HTTP and HTTPS?
TomcatEmbeddedServletContainerFactory
@SpringBootApplication
public class TwoConnectorsApplication {
public Integer port() {
return 8080;
}
@Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory tomcat = new
TomcatEmbeddedServletContainerFactory();
// Add Connectors in addition to the default connector, e.g. for SSL or AJP
tomcat.addAdditionalTomcatConnectors(createStandardConnector());
return tomcat;
}
private Connector createStandardConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setPort(port());
return connector;
}
}
4장. 스프링 부트 테스트 하기
Spring
컴포넌트
검색
자동
연결
Transa
ction
보안
Spring
컴포넌트
검색
자동
연결
Transa
ction
보안
TEST
가장 기본적인 스프링 통합 테스트
@RunWith(SpringJUnit4ClassRunner.class)// 스프링 통합 테스트 활성화
@ContextConfiguration(classes = ChoongsApplication.class)
public class ChoongsApplicationTests {
@Autowired
private AddressSercvice addressService;
@Test
public void testSerice(){
Address address = addressService.findByLastName();
aseertEquals(“P”, address.getFirstName());
aseertEquals(“Sherman”, address.getLastName());
aseertEquals(“42 Wallaby Way”, address.getAddressLine1());
aseertEquals(“Syndey”, address.getCity());
aseertEquals(“New South wales”, address.getState());
}
}
@SpringApplicationConfiguration
• @ContextConfiguration은 스프링 애플리케이션을 로드는 하지만 완전하게
Spring boot 기능을 완전하게 로드 하지 못한다.
• @SpringApplicationConfiguration을 사용하거나
SpringBootServletInitializer을 사용해야한다.
• 로깅과 외부 프로퍼티, spring boot의 다른기능 활성화
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes=ChoongConfiguration.class)
Public class ChoongServiceTests{
…
}
Controller TEST?
@RequestMapping(method=RequestMethod.POST)
public String addToRedingList(Book book){
book.setReader(reader);
redingListRepository.save(book);
return “redirect:/”;
}
//POJO를 이용한 테스트 코드 작성, RequestMappling 무시하면 일반적인 method
@RequestMapping(method=RequestMethod.POST)
public String addToRedingList(Book book){
book.setReader(reader);
redingListRepository.save(book);
return “redirect:/”;
}
* POST로 들어오는 요청에 대한 테스트는 불가능, redirect 되는지 테스트 불가능.
Spring boot에서 제공하는 Web test option
• Spring Mock MVC
• Application server를 구동하지 않고, 목 구현체로 테스트
• 웹 통합 테스트.
• 톰캣, 제티 내장 서블릿 컨테이너에서 애플리케이션을 실행 하여 실제 서버
에서 테스트.
• 당연히 테스트 시간이 오래 걸림..
Spring MVC 모킹
• Spring 3.2 부터 제공
• 실제 서블릿 컨테이너에서 컨트롤로를 실행하지 않고, HTTP 요청 가능.
• Mock MVC는 서블릿 컨테이너를 실행 하는 것처럼 보이지만 컨테이너
를 실행 하지 않는다.
• Mock MVC를 설정 하려면 MockMVCBuilders를 사용
MockMVCBuilders
• standaloneSetup()
• 수동으로 생성하고 구성한 컨트롤러 1개 이상을 서비스 할 MockMVC를 만든
다.
• Unit test
• webAppContextSetup()
• 컨트롤러 1개 이상 포함하는 spring application Context를 사용하여
MokMVC를 만든다.
• 통합 테스트
MockMVCBuilders.webAppContextSetup()
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes=ReadingListApplication.class)
@WebAppConfiguration
public class MockMVCWebTests {
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Before
public void setup(){
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
}
하지만 SpringApplicationConfiguration는
현재 Deprecated
@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.DEFINED_PORT,
classes=ReadingListApplication.class)
public class MockMVCWebTests2 {
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Before
public void setup(){
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
}
Contextconfiguration, SpringApplicationConfiguration, WebAppConfiguration 등은
1.4 부터 @SpringBootTest 로 통합
@ContextConfiguration(classes=xxx.class) , @SpringApplicationConfiguration(classes=xxx.class)
-> @SpringBootTest(classes=xxx.class)
@WebAppConfiguration -> @SpringBootTest(webEnvironment=WebEnvironment.DEFINED_PORT)
webEnvironment=WebEnvironment.DEFINED_PORT
webApplicationContext를 생성하기 위한 선언
MockMVCTest
public class MockMVCWebTests2 {
@Test
public void test1() throws Exception{
mockMvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.view().name("readingList"))
.andExpect(MockMvcResultMatchers.model().attributeExists("books"))
.andExpect(MockMvcResultMatchers.model().attribute("books",
Matchers.is(Matchers.empty())));
}
}
조금 더 간결한 MockMVCTest
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
public class MockMVCWebTests2 {
@Test
public void test1() throws Exception{
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(view().name("readingList"))
.andExpect(model().attributeExists("books"))
.andExpect(model().attribute("books", is(empty())));
}
}
POST method Test
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
public class MockMVCWebTests4 {
@Test
public void test1() throws Exception{
mockMvc.perform(post("/").contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("title", "Book Title")
.param("author", "Book Author")
.param("isbn", "123456789")
.param("description", "Description")).andExpect(status().is3xxRedirection())
.andExpect(header().string("Location", "/"));
}
}
웹 보안 테스트
gradle
testCompile('org.springframework.security:spring-security-test')
maven
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
Spring Security Configure 적용
import static
org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurer
s.springSecurity;
public class MockMVCWebTests5 {
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Before
public void setup(){
mockMvc =
MockMvcBuilders.webAppContextSetup(context).apply(springSec
urity()).build(); //springSecurity()는 스프링 시큐리티를 활성화 하여
} //mockMvc로 수행하는 모든 요청에 스프링 시큐리티 적용
}
@WithMockUser
@Test
@WithMockUser(username="yarn", password="yarn", roles="READER")
public void testAuthenticatedUser(){
}
• 지정한 사용자 이름, 패스워드, 권한으로 UserDetails를 생성 한 후 보
안 컨텍스트를 로드한다.
• UserDetails 객체 조회를 건너 뛰고, 지정된 값으로 UserDetails 생
성.
@WithUserDetails
import static org.hamcrest.Matchers.samePropertyValuesAs;
public class MockMVCWebTests5 {
@Test
@WithUserDetails("yarn")
public void testtestAuthenticatedUsers() throws Exception{
Reader expectedReader = new Reader();
expectedReader.setUsername("yarn");
expectedReader.setPassword("yarn");
expectedReader.setFullname("Yarn");
mockMvc.perform(get("/"))
.andExpect(status().isOk()).andExpect(view().name("readingList"))
.andExpect(model().attribute("reader",
samePropertyValuesAs(expectedReader)));
}
}
• 지정한 사용자 이름으로 UserDetails 객체를 조회하여 보안 컨텍스트를 로드.

Spring boot 공작소(1-4장)

  • 1.
  • 2.
  • 3.
    배보다 배꼽이 더큰 스프링 설정 환경 설정 > 코딩
  • 4.
    스프링으로 Hello world구현 할려면 Web.xmlmaven dispatch servelt Controller class tomcat Hello world Web Application
  • 5.
    스프링 부트 핵심 •자동구성 • 공통으로 필요한기능을 자동으로 구성 • 스타터 의존성 • 어떤 기능이 필요한지 알려주면 LIB를 빌드에 추가 • 명령줄 인터페이스(CLI) • 액추에이터
  • 6.
    스프링에서 jdbcTemplate 사용하기 •jdbcTemplate bean생성 • Datasource 의존성 주입 • 이외에 많은 구성들이 이런식으로 작성. •일일이 작성할 필요가 있을까?
  • 7.
    자동 구성 • Classpath에H2라이브러리, jdbctemplate를 발견하면 spring boot에서 자동으로 구성. • Spring boot는 구성의 부담을 제공하는 여러가지 방법을 제공.
  • 8.
    프로젝트 빌드에 의존성추가하기 • 어떤 라이브러리가 필요한가? • 라이브러리 그룹과 아티팩트는 ? • 버전은 ? • 다른 라이브러리와는 잘 동작할까?
  • 9.
    스타터 의존성 • Springboot는 스타터 의존성 수단으로 프로젝트 의존성을 쉽게 관리. •org.springframework:spring-core •org.springframework:srping-web •org.springframework:spring-webmvc •Com.fasterxml.jackson.core:jackson-databind •Org.hibernate:hiber-validator •org.apache.tomcat.embed:tomcat-embed-core •org.apche.tomcat.embet:tomcat-embet-el •org.apache.tomcat.embed:tomcat-embed-logging-juli = org.srpingframework.boot:spring-boot-starter-web Rest API Spring MVC 구성
  • 10.
    CLI • Spring boot는spring application을 빠르게 작성 하기 위해서 새로운 방법을 제공. • CLI는 개발자가 코드에만 집중하게 하여, 코드만 작성해도 application을 개발되어지게 한다.
  • 11.
    Actuator • 작동중인 application내부를 살펴 볼 수 있는 기능을 제공. • 컨텍스트에 구성된 bean • 자동구성으로 구성된 것 • 환경 변수, 시스템 프로퍼티, 명령줄 인자 • 구동중인 현재 thread 상태 • 메모리 사용량, 가비지 컬렉션, 데이터 소스 사용량
  • 12.
    Spring Initializr • Springinitializr는 spring boot 프로젝트 구조를 만드는 web application. • 기본적인 프로젝트 구조와 코드를 빌드하는데 필요한 메이븐이나 그레이들 빌드 명세를 만든다. • 제공 • 웹기반 인터페이스 (start.spring.io) • Spring Tool Suite • IntelliJ IDEA • Spring boot CLI
  • 13.
    Initializr 기본 구조 •build.gradle: gradle build 명세서, (=pom.xml) •gradlew : gradle wrapper, 시스템에 gradle 설치 하지 않았으면 Gradle 명령대신에 이스크립트를 사용… •Application.java : main class •application.properties : 필요한 구성을 추가하는 프로퍼티. •ApplicationTest.sjava : Junit Test class
  • 14.
    Application.java @SpringBootApplication //컴포넌트 구성과 자동구성 public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } @SpringBootApplication @Configuration @ComponentScan @EnableAutoConfiguration
  • 15.
    application.properties • 선택적인 파일(사용 안 해도 됨) • 명시적으로 요청하는 부분이없음 • Spring boot가 로드되면 자동으로 이 파일을 로드.
  • 16.
    조건 별 자동구성 •조건에 따라 자동 구성을 제어 할 수 있다. • 특정 클래스가 classloader에 load 되었는지 확인 하여 자동 구성 사 용. • @ 어노테이션으로도 조건 별 구성 가능.
  • 17.
    자동구성에 사용하는 조건annotation • @conditionalOnBean • @conditionalOnMissingBean • @conditionalOnClass • @conditionalOnMissingClass • @conditionalOnExpresstion • @conditionalOnJava • @conditionalOnJndi • @conditionalOnProperty • @conditionalOnResource • @conditionalOnWebApplication • @conditionalOnNotWebApplication
  • 18.
    스프링에서 자신만의 조건을구성하는 방법. Public class JdbcTemplateCondition implements Condition { try{ context.getClassLoader().loadClass(“org.springframework.jd bc.core.JdbcTemplate”); return false; } catch(Exception e) { return false; } } Condtion class 사용ㅇ하여 matches 함수를 override. ClassLoader에 해당 class가 로드가 되었을 경우 처리.
  • 19.
    Annotation을 사용하여 조건별자동구성 @Conditional(JdbcTemplateCondition.class) @Bean public MyService myService() { …. } JdbcTemplateCondition가 class에 있을 경우 Bean 생성.
  • 20.
  • 21.
    Book.java @Entity //JPA에서 관리하기위해설정 public class Book { @Id //id라는 변수에 유일성을 부여 (PK) @GeneratedValue(strategy = GenerationType.AUTO) //기본키 자동생성 private Long id; private String reader; private String isbn; private String title; private String author; private String description; //getter setter 생략. } GenerationType.* IDENTITY : 데이터베이스에 위임 SEQUENCE : 시퀀스를 사용 TABLE : 키 생성 테이블을 사용 AUTO : DB에 의존하지 않고 자동으로 생성
  • 22.
    ReadingListRepository.java public interface ReadingListRepositoryextends JpaRepository<Book, Long> { List<Book> findByReader(String reader); //reader 변수기준으로 검색하는 메소드 추가. } interface JpaRepository<T, ID extends Serializable> 18개의 영속성 method 사용가능 (이미 만들어져있음) T : Repository가 사용할 domain type Class ID의 propertie type
  • 23.
    ReadingListController.java @Controller @RequestMapping("/") public class ReadingListController{ private static final String reader = "craig"; private ReadingListRepository readingListRepository; @Autowired public ReadingListController(ReadingListRepository readingListRepository) { this.readingListRepository = readingListRepository; } @RequestMapping(method = RequestMethod.GET) public String readersBooks(Model model) { List<Book> readingList = readingListRepository.findByReader(reader); if (readingList != null) model.addAttribute("books", readingList); return "readingList"; } @RequestMapping(method = RequestMethod.POST) public String addToReadingList(Book book) { book.setReader(reader); readingListRepository.save(book); //JpaRepository에 이미 구현 된 method return "redirect:/"; } }
  • 24.
    readingList.html #1 <!DOCTYPE html> <html> <head> <title>ReadingList</title> <link rel="stylesheet" th:href="@{/css/style.css}"></link> </head> <body> <h2>Your Reading List</h2> <div th:unless="${#lists.isEmpty(books)}"> <dl th:each="book : ${books}"> <dt class="bookHeadline"> <span th:text="${book.title}">Title</span> by <span th:text="${book.author}">Author</span> (ISBN: <span th:text="${book.isbn}">ISBN</span>) </dt> <dd class="bookDescription"> <span th:if="${book.description}" th:text="${book.description}">Description</span> <span th:if="${book.description eq null}"> No description available</span> </dd> </dl> </div>
  • 25.
    readingList.html #2 <div th:if="${#lists.isEmpty(books)}"> <p>Youhave no books in your book list</p> </div> <hr/> <h3>Add a book</h3> <form method="POST" th:action="@{/}"> <label for="title">Title:</label> <input type="text" name="title" size="50"></input><br /> <label for="author">Author:</label> <input type="text" name="author" size="50"></input><br /> <label for="isbn">ISBN:</label> <input type="text" name="isbn" size="15"></input><br /> <label for="description">Description:</label><br /> <textarea name="description" cols="80" rows="5"></textarea><br /> <input type="submit" value="Add Book" /> </form> </body> </html>
  • 26.
    style.css body { background-color: #cccccc; font-family:arial, helvetica, sans-serif; } .bookHeadline { font-size: 12pt; font-weight: bold; } .bookDescription { font-size: 10pt; } label { font-weight: bold; }
  • 27.
  • 28.
    피자를 주문 할때 VS 완전체 피자 자동구성 설정 입맛에 맞게 토핑 구성 사용자구성 설정
  • 29.
    자동 구성 사용하지않기 • 자동 구성에 영향을 줄 수 있는 2가지 방법 • 명시적으로 오버라이드 하는 방법 • 프로퍼티로 구성하는 방법
  • 30.
    자동 구성 오버라이드 •Springboot이 제공하는 모든 자동 구성이 기능으로서 100% 만족 시켜주 지 않는다. • 대표적으로 보안을 적용 할때 사용되는 자동구성은 적합하지 않다. • Spring-boot-starter-security
  • 31.
    애플리케이션 보안 설정하기(자동 구성) • gradle compile('org.springframework.boot:spring-boot- starter-security') • maven <dependency> <groupId>org.springframework.boot</gropuId> <artifactId>spring-boot-starter-security</ artifactId> </dependency>
  • 32.
    자동구성 되어지는 보안의문제점 • HTTP 기본 인증 • 기본 password가 log에 노출 된다. • Using default security password: c84eb182-978f-496b- aa1b-14317b80c6a2
  • 33.
  • 34.
    SecurityConfig.java @Configuration //스프링 환경설정을 java에서 한다는 의미 @EnableWebSecurity //웹 보안을 활성화 한다. public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private ReaderRepository readerRepository; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/").access("hasRole('READER')“).antMatchers("/ **").permitAll() .and().formLogin().loginPage("/login“).failureUrl("/login?error=true"); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(new UserDetailsService() { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return readerRepository.findOne(username); } }); } }
  • 35.
    WebSecurityConfigurerAdapter.java • confiure(WebSecurity) • 스프링시큐리티의 필터 연결설정 하기 위한 오버라이딩. • configure(HttpSecurity) • 인터셉터로 요청을 안전하게 보호하는 방법을 설정하기 위한 오버라이딩 • configure(AuthenticationManagerBuil der) • 사용자 세부 서비스를 설정 하기 위한 오버라이딩
  • 36.
    login.html<!DOCTYPE html> <html> <head> <title>Login</title> <link rel="stylesheet"th:href="@{/css/style.css}"></link> </head> <body onload='document.f.username.focus();'> <div id="loginForm"> <h3>Login With Username and Password</h3> <div class="error" th:if="${param.error}"> Incorrect username or password. Try again. </div> <form name='f' th:action="@{/login}" method='POST'> <table> <tr> <td>User:</td> <td><input type='text' name='username' value=''/></td> </tr> <tr> <td>Password:</td> <td><input type='password' name='password'/></td> </tr> <tr> <td colspan='2'><input name="submit" type="submit" value="Login"/></td> </tr> </table> </form> </div> </body> </html>
  • 37.
    ReadingListApplication.java @SpringBootApplication public class ReadingListApplicationextends WebMvcConfigurerAdapter { public static void main(String[] args) { SpringApplication.run(ReadingListApplication.class, args); } @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/login").setViewName("login"); //controller 없이 url path와 view name을 설정 } @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(new ReaderHandlerMethodArgumentResolver()); //resolver를 등록하여 controller method에 arguments type의 대한 mapping 을 도와준다. // 사용하기 위해서는 RequstMappingHanlderAdapter 를 설정해줘야한다. } }
  • 38.
    ReaderHandlerMethodAgrumentResolver.java public class RederHandlerMethodArgumentResolverimplements HandlerMethodArgumentResolver { @Override //Resolver가 적용가능 한지 검사하는 역할 public boolean supportsParameter(MethodParameter parameter) { return Reader.class.isAssignableFrom(parameter.getParameterType()); } @Override //들어오는 paramater을 이용해서 수정하거나 추가 할 수 있는부분 public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Authentication auth = (Authentication) webRequest.getUserPrincipal(); return auth != null && auth.getPrincipal() instanceof Reader ? auth.getPrincipal() : null; } } HandlerMethodArgumentResolver class는 controller로 들어오는 paramater를 수정하거나 공통적으로 추가를 해 줘야 하는 경우 에 사용한다.
  • 39.
    ReadingListController.java @Controller @RequestMapping("/") public class ReadingListController{ private ReadingListRepository readingListRepository; @Autowired public ReadingListController(ReadingListRepository readingListRepository) { this.readingListRepository = readingListRepository; } @RequestMapping(method=RequestMethod.GET) public String readerBooks(Reader reader, Model model) { //Reader parameter추가 List<Book> readingList = readingListRepository.findByReader(reader); //reader 객체로 변경 if (readingList != null) { model.addAttribute("books", readingList); model.addAttribute("reader", reader); } return "readingList"; } @RequestMapping(method=RequestMethod.POST) public String addToReadingList(Reader reader, Book book){ book.setReader(reader); readingListRepository.save(book); return "redirect:/"; } }
  • 40.
    ReaderRepository.java public interface ReaderRepositoryextends JpaRepository<Reader, String> { } //Reader class의 대한 JpaRepository 설정
  • 41.
    ReadingListRepository.java public interface ReadingListRepositoryextends JpaRepository<Book, Long> { List<Book> findByReader(Reader reader); //reader 변수기준으로 검색하는 메소드 추가. } //parameter type String -> Reader로 변경
  • 42.
    Book.java @Entity //JPA에서 관리하기위해설정 public class Book { private Reader reader; //Reader class 추가 //기존 변수 생략 //getter setter 생략. }
  • 43.
    Reader.java @Entity public class Readerimplements UserDetails { private static final long SerialVersionUID = -1L; @Id private String username; private String fullname; private String password; //getter setter 생략… @Override public Collection<? extends GrantedAuthority> getAuthorities() { return Arrays.asList(new SimpleGrantedAuthority("ROLER_READER")); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
  • 44.
    /resource/data.sql insert into Reader(username, password,fullname) values('yarn', '1234', 'choonghyun')
  • 45.
    프로퍼티를 이용하여 외부적으로구성 • 명령줄 인자 • java:comp/env • JVM 시스템 프로퍼티 • 운영체제의 환경변수 • random.*로 시작 하는 프로퍼티 때문에 생성되는 무작위 값 • 외부에 있는 application.properties, application.yml • 내부에 있는 application.properties, application.yml • @PropertySource로 지정된 프로퍼티 소스 • 기본 프로퍼티 우 선 순 위 순
  • 46.
    application.properties, application.yml • application.properties •xxx.xxx.xxx=xxxx • application.yml spring: main: show-banner: false • application.yml > application.properties
  • 47.
    프로퍼티 값 가져오기 @ConfigurationProperties(prefix=“amazon”) publicclass xxxxController { private String amazonId; private void setAmazonId(String amazonId) { this.amazonId = amazonId; } //생략 } amazon.amazonId=yarn //applicatiopn.properties에 설정 @ConfigurationProperties(prefix=“amazon”) properties에 prefix가 amazon 인 속성을 가져옴
  • 48.
    Class 하나에 프로퍼티모으기 @Component @ConfigurationProperties(“amazon”) Public class AmazonProperties { private String amazonId; public void setAmazonId(String amazonId) { this.amazonId = amazonId; } //생략 } @ConfigurationProperties “xxx” 접두어가 붙은 프로퍼티 가져와서 주입
  • 49.
    HTTP and HTTPS •그러면 HTTP와 HTTPS를 프로토콜을 동시에 사용 할려면 어떻게 해 야 할까? • server.port를 하나 더 만들면 될까? • Spring boot는 단 하나의 포트를 application.properties or application.yml에 설정 할 수 있다.
  • 50.
    How to configureHTTP and HTTPS?
  • 51.
    TomcatEmbeddedServletContainerFactory @SpringBootApplication public class TwoConnectorsApplication{ public Integer port() { return 8080; } @Bean public EmbeddedServletContainerFactory servletContainer() { TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory(); // Add Connectors in addition to the default connector, e.g. for SSL or AJP tomcat.addAdditionalTomcatConnectors(createStandardConnector()); return tomcat; } private Connector createStandardConnector() { Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); connector.setPort(port()); return connector; } }
  • 52.
    4장. 스프링 부트테스트 하기
  • 53.
  • 54.
  • 55.
    가장 기본적인 스프링통합 테스트 @RunWith(SpringJUnit4ClassRunner.class)// 스프링 통합 테스트 활성화 @ContextConfiguration(classes = ChoongsApplication.class) public class ChoongsApplicationTests { @Autowired private AddressSercvice addressService; @Test public void testSerice(){ Address address = addressService.findByLastName(); aseertEquals(“P”, address.getFirstName()); aseertEquals(“Sherman”, address.getLastName()); aseertEquals(“42 Wallaby Way”, address.getAddressLine1()); aseertEquals(“Syndey”, address.getCity()); aseertEquals(“New South wales”, address.getState()); } }
  • 56.
    @SpringApplicationConfiguration • @ContextConfiguration은 스프링애플리케이션을 로드는 하지만 완전하게 Spring boot 기능을 완전하게 로드 하지 못한다. • @SpringApplicationConfiguration을 사용하거나 SpringBootServletInitializer을 사용해야한다. • 로깅과 외부 프로퍼티, spring boot의 다른기능 활성화 @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes=ChoongConfiguration.class) Public class ChoongServiceTests{ … }
  • 57.
    Controller TEST? @RequestMapping(method=RequestMethod.POST) public StringaddToRedingList(Book book){ book.setReader(reader); redingListRepository.save(book); return “redirect:/”; } //POJO를 이용한 테스트 코드 작성, RequestMappling 무시하면 일반적인 method @RequestMapping(method=RequestMethod.POST) public String addToRedingList(Book book){ book.setReader(reader); redingListRepository.save(book); return “redirect:/”; } * POST로 들어오는 요청에 대한 테스트는 불가능, redirect 되는지 테스트 불가능.
  • 58.
    Spring boot에서 제공하는Web test option • Spring Mock MVC • Application server를 구동하지 않고, 목 구현체로 테스트 • 웹 통합 테스트. • 톰캣, 제티 내장 서블릿 컨테이너에서 애플리케이션을 실행 하여 실제 서버 에서 테스트. • 당연히 테스트 시간이 오래 걸림..
  • 59.
    Spring MVC 모킹 •Spring 3.2 부터 제공 • 실제 서블릿 컨테이너에서 컨트롤로를 실행하지 않고, HTTP 요청 가능. • Mock MVC는 서블릿 컨테이너를 실행 하는 것처럼 보이지만 컨테이너 를 실행 하지 않는다. • Mock MVC를 설정 하려면 MockMVCBuilders를 사용
  • 60.
    MockMVCBuilders • standaloneSetup() • 수동으로생성하고 구성한 컨트롤러 1개 이상을 서비스 할 MockMVC를 만든 다. • Unit test • webAppContextSetup() • 컨트롤러 1개 이상 포함하는 spring application Context를 사용하여 MokMVC를 만든다. • 통합 테스트
  • 61.
    MockMVCBuilders.webAppContextSetup() @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes=ReadingListApplication.class) @WebAppConfiguration public class MockMVCWebTests{ @Autowired private WebApplicationContext context; private MockMvc mockMvc; @Before public void setup(){ mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); } } 하지만 SpringApplicationConfiguration는 현재 Deprecated
  • 62.
    @SpringBootTest @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(webEnvironment=WebEnvironment.DEFINED_PORT, classes=ReadingListApplication.class) public class MockMVCWebTests2{ @Autowired private WebApplicationContext context; private MockMvc mockMvc; @Before public void setup(){ mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); } } Contextconfiguration, SpringApplicationConfiguration, WebAppConfiguration 등은 1.4 부터 @SpringBootTest 로 통합 @ContextConfiguration(classes=xxx.class) , @SpringApplicationConfiguration(classes=xxx.class) -> @SpringBootTest(classes=xxx.class) @WebAppConfiguration -> @SpringBootTest(webEnvironment=WebEnvironment.DEFINED_PORT) webEnvironment=WebEnvironment.DEFINED_PORT webApplicationContext를 생성하기 위한 선언
  • 63.
    MockMVCTest public class MockMVCWebTests2{ @Test public void test1() throws Exception{ mockMvc.perform(MockMvcRequestBuilders.get("/")) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.view().name("readingList")) .andExpect(MockMvcResultMatchers.model().attributeExists("books")) .andExpect(MockMvcResultMatchers.model().attribute("books", Matchers.is(Matchers.empty()))); } }
  • 64.
    조금 더 간결한MockMVCTest import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; public class MockMVCWebTests2 { @Test public void test1() throws Exception{ mockMvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(view().name("readingList")) .andExpect(model().attributeExists("books")) .andExpect(model().attribute("books", is(empty()))); } }
  • 65.
    POST method Test importstatic org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; public class MockMVCWebTests4 { @Test public void test1() throws Exception{ mockMvc.perform(post("/").contentType(MediaType.APPLICATION_FORM_URLENCODED) .param("title", "Book Title") .param("author", "Book Author") .param("isbn", "123456789") .param("description", "Description")).andExpect(status().is3xxRedirection()) .andExpect(header().string("Location", "/")); } }
  • 66.
  • 67.
    Spring Security Configure적용 import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurer s.springSecurity; public class MockMVCWebTests5 { @Autowired private WebApplicationContext context; private MockMvc mockMvc; @Before public void setup(){ mockMvc = MockMvcBuilders.webAppContextSetup(context).apply(springSec urity()).build(); //springSecurity()는 스프링 시큐리티를 활성화 하여 } //mockMvc로 수행하는 모든 요청에 스프링 시큐리티 적용 }
  • 68.
    @WithMockUser @Test @WithMockUser(username="yarn", password="yarn", roles="READER") publicvoid testAuthenticatedUser(){ } • 지정한 사용자 이름, 패스워드, 권한으로 UserDetails를 생성 한 후 보 안 컨텍스트를 로드한다. • UserDetails 객체 조회를 건너 뛰고, 지정된 값으로 UserDetails 생 성.
  • 69.
    @WithUserDetails import static org.hamcrest.Matchers.samePropertyValuesAs; publicclass MockMVCWebTests5 { @Test @WithUserDetails("yarn") public void testtestAuthenticatedUsers() throws Exception{ Reader expectedReader = new Reader(); expectedReader.setUsername("yarn"); expectedReader.setPassword("yarn"); expectedReader.setFullname("Yarn"); mockMvc.perform(get("/")) .andExpect(status().isOk()).andExpect(view().name("readingList")) .andExpect(model().attribute("reader", samePropertyValuesAs(expectedReader))); } } • 지정한 사용자 이름으로 UserDetails 객체를 조회하여 보안 컨텍스트를 로드.