@Autowired 대신 Spring Constructor Dependency Injection을 사용하는 이유

자동 유선



내가 본 대부분의 Spring 개발자는 클래스에 종속성을 주입하기 위해 여전히 @Autowired 구문을 사용하고 있습니다. 이것은 더 이상 권장되지 않으며 Intellij는 심지어 사용하려고 할 때 경고를 표시합니다.


오늘 우리는 이러한 방식으로 종속성 주입을 사용하지 않는 한 가지 이유를 살펴보겠습니다. 나는 또한 이것이 이전에도 생산 사건을 일으키는 것을 보았습니다. 필드에 @Autowired가 있으면 필드가 잠재적으로 변경 가능하고 테스트하기가 더 어렵습니다. 주입된 필드를 변경할 수 있게 하면 Spring이 CGLIB 라이브러리를 사용하여 이러한 주입된 클래스를 프록시하는 방법을 방해할 수 있습니다.

DTO 설정



테스트 프로젝트에서 우리는 사용자에게 세션 범위가 지정되어야 하는 DTO를 설정하고 있습니다. 따라서 각 사용자는 자신의 세션과 관련된 Bean의 인스턴스만 볼 수 있습니다.

UserDataDto.java 클래스:

import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.SessionScope;

@Component
@SessionScope
public class UserDataDto {
    private String userName;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }


Spring Security 구성 설정



또한 여기에서 테스트를 위해 사용하는 두 사용자 인스턴스를 모두 초기화하기 위해 몇 가지 Spring Security 설정도 있습니다.

SecurityConfig.class:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {
        UserDetails user1 = User.withUsername("user1")
                .password(passwordEncoder.encode("password"))
                .roles("ADMIN")
                .build();
        UserDetails user2 = User.withUsername("user2")
                .password(passwordEncoder.encode("password"))
                .roles("ADMIN")
                .build();
        return new InMemoryUserDetailsManager(user1, user2);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .httpBasic()
                .and()
                .authorizeRequests()
                .antMatchers("/**")
                .hasRole("ADMIN")
                .anyRequest()
                .authenticated();
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder()
    {
        return new BCryptPasswordEncoder();
    }
}


컨트롤러 클래스 설정



마지막으로 컨트롤러 클래스가 있습니다.
UserController.class:

@RestController("/")
public class UserController {
    @Autowired
    UserDataDto userDataDto;

    @PostMapping("/saveUserName")
    public void saveUserName(@RequestBody String userName){
        userDataDto.setUserName(userName);
    }

    @PostMapping("/saveUserNameReplaceInstance")
    public void saveUserNameReplaceInstance(@RequestBody String userName){
        UserDataDto userDataDto = new UserDataDto();
        userDataDto.setUserName(userName);
        this.userDataDto = userDataDto;
    }

    @GetMapping("/getUserName")
    public String getUserName(){
        return userDataDto.getUserName();
    }
}


테스트 의도된 효과



이제 Spring Boot 애플리케이션을 실행하고 테스트를 시작하겠습니다!
Postman을 사용하여 user1을 사용하여/saveUserName 끝점에 POST합니다.




이제 별도의 Chrome 시크릿 창을 사용하여 user2로/getUserName 엔드포인트에서 GET을 시도합니다. 아무것도 보여주지 않아, 좋아!




세션 범위 빈을 싱글톤으로 덮어쓰기



이제 Autowired 빈을 대체할 새 엔드포인트/saveUserNameReplaceInstance로 의도적으로 문제를 일으킬 수 있습니다.

    @PostMapping("/saveUserNameReplaceInstance")
    public void saveUserNameReplaceInstance(@RequestBody String userName){
        UserDataDto userDataDto = new UserDataDto();
        userDataDto.setUserName(userName);
        this.userDataDto = userDataDto;
    }


이번에는 user1과 함께 새 끝점을 사용하여 Postman을 통해 동일한 내용을 보냅니다.


그리고 user2가 있는 Chrome에서 이제 user1의 데이터를 볼 수 있습니다!



안 돼!! 여기서 큰 문제는 기본적으로 SessionScope 빈을 모든 인스턴스 간에 공유되는 싱글톤으로 바꿨다는 것입니다!

생성자 주입



이제 생성자 주입이 이것을 방지하는 방법은 무엇입니까? Intellij는 @Autowired 주석을 클릭하고 창에서 Alt-Enter를 누르고 생성자 생성을 클릭하면 Autowired 코드를 자동으로 리팩터링합니다.



그러나 편의를 위해 다른 수업에서 이것을 했습니다.
ConstructorInjectionController.java:

@RestController
public class ConstructorInjectionController {
    final
    UserDataDto userDataDto;

    public ConstructorInjectionController(UserDataDto userDataDto) {
        this.userDataDto = userDataDto;
    }

    @PostMapping("/saveUserNameCi")
    public void saveUserName(@RequestBody String userName){
        userDataDto.setUserName(userName);
    }

    @PostMapping("/saveUserNameReplaceInstanceCi")
    public void saveUserNameReplaceInstance(@RequestBody String userName){
        UserDataDto userDataDto = new UserDataDto();
        userDataDto.setUserName(userName);
        //this.userDataDto = userDataDto;
    }

    @GetMapping("/getUserNameCi")
    public String getUserName(){
        return userDataDto.getUserName();
    }
}


saveUserNameReplaceInstanceCi 매핑이 포함된 줄의 주석을 제거하면 IDE에서 큰 실수를 방지할 수 있습니다. 이렇게 하면 컨트롤러 클래스를 인스턴스화한 후 주입된 필드를 최종적이고 변경할 수 없는 것으로 선언할 수 있습니다.



언제나처럼 코드가 켜져 있습니다Github.

좋은 웹페이지 즐겨찾기