mirror of https://github.com/apache/maven.git
[MNG-7662] Use proxies for session scoped beans (#950)
This commit is contained in:
parent
e6d1b4c5de
commit
4bd12915c9
|
@ -174,6 +174,12 @@ under the License.
|
|||
<artifactId>plexus-testing</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<version>${mockitoVersion}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -18,11 +18,16 @@
|
|||
*/
|
||||
package org.apache.maven.session.scope.internal;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.OutOfScopeException;
|
||||
|
@ -89,7 +94,47 @@ public class SessionScope implements Scope {
|
|||
|
||||
public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) {
|
||||
// Lazy evaluating provider
|
||||
return () -> getScopeState().scope(key, unscoped).get();
|
||||
return () -> {
|
||||
if (values.isEmpty()) {
|
||||
return createProxy(key, unscoped);
|
||||
} else {
|
||||
return getScopeState().scope(key, unscoped).get();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T createProxy(Key<T> key, Provider<T> unscoped) {
|
||||
InvocationHandler dispatcher = (proxy, method, args) -> {
|
||||
method.setAccessible(true);
|
||||
return method.invoke(getScopeState().scope(key, unscoped).get(), args);
|
||||
};
|
||||
Class<T> superType = (Class<T>) key.getTypeLiteral().getRawType();
|
||||
for (Annotation a : superType.getAnnotations()) {
|
||||
Class<? extends Annotation> annotationType = a.annotationType();
|
||||
if ("org.eclipse.sisu.Typed".equals(annotationType.getName())
|
||||
|| "javax.enterprise.inject.Typed".equals(annotationType.getName())) {
|
||||
try {
|
||||
Class<?>[] value =
|
||||
(Class<?>[]) annotationType.getMethod("value").invoke(a);
|
||||
if (value.length == 0) {
|
||||
value = superType.getInterfaces();
|
||||
}
|
||||
List<Class<?>> nonInterfaces =
|
||||
Stream.of(value).filter(c -> !c.isInterface()).collect(Collectors.toList());
|
||||
if (!nonInterfaces.isEmpty()) {
|
||||
throw new IllegalArgumentException(
|
||||
"The Typed annotation must contain only interfaces but the following types are not: "
|
||||
+ nonInterfaces);
|
||||
}
|
||||
return (T) java.lang.reflect.Proxy.newProxyInstance(superType.getClassLoader(), value, dispatcher);
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("The use of session scoped proxies require "
|
||||
+ "a org.eclipse.sisu.Typed or javax.enterprise.inject.Typed annotation");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.apache.maven.session.scope;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import com.google.inject.OutOfScopeException;
|
||||
import org.apache.maven.SessionScoped;
|
||||
import org.apache.maven.api.Session;
|
||||
import org.apache.maven.session.scope.internal.SessionScope;
|
||||
import org.codehaus.plexus.PlexusContainer;
|
||||
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
|
||||
import org.codehaus.plexus.testing.PlexusTest;
|
||||
import org.eclipse.sisu.Typed;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@PlexusTest
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class SessionScopeProxyTest {
|
||||
|
||||
@Mock
|
||||
Session session;
|
||||
|
||||
@Inject
|
||||
SessionScope sessionScope;
|
||||
|
||||
@Inject
|
||||
PlexusContainer container;
|
||||
|
||||
@Test
|
||||
void testProxiedSessionScopedBean() throws ComponentLookupException {
|
||||
ComponentLookupException e =
|
||||
assertThrows(ComponentLookupException.class, () -> container.lookup(MySingletonBean2.class));
|
||||
assertTrue(e.getMessage().matches("[\\s\\S]*: Can not set .* field .* to [\\s\\S]*"));
|
||||
|
||||
MySingletonBean bean = container.lookup(MySingletonBean.class);
|
||||
assertNotNull(bean);
|
||||
assertNotNull(bean.anotherBean);
|
||||
assertSame(bean.anotherBean.getClass(), AnotherBean.class);
|
||||
assertNotNull(bean.myBean);
|
||||
assertNotSame(bean.myBean.getClass(), MySessionScopedBean.class);
|
||||
|
||||
assertThrows(OutOfScopeException.class, () -> bean.myBean.getSession());
|
||||
|
||||
sessionScope.enter();
|
||||
sessionScope.seed(Session.class, this.session);
|
||||
assertNotNull(bean.myBean.getSession());
|
||||
assertNotNull(bean.myBean.getAnotherBean());
|
||||
assertSame(bean.myBean.getAnotherBean().getClass(), AnotherBean.class);
|
||||
}
|
||||
|
||||
@Named
|
||||
static class MySingletonBean {
|
||||
@Inject
|
||||
@Named("scoped")
|
||||
BeanItf myBean;
|
||||
|
||||
@Inject
|
||||
@Named("another")
|
||||
BeanItf2 anotherBean;
|
||||
}
|
||||
|
||||
@Named
|
||||
static class MySingletonBean2 {
|
||||
@Inject
|
||||
@Named("scoped")
|
||||
MySessionScopedBean myBean;
|
||||
|
||||
@Inject
|
||||
@Named("another")
|
||||
BeanItf2 anotherBean;
|
||||
}
|
||||
|
||||
interface BeanItf {
|
||||
Session getSession();
|
||||
|
||||
BeanItf2 getAnotherBean();
|
||||
}
|
||||
|
||||
interface BeanItf2 {}
|
||||
|
||||
@Named("another")
|
||||
@Singleton
|
||||
static class AnotherBean implements BeanItf2 {}
|
||||
|
||||
@Named("scoped")
|
||||
@SessionScoped
|
||||
@Typed
|
||||
static class MySessionScopedBean implements BeanItf {
|
||||
@Inject
|
||||
Session session;
|
||||
|
||||
@Inject
|
||||
BeanItf2 anotherBean;
|
||||
|
||||
public Session getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public BeanItf2 getAnotherBean() {
|
||||
return anotherBean;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue