교착 상태에 빠지지 않게 하기 위해 비관점 잠금 타임아웃을 설정할 수 있다. 

Map<String, Object> hints = new HashMap<>();
hints.put("javax.persistence.lock.timeout", 2000);
Order order = entityManager.find(Order.class, LockModeType.PESSIMISTIC_WRITE, hints);

 

@Lock(LockModeType.PESSIMISTIC_WRITE)
@QueryHints({
	@QueryHint(name="javax.persistence.lock.timeout", value="2000" )
})
@Query("select m from Member where m.id = :id")
Optional<Member> findByIdForUpdate(@Param("id") MemberId memberId);

 

조금이라도 정적인 자원들에 대해서 빨리 로딩하는 방법이 없을까 고민하고 있었는데  우연히 정적 리소스에 대한 cache와 compress를 보았다. 그래서 한 번 테스트 해볼까 한다. 

Cache 

application.json 에 아래 내용을 추가한다. (초단위 60x60x10 = 10시간) -> 테스트시간임

spring.resources.cache.period=36000

처음 호출 

처음 호출했을 경우 200과 함께 사이즈 시간을 볼 수있다. 

새로고침 을 누르고 다시 조회 보면 304 와 함께 max-age를 통해 캐쉬가 됨을 확인

사이즈와 시간이 줄어듬을 확인

처음 호출 이후에 지정된 시간까지 캐쉬를 통해 절약할 수 있음 알았다. 

자바소스에서는 WebMvcConfigurer 를 implements 하여 쓸 수 있다. 

registry.addResourceHandler("/**")
                .addResourceLocations("classpath:/static/")
                .setCachePeriod(36000)
        ;

Compress

압축을 해보자 . application.json 에 다음을 추가하자

server.compression.enabled=true
server.compression.mime-types=text/html,text/xml,text/plain,text/css,text/javascript,application/javascript
server.compression.min-response-size=2048

그리고 호출 

압축 안함
압축 이후에 시간 사이즈와 시간 비교이다 
gzip으로 압축됨을 표시

네트워크 환경에 따라서 속도는 다르게 나올 수 있다 하지만 사이즈가 엄청 크거나 로딩이 느리다면

고민해서 활용을 해보면 좋을 것 같다

@Bean 
UiConfiguration uiConfig() { 
	return UiConfigurationBuilder.builder() 
    .docExpansion(DocExpansion.LIST) // or DocExpansion.NONE or DocExpansion.FULL 
    .build(); 
}

source : http://springfox.github.io/springfox/docs/current/#springfox-swagger2-with-spring-mvc-and-spring-boot

 

Springfox Reference Documentation

The Springfox suite of java libraries are all about automating the generation of machine and human readable specifications for JSON APIs written using the spring family of projects. Springfox works by examining an application, once, at runtime to infer API

springfox.github.io

swagger (2.6.1)버전이 다른 경우 

@Bean
public UiConfiguration uiConfig() {
	return new UiConfiguration(
                null,
                "list",
                "alpha",
                "schema",
                UiConfiguration.Constants.DEFAULT_SUBMIT_METHODS,
                false,
                true,
                null
        );
}

 

서비스에 어노테이션으로 aop를 조정할 수 있다. 만약 서비스에서 로그인 관련 체크를 한다면 어노테이션을 선언하고 메서드에 어노테이션을 선언하는 것 만으로 aop 를 적용할 수 있다.

1. 인터페이스 선언 

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SecurityCheck {
}

타겟을 메서드에 준다

2. 체크할 포인트를 선언 

@Aspect
@Component
public class SecurityChecker {

  private static final Logger logger = LoggerFactory.getLogger(SecurityChecker.class);

  @Pointcut("@annotation(SecurityCheck)")
  public void checkMethodSecurity() {}

  @Around("checkMethodSecurity()")
  public Object checkSecurity (ProceedingJoinPoint joinPoint) throws Throwable {
    logger.debug("Checking method security...");
    // TODO Implement security check logics here
    Object result = joinPoint.proceed();
    return result;
  }
}

TODO 부분에 실제로 체크해야할 내용을 삽입한다.

3. 서비스 메서드에 적용 

  @SecurityCheck
  @Transactional(noRollbackFor = { UnsupportedOperationException.class })
  public Message save(String text)
  1. HttpServletRequest.getUserPrincipal() 으로 현재 사용자 정보를 가져올 수 있다.  
  2. SecurityContextHolder로 SecurityContextHolder.getContext().getAuthentication() 
  3. 스프링 시큐리트의 @AuthenticationPrincipal이 적용된 어노테이션을 생성할 있다. 그리고 스프링에 UserDetails 구현체를 API 핸들러 메소드에 주입하여 요청

3번의 예제

@Target({ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@AuthenticationPrincipal
public @interface CurrentUser {
}

Controller 에서 변환하여 사용

public ResponseEntity<ApiResult> createBoard(
            @RequestBody CreateBoardPayload payload,
            @CurrentUser SimpleUser currentUser) {
...
spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/task_agile_test
    username: sa
    password:
    driver-class-name: org.h2.Driver

  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    open-in-view: false
    hibernate:
      ddl-auto: create-drop
    properties:
      hibernate:
        show_sql: true
        format_sql: true
        
logging:
  level:
    org.hibernate.type.descriptor.sql: trace
  • spring.datasource : DB 연결정보 
  • spring.jpa.database-platform : 데이터 플래폼을 지정, 참고 
  • spring.jpa.open-in-view : osiv 웹요청이 완료될때까지 영속성을 가짐 (성능상 안 좋음)
  • spring.jpa.hibernate.ddl-auto :
    • none : No action is performed. The schema will not be generated.
    • create : create the schema, the data previously present (if there) in the schema is lost
    • create-drop : (default) create the schema with destroying the data previously present(if there). It also drop the database schema when the SessionFactory is closed.
    • update : update the schema with the given values.
    • validate : validate the schema. It makes no change in the DB.
  • spring.jpa.properties.hibernate.format_sql : 실행쿼리를 가독성있게 표현
  • spring.jpa.properties.hibernate.show-sql : 콘솔에 jpa 실행쿼리
  • logging.level.org.hibernate.type.descriptor.sql : ? 부문을 로그로 출력해줌

Before 어드바이스는 스프링이 제공하는 가장 유용한 어드바이스 중 하나다. Before 어드바이스는 메서드로 넘겨주는 인자를 수정할 수 있으며 메서드를 실행하기 전에 예외를 발생시켜서 실행을 막을 수도 있다.

import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;

import java.lang.reflect.Method;

public class SimpleBeforeAdvice  implements MethodBeforeAdvice {

    public static void main(String[] args) {
        MessageWriter target = new MessageWriter();

        // create the proxy
        ProxyFactory pf = new ProxyFactory();

        pf.addAdvice(new SimpleBeforeAdvice());
        pf.setTarget(target);

        MessageWriter proxy = (MessageWriter) pf.getProxy();

        // write the messages
        proxy.writeMessage();
    }

    public void before(Method method, Object[] args, Object target)
            throws Throwable {
        System.out.println("Before method: " + method.getName());
    }

}

결과

Before method : writeMessage
World


Before 어드바이스를 사용하여 메서드 접근 보안하기

SecureBean클래스 - 보안처리를 할 클래스

package com.lastjava.spring.ch05.security;

public class SecureBean {
 public void writeSecureMessage() {
        System.out.println("Every time I learn something new, "
                + "it pushes some old stuff out of my brain");
    }
}

UserInfo클래스 - 사용자 정보를 저장

package com.lastjava.spring.ch05.security;

public class UserInfo {
 private String username;
    private String password;
   
    public UserInfo(String username, String password) {
        this.username = username;
        this.password = password;
    }
   
    public String getPassword() {
        return password;
    }

    public String getUsername() {
        return username;
    }
}

SecurityManager클래스 - 사용자 인증을 담당하고 그들의 계정 정보를 담아뒸더가 나중에 참조

package com.lastjava.spring.ch05.security;

public class SecurityManager {

    private static ThreadLocal<UserInfo> threadLocal = new ThreadLocal<UserInfo>();

    public void login(String username, String password) {
        // assumes that all credentials
        // are valid for a login
        threadLocal.set(new UserInfo(username, password));
    }

    public void logout() {
        threadLocal.set(null);
    }
   
    public UserInfo getLoggedOnUser() {
        return threadLocal.get();
    }
}

SecurityAdvice클래스 - 해당 메서드 접근전에 실행 메서드실행을 예외처리로 방지한다.

package com.lastjava.spring.ch05.security;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

public class SecurityAdvice implements MethodBeforeAdvice {

    private SecurityManager securityManager;

    public SecurityAdvice() {
        this.securityManager = new SecurityManager();
    }

    public void before(Method method, Object[] args, Object target)
            throws Throwable {
        UserInfo user = securityManager.getLoggedOnUser();

        if (user == null) {
            System.out.println("No user authenticated");
            throw new SecurityException(
                    "You must login before attempting to invoke the method: "
                            + method.getName());
        } else if ("janm".equals(user.getUsername())) {
            System.out.println("Logged in user is janm - OKAY!");
        } else {
            System.out.println("Logged in user is " + user.getUsername()
                    + " NOT GOOD :(");
            throw new SecurityException("User " + user.getUsername()
                    + " is not allowed access to method " + method.getName());
        }

    }
}

SecurityExample 클래스

package com.lastjava.spring.ch05.security;

import org.springframework.aop.framework.ProxyFactory;

public class SecurityExample {
 public static void main(String[] args) {
        // get the security manager
        SecurityManager mgr = new SecurityManager();

        // get the bean
        SecureBean bean = getSecureBean();

        // try as robh
        mgr.login("janm", "*****");
        bean.writeSecureMessage();
        mgr.logout();

        // try as janm
        try {
            mgr.login("mallory", "****");
            bean.writeSecureMessage();
        } catch(SecurityException ex) {
            System.out.println("Exception Caught: " + ex.getMessage());
        } finally {
            mgr.logout();
        }

        // try with no credentials
        try {
            bean.writeSecureMessage();
        } catch(SecurityException ex) {
            System.out.println("Exception Caught: " + ex.getMessage());
        }

    }

     private static SecureBean getSecureBean() {
        // create the target
        SecureBean target = new SecureBean();

        // create the advice
        SecurityAdvice advice = new SecurityAdvice();

        // get the proxy
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(target);
        factory.addAdvice(advice);

        return (SecureBean)factory.getProxy();

    }
}

결과

Logged in user is janm - OKAY!
Every time I learn something new, it pushes some old stuff out of my brain
Logged in user is mallory NOT GOOD :(
Exception Caught: User mallory is not allowed access to method writeSecureMessage
No user authenticated
Exception Caught: You must login before attempting to invoke the method: writeSecureMessage


 

스프링 AOP 아키텍처
 스프링 AOP의 핵심 아키텍처는 프록시를 기반으로 하고 있다. 어드바이스가 적용된 클래스의 객체를 생성하고 싶다면, 먼저 프록시의 위빙할 모든 액스팩트를 ProxyFactory에 제공하고 반드시 ProxyFactory클래스를 사용하여 해당 클래스 인스턴스의 폭시를 만들어야 한다. 내부적으로 스프링은 두 개의 프록시 구현체가 있다. JDK동적 프록시와 CGLIB프록시다.

스프링 조인포인트
 스프링 AOP에서 주목해야 할 것 하나는 오직 한 종류의 조인포인트인 메서드 호출 조인포인트만 지원한다는 것이다. 이런 단순함이 스프링의 접근성을 높여준다.

스프링 애스팩트
 
스프링 AOP에서 에스팩트는 Advisor 인터페이스를 구현한 클래스의 인스턴스를 말한다. Advisor는 두 개의 하위 인터페이스 IntroductionAdvisor와 PointcutAdvisor가 있다. PointcutAdvisor인터페이스는 포인트컷을 사용하는 모든 어드바이저들이 구현하고 있다. 이것을 사용하여 해당 조인포인트에 어드바이스의 기능을 적용한다.

ProxyFactory 클래스
 
ProxyFactory 클래스는 스프링 AOP에서 프록시 생성과 위빙을 제어한다.
 어드바이스이름  인터페이스  설명
 Before  org.springframework.aop.
MethodBeforeAdivce
 Before 어드바이스를 사용하여, 조인포인트를 실행가히 전에 어떤 처리를 할 수 있다. 스프링에서 조인포인트는 항상 메서드 호출이기 때문에 해당 메서드를 처리하기 전에 전처리 과정을 수행하는 데 사용할 수 있다. Before 어드바이스는 호출하는 메서드에 넘겨주는 인자들을 비롯한 타켓 객체에도 접근할 수 있지만 메서드 실행 자체를 제어할 수는 없다.
 After Returning  org.springframework.aop.
AfterReturningAdvice
 After Returning 어드바이스는 조인포인트에 있는 메서드가 실행을 끝내고 값을 반환 했을 때 실행된다. After Returning 어드바이스는 해당 메서드에 넘겨주는 인자 타켓 객체 그리고 반환값에 접근 할 수 있다. After Returning 어드바이스를 적용하기 전에 이미 메서드는 실행을 마쳤기 때문에 메서드 호출 자체를 제어할 수는 없다.  
 Around  org.aopalliance.intercept.
MethodInterceptor
 스프링에서 Around 어드바이스는 AOP연합 표준
MethodInterceptor를 사용하도록 만들었다. 여러분이 만든 어드바이스는 메서드 실행전과 후에 어떤 작업을 수행할 수 있으며, 어느 시점에 메서드를 실행하지 않을지 제어할 수 있다. 원한다면 해당 메서드 대신에 별도의 로직을 만들어 사용할 수 있다.
 Throws  org.springframework.aop.
ThrowsAdvice
 Throws 어드바이스는 메서드가 호출된 후에 실행되는데 단 해당 메서드가 예외를 던졌을 경우에만 실행한다. 원하면 특정 예외만 잡아내도록 설정할 수 있으며, 해당 예외를 발생시킨 메서드에 넘겨준 인자와 타켓 객체에 접근할 수 있다.
 Introduction  org.springframework.aop.
IntroductionInterceptor
 스프링은 인트로덕션을 특별한 인터셉터 종류로 모델링했다. 인트로덕션 인터셉터를 사용하여, 여러분은 어드바이스를 적용할 메서드 구현체를 지정할 수 있다.

 

+ Recent posts