Java 对非实体调用Spring JPA后处理BeforeDestroyment

Java 对非实体调用Spring JPA后处理BeforeDestroyment,java,spring,jpa,Java,Spring,Jpa,描述环境有点复杂,但我会尽力的 我没有看到任何函数错误,但是我非常担心在spring上下文被关闭(这是一个执行然后关闭的批处理应用程序)后在日志中看到的一个错误,因为它在一个与JPA无关的对象上触发了一些JPA破坏 java.lang.UnsupportedOperationException: Method org.springframework.web.client.RestOperations.hashCode not implemented. at net.mycompany.authe

描述环境有点复杂,但我会尽力的

我没有看到任何函数错误,但是我非常担心在spring上下文被关闭(这是一个执行然后关闭的批处理应用程序)后在日志中看到的一个错误,因为它在一个与JPA无关的对象上触发了一些JPA破坏

java.lang.UnsupportedOperationException: Method org.springframework.web.client.RestOperations.hashCode not implemented.
at net.mycompany.authenticationclient.RestOperationsWithAuthentication.invoke(RestOperationsWithAuthentication.java:29)
at com.sun.proxy.$Proxy25.hashCode(Unknown Source)
at java.util.concurrent.ConcurrentHashMap.hash(ConcurrentHashMap.java:333)
at java.util.concurrent.ConcurrentHashMap.remove(ConcurrentHashMap.java:1175)
at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.postProcessBeforeDestruction(PersistenceAnnotationBeanPostProcessor.java:358)
at org.springframework.beans.factory.support.DisposableBeanAdapter.destroy(DisposableBeanAdapter.java:238)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroyBean(DefaultSingletonBeanRegistry.java:540)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingleton(DefaultSingletonBeanRegistry.java:516)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingleton(DefaultListableBeanFactory.java:824)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingletons(DefaultSingletonBeanRegistry.java:485)
at org.springframework.context.support.AbstractApplicationContext.destroyBeans(AbstractApplicationContext.java:921)
at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:895)
at org.springframework.context.support.AbstractApplicationContext.close(AbstractApplicationContext.java:841)
有一些JPA配置,但它正在侦听另一个包:

<bean id="dashboard_entity_manager_factory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="packagesToScan" value="net.mycompany.dashboard.domain" />
    <property name="persistenceUnitName" value="dashboard"/>
    <property name="dataSource" ref="dashboard_connection_pool" />
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="database" value="MYSQL" />
            <property name="showSql" value="true" />
        </bean>
    </property>
</bean>

调用处理程序:

