티스토리 뷰
프록시 패턴이란?
리얼 서브젝트는 자신이 해야 할 일만 하면서 프록시를 사용하여 부가적인 기능을 제공할 때 쓰는 패턴입니다.
프록시와 리얼 서브젝트가 공유하는 인터페이스(서브젝트)가 있고,
클라이언트가 해당 인터페이스(서브젝트) 타입으로 프록시를 사용합니다.
클라이언트는 프록시를 거쳐서 리얼 서브젝트를 사용하기 때문에 프록시는
리얼 서브젝트에 대한 접근을 관리하거나 부가기능을 제공하거나, 리턴값을 변경할 수도 있습니다.
그럼 프로젝트를 생성해서 간단히 살펴보겠습니다.
package me.whiteship;
public class Book {
private Integer id;
private String title;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
package me.whiteship;
public interface BookService {
void rent(Book book);
}
서브젝트
package me.whiteship;
public class BookServiceProxy implements BookService {
BookService bookService;
public BookServiceProxy(BookService bookService) {
this.bookService = bookService;
}
@Override
public void rent(Book book) {
System.out.println("aaaaa");
bookService.rent(book);
System.out.println("bbbbb");
}
}
프록시
package me.whiteship;
public class DefaultBookService implements BookService {
@Override
public void rent(Book book) {
System.out.println("rent : " + book.getTitle());
}
}
리얼 서브젝트
package me.whiteship;
import org.junit.Test;
import static org.junit.Assert.*;
public class BookServiceTest {
BookService bookService = new DefaultBookService();
BookService bookServiceProxy = new BookServiceProxy(new DefaultBookService());
@Test
public void noProxy() {
Book book = new Book();
book.setTitle("spring");
bookService.rent(book);
}
@Test
public void proxy() {
Book book = new Book();
book.setTitle("spring");
bookServiceProxy.rent(book);
}
}
noProxy 메소드는 spring이 출력되며,
proxy() 예상대로
aaaaa
rent : spring
bbbbb
이렇게 출력됩니다.
이런 식으로 프록시 패턴을 구현할 수도 있으며
다른 방법으로 자바에서 지원하는 Dynamic Proxy를 살펴보겠습니다.
Dynamic Proxy란?
Runtime에 특정 interface들을 구현하는 class 또는 instance를 만드는 기술입니다.
애플리케이션은 다이나믹 프록시를 사용하여 여러 임의의 이벤트 인터페이스를 구현하는 객체를 만들 수 있습니다.
Object Proxy.newProxyInstance(ClassLoader, Interfaces, InvocationHandler)
https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html
실습을 위해 프로젝트를 생성하고 살펴보겠습니다.
package me.whiteship;
public class Book {
private Integer id;
private String title;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
package me.whiteship;
public interface BookService {
void rent(Book book);
void returnBook(Book book);
}
package me.whiteship;
public class DefaultBookService implements BookService {
@Override
public void rent(Book book) {
System.out.println("rent : " + book.getTitle());
}
@Override
public void returnBook(Book book) {
System.out.println("return : "+ book.getTitle());
}
}
package me.whiteship;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import static org.junit.Assert.*;
public class BookServiceTest {
BookService bookService = (BookService) Proxy.newProxyInstance(BookService.class.getClassLoader(), new Class[]{BookService.class}
, new InvocationHandler() {
BookService bookService = new DefaultBookService();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("rent")) {
System.out.println("aaaa");
Object invoke = method.invoke(bookService, args);
System.out.println("bbbb");
return invoke;
}
return method.invoke(bookService, args);
}
});
@Test
public void dynamicProxy() {
Book book = new Book();
book.setTitle("spring");
bookService.rent(book);
bookService.returnBook(book);
}
}
Proxy.newProxyInstance를 사용하여 이전의 실습한 BookServiceProxy 클래스를 구현한 코드입니다.
dynamicPorxy 메소드의 결과값은
aaaa
rent : spring
bbbb
return : spring 이렇게 출력됩니다.
BookService의 타입을 DefaultBookService로 바꾸면 에러가 납니다.
그 이유는 Interface 기반의 proxy는 생성할 수 있지만, class 기반의 proxy는 생성 할 수가 없기 때문입니다.
따라서, java proxy는 무조건 interface를 써야 한다는 제약 조건이 있습니다.
그러면 class기반의 proxy가 필요할 경우 어떻게 해야 할까요?
class기반의 proxy는 서브 클래스를 만들 수 있는 라이브러리를 사용하여 proxy를 만들 수 있습니다.
서브 클래스를 만드는 방법의 단점을 살펴보겠습니다.
- 상속을 사용하지 못하는 경우 프록시를 만들 수 없습니다.
- private 생성자만 있는 경우
- Final 클래스인 경우
라이브러리 ByteBuddy를 사용하여 실습해 보겠습니다.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.whiteship</groupId>
<artifactId>javaproxy</artifactId>
<version>1.0-SNAPSHOT</version>
<name>demospringdi</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.12.8</version>
</dependency>
</dependencies>
</project>
package me.whiteship;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.InvocationHandlerAdapter;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static org.junit.Assert.*;
public class BookServiceTest {
@Test
public void dynamicProxy() throws Exception{
Class<? extends DefaultBookService> proxyClass = new ByteBuddy().subclass(DefaultBookService.class)
.method(named("rent")).intercept(InvocationHandlerAdapter.of(new InvocationHandler() {
DefaultBookService defaultBookService = new DefaultBookService();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("aaaa");
Object invoke = method.invoke(defaultBookService, args);
System.out.println("bbbb");
return invoke;
}
}))
.make().load(DefaultBookService.class.getClassLoader()).getLoaded();
DefaultBookService bookService = proxyClass.getConstructor(null).newInstance();
Book book = new Book();
book.setTitle("spring");
bookService.rent(book);
bookService.returnBook(book);
}
}
자바 Dynamic Proxy 실습과 마찬가지로 똑같이 구현되게 테스트 코드를 작성하였습니다.
dynamicProxy 메소드의 결과값은
aaaa
rent : spring
bbbb
return : spring 이렇게 출력됩니다.
Dynamic Proxy는 런타임에 인터페이스 또는 클래스의 인스턴스 또는 클래스를 만들어 사용하는 프로그래밍 기법입니다.
사용처는 Spring data JPA, Spring AOP, Mockito 등이 있습니다.
이로써 공부한 내용을 간략히 정리해보았습니다.
감사합니다.
출처
https://www.inflearn.com/course/the-java-code-manipulation
'Java' 카테고리의 다른 글
자바 인터페이스(Interface) (0) | 2022.04.13 |
---|---|
애노테이션 프로세서(Annotation processor) (0) | 2022.04.06 |
자바 리플렉션(Reflection)2 - Annotation, Dependency Injection(DI) (0) | 2022.04.04 |
자바 리플렉션(Reflection) (0) | 2022.03.31 |
바이트 코드를 조작하는 방법2 - ByteBuddy, Javaagent (1) | 2022.03.30 |
- Total
- Today
- Yesterday
- 애노테이션
- classloder
- 자바 리플렉션
- 바이트 코드
- 람다표현식
- jvm
- java abstractprocessor
- 리플렉션
- 클래스로더
- javaagent
- java proxy pattern
- optional api
- Functional Interface
- Annotation Processor
- bytebuddy
- 깃 기초
- Reflection
- java
- java11 optional
- JRE와 JDK의 차이점
- 애노테이션 프로세서
- java optional
- javassist
- 실행 엔진
- JVM 구조
- 코드 커버리지
- 프록시패턴
- Java Reflection
- dromos
- 자바 프록시 패턴
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |