728x90

 Spring Boot를 이용해서 개발을 진행하면 @Transactional이란 어노테이션을 이용하여 데이터의 영속성을 지키고자 한다. 제대로 알고 사용하는 것일까? 프록시조차 모르는 나는 Spring Boot의 의도에 대로 비즈니스 로직에만 신경을 쓰는 것이었을까? 오늘은 그저 사용 중이었던 @Transactional에 대한 내 생각을 서술한 글을 쓴다.

@Transactional을 제대로 이해하려면 유튜브 뉴렉처 강의가 직관적이었다. Spring 자체를 이용해본 적이 없기 때문에 Bean을 xml에 직접적으로, 프록시를 직접적으로 설정해서 AOP를 구현하는 것을 본 것이 이해가 잘 가는 부분이었다.

https://www.youtube.com/watch?v=y2JkXjOocZ4&t=2s

문제는 그 다음이다. Spring Boot는 AspectJ 라이브러리를 이용한 것이 아니라, 컴파일 단계에 AOP를 구현한 것이라 Spring Aop 라이브러리는 프록시를 이용하였기에 Self-invocation이란 문제가 발생한다.

해당 문제는 자기 자신의 메소드를 호출할 때 AOP가 제대로 적용되지 않는 문제가 발생한다.

A 메소드에서 @Tranascational이 붙은 B를 호출하면 @Transcational이 적용이 안 된다. 하지만 A가 @Transcaional이 붙어 있으면 제대로 적용된다.

 해당 문제를 보면서 서비스를 하나 더 생성하여 해당 문제를 해결하고자 하였는데, 생각해보니 @Transcaional이 붙은 B는 insert 만 실행하는 코드가 하나만 있다. 그렇기에 해당 메소드에는 어노테이션이 필요없겠다는 생각이 들었다. Rollback할 요소가 없기 때문이다. Insert가 실패하면 어차피 데이터가 삽입되지 않기 때문이다.

문제는 반대로 A에서 @Transcational을 붙여서 해결할 수도 있는데 이때 DB Pool을 잡아 먹는 것인지는 더 알아볼만한 요소라 찾고 이후 글을 하나 더 작성하려고 한다.

반응형
728x90

타임리프를 사용하다보면 자바 컨트롤러에서 던져주는 model 객체 내 값들을 JS에서 쓰고 싶은 욕구가 생긴다.

왜냐하면 Js에서 어떤 작업을 해야 하는데 필요한 값들이 페이지 내에 존재하는 경우가 많고, 이를 HTML 태그에서 갈무리를 해서 써야 한다. 그러면 document나 Jquery를 이용해서 HTML 내에 값을 가져와서 사용한다.

HTML 내 태그에 값을 숨겨 놓고 그것을 가져다 쓰는 작업이 꽤나 귀찮다. 

애초에 JS 변수 내에 값이 있으면 해결되는 문제가 아닐까?

그렇게 하는 방법이 있다.

package com.example.kg;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class KgController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("Hello", "hello");
        return "/kg";
    }
}

그냥 단순한 예제이니 model에 넣은 값만 확인하자.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
    <div th:text="${Hello}"></div>
</body>
<script th:inline="javascript" type="text/javascript">
    const hello = [[${Hello}]];
</script>
<script  type="text/javascript" th:src="@{/kg.js}"></script>
</html>

위처럼 inline에 넣어두면 된다.

여기서 th:inline을 사용하면 js 변수 내에 [[${ }]]로 가능하다.
이게 왜 좋냐면 th:inline을 사용하지 않으면 [[${}]] 양 옆에 /* */를 사용해야 한다.
그리고 외부 Js에서는 이렇게 사용할 수가 없다. 
그런 이유는 html을 렌더링? 서버에서 템플릿 작업을 해주면서 템플릿 언어에 해당하는 문법을 보고 작업을 해준다.

그렇기 때문에 html에 외부 js 파일이 내장되어 있지 않으니 정상적으로 변수에 값이 들어가지 않는 것이다.

그리고 th:inline이 없으면 템플릿 엔진이 이를 작업해주지 않는 것 같다.

? 그런데 /* */는 로 해주면 어떻게 읽어서 작업해주는건지 신기 하다.

중요한건 th:inline을 하고 /* */ 없이 JS에 값을 쓸 수 있다는 사실이다.

