spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.77k stars 38.16k forks source link

o.s.b.f.s.DisposableBeanAdapter trying to call non-existing destroy() method, then logs WARN [SPR-10863] #15490

Closed spring-projects-issues closed 5 years ago

spring-projects-issues commented 11 years ago

Hendy Irawan opened SPR-10863 and commented

18:59:42.985 | WARN  | Thread-24        | o.s.b.f.s.DisposableBeanAdapter  | ry.support.DisposableBeanAdapter  247 | Invocation of destroy method failed on bean with name 'siteTenantConfig.PersonRequestConfig': java.lang.NoSuchMethodError: com.quikdo.hub.app.SiteTenantConfig$PersonRequestConfig.destroy()V

call stack:

at org.springframework.beans.factory.support.DisposableBeanAdapter.destroy(DisposableBeanAdapter.java:247)
at org.springframework.beans.factory.support.DisposableBeanAdapter.run(DisposableBeanAdapter.java:214)
at org.springframework.web.context.request.AbstractRequestAttributes.executeRequestDestructionCallbacks(AbstractRequestAttributes.java:91)
at org.springframework.web.context.request.AbstractRequestAttributes.requestCompleted(AbstractRequestAttributes.java:47)
at org.soluvas.commons.shell.ExtCommandSupport.execute(ExtCommandSupport.java:62)
at org.apache.felix.gogo.commands.basic.AbstractCommand.execute(AbstractCommand.java:35)
at org.apache.felix.gogo.runtime.Closure.executeCmd(Closure.java:477)
at org.apache.felix.gogo.runtime.Closure.executeStatement(Closure.java:403)

It results from the following @Configuration :

@Configuration @Scope(value="request")
@Import(TenantDataConfig.class)
public class SiteTenantConfig {
    @Inject @DataFolder
    private String dataFolder;
    @Inject
    private TenantRef tenant;

    @Inject
    private Environment env;

    @Bean @Scope("request")
    public PermalinkCatalog permalinkCatalog() {
        return new StaticXmiLoader<PermalinkCatalog>(SitePackage.eINSTANCE,
                dataFolder + "/common/custom.PermalinkCatalog.xmi").get();
    }

    @Bean @Scope("request")
    public PermalinkManager permalinkMgr() {
        return new PermalinkManagerImpl(permalinkCatalog());
    }

    @Bean(name="cDbCon") @Scope("request") @Deprecated
    public CouchDbConnector cDbCon() throws MalformedURLException {
        final HttpClient httpClient = new StdHttpClient.Builder()
            .url(env.getRequiredProperty("couchDbUrl"))
            .username(env.getRequiredProperty("couchDbUsername"))
            .password(env.getRequiredProperty("couchDbPassword"))
            .build();

        final String dbNameCouchDb = tenant.getTenantId() + "_" + tenant.getTenantEnv();
        final StdCouchDbInstance stdCouchDbInstance = new StdCouchDbInstance(httpClient);
        final StdCouchDbConnector cDbCon = new StdCouchDbConnector(dbNameCouchDb, stdCouchDbInstance);
        cDbCon.createDatabaseIfNotExists();
        return cDbCon;
    }

    @Bean @Scope("request")
    public DelegatingSupplier<SecurityCatalog> securityCatalogSupplier() {
        return new AggregatingSupplier<SecurityCatalog>(SecurityFactory.eINSTANCE,
                SecurityPackage.Literals.SECURITY_CATALOG,
                ImmutableList.<Supplier<SecurityCatalog>>of());
    }

    @Bean @Scope("request")
    public SupplierXmiClasspathScanner<SecurityCatalog> securitySecurityCatalogScanner() {
        return new SupplierXmiClasspathScanner<>(SecurityPackage.eINSTANCE,
                SecurityCatalog.class, securityCatalogSupplier(),
                SecurityPackage.class);
    }

    @Bean @Scope(value="request", proxyMode=ScopedProxyMode.TARGET_CLASS)
    public Realm shiroRealm() throws MalformedURLException {
        return new SoluvasCouchDbRealm(securityCatalogSupplier(), cDbCon());
    }

    @Configuration @Lazy
    public static class PersonConfig {
        private static final Logger log = LoggerFactory
                .getLogger(SiteTenantConfig.PersonConfig.class);
        private Map<String, CouchDbPersonRepository> personRepoMap;
        @Inject
        private Environment env;
        @Inject
        private ClientConnectionManager connMgr;
        @Resource(name="sysConfigMap")
        private Map<String, QuikdoSysConfig> sysConfigMap;

        @PostConstruct
        public void init() {
            log.info("Initializing {} CouchDB Person repositories: {}", sysConfigMap.size(), Iterables.limit(sysConfigMap.keySet(), 10));
            final ImmutableMap.Builder<String, CouchDbPersonRepository> builder = ImmutableMap.builder();
            final String tenantEnv = env.getRequiredProperty("tenantEnv");
            for (QuikdoSysConfig sysConfig : sysConfigMap.values()) {
                final CouchDbPersonRepository personRepo = new CouchDbPersonRepository(connMgr, sysConfig.getCouchDbUri(), sysConfig.getTenantId() + "_" + tenantEnv);
                builder.put(sysConfig.getTenantId(), personRepo);
            }
            personRepoMap = builder.build();
        }

        @PreDestroy
        public void destroy() {
            log.info("Shutting down {} CouchDB Person repositories: {}", personRepoMap.size(), Iterables.limit(personRepoMap.keySet(), 10));
            for (CouchDbPersonRepository personRepo : personRepoMap.values()) {
                personRepo.destroy();
            }
        }

