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.
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.
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
.
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 ()
.
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.
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