반응형
728x90

https://koolreview.tistory.com/127

 

Spring Boot Security 5 - Oauth2.0 구글 로그인 (HTTP BASIC 탐방) - 1

단순하게 Spring Boot Security 5에 대해 코드만 작성하는 글이 아니라, 이해를 위한 글을 작성하기 노력하고 있습니다. Oauth2.0을 알아보기 전에 기본인 HTTP Basic 로그인에 대해 알아보겠습니다. 1. 종속

koolreview.tistory.com

이전 글에서 HTTP BASIC 로그인에 대해 알아보았습니다. 이번에는 HTTP OAUTH2.0로그인에 대해 알아보겠습니다.

1. 라이브러리 추가

implementation group: 'org.springframework.boot', name: 'spring-boot-starter-oauth2-client', version: '2.7.1'

 

반응형
728x90

단순하게 Spring Boot Security 5에 대해 코드만 작성하는 글이 아니라, 이해를 위한 글을 작성하기 노력하고 있습니다.

Oauth2.0을 알아보기 전에 기본인  HTTP Basic 로그인에 대해 알아보겠습니다.

1. 종속성 추가하기

먼저 종속성을 추가해줍니다. 기존 프로젝트에 시큐리티만 추가하면 됩니다.
lombok은 코드를 간편하게 쓰기 위해 추가했습니다.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
}

1-2. 시큐리티 추가만으로 생기는 보안

이렇게 라이브러리를 단순히 추가함으로 왼쪽(추가 전)에 되던 통신이 오른쪽처럼 401로 막히게 됩니다.
정확하지는 않지만, 실제 코드 실행 시 막히는 현상을 파악했습니다.

TIP ^V^

401은 사용자가 로그인이 되어 있지 않은 상태 => 인증 실패 (개발자는 앱 설계 시 자격 증명이 누락되거나 잘못되었을 때 )
403은 사용자가 권한이 없는 상태 => 권한 부여 실패, 호출자를 식별하였지만, 이용 권리 없음을 나타냄

2. 스프링 시큐리티 인증 절차 설명

조금 복잡하게 되어있지만 하이라이트만 확인해보면 좋겠습니다. Oauth2.0을 사용하면 위에 정의된 아키텍쳐에 들어가는 구성 요소의 이름이 조금 바뀝니다.

1. 인증 필터 (Authentication Filter): 인증 요청을 인증 관리자에게 위임하고 응답을 바탕으로 보안 컨텍스트를 구성함
2. 인증 관리자 (Authentication Manager): 인증 공급자를 이용하여 인증을 처리한다.
3. 인증  공급자 (Authentication Provider):
     -  사용자 관리 책임을 구현하는 사용자 세부 정보 서비스를 인증 논리에 이용한다.
     -  암호 관리를 구현하는 암호 인코더를 인증 논리에 이용한다.
4. 유저 세부 정보 서비스 (User Details Service)
5. 보안 컨텍스트(Security Context): 인증 프로세스 후 인증 데이터를 유지한다.

3. 자동으로 구성되는 빈

1. UserDetailsService
2. PasswordEncoder
인증 공급자 (Authentication Provider)는 위 두 개의 빈을 이용하여 1) 사용차를 찾고, 2) 암호를 확인합니다.

사용자 정보 관리하는 UserDetailsService

스프링 시큐리티에서 기본적으로 제공하는 기본 구현을 이용했지만, 우리는 입맛에 맞게 바꿔서 사용한다.

1차 정리

1. 기본적으로 UserDetailsService와 PasswordEncoder가 Basic 인증 흐름에 꼭 필요하다는 사실을 인지해야 한다.
2. Basic 인증에서는 클라이언트가 사용자 이름과 암호를 HTTP Authorization 헤더를 통해 보내기만 하면 된다.
=> 헤더 값에 접두사 Basic을 붙이고 그 뒤에 :으로 구분된 사용자 이름과 암호가 포함된 문자열을 Base64 인코딩하여 붙인다.

4. 문제🙄: 기존 websecurityconfigureradapter deprecated

기존에 사용하던 WebSecurityConfigureradapter가 Deprecated 되었다. 거의 대부분 인터넷이나 서적을 찾아보면 이를 사용하는 코드가 적혀있지만, 현재 기준으로 Deprecated가 되어 다른 코드를 사용해야 한다. 자세한 내용은 아래 하단 링크를 통해 확인하면 된다.

https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter

 

Spring Security without the WebSecurityConfigurerAdapter

<p>In Spring Security 5.7.0-M2 we <a href="https://github.com/spring-projects/spring-security/issues/10822">deprecated</a> the <code>WebSecurityConfigurerAdapter</code>, as we encourage users to move towards a component-based security configuration.</p> <p

spring.io

- 고민해야하는 것, Bean? 메소드 재정의?

 시큐리티를 이용할 때 두 가지 방법을 사용할 수 있다. 둘 중 하나만으로 구현해야지 일관성이 있고, 코드가 헷갈리지 않는다. 

@EnableSecrurity 어노테이션

EnableWebSecurity Interface

클래스 밖에 Configuration 어노테이션을 붙일 필요가 없다. 이미 EnableWebSecurity 인터페이스 안에 우리가 사용하고자 하는 어노테이션이 있으니 Configuration 어노테이션을 붙일 필요없다.

6. HttpBasic 활성화

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http.httpBasic();
        http.authorizeRequests()
            .anyRequest().authenticated();

        return http.build();
    }

https://docs.spring.io/spring-security/site/docs/current/api/

 

Overview (spring-security-docs 5.7.3 API)

Authentication processing mechanisms, which respond to the submission of authentication credentials using various protocols (eg BASIC, CAS, form login etc).

docs.spring.io

7. User Entity 생성

로그인 구현하기 위해 DB상에 user 테이블을 생성합니다. 생성하는 이유는 유저의 정보를 DB 상에 저장하기 위함입니다.
만약에 이를 이용하지 않는다면 메모리에 저장해두면 됩니다. 이 방법은 따로 이 글에선 알아보지 않겠습니다.

import javax.persistence.*;
import java.util.List;

@Entity
public class User {

    @Id
    @GeneratedValue
    private Integer id;

    private String username;
    private String password;

    @OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
    private List<Authority> authorities;

    public List<Authority> getAuthorities() {
        return authorities;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

}

8.UserRepository 생성

FindUserByUsername 메소드를 하나 작성해줍니다. 작성 이유는 Username으로 값을 조회하기 위함입니다.

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
    Optional<User> findUserByUsername(String username);
}

9. CustomUserDetail 생성

이 클래스를 생성하는 이유는 Security Context에 저장되는 인증 객체를 만들기 위함입니다. 
이 클래스는 추후 컨트롤러에서 파라미터로 받아오는 객체의 클래스입니다.

먼저  코드를 살펴보면 UserDetails를 구현합니다. 코드를 확인해봅시다.

import com.example.demo.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.stream.Collectors;

public class CustomUserDetails implements UserDetails {

    private final User user;

    public CustomUserDetails(User user) {
        this.user = user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return user.getAuthorities().stream().map(a -> new SimpleGrantedAuthority(a.getName())).collect(Collectors.toList());
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    public final User getUser() {
        return user;
    }

}

실제 코드를 확인해보면 위와 같은 주석이 있습니다.

파파고 번역

Authentication 객체에 캡슐화가 된다는 부분이 중요합니다. 왜냐하면 나중에 Security Context에 넣을 때 Authentication 객체만 들어가기 때문입니다. 그래서 우리는 User 객체를 가지고 있지만 Authentication으로 캡슐화가 될 수 있는 UserDetails를 구현합니다.

https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/core/userdetails/UserDetails.html

권한, 비밀번호, 아이디 등에 관한 메소드를 정의하고 있습니다.

authentication 인터페이스는 아래 링크를 통해 확인할 수 있습니다.

https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/authentication/package-summary.html

10. JpaUserService 생성

Jpa라고 붙은 이유는 Data Jpa 라이브러리를 통해서 디비 상에 값이 존재하는지 확인하기 때문입니다.
또한 이 클래스의 loadUserByUsername메소드는 위에서 정의한 CustomUserDeatils 객체를 반환합니다.

import com.example.demo.User;
import com.example.demo.UserRepository;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.function.Supplier;

@Service
public class JpaUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    public JpaUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Supplier<UsernameNotFoundException> s = () -> new UsernameNotFoundException("Problem during authentication!");

        User user = userRepository.findUserByUsername(username).orElseThrow(s);

        return new CustomUserDetails(user);
    }

}

11. AuthenticationProviderService 생성

상단 부분을 다시 읽어보면, Provider가 무슨 일을 하는 지 알 수 있습니다. 다음과 같습니다.

3. 인증  공급자 (Authentication Provider):
     -  사용자 관리 책임을 구현하는 사용자 세부 정보 서비스를 인증 논리에 이용한다.
     -  암호 관리를 구현하는 암호 인코더를 인증 논리에 이용한다.

결국 인증한다는 사실만 기억하세요!

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class AuthenticationProviderService implements AuthenticationProvider {

    private final JpaUserDetailsService userDetailsService;

    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    public AuthenticationProviderService(JpaUserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.userDetailsService = userDetailsService;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        CustomUserDetails user = userDetailsService.loadUserByUsername(username);

        return checkPassword(user, password, bCryptPasswordEncoder);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }

    public Authentication checkPassword(CustomUserDetails user, String rawPassword, PasswordEncoder encoder) {
        if(encoder.matches(rawPassword, user.getPassword())) {
            return new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getAuthorities());
        } else {
            throw new BadCredentialsException("Bad credentials");
        }
    }
}

12. 설정

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class ProjectConfig {

    private final AuthenticationProviderService authenticationProvider;

    public ProjectConfig(AuthenticationProviderService authenticationProvider) {
        this.authenticationProvider = authenticationProvider;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http.httpBasic();

        http.authorizeRequests()
            .anyRequest().authenticated();

        http.authenticationProvider(authenticationProvider);

        http.formLogin()
                .defaultSuccessUrl("/main", true);

        return http.build();
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

AuthenticationProvder도 설정해주시면 됩니다.

이정도면 HTTP BASIC은 성공입니다.

다음 글에서는 Oauth2.0 로그인을 알아보겠습니다.

반응형
728x90

 Oauth2.0에 대해 작년에 아무런 지식없이 인터넷을 방황하며 코드를 작성했었다. 당시 Nestjs로 작성했었는데 이제는 다른 프레임워크로 작성해야했다. 이번에는 스프링 부트로 oauth를 이용하려고 하고 라이브러리를 찾아본 결과 시큐리티 5에 있는 oauth2.0-client를 이용했다.

어려운 부분은 크게 2가지였다.

1. 트위치 Oauth에 적용하기
2. Oauth2.0에 대한 지식

해결했던 방법

1. 트위치 Oauth2.o에 적용을 해결했던 방법

 카카오와 네이버도 스프링 Oauth2.0 라이브러리에서 제공하는 기본 제공자가 아니다. 이 두개의 적용 방법과 트위치 개발자 페이지와 검색을 통해서 해결했다.

spring.security.oauth2.client.registration.twitch.client-id = 
spring.security.oauth2.client.registration.twitch.client-secret = 
spring.security.oauth2.client.registration.twitch.client-name=Twitch
spring.security.oauth2.client.registration.twitch.authorization-grant-type = authorization_code
spring.security.oauth2.client.registration.twitch.client-authentication-method=post
spring.security.oauth2.client.registration.twitch.redirect-uri = http://localhost:8080/login/oauth2/code/twitch
spring.security.oauth2.client.registration.twitch.response_type=token
spring.security.oauth2.client.registration.twitch.scope=user:read:email

# Twitch Provider 등록
spring.security.oauth2.client.provider.twitch.authorization-uri=https://id.twitch.tv/oauth2/authorize
spring.security.oauth2.client.provider.twitch.token-uri=https://id.twitch.tv/oauth2/token
spring.security.oauth2.client.provider.twitch.user-info-uri=https://id.twitch.tv/oauth2/userinfo
spring.security.oauth2.client.provider.twitch.user-name-attribute=token

 2. 기본 지식의 부족

 code를 받아서 token를 받아오는 방식이 있다. => 권한 코드

브라우저 기반 클라이언트 사이드 앱 암묵적 허가 => 액세스 토큰 발급

총 4가지 방식 중에서 2가지를 헷갈렸었다.

권한 코드는 백엔드에서 처리하는 방식에 적합하고 위 설정 파일에서spring.security.oauth2.client.registration.twitch.authorization-grant-type = authorization_code 라고 적은 부분도 어떤 방식을 이용하겠다고 설정한 것이다.

방법의 종류와 방법의 진행 방식을 책을 통해서 학습 후 진행하니까 수월해졌다.
 

반응형
728x90

https://susuhan.notion.site/Spring-DI-6113d9eefba446c99413d1323abe9276

- 이쁘게 보기 -

 

Spring DI 방법론

글의 목적 필드 의존성 주입과 setter 의존성 주입, 생성자 의존성 주입에 관한 차이를 알아가기

susuhan.notion.site

Spring DI 방법론

A. 문제의 발단

코드 컨밴션이 존재하지 않아, 프로젝트 내에 코드의 일관성이 일치하지 않는 현상이 발생하였다.
그 중 하나로 의존성 주입하는 방식이 거론되어, 의존성 주입 방식의 차이와 실제 오류가 발생하는지를 알아보게 되었다.

의존성 주입 방식에는 크게 3가지가 존재한다.

  1. 필드 의존성
  2. 생성자 의존성
  3. setter 의존성

1. 필드 의존성 주입 (Field Injection)

public class A {
    @Autowired
    private AService aService;
}

2. setter 의존성 주입 (Setter based Injection)

public class A {
    private AService aService;

    @Autowired
    public void setAService(AService aService){
        this.aService = aService;
    }
}

3. 생성자 의존성 주입 (Constructor based Injection)

// 의존성 주입 대상이 한 개일 경우, 생성자에 @Autowired 어노테이션을 붙이지 않아도 된다.
public class A {
    private final AService aService;

    public A(AService aService){
        this.aService = aService;
    }
}

// 의존성 주입 대상이 2개이상인 경우, 생성자에 @Autowired 어노테이션을 붙여야 한다.
public class A {
    private final AService aService;
    private final BService bService;

    @Autowired
    public A(AService aService, BSerivce bService){
        this.aService = aService;
        this.bService = vService;
    }
}

B. 차이점: Bean 주입 순서

1. 필드 의존성 주입

  1. 주입 받으려는 빈의 생성자를 호출하여, 빈을 찾거나 빈 팩토리에 등록한다.
  2. 생성자 인자를 사용하는 빈을 찾거나 만든다.
  3. 필드에 주입한다.

2. Setter 의존성 주입

  1. 주입받으려는 빈의 생성자를 호출하여, 빈을찾거나 빈 팩토리에 등록한다.
  2. 생성자의 인자를 사용하는 빈을 찾거나 만든다.
  3. 주입하려는 빈 객체의 수정자(setter)를 호출하여 주입한다.

🟥 필드, Setter 의존성 방식은 런타임에서 의존성을 주입하여, 의존성을 주입하지 않아도 객체가 생성될 수 있다.

3. 생성자 의존성 주입

  1. 생성자의 인자에 사용되는 빈을 찾거나 빈 팩토리에서 만든다.
  2. 찾은 파라미터 빈으로 주입하려는 생성자를 호출한다.

🟥 객체가 생성되는 시점에 빈을 주입하여, 의존성 주입되지 않아 발생할 수 있는 NullPointerException을 방지한다.

C. 순환 참조

A에서 B를 호출하고, B에서 A를 호출하는 관계를 순환 참조라 한다. 이런 경우가 발생하는 일은 한 프로젝트 내에 관리해야하는 클래스가 많아지는 경우 실수로 발생하게 된다.

@Service
public class Aservice {
    @Autowired
    private Bservice bservice;
}

@Service
public class Bservice {
    @Autowired
    private Aservice aservice;
}
  1. 필드와 Setter 방식에서는 빈이 생성된 후에 참조를 하기 때문에 애플리케이션이 아무런 오류나 경고없이 구동된다. 실제 코드가 호출될 때까지 알 수 없다는 의미이다.
  2. 생성자를 통해 의존성 주입할 경우 BeanCurrentlyInCreationException이 발생한다. 순환참조뿐만 아니라 의존 관계에 내용을 외부로 노출 시킴으로 애플리케이션이 실행하는 시점에서 오류를 확인할 수 있다.

D. 코드의 깔끔함

1. 필드 의존성 주입

@Service
public class Aservice {
    @Autowired
    private Bservice bservice;
}

장점: 한 필드에 어노테이션을 붙임으로써 생성자를 만들지 않아 코드의 수가 줄어든다.

단점: 필드가 많아질 경우 한 줄씩 어노테이션이 추가적으로 붙게 된다.

2.생성자 주입과 Loombok

매번 생성자를 만들어서 주입하는 것이 코드의 깔끔함을 저해시킨다. 하지만 Lombok을 이용한다면 생성자를 자동으로 만들어준다.

@RequiredArgsConstructor
@Service
public class Aservice {
    private final Bservice bservice;
}

RequiredArgsConstructor는 final로 선언된 필드를 가지고 생성자를 만들어준다.

E. final 사용

필드나 setter 방식을 이용시에 final 키워드를 사용할 수 없다.

final 키워드 사용 시 런타임 상에서 의존성이 변경되는 가능성을 제거해준다.

F. 실제 코드 실행 시 발생 상황 관찰하기

@Service
public class A {

    @Autowired
    B b;

}

@Service
public class B {

    @Autowired
    A a;
}

@Service
public class A {

    private final B b;

    public A(B b) {
        this.b = b;
    }
}
@Service
public class B {

    private final A a;

    public B(A a) {
        this.a = a;
    }
}

오류가 발생하지 않았다.

앞에서 한 말이 거짓말일까?

2.6.0 버전 특징

기본적으로 순환참조가 발생하지 않는다.

spring.main.allow-circular-references=true

필드 주입 = 속성 추가후 발생 시 순환참조 오류가 발생하지 않고 그대로 동작한다.

결론

  1. 2.6.0 버전 이후부터는 순환참조 오류를 미리 잡아주게 되었다. 순환참조 상의 오류때문에 필드, set방식을 안 쓸 이유는 없다.
  2. 생성자 의존성 주입 방식의 이점은 런타임 상에서 bean 객체가 변경되지 않는 것을 선호한다는 이유만 남은 것 같다. 하지만 런타임 상에서 bean 객체를 의도적으로 변경하는 사람이 존재할까? 하지만 이런 생각은 이전에 순환참조를 안 만들면 된다는 생각과 동일하기 때문에, 간단하게 막을 수 있다면 막아두는 것이 좋다고 생각한다.
  3. 코드의 깔끔함에 있어서 사람들의 견해가 다르다고 생각한다.
    누군가는 어노테이션을 사용함에 있어 깔끔하다고 생각하고, 누군가는 어노테이션이 없는 것에 깔끔함을 느낄 수 있다.
    만약에 생성자 주입이 직접 설정해야하는 번거로움이 걱정된다면 final이 있는 필드만 생성자를 만들어주는 @RequiredArgsConstructor를 사용하면 될 것 같다.
  4. set방식도 구현하는 방식만 다르지 필드 의존성 주입과의 단점과 동일하여 위에 논한 것으로 충분하다고 생각한다.
반응형
728x90

Home Screen에 띄우는 위젯을 만들려고 한다.

버튼을 클릭하면 클릭하는데로 위젯의 화면이 바뀌게 하려고 하는데 Activity에서는 view객체를 findViewById()함수로 Button에 캐스팅하고 getText()라거나 setText()를 써서 가능할 것이라 생각했는데 안된다.


삽질을 하다보니 알게되었는데 위젯의 기능을 정의하는 class가 액티비티를 extends를 하고 있지 않아 findViewById()가 사용이 불가능하다.

그럼 어찌할까 또 삽지를 하다보니 알게된 사실

Button Click Event 처리를 setOnClickPendingIntent가 할 수 있다는 것

구글바다에 검색해보면 PendingIntent함수를 사용하여 새로운 액티비티창을 띄워주는 간단한 예제를 발견할 수 있다. 그게 끝이다

따라 해보았는 데 결국 그것만 하다 끝났다.....


젠장...


그래서 어찌 하리 왜 내가 생각하는게 안 될까 분명 될텐데 생각하다 결국 apk 디컴파일을 했다.

찾아보니 위에서 했던 setOnClickPendingIntent함수로 하는 것이 맞았고 그 처리를 get~~Service가 하는 것으로 보았으나

내가 Intent에 관한 내용이나 PendingIntent에 대한 내용을 정확하게 몰라서 방법은 찾았다하나 적용이 불가능하다..


시간을 두고 찾아보고 해봐야할듯... 성공하면 여기다 다시 코드를  올려볼 생각이다.


반응형

+ Recent posts