반응형

전략 패턴이란?

하나의 메시지와 책임을 정의하고, 이를 수행할 수 있는 다양한 전략을 만든 후, 다형성을 통해 전략을 선택해 구현을 실행하는 패턴.

전략 패턴은 GoF의 디자인패턴 중에서 행위 패턴 중에 하나에 해당함

전략 패턴 UML

Spring에서 사용하는 전략패턴 예시

package com.ji.behavioral_patterns.strategy.java;

import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.cache.CacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.transaction.PlatformTransactionManager;

public class StrategyInSpring {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext();
        ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext();
        ApplicationContext applicationContext2 = new AnnotationConfigApplicationContext();

        BeanDefinitionParser parser;

        PlatformTransactionManager platformTransactionManager;

        CacheManager cacheManager;

    }
}

자바에서 사용하는 전략 패턴

https://stackoverflow.com/questions/1673841/examples-of-gof-design-patterns-in-javas-core-libraries/2707195#2707195

 

Examples of GoF Design Patterns in Java's core libraries

I am learning GoF Java Design Patterns and I want to see some real life examples of them. What are some good examples of these Design Patterns in Java's core libraries?

stackoverflow.com

 

배경

전략 패턴의 핵심은 Context를 기반으로 해당 개발자가 필요에 따라서 원하는 기능을 다형성에 의해서 원하는 전략을 선택한 것에 있음

현재 코드에서 Context가 로그 조회 기능이고 요청에 따라서 구현체가 선택되어 기능이 동작하게 구현하면 되기 때문에 전략 패턴 적용하기에 용이하다고 판단함

전략 패턴 적용 전 예시

전략 패턴 적용 전 레거시 코드

각 API 별로 각 요청에 해당하는 메서드를 가져와서 return하는 구조였음

@ApiOperation(value = "계정 관련 로그")
@PostMapping("/list/account")
public ResultVO getAccountLog(@Valid @RequestBody SearchListVO vo) {
    log.info("[/list/account] :" + vo.toString());
    SearchResultVO result;

    try {

        result = logService.getAccountLogList(vo);

    } catch (Exception e) {
        e.printStackTrace();
        return APIUtil.resResult(ErrorCode.SERVER_ERR.getErrorCode(), "계정 로그 조회가 실패되었습니다.", null);
    }

    return APIUtil.resResult(ErrorCode.SUCCESS.getErrorCode(), "계정 로그 조회가 완료되었습니다.", result);

}

@ApiOperation(value = "파일 보호 이벤트 로그")
@PostMapping("/list/file-protect")
public ResultVO getFileProtect(@Valid @RequestBody SearchListVO vo) {
    log.info("[/list/file-protect] :" + vo.toString());
    SearchResultVO result;

    try {
        result = logService.getClientFileProtectLog(vo);

    } catch (Exception e) {
        e.printStackTrace();
        return APIUtil.resResult(ErrorCode.SERVER_ERR.getErrorCode(), "파일 보호 이벤트 로그 조회가 실패되었습니다.", null);
    }

    return APIUtil.resResult(ErrorCode.SUCCESS.getErrorCode(), "파일 보호 이벤트 로그 조회가 완료되었습니다..", result);

}

@ApiOperation(value = "서버 상태 로그")
@PostMapping("/list/server-status")
public ResultVO getServerStatusLog(@Valid @RequestBody SearchListVO vo) {
    log.info("[/list/server-status] :" + vo.toString());
    SearchResultVO result;

    try {

        result = logService.getServerStatusLog(vo);

    } catch (Exception e) {
        e.printStackTrace();
        return APIUtil.resResult(ErrorCode.SERVER_ERR.getErrorCode(), "서버 상태 로그 조회가 실패되었습니다.", null);
    }

    return APIUtil.resResult(ErrorCode.SUCCESS.getErrorCode(), "서버 상태 로그 조회가 완료되었습니다..", result);

}
public interface LogService {

	public SearchResultVO getAccountLogList(SearchListVO vo) throws Exception;

	public SearchResultVO getClientFileProtectLog(SearchListVO vo) throws Exception;

	public SearchResultVO getServerStatusLog(SearchListVO vo) throws Exception;

}
@Override
public SearchResultVO getAccountLogList(SearchListVO vo) throws Exception {
	List<AccountLogDto> result = new ArrayList<AccountLogDto>();
	
	SearchResultVO daoVO = logDao.getAccoutLogList(vo);
	
	//date 포맷 변경
	List<AccountLogDto> searchedList = (List<AccountLogDto>) daoVO.getSearchedList();
	for(AccountLogDto dto : searchedList) {
		dto.set_logTime(DateUtils.parseDateFormatHHMMSSss(dto.getLogTime()));
		result.add(dto);
	}
	
	return new SearchResultVO(daoVO.getTotal(), result);
}

 

위 구조의 레거시 코드를 전략패턴을 적용하여 리팩터링 하였음.

전략 패턴 적용  예시

다음 UML과 같의 LogStrategyService라는 strategy 클래스를 선언하였고, 각 로그 유형에 해당하는 cocreate class가 동일한 인터페이스를 상속하여 구현하고 있음

전략 패턴 적용 후 UML

코드를 자세이 보도록 하자

strategy 클래스에 해당하는 LogStrategyService 인터페이스

public interface LogStrategyService {

    SearchResultVO getLogList(SearchListVO searchListVO) throws Exception;

    SearchResultVO getLogList(SearchListVO searchListVO, String lang) throws Exception;

    boolean isTarget(String logType);

}

ConcreateStrategy1~ 클래스에 해당하는 AccountLogServiceImpl 클래스 구현체

package com.smt.service.log;

import com.smt.dao.log.LogDao;
import com.smt.dto.AccountLogDto;
import com.smt.util.DateUtils;
import com.smt.util.enums.LogType;
import com.smt.vo.common.SearchListVO;
import com.smt.vo.common.SearchResultVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
@RequiredArgsConstructor
public class AccountLogServiceImpl implements LogStrategyService {

   private final LogDao logDao;

    @Override
    public SearchResultVO getLogList(SearchListVO searchListVO) throws Exception {
        List<AccountLogDto> result = new ArrayList<AccountLogDto>();

        SearchResultVO daoVO = logDao.getAccoutLogList(searchListVO);

        //date 포맷 변경
        List<AccountLogDto> searchedList = (List<AccountLogDto>) daoVO.getSearchedList();
        for (AccountLogDto dto : searchedList) {
            dto.set_logTime(DateUtils.parseDateFormatHHMMSSss(dto.getLogTime()));
            result.add(dto);
        }

        return new SearchResultVO(daoVO.getTotal(), result);
    }

    @Override
    public SearchResultVO getLogList(SearchListVO searchListVO, String lang) throws Exception {
        return null;
    }

    @Override
    public boolean isTarget(String logTypeUrl) {
        return logTypeUrl.equals(LogType.ACCOUNT.getUrl());
    }
}

Context에 해당하는 Controller 클래스

또한 다음 Controller에서 @PathVariable를 활용해서 기존에 URL에 요청하는 Controller 메서드가 동작하는 구조였지만, 하나는 Controller 메서드에서 로그 type에 해당하는 상속 클래스를 Spring에 singleton으로 주입된 해당 클래스를 찾아서 초기화 하도록 구현함에 따라서 코드를 간략하게 리팩토링 하였음.

참고로, 현재는 List를 활용해서 해당 하위 클래스를 접근하는 방식이지만, 현재는 Map과 Spring에서 제공하는 @Service("account") 속성을 활용해서 클래스를 더욱 빠른 속도로 찾을 수 있게 개선해 놓은 상태임

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/log")
@Validated
@Api(value = "LogController", description = "로그 관련(참고문서 : 구글 공유 문서 독스토리 서버 ReturnCode)")
public class LogController {

  private final LogService logService;

  private final List<LogStrategyService> logStrategyServices;

