[JAVA] A story addicted to toString () of Interface proxied with JdkDynamicAopProxy

Development environment

background

Service implementation

Suppose you have the following unconventional ToStringService interface and its implementation class ToStringServiceImpl.

ToStringService.java


public interface SimpleService {

  void foo();

}

ToStringServiceImpl.java


import org.springframework.stereotype.Service;

@Service
public class SimpleServiceImpl implements SimpleService {

  @Override
  public void foo() {
    System.out.println("foo");
  }

  @Override
  public String toString() {
    return this.getClass().getSimpleName();
  }
}

Needless to say, when you execute toString () of SimpleServiceImpl," SimpleServiceImpl "is output.

Proxy

Create a class that inherits ProxyConfig and proxies any class. The name is appropriate.

ToStringProxy.java


import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyConfig;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

public class ToStringProxy<T> extends ProxyConfig {

  private MultiValueMap<Class<?>, Advisor> advisorMap = new LinkedMultiValueMap<>();

  public T apply(T target) {
    List<Advisor> advisors = findAdvisors(target.getClass());
    return createProxy(target, advisors);
  }

  public void addAdvisor(Class<?> target, Advisor... advisors) {
    for (Advisor advisor : advisors) {
      this.advisorMap.add(target, advisor);
    }
  }

  @SuppressWarnings("unchecked")
  private T createProxy(T target, List<Advisor> advisors) {
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.setTarget(target);
    proxyFactory.addAdvisors(advisors);
    if (!isProxyTargetClass()) {
      proxyFactory.setInterfaces(ClassUtils.getAllInterfaces(target));
    }
    proxyFactory.setProxyTargetClass(isProxyTargetClass());
    proxyFactory.setExposeProxy(isExposeProxy());
    proxyFactory.setFrozen(isFrozen());
    proxyFactory.setOpaque(isOpaque());
    proxyFactory.setOptimize(isOptimize());
    return (T) proxyFactory.getProxy();
  }

  private List<Advisor> findAdvisors(Class<?> targetClass) {
    for (Map.Entry<Class<?>, List<Advisor>> entry : advisorMap.entrySet()) {
      if (entry.getKey().isAssignableFrom(targetClass)) {
        return entry.getValue();
      }
    }
    return Collections.emptyList();
  }
}

Next, create an advisor class that proxies SimpleService.

ToStringAdvisor.java


import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor;
import org.springframework.stereotype.Component;

@Component
public class ToStringAdvisor {

  private ToStringProxy<SimpleService> proxy;

  public ToStringAdvisor() {
    proxy = new ToStringProxy<>();
    //Method toString()Target
    String expression = "execution(* " + SimpleService.class.getName() + ".toString())";
    AspectJExpressionPointcutAdvisor pointcutAdvisor = new AspectJExpressionPointcutAdvisor();
    pointcutAdvisor.setAdvice(new ToStringInterceptor());
    pointcutAdvisor.setExpression(expression);
    proxy.addAdvisor(SimpleService.class, pointcutAdvisor);
  }

  public SimpleService apply(SimpleService target) {
    return proxy.apply(target);
  }

  private static class ToStringInterceptor implements MethodInterceptor {

    private ToStringInterceptor() {}

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
      return "proxied: ".concat(String.valueOf(invocation.proceed()));
    }
  }
}

It is assumed that "proxied: SimpleServiceImpl" will be output when toString () of SimpleServiceImpl is called.

Try to test

Now, let's test whether the process written in ToStringInterceptor is called.

ProxySampleApplicationTests.java


import static org.hamcrest.CoreMatchers.equalTo;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ProxySampleApplicationTests {

  @Autowired
  SimpleService service;

  @Autowired
  ToStringAdvisor adviser;

  @Test
  public void proxiedToStringTest() {
    service = adviser.apply(service);
    Assert.assertThat(service.toString(), equalTo("proxied: SimpleServiceImpl"));
  }
}

Result is……

errorlog.txt


java.lang.AssertionError: 
Expected: "proxied: SimpleServiceImpl"
     but: was "SimpleServiceImpl"
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
	at org.junit.Assert.assertThat(Assert.java:956)
	at org.junit.Assert.assertThat(Assert.java:923)
	at com.neriudon.example.ProxySampleApplicationTests.proxiedToStringTest(ProxySampleApplicationTests.java:27)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
	at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
	at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
	at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
	at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)

