From efe3a35da871a7ef34148dfb65d6a96cac1fccb9 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 9 May 2015 20:32:18 +0200 Subject: [PATCH] Introduce AOP testing utilities This commit introduces support in the spring-test module for obtaining a reference to the underlying target object hidden behind one or more proxies. Specifically this commit introduces AopTestUtils with two methods: - getTargetObject(Object) - getUltimateTargetObject(Object) Issue: SPR-13005 --- .../test/util/AopTestUtils.java | 96 ++++++++++++ .../test/util/AopTestUtilsTests.java | 138 ++++++++++++++++++ 2 files changed, 234 insertions(+) create mode 100644 spring-test/src/main/java/org/springframework/test/util/AopTestUtils.java create mode 100644 spring-test/src/test/java/org/springframework/test/util/AopTestUtilsTests.java diff --git a/spring-test/src/main/java/org/springframework/test/util/AopTestUtils.java b/spring-test/src/main/java/org/springframework/test/util/AopTestUtils.java new file mode 100644 index 000000000000..f49b30551c08 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/util/AopTestUtils.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.util; + +import org.springframework.aop.framework.Advised; +import org.springframework.aop.support.AopUtils; + +/** + * {@code AopTestUtils} is a collection of AOP-related utility methods for + * use in unit and integration testing scenarios. + * + *

For Spring's core AOP utilities, see + * {@link org.springframework.aop.support.AopUtils AopUtils} and + * {@link org.springframework.aop.framework.AopProxyUtils AopProxyUtils}. + * + * @author Sam Brannen + * @since 4.2 + * @see org.springframework.aop.support.AopUtils + * @see org.springframework.aop.framework.AopProxyUtils + */ +public class AopTestUtils { + + /** + * Get the target object of the supplied {@code candidate} object. + *

If the supplied {@code candidate} is a Spring + * {@linkplain AopUtils#isAopProxy proxy}, the target of the proxy will + * be returned; otherwise, the {@code candidate} will be returned + * as is. + * + * @param candidate the instance to check (potentially a Spring AOP proxy) + * @return the target object or the {@code candidate}; never {@code null} + * @throws IllegalStateException if an error occurs while unwrapping a proxy + * @see Advised#getTargetSource() + * @see #getUltimateTargetObject + */ + @SuppressWarnings("unchecked") + public static T getTargetObject(Object candidate) { + try { + if (AopUtils.isAopProxy(candidate) && (candidate instanceof Advised)) { + return (T) ((Advised) candidate).getTargetSource().getTarget(); + } + } + catch (Exception e) { + throw new IllegalStateException("Failed to unwrap proxied object.", e); + } + + // else + return (T) candidate; + } + + /** + * Get the ultimate target object of the supplied {@code candidate} + * object, unwrapping not only a top-level proxy but also any number of + * nested proxies. + *

If the supplied {@code candidate} is a Spring + * {@linkplain AopUtils#isAopProxy proxy}, the ultimate target of all + * nested proxies will be returned; otherwise, the {@code candidate} + * will be returned as is. + * + * @param candidate the instance to check (potentially a Spring AOP proxy) + * @return the ultimate target object or the {@code candidate}; never + * {@code null} + * @throws IllegalStateException if an error occurs while unwrapping a proxy + * @see Advised#getTargetSource() + * @see org.springframework.aop.framework.AopProxyUtils#ultimateTargetClass + */ + @SuppressWarnings("unchecked") + public static T getUltimateTargetObject(Object candidate) { + try { + if (AopUtils.isAopProxy(candidate) && (candidate instanceof Advised)) { + return (T) getUltimateTargetObject(((Advised) candidate).getTargetSource().getTarget()); + } + } + catch (Exception e) { + throw new IllegalStateException("Failed to unwrap proxied object.", e); + } + + // else + return (T) candidate; + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/util/AopTestUtilsTests.java b/spring-test/src/test/java/org/springframework/test/util/AopTestUtilsTests.java new file mode 100644 index 000000000000..21070c5646ac --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/util/AopTestUtilsTests.java @@ -0,0 +1,138 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.util; + +import org.junit.Test; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.support.AopUtils; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.springframework.test.util.AopTestUtils.*; + +/** + * Unit tests for {@link AopTestUtils}. + * + * @author Sam Brannen + * @since 4.2 + */ +public class AopTestUtilsTests { + + private final FooImpl foo = new FooImpl(); + + + @Test + public void getTargetObjectForNonProxiedObject() { + Foo target = getTargetObject(foo); + assertSame(foo, target); + } + + @Test + public void getTargetObjectWrappedInSingleJdkDynamicProxy() { + Foo target = getTargetObject(jdkProxy(foo)); + assertSame(foo, target); + } + + @Test + public void getTargetObjectWrappedInSingleCglibProxy() { + Foo target = getTargetObject(cglibProxy(foo)); + assertSame(foo, target); + } + + @Test + public void getTargetObjectWrappedInDoubleJdkDynamicProxy() { + Foo target = getTargetObject(jdkProxy(jdkProxy(foo))); + assertNotSame(foo, target); + } + + @Test + public void getTargetObjectWrappedInDoubleCglibProxy() { + Foo target = getTargetObject(cglibProxy(cglibProxy(foo))); + assertNotSame(foo, target); + } + + @Test + public void getUltimateTargetObjectForNonProxiedObject() { + Foo target = getUltimateTargetObject(foo); + assertSame(foo, target); + } + + @Test + public void getUltimateTargetObjectWrappedInSingleJdkDynamicProxy() { + Foo target = getUltimateTargetObject(jdkProxy(foo)); + assertSame(foo, target); + } + + @Test + public void getUltimateTargetObjectWrappedInSingleCglibProxy() { + Foo target = getUltimateTargetObject(cglibProxy(foo)); + assertSame(foo, target); + } + + @Test + public void getUltimateTargetObjectWrappedInDoubleJdkDynamicProxy() { + Foo target = getUltimateTargetObject(jdkProxy(jdkProxy(foo))); + assertSame(foo, target); + } + + @Test + public void getUltimateTargetObjectWrappedInDoubleCglibProxy() { + Foo target = getUltimateTargetObject(cglibProxy(cglibProxy(foo))); + assertSame(foo, target); + } + + @Test + public void getUltimateTargetObjectWrappedInCglibProxyWrappedInJdkDynamicProxy() { + Foo target = getUltimateTargetObject(jdkProxy(cglibProxy(foo))); + assertSame(foo, target); + } + + @Test + public void getUltimateTargetObjectWrappedInCglibProxyWrappedInDoubleJdkDynamicProxy() { + Foo target = getUltimateTargetObject(jdkProxy(jdkProxy(cglibProxy(foo)))); + assertSame(foo, target); + } + + private Foo jdkProxy(Foo foo) { + ProxyFactory pf = new ProxyFactory(); + pf.setTarget(foo); + pf.addInterface(Foo.class); + Foo proxy = (Foo) pf.getProxy(); + assertTrue("Proxy is a JDK dynamic proxy", AopUtils.isJdkDynamicProxy(proxy)); + assertThat(proxy, instanceOf(Foo.class)); + return proxy; + } + + private Foo cglibProxy(Foo foo) { + ProxyFactory pf = new ProxyFactory(); + pf.setTarget(foo); + pf.setProxyTargetClass(true); + Foo proxy = (Foo) pf.getProxy(); + assertTrue("Proxy is a CGLIB proxy", AopUtils.isCglibProxy(proxy)); + assertThat(proxy, instanceOf(FooImpl.class)); + return proxy; + } + + + static interface Foo { + } + + static class FooImpl implements Foo { + } + +}