  @ApiOperation(value = "통합 로그 조회 API-정책 설정 로그 제외")
  @PostMapping("/list/{type}")
  public ResultVO getLogStrategy(@PathVariable("type") String logType, @Valid @RequestBody SearchListVO searchListVO) throws Exception {
      log.info("[/list/{type}] :" + searchListVO.toString() + "type : " + logType);

      SearchResultVO searchResult =  logStrategyServices.stream()
                                                                .filter(logService -> logService.isTarget(logType))
                                                                .findFirst()
                                                                .get()
                                                                .getLogList(searchListVO);

      return APIUtil.resResult(ErrorCode.SUCCESS.getErrorCode(), "통합 로그 조회가 완료되었습니다.", searchResult);
  }

결론

만약 PathVariable을 이용하고 전략 패턴을 적용하지 않았다면 path Type에 따라서 if문을 통해서 해당 메서드를 호출되었을 되었을 것임

하지만, 전략 패턴을 적용함으로써 if문이 아닌 반복문(다형성)을 이용하게됨

다른 유형의 로그 유형을 구현(추가)해야할 경우 Strategy 인터페이스를 상속 받아서 ConcreateStrategy~ 클래스를 구현하면됨

즉, 전략패턴을 적용함에 따라서 SOLID의 원칙 중 OCP(Open-Closed Principle)를 준수하게 됨

(확장에 열려있고, 변경에는 닫힘)

728x90
반응형

도커 설정 파일 생성

해당패키지에 도커 설정 파일(Dockerfile)

# Start with a base image containing Java runtime
FROM openjdk:8-jdk-alpine

# Add Author info
LABEL maintainer="jshag1990@gmail.com"

# Make port 8080 available to the world outside this container
# 해당 패키지는 8080포트로 외부로 열겠다라는 의미
EXPOSE 8080

# The application's jar file
ARG JAR_FILE=build/libs/DockerStudy-0.0.1-SNAPSHOT.jar 

# Add the application's jar to the container
ADD ${JAR_FILE} to-do-springboot.jar

# Run the jar file
ENTRYPOINT ["java","-jar","/to-do-springboot.jar"]

윈도우에서는 powershell을 이용

도커 이미지 생성

docker build --tag docker-study:0.1 .

도커 이미지 실행

docker run -p 18080:8080 docker-study:0.1
728x90
반응형

1. 스프링 시큐리티란?

스프링 시큐리티는 스프링 기반의 어플리케이션의 보안(인증과 권한)을 담당하는 프레임워크이다. 만약 스프링시큐리티를 사용하지 않았다면, 자체적으로 세션을 체크하고 redirect 등을 해야할 것이다. 스프링 시큐리티는 보안과 관련해서 체계적으로 많은 옵션들로 이를 지원해준다. spring security는 filter 기반으로 동작하기 때문에 spring MVC 와 분리되어 관리 및 동작한다. 참고로 security 3.2부터는 XML로 설정하지 않고도 자바 bean 설정으로 간단하게 설정할 수 있도록 지원한다.

여기서는 현재기준 최신버전인 스프링 4.3.18과 시큐리티 4.2.7을 가지고 진행했다.

보안관련 용어부터 정리하자

  • 접근 주체(Principal) : 보호된 대상에 접근하는 유저
  • 인증(Authenticate) : 현재 유저가 누구인지 확인(ex. 로그인)
    • 애플리케이션의 작업을 수행할 수 있는 주체임을 증명
  • 인가(Authorize) : 현재 유저가 어떤 서비스, 페이지에 접근할 수 있는 권한이 있는지 검사
  • 권한 : 인증된 주체가 애플리케이션의 동작을 수행할 수 있도록 허락되있는지를 결정
    • 권한 승인이 필요한 부분으로 접근하려면 인증 과정을 통해 주체가 증명 되어야만 한다
    • 권한 부여에도 두가지 영역이 존재하는데 웹 요청 권한, 메소드 호출 및 도메인 인스턴스에 대한 접근 권한 부여

2. spring security 의 구조

2-1. 인증관련 architecture



출처: https://sjh836.tistory.com/165 [빨간색코딩]


3. 실습

1. pom.xml 에 depencency 추가 

<dependency>

<groupId>org.springframework.security</groupId>

<artifactId>spring-security-config</artifactId>

<version>3.2.5.RELEASE</version>

</dependency>


<dependency>

<groupId>org.springframework.security</groupId>

<artifactId>spring-security-core</artifactId>

<version>3.2.5.RELEASE</version>

</dependency>


<dependency>

<groupId>org.springframework.security</groupId>

<artifactId>spring-security-web</artifactId>

<version>3.2.5.RELEASE</version>

</dependency>


<dependency>

<groupId>org.springframework.security</groupId>

<artifactId>spring-security-taglibs</artifactId>

<version>3.2.4.RELEASE</version>

</dependency>

2. web.xml > context 추가 &  filter 작성

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>

/WEB-INF/spring/root-context.xml

/WEB-INF/spring/appServlet/security-context.xml

</param-value>

</context-param>           

<filter>

        <filter-name>springSecurityFilterChain</filter-name>

        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

    </filter>

    <filter-mapping>

        <filter-name>springSecurityFilterChain</filter-name>

        <url-pattern>/*</url-pattern>

    </filter-mapping>

3. security-context.xml 에 내용 작성 (로그인/로그아웃 상태 등의 응용 로직 추가)

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:security="http://www.springframework.org/schema/security"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/security

http://www.springframework.org/schema/security/spring-security-3.2.xsd">


<security:http auto-config="true">

<security:form-login login-page="/loginForm.html"/>

<security:intercept-url pattern="/login.html*" access="ROLE_USER"/>

<security:intercept-url pattern="/welcome.html*" access="ROLE_ADMIN"/>

</security:http>

<security:authentication-manager>

<security:authentication-provider>

<security:user-service>

<security:user name="user" password="123" authorities="ROLE_USER"/>

<security:user name="admin" password="123" authorities="ROLE_ADMIN,ROLE_USER"/>

</security:user-service>

</security:authentication-provider>

</security:authentication-manager>


</beans>



[출처]https://seouliotcenter.tistory.com/89?category=663840

728x90

+ Recent posts