public class RestOperationsWithAuthentication implements InvocationHandler {

protected RestOperations restClient;
protected AuthenticationClient authClient;

protected boolean authenticated;

public RestOperationsWithAuthentication(RestOperations restClient, AuthenticationClient authClient) {
    this.restClient = restClient;
    this.authClient = authClient;
    this.authenticated = false;
}

@Override
public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {
    if (arguments == null || !(arguments[0] instanceof String)) {
        throw new UnsupportedOperationException("Method " + RestOperations.class.getCanonicalName() + "."
                + method.getName() + " not implemented.");
    }

    String url = arguments[0].toString();

    if (!authenticated) {
        authClient.authenticate(null);
        authenticated = true;
    }

    AuthenticationUrl<String> authUrl = authClient.addAuthParameters(url);
    arguments[0] = authUrl.getUrl();

    try {
        return invoke(method, arguments);
    } catch (HttpClientErrorException e) {
        if (e.getStatusCode().equals(HttpStatus.FORBIDDEN)) {
            authClient.authenticate(authUrl.getSessionToken());
            arguments[0] = authClient.addAuthParameters(url).getUrl();

            return invoke(method, arguments);
        }

        throw e;
    }
}

protected Object invoke(Method method, Object[] arguments) throws Throwable {
    try {
        return method.invoke(restClient, arguments);
    } catch (InvocationTargetException e) {
        throw e.getCause();
    }
}
公共类RestOperationsWithAuthentication实现调用处理程序{
受保护的restClient操作;
受保护的AuthenticationClient authClient;
保护布尔认证;
公共RestOperationsWithAuthentication(RestOperations restClient、AuthenticationClient authClient){
this.restClient=restClient;
this.authClient=authClient;
this.authenticated=false;
}
@凌驾
公共对象调用(对象代理、方法、对象[]参数)抛出Throwable{
if(参数==null | |!(参数[0]字符串实例)){
抛出新的UnsupportedOperationException(“方法”+RestOperations.class.getCanonicalName()+”
+方法。getName()+“未实现”。);
}
字符串url=参数[0]。toString();
如果(!已验证){
authClient.authenticate(null);
已验证=真;
}
AuthenticationUrl authUrl=authClient.addAuthParameters(url);
参数[0]=authUrl.getUrl();
试一试{
返回调用(方法、参数);
}捕获(HttpClientErrorE异常){
如果(例如getStatusCode().equals(HttpStatus.FORBIDDEN)){
authClient.authenticate(authUrl.getSessionToken());
参数[0]=authClient.addAuthParameters(url.getUrl();
返回调用(方法、参数);
}
投掷e;
}
}
受保护的对象调用(方法,对象[]参数)抛出可丢弃的{
试一试{
invoke(restClient,参数);
}捕获(调用TargetException e){
抛出e.getCause();
}
}
}

重要对象的创建是

<bean id="auth_http_client" class="org.springframework.web.client.RestOperations" 
      factory-bean="auth_http_client_factory" factory-method="createRestClientWithAuthentication" />

这只是RestTemplate上的一个decorator,在API上引入了一些自定义的身份验证层。我尝试将该类声明为RestTemplate,但没有区别

现在,在所有情况下,都没有功能问题,事情按预期进行,我关心的是,为什么一些Spring JPA后处理器会与一些随机bean挂钩。这是一个非常边缘的案例,但它只是击败了我。。。我似乎不能很好地理解正在发生的事情

Spring版本是4.0.2。我试过旧版本,似乎没什么不同

我的问题是为什么会发生这种情况,我如何才能避免它

注意,调试后我发现所有定义的bean似乎都包含在这个ConcurrentHashMap中。也许是将XML与注释混合的问题

我在导入到上下文中的一个XML中有这样的内容:

<tx:annotation-driven transaction-manager="dashboard_transaction_manager" proxy-target-class="true"/>
<context:annotation-config/>


但是在持久化后处理器中调用的bean没有使用事务性注释。

请查看
持久化注释BeanPostProcessor
PABPP
的Javadoc,然后查看其源代码。它包含一个名为
extendedEntityManagersToClose
ConcurrentHashMap
。该映射的关键点是,可能由于
PersistenceUnit
PersistenceContext
注释而注入了
EntityManager
EntityManagerFactory
的bean

现在
PABPP
实现了
DestructionAwareBeanPostProcessor
,因此在容器销毁过程中调用其
postprocessBeforeDistruction()
方法,销毁每个bean。此方法的实现在映射上调用
remove()
,指定要销毁的bean作为键,以防有关联的
EntityManager*
。依次调用
ConcurrentHashMap.remove()
调用键上的
hashCode()
,即对每个bean调用


它爆炸是因为当通过
RestOperationsWithAuthentication.invoke()
调用
hashCode()
时,它违反了期望方法参数的guard子句。

PersistenceAnnotationBeanPostProcessor是Spring组件中处理实体管理器注入的Spring组件。它针对应用了
@PersistenceContext
注释的组件

当Spring上下文在作业停止时被销毁时,后处理器将清除它所持有的在扩展模式下运行的实体管理器的静态缓存

RestOperationsWithAuthentication
可能会在该缓存中结束的一种方式是在该类中注入
EntityManager
,而该类不应该被注入

一种解决方案和最佳实践是,不要只在持久性层中注入实体管理器,而不要在web层控制器或持久性层中注入实体管理器

此外,如果作业正在停止且JVM正在关闭,则可以安全地忽略此消息。销毁方法尝试清理缓存的唯一原因是防止重新部署时内存泄漏

如果在重新部署期间一直填充缓存,则类内的对象将通过其类对象classloader引用,从而防止旧版本的应用程序被垃圾收集


如果作业停止并不意味着JVM关闭,那么这个错误确实值得担心,因为它表明了一个潜在的大内存泄漏,其中有很多以前版本的应用程序的类被挂起。

否,身份验证是通过另一个web服务完成的,没有JPA,JPA用于将状态更改持久化到