        @Bean
        public Map<String, CouchDbPersonRepository> personRepoMap() {
            return personRepoMap;
        }

    }

    @Configuration @Lazy @Scope("request")
    public static class PersonRequestConfig {

        @Inject
        private TenantRef tenant;
        @Resource(name="personRepoMap")
        private Map<String, PersonRepository> personRepoMap;

        @Bean(name={"personRepo", "personLookup"}) @Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)
        public PersonRepository personRepo() {
            return personRepoMap.get(tenant.getTenantId());
        }

    }

}

PersonRequestConfig is @Configuration @Lazy @Scope("request") that has no destroy method. So even if Spring tries to call one (which it shouldn't in the first place), the log should be DEBUG.

And how did it determine the "destroy()" method name?


Affects: 3.2.4

spring-projects-issues commented 11 years ago

Hendy Irawan commented

Is there any way this relates to our implementation of RequestAttributes ?

public class CommandRequestAttributes extends AbstractRequestAttributes {

//  private static final ThreadLocal<CommandRequestAttributes> commandRequestAttributes = new NamedThreadLocal<>("Command RequestAttributes");
    /**
     * Stores the {@link RequestAttributes#SCOPE_REQUEST} attributes.
     * Unlike in {@link CommandSession}, they will be destroyed after command execution completes. 
     */
    private final Map<String, Object> requestVars = new HashMap<>();

    private final CommandSession session;

    public CommandRequestAttributes(CommandSession session) {
        super();
        this.session = session;
    }

    public CommandSession getSession() {
        return session;
    }

    /**
     * Return the RequestAttributes currently bound to the thread.
     * <p>Exposes the previously bound RequestAttributes instance, if any.
     * @return the RequestAttributes currently bound to the thread
     * @throws IllegalStateException if no RequestAttributes object
     * is bound to the current thread
     * @see RequestContextHolder#currentRequestAttributes()
     */
    public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
        return ExtCommandSupport.currentRequestAttributes();
    }

    /* (non-Javadoc)
     * @see org.springframework.web.context.request.RequestAttributes#getAttribute(java.lang.String, int)
     */
    @Override
    public Object getAttribute(String name, int scope) {
        switch (scope) {
        case SCOPE_REQUEST:
            return requestVars.get(name);
        case SCOPE_SESSION:
        case SCOPE_GLOBAL_SESSION:
            return session.get(name);
        default:
            throw new IllegalArgumentException("Unknown scope: " + scope);
        }
    }

    /* (non-Javadoc)
     * @see org.springframework.web.context.request.RequestAttributes#setAttribute(java.lang.String, java.lang.Object, int)
     */
    @Override
    public void setAttribute(String name, Object value, int scope) {
        switch (scope) {
        case SCOPE_REQUEST:
            requestVars.put(name, value);
            break;
        case SCOPE_SESSION:
        case SCOPE_GLOBAL_SESSION:
            session.put(name, value);
            break;
        }
    }

    @Override
    public void removeAttribute(String name, int scope) {
        switch (scope) {
        case SCOPE_REQUEST:
            requestVars.remove(name);
            break;
        case SCOPE_SESSION:
        case SCOPE_GLOBAL_SESSION:
            session.put(name, null);
            break;
        }
    }

    @Override
    public String[] getAttributeNames(int scope) {
        switch (scope) {
        case SCOPE_REQUEST:
            return requestVars.keySet().toArray(new String[] {});
        case SCOPE_SESSION:
        case SCOPE_GLOBAL_SESSION:
            // see https://issues.apache.org/jira/browse/FELIX-4206
            final Collection<String> keySet = (Collection<String>) session.get(null);
            return keySet.toArray(new String[] {});
        default:
            throw new IllegalArgumentException("Unknown scope: " + scope);
        }
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback,
            int scope) {
        if (scope == SCOPE_REQUEST) {
            registerRequestDestructionCallback(name, callback);
        } else {
            registerSessionDestructionCallback(name, callback);
        }
    }

    /**
     * Register the given callback as to be executed after session termination.
     * <p>Note: The callback object should be serializable in order to survive
     * web app restarts.
     * @param name the name of the attribute to register the callback for
     * @param callback the callback to be executed for destruction
     */
    protected void registerSessionDestructionCallback(String name, Runnable callback) {
//      HttpSession session = getSession(true);
//      session.setAttribute(DESTRUCTION_CALLBACK_NAME_PREFIX + name,
//              new DestructionCallbackBindingListener(callback));
    }

    @Override
    public Object resolveReference(String key) {
        if (REFERENCE_REQUEST.equals(key)) {
//          return this.request;
            return requestVars;
        }
        else if (REFERENCE_SESSION.equals(key)) {
            return session;
        }
        else {
            return null;
        }
    }

    @Override
    public String getSessionId() {
        return Integer.toHexString(session.hashCode());
    }

    @Override
    public Object getSessionMutex() {
        return null;
    }

    @Override
    protected void updateAccessedSessionAttributes() {
    }

}
spring-projects-issues commented 11 years ago

Juergen Hoeller commented

That stacktrace indicates that your bean instance implements the DisposableBean interface... But then, at invocation time, the implementation class doesn't seem to have an actual implementation of DisposableBean's destroy() method. I have only seen such an effect happening when the compiled version of an implementation class was out of sync with an interface that it implements... Not sure what the cause is in your case.

Juergen

spring-projects-issues commented 5 years ago

Bulk closing outdated, unresolved issues. Please, reopen if still relevant.