Hmm: thinking: By the way, when I applied a breakpoint to service = adviser.apply (service);, I was able to confirm that it was proxied with JdkDynamicAopProxy.

JdkDynamicAopProxy is useless

If you think about toString () here, the method definition is defined in the ʻObject class ... but that ʻObject class doesn't implement any interface. So if you proxy with the interface-based JdkDynamicAopProxy, you can't interrupt the process withtoString ().

Proxyed with CGLIB and solved

So, proxy with CGLIB to proxy with the entity class.

ToStringAdvisor.java


  public ToStringAdvisor() {
    proxy = new ToStringProxy<>();
    //Proxy with CGLIB
    proxy.setProxyTargetClass(true);
    //Entity class toString()Target
    String expression = "execution(* " + SimpleServiceImpl.class.getName() + ".toString())";
    AspectJExpressionPointcutAdvisor pointcutAdvisor = new AspectJExpressionPointcutAdvisor();
    pointcutAdvisor.setAdvice(new ToStringInterceptor());
    pointcutAdvisor.setExpression(expression);
    proxy.addAdvisor(SimpleService.class, pointcutAdvisor);

As a result, congratulations and congratulations.

Background to the posting

Actually, I used to proxy a more complicated interface and called toString (), but I couldn't notice the cause because I could call toString () on the interface as well. There are only two types of proxies, JdkDynamicAopProxy and CGLIB, but it's deep: confounded :.

However, if I want to put the process in toString () of a specific interface, do I have to proxy it with CGLIB for all the classes that implement that interface?

Recommended Posts

A story addicted to toString () of Interface proxied with JdkDynamicAopProxy
A story addicted to EntityNotFoundException of getOne of JpaRepository
A story I was addicted to with implicit type conversion of ActiveRecord during unit testing
A story of connecting to a CentOS 8 server with an old Ansible
The story of toString () starting with passing an array to System.out.println
A story addicted to JDBC Template placeholders
The story of making a reverse proxy with ProxyServlet
A story about trying to get along with Mockito
A story about reducing memory consumption to 1/100 with find_in_batches
Story of making a task management application with swing, java
The story of pushing a Docker container to GitHub Package Registry and Docker Hub with GitHub Actions
Port C code with a lot of typecasts to Swift
A story packed with the basics of Spring Boot (solved)
A story I was addicted to in Rails validation settings
A series of steps to create portfolio deliverables with Rails
How to move another class with a button action of another class.
A story stuck with NotSerializableException
[Circle CI] A story I was addicted to at Start Building
The story of the first Rails app refactored with a self-made helper
Try to imitate the idea of a two-dimensional array with a one-dimensional array
A story that I struggled to challenge a competition professional with Java
A story of frustration trying to create a penetration environment on Ubuntu 20.04
A story about hitting the League Of Legends API with JAVA
A story that struggled with the introduction of Web Apple Pay
Conditional branching with a flowing interface
Exception handling with a fluid interface
Create exceptions with a fluid interface
The story of making it possible to build a project that was built by Maven with Ant
A story that I wanted to write a process equivalent to a while statement with the Stream API of Java8
A memo that was soberly addicted to the request of multipart / form-data
The story of forgetting to close a file in Java and failing
The story of making a game launcher with automatic loading function [Java]
A story I was addicted to when testing the API using MockMVC
I was addicted to a simple test of Jedis (Java-> Redis library)
Extract a part of a string with Ruby
A little addictive story with def initialize
Ability to display a list of products
I tried to implement a function equivalent to Felica Lite with HCE-F of Android
How to get the ID of a user authenticated with Firebase in Swift
Send a notification to slack with the free version of sentry (using lambda)
Rails6 I want to make an array of values with a check box
SpringSecurity I was addicted to trying to log in with a hashed password (solved)
I tried to clone a web application full of bugs with Spring Boot
I want to recursively get the superclass and interface of a certain class
A story that people who did iOS solidly may be addicted to the implementation of Listener when moving to Android