public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByOrganization(Organization organization);
List<Member> findByOrganizationId(Long organizationId);
}
🤔 MemberRepository는 인터페이스고, @Repository 애노테이션을 붙여 놓지도 않았는데, 다음과 같은 코드가 가능할까?
memberRepository의 실제 객체를 보니 Proxy가 주입된다.
그리고 그 Proxy는 SimpleJpaRepository를 타겟으로 가지고 있다.
결과적으로 다음과 같은 구조이다.
데이터베이스 마커 인터페이스로, 타입과 id의 타입을 설정한다. ( 마커 인터페이스 )
CRUD 메서드를 제공한다. 생성, 읽기, 삭제 메서드가 제공된다.
페이징과 정렬 메서드를 제공한다.
JPA 관련 메서드를 제공한다 ( CrudRepository에 없는 batch 삭제 혹은 영속성 flush 관련 기능 제공 )
CrudRepository의 기본 구현체이다. EntityManager 필드를 사용하여 실제로 데이터 영속성을 다룬다. 모든 저장소에 적용되는 공통 메서드를 추가하고 싶으면 해당 클래스를 상속하여 메서드를 구현할 수 있다.
public class Item {
public static String id = "oldId";
private String name = "book";
public Item() {
}
private Item(String name) {
this.name = name;
}
private int sum(int a, int b) {
return a + b;
}
@Override
public String toString() {
return name;
}
}
@Slf4j
public class ItemApp {
public static void main(String[] args) throws
NoSuchMethodException,
InvocationTargetException,
InstantiationException,
IllegalAccessException,
NoSuchFieldException,
ClassNotFoundException {
Class<Item> itemClass = (Class<Item>)Class.forName("com.springstudy.jpa.item.Item");
Constructor<Item> defaultConstructor = itemClass.getDeclaredConstructor(null);
Item item1 = defaultConstructor.newInstance();
log.info("item1 : {}", item1);
Constructor<Item> constructor = itemClass.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Item item2 = constructor.newInstance("cup");
log.info("item2 : {}", item2);
Field id = Item.class.getDeclaredField("id");
log.info("id : {}", id.get(null));
id.set(null, "newId");
log.info("id : {}", id.get(null));
Field name = Item.class.getDeclaredField("name");
name.setAccessible(true);
log.info("name : {}", name.get(item2));
name.set(item2, "phone");
log.info("name : {}", name.get(item2));
Method sum = itemClass.getDeclaredMethod("sum", int.class, int.class);
sum.setAccessible(true);
Object result = sum.invoke(item1, 1, 2);
log.info("result : {}", result);
}
}
리플렉션
리플렉션 기술을 사용하면 클래스나 메서드의 메타 정보를 동적으로 획득하고, 코드도 동적으로 호출할 수 있다. 심지어 private 접근 제어자가 붙어있는 메서드에도 접근할 수 있다. 이렇게 메타정보를 이용해서 클래스, 필드, 메서드 정보를 얻는다는 것은 정보를 동적으로 변경할 수도 있게 된다. 결과적으로 동적인 객체 생성, 동적 메서드 호출 기능 등을 사용 할 수 있는데 Spring에서는 DI, Proxy 등에서 리플렉션이 사용된다.
코드를 작성할 시점에는 어떤 타입의 클래스를 사용할지 모르지만, 런타임 시점에 지금 실행되고 있는 클래스를 가져와서 실행해야 하는 경우
프레임워크나 IDE에서 이런 동적인 바인딩을 이용한 기능을 제공한다. intelliJ의 자동완성 기능, 스프링의 어노테이션이 리플렉션을 이용한 기능이다.
public interface Repository {
void save(String itemId);
}
@Slf4j
public class SimpleRepository implements Repository {
@Override
public void save(String itemId) {
log.info("Save Item. itemId = {}", itemId);
}
}
public interface Repository {
void save(String itemId);
}
@Slf4j
public class RepositoryHandler implements InvocationHandler {
private final Repository target;
public RepositoryHandler(Repository target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("save".equals(method.getName())) {
log.info("save() in proxy");
return method.invoke(target, args);
}
return method.invoke(target, args);
}
Handler로 프록시를 생성
public class ReflectionTest {
@Test
void reflectionTest() {
RepositoryHandler repositoryHandler = new RepositoryHandler(new SimpleRepository());
CustomRepository customRepository = (CustomRepository) Proxy.newProxyInstance(
CustomRepository.class.getClassLoader(),
new Class[]{CustomRepository.class},
repositoryHandler
);
customRepository.save("ITEM22");
}
}
18:47:12.038 [main] INFO com.example.reflection.RepositoryHandler - save() in proxy
18:47:12.041 [main] INFO com.example.reflection.SimpleRepository - Save Item. itemId = ITEM22
디버깅을 내용을 살펴보면 interface는 MemberRepository, target은 SimpleJpaRepository인 것을 확인할 수 있다. 이러한 과정을 보면 스프링은 MemberRepository를 구현하는 객체를 생성해주고 있다. 처음에 나왔던 save()메소드는 target인 SimpleJapRepository에게 요청을 위임하고, 사용자가 만들었던 findAllByName() 메서드도 동적으로 만들어주고 있다.
한 줄로 정리를 해보면 스프링은 사용자가 정의한 Repository 인터페이스를 구현하고 SimpleJpaRepository를 target으로 포함하는 Proxy를 동적으로 만들어준다.
또, 그 Proxy를 Bean으로 등록해주고 연관관계 설정이 필요한 곳에 주입도 해준다.