Compare commits
4 Commits
495309d132
...
8b1cf5b3e5
Author | SHA1 | Date |
---|---|---|
|
8b1cf5b3e5 | |
|
7e6874ad80 | |
|
097463e3b7 | |
|
19459d09ec |
|
@ -37,18 +37,18 @@ Kotlin::
|
|||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
[TIP]
|
||||
====
|
||||
As of Spring Framework 4.3, an `@Autowired` annotation on such a constructor is no longer
|
||||
necessary if the target bean defines only one constructor to begin with. However, if
|
||||
several constructors are available and there is no primary/default constructor, at least
|
||||
one of the constructors must be annotated with `@Autowired` in order to instruct the
|
||||
container which one to use. See the discussion on
|
||||
xref:core/beans/annotation-config/autowired.adoc#beans-autowired-annotation-constructor-resolution[constructor resolution] for details.
|
||||
An `@Autowired` annotation on such a constructor is not necessary if the target bean
|
||||
defines only one constructor. However, if several constructors are available and there is
|
||||
no primary or default constructor, at least one of the constructors must be annotated
|
||||
with `@Autowired` in order to instruct the container which one to use. See the discussion
|
||||
on xref:core/beans/annotation-config/autowired.adoc#beans-autowired-annotation-constructor-resolution[constructor resolution]
|
||||
for details.
|
||||
====
|
||||
|
||||
You can also apply the `@Autowired` annotation to _traditional_ setter methods,
|
||||
as the following example shows:
|
||||
You can apply the `@Autowired` annotation to _traditional_ setter methods, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
@ -84,8 +84,8 @@ Kotlin::
|
|||
----
|
||||
======
|
||||
|
||||
You can also apply the annotation to methods with arbitrary names and multiple
|
||||
arguments, as the following example shows:
|
||||
You can apply `@Autowired` to methods with arbitrary names and multiple arguments, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
@ -176,14 +176,15 @@ Kotlin::
|
|||
====
|
||||
Make sure that your target components (for example, `MovieCatalog` or `CustomerPreferenceDao`)
|
||||
are consistently declared by the type that you use for your `@Autowired`-annotated
|
||||
injection points. Otherwise, injection may fail due to a "no type match found" error at runtime.
|
||||
injection points. Otherwise, injection may fail due to a "no type match found" error at
|
||||
runtime.
|
||||
|
||||
For XML-defined beans or component classes found via classpath scanning, the container
|
||||
usually knows the concrete type up front. However, for `@Bean` factory methods, you need
|
||||
to make sure that the declared return type is sufficiently expressive. For components
|
||||
that implement several interfaces or for components potentially referred to by their
|
||||
implementation type, consider declaring the most specific return type on your factory
|
||||
method (at least as specific as required by the injection points referring to your bean).
|
||||
implementation type, declare the most specific return type on your factory method (at
|
||||
least as specific as required by the injection points referring to your bean).
|
||||
====
|
||||
|
||||
.[[beans-autowired-annotation-self-injection]]Self Injection
|
||||
|
@ -312,8 +313,8 @@ through `@Order` values in combination with `@Primary` on a single bean for each
|
|||
====
|
||||
|
||||
Even typed `Map` instances can be autowired as long as the expected key type is `String`.
|
||||
The map values contain all beans of the expected type, and the keys contain the
|
||||
corresponding bean names, as the following example shows:
|
||||
The map values are all beans of the expected type, and the keys are the corresponding
|
||||
bean names, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
@ -431,7 +432,7 @@ annotated constructor does not have to be public.
|
|||
====
|
||||
|
||||
Alternatively, you can express the non-required nature of a particular dependency
|
||||
through Java 8's `java.util.Optional`, as the following example shows:
|
||||
through Java's `java.util.Optional`, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -445,8 +446,8 @@ through Java 8's `java.util.Optional`, as the following example shows:
|
|||
----
|
||||
|
||||
You can also use a parameter-level `@Nullable` annotation (of any kind in any package --
|
||||
for example, `javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in
|
||||
null-safety support:
|
||||
for example, `org.jspecify.annotations.Nullable` from JSpecify) or just leverage Kotlin's
|
||||
built-in null-safety support:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
@ -477,13 +478,6 @@ Kotlin::
|
|||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A type-level `@Nullable` annotation such as from JSpecify is not supported in Spring
|
||||
Framework 6.2 yet. You need to upgrade to Spring Framework 7.0 where the framework
|
||||
detects type-level annotations and consistently declares JSpecify in its own codebase.
|
||||
====
|
||||
|
||||
You can also use `@Autowired` for interfaces that are well-known resolvable
|
||||
dependencies: `BeanFactory`, `ApplicationContext`, `Environment`, `ResourceLoader`,
|
||||
`ApplicationEventPublisher`, and `MessageSource`. These interfaces and their extended
|
||||
|
@ -528,5 +522,6 @@ class MovieRecommender {
|
|||
The `@Autowired`, `@Inject`, `@Value`, and `@Resource` annotations are handled by Spring
|
||||
`BeanPostProcessor` implementations. This means that you cannot apply these annotations
|
||||
within your own `BeanPostProcessor` or `BeanFactoryPostProcessor` types (if any).
|
||||
|
||||
These types must be 'wired up' explicitly by using XML or a Spring `@Bean` method.
|
||||
====
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright 2002-2024 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
|
||||
*
|
||||
* https://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.aop.interceptor;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.factory.AdapterFinderBean;
|
||||
/**
|
||||
* A proxy interceptor for finding a concrete adapter implementation of an abstracted interface using a
|
||||
* {@link AdapterFinderBean}. This is in the vein of
|
||||
* {@link org.springframework.beans.factory.config.ServiceLocatorFactoryBean}, but also without the client code
|
||||
* pollution of acquiring the service prior to calling the intended interceptor. The {@code AdapterFinderBean} uses
|
||||
* the method and arguments to determine the appropriate concrete adapter to call.
|
||||
*
|
||||
* <p>By way of an example, consider the following adapter interface.
|
||||
* Note that this interface is not dependent on any Spring APIs.
|
||||
*
|
||||
* <pre class="code">package a.b.c;
|
||||
*
|
||||
*public interface MyService {
|
||||
*
|
||||
* byte[] convert(IMAGE_TYPE to, IMAGE_TYPE from, byte[] source);
|
||||
* enum IMAGE_TYPE {
|
||||
* GIF,
|
||||
* JPEG,
|
||||
* PNG
|
||||
* }
|
||||
*}</pre>
|
||||
*
|
||||
* <p>An {@link AdapterFinderBean}.
|
||||
* <pre class="code">package a.b.c;
|
||||
*
|
||||
*public class MyServiceFinder implements AdapterFinderBean<MyService> {
|
||||
*
|
||||
* private final MyService gifService;
|
||||
* private final MyService jpgService;
|
||||
* private final MyService pngService;
|
||||
*
|
||||
* public MyServiceFinder(MyService gifService, MyService jpgService, MyService pngService) {
|
||||
* this.gifService = gifService;
|
||||
* this.jpgService = jpgService;
|
||||
* this.pngService = pngService;
|
||||
* }
|
||||
*
|
||||
* @Nullable MyService findBean(Method method, Object[] args) {
|
||||
* IMAGE_TYPE type = (IMAGE_TYPE) args[0];
|
||||
* if (type == GIF) {
|
||||
* return gifService;
|
||||
* }
|
||||
*
|
||||
* if (type == JPEG) {
|
||||
* return jpgService;
|
||||
* }
|
||||
*
|
||||
* if (type == PNG) {
|
||||
* return pngService;
|
||||
* }
|
||||
*
|
||||
* return null; // will throw an IllegalArgumentException!
|
||||
* }
|
||||
*}</pre>
|
||||
*
|
||||
* <p>A spring configuration file.
|
||||
* <pre class="code">package a.b.c;
|
||||
*
|
||||
*@Configuration
|
||||
*class MyServiceConfiguration {
|
||||
*
|
||||
* @Bean
|
||||
* MyServiceFinder myServiceFinder(MyGifService gifService, MyJpegService jpgService, MyPngService pngService) {
|
||||
* return new MyServiceFinder(gifService, jpgService, pngService);
|
||||
* }
|
||||
*
|
||||
* @Bean
|
||||
* @Primary
|
||||
* MyService myService(MyServiceFinder finder) {
|
||||
* return AdapterFinderInterceptor.proxyOf(finder, MyService.class);
|
||||
* }
|
||||
*}
|
||||
* </pre>
|
||||
*
|
||||
* <p>A client bean may look something like this:
|
||||
*
|
||||
* <pre class="code">package a.b.c;
|
||||
*
|
||||
*public class MyClientBean {
|
||||
*
|
||||
* private final MyService myService;
|
||||
*
|
||||
* public MyClientBean(MyService myService) {
|
||||
* this.myService = myService;
|
||||
* }
|
||||
*
|
||||
* public void doSomeBusinessMethod(byte[] background, byte[] foreground, byte[] border) {
|
||||
* byte[] gifBackground = myService.convert(PNG, GIF, background);
|
||||
* byte[] gifForeground = myService.convert(PNG, GIF, foreground);
|
||||
* byte[] gifBorder = myService.convert(PNG, GIF, border);
|
||||
*
|
||||
* // no do something with the gif stuff.
|
||||
* }
|
||||
*}</pre>
|
||||
*
|
||||
* @author Joe Chambers
|
||||
* @param <T> the service the interceptor proxy's.
|
||||
*/
|
||||
public final class AdapterFinderInterceptor<T> implements MethodInterceptor {
|
||||
|
||||
private final AdapterFinderBean<T> finder;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param finder the {@code AdapterFinder} to use for obtaining concrete instances
|
||||
*/
|
||||
private AdapterFinderInterceptor(AdapterFinderBean<T> finder) {
|
||||
this.finder = finder;
|
||||
}
|
||||
|
||||
/**
|
||||
* The implementation of the {@link MethodInterceptor#invoke(MethodInvocation)} method called by the proxy.
|
||||
* @param invocation the method invocation joinpoint
|
||||
* @return the results of the concrete invocation call
|
||||
* @throws Throwable if no adapter is found will throw {@link IllegalArgumentException} otherwise will re-throw what the concrete invocation throws
|
||||
*/
|
||||
@Override
|
||||
public @Nullable Object invoke(@NonNull MethodInvocation invocation) throws Throwable {
|
||||
T implementation = this.finder.findAdapter(invocation.getMethod(), invocation.getArguments());
|
||||
if (implementation == null) {
|
||||
throw new IllegalArgumentException("Adapter not found: " + invocation.getMethod());
|
||||
}
|
||||
|
||||
try {
|
||||
return invocation.getMethod().invoke(implementation, invocation.getArguments());
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw ex.getTargetException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a proxy using an {@code AdapterFinderInterceptor}.
|
||||
* @param finder the finder bean to create the proxy around.
|
||||
* @param proxyClass the {@link Class} of the {@code Interface} being exposed.
|
||||
* @param <T> the type of the interface the proxy exposes.
|
||||
* @return a {@code Proxy} that uses the finder to determine which adapter to direct on a call by call basis.
|
||||
*/
|
||||
public static <T> T proxyOf(AdapterFinderBean<T> finder, Class<T> proxyClass) {
|
||||
return ProxyFactory.getProxy(proxyClass, new AdapterFinderInterceptor<>(finder));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright 2002-2024 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
|
||||
*
|
||||
* https://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.aop.interceptor;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.beans.factory.AdapterFinderBean;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for class {@link AdapterFinderInterceptor}
|
||||
*
|
||||
* @author Joe Chambers
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AdapterFinderInterceptorTests {
|
||||
|
||||
@Mock
|
||||
EvenOddService evenService;
|
||||
|
||||
@Mock
|
||||
EvenOddService oddService;
|
||||
|
||||
@Spy
|
||||
EvenOddServiceFinder evenOddFinder = new EvenOddServiceFinder();
|
||||
|
||||
Method expectedMethod;
|
||||
|
||||
EvenOddService evenOddService;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
evenOddService = AdapterFinderInterceptor.proxyOf(evenOddFinder, EvenOddService.class);
|
||||
expectedMethod = EvenOddService.class.getMethod("toMessage", int.class, String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void callEvenService() {
|
||||
String expectedMessage = "4 even message";
|
||||
|
||||
given(evenService.toMessage(eq(4), eq("message"))).willReturn(expectedMessage);
|
||||
|
||||
String actualMessage = evenOddService.toMessage(4, "message");
|
||||
|
||||
assertThat(actualMessage)
|
||||
.isEqualTo(expectedMessage);
|
||||
|
||||
verify(evenService).toMessage(eq(4), eq("message"));
|
||||
verify(oddService, never()).toMessage(anyInt(), anyString());
|
||||
verify(evenOddFinder).findAdapter(eq(expectedMethod), eq(new Object[] { 4, "message" }));
|
||||
}
|
||||
|
||||
@Test
|
||||
void callOddService() {
|
||||
String expectedMessage = "5 odd message";
|
||||
|
||||
given(oddService.toMessage(eq(5), eq("message")))
|
||||
.willReturn(expectedMessage);
|
||||
|
||||
String actualMessage = evenOddService.toMessage(5, "message");
|
||||
|
||||
assertThat(actualMessage)
|
||||
.isEqualTo(expectedMessage);
|
||||
|
||||
verify(oddService).toMessage(eq(5), eq("message"));
|
||||
verify(evenService, never()).toMessage(anyInt(), anyString());
|
||||
verify(evenOddFinder).findAdapter(eq(expectedMethod), eq(new Object[] { 5, "message" }));
|
||||
}
|
||||
|
||||
@Test
|
||||
void throwExceptionWhenNumberIsZero() {
|
||||
String expectedMessage = "Adapter not found: public abstract java.lang.String org.springframework.aop.interceptor.AdapterFinderInterceptorTests$EvenOddService.toMessage(int,java.lang.String)";
|
||||
|
||||
assertThatThrownBy(() -> evenOddService.toMessage(0, "message"))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage(expectedMessage)
|
||||
.hasNoCause();
|
||||
|
||||
verify(evenService, never()).toMessage(anyInt(), anyString());
|
||||
verify(oddService, never()).toMessage(anyInt(), anyString());
|
||||
verify(evenOddFinder).findAdapter(eq(expectedMethod), eq(new Object[] { 0, "message" }));
|
||||
}
|
||||
|
||||
protected interface EvenOddService {
|
||||
String toMessage(int number, String message);
|
||||
}
|
||||
|
||||
protected class EvenOddServiceFinder implements AdapterFinderBean<EvenOddService> {
|
||||
@Override
|
||||
@Nullable
|
||||
public EvenOddService findAdapter(Method method, @Nullable Object[] args) {
|
||||
if (method.getParameterCount() > 0 && method.getParameterTypes()[0] == int.class && args[0] != null) {
|
||||
int number = (int) args[0];
|
||||
if (number != 0) {
|
||||
return ((number % 2 == 0) ? evenService : oddService);
|
||||
}
|
||||
}
|
||||
return null; // method not found, or 0.
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2002-2024 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
|
||||
*
|
||||
* https://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.beans.factory;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A {@code bean} to locate an {@code Adapter} based on the method called and
|
||||
* the parameters passed in.
|
||||
*
|
||||
* <p>For use when multiple implementations of an {@code interface} (Adapters) exist
|
||||
* to handle functionality in various ways such as sending and monitoring shipments
|
||||
* from different providers. The {@link AdapterFinderBean} can look at the method
|
||||
* parameters and determine which shipping provider {@code Adapter} to use.
|
||||
*
|
||||
* <p>If the {@code AdapterFinderBean} cannot find an implementation appropriate for
|
||||
* the parameters, then it will return {@code null}.
|
||||
*
|
||||
* @author Joe Chambers
|
||||
* @param <T> the service type the finder returns
|
||||
*/
|
||||
public interface AdapterFinderBean<T> {
|
||||
|
||||
/**
|
||||
* Lookup the adapter appropriate for the {@link Method} and {@code Arguments}
|
||||
* passed to the implementation.
|
||||
* @param method the {@link Method} being called
|
||||
* @param args the {@code Arguments} being passed to the invocation
|
||||
* @return the implementation of the {@code Adapter} that is appropriate or {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
T findAdapter(Method method, @Nullable Object[] args);
|
||||
}
|
Loading…
Reference in New Issue