Jak utworzyć kontekst JNDI w Spring Boot z wbudowanym kontenerem Tomcat

import org.apache.catalina.Context;
import org.apache.catalina.deploy.ContextResource;
import org.apache.catalina.startup.Tomcat;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

@Configuration
@EnableAutoConfiguration
@ComponentScan
@ImportResource("classpath:applicationContext.xml")
public class Application {

    public static void main(String[] args) throws Exception {
        new SpringApplicationBuilder()
                .showBanner(false)
                .sources(Application.class)
                .run(args);
}

@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
    return new TomcatEmbeddedServletContainerFactory() {
        @Override
        protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
                Tomcat tomcat) {
            tomcat.enableNaming();
            return super.getTomcatEmbeddedServletContainer(tomcat);
        }
    };
}

@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {
    return new EmbeddedServletContainerCustomizer() {
        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            if (container instanceof TomcatEmbeddedServletContainerFactory) {
                TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory = (TomcatEmbeddedServletContainerFactory) container;
                tomcatEmbeddedServletContainerFactory.addContextCustomizers(new TomcatContextCustomizer() {
                    @Override
                    public void customize(Context context) {
                        ContextResource mydatasource = new ContextResource();
                        mydatasource.setName("jdbc/mydatasource");
                        mydatasource.setAuth("Container");
                        mydatasource.setType("javax.sql.DataSource");
                        mydatasource.setScope("Sharable");
                        mydatasource.setProperty("driverClassName", "oracle.jdbc.driver.OracleDriver");
                        mydatasource.setProperty("url", "jdbc:oracle:thin:@mydomain.com:1522:myid");
                        mydatasource.setProperty("username", "myusername");
                        mydatasource.setProperty("password", "mypassword");

                        context.getNamingResources().addResource(mydatasource);

                    }
                });
            }
        }
    };
}

}

Używam spring boot i próbuję uruchomić z wbudowanym tomcat, który tworzy kontekst JNDI dla moich źródeł danych:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
        <version>1.1.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <version>1.1.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-oracle</artifactId>
        <version>1.0.0.RELEASE</version>
    </dependency>

Jeśli usunę @ ImportResource moja aplikacja uruchomi się dobrze. Mogę połączyć się z instancją tomcat. Mogę sprawdzić wszystkie punkty końcowe siłownika. Używając JConsole, mogę połączyć się z aplikacją mogę zobaczyć moje źródło danych w MBeans (Catalina -> Resource - > Context - > " / " - > localhost - > javax.sql.DataSource - >jdbc/mydatasource)

Mam również MBeans wyświetlane, przez JConsole, tutaj (Tomcat - > DataSource - > / - > localhost - > javax.sql.DataSource - > jdbc/mydatasource)

Jednak, kiedy @ImportResource to, co faktycznie szuka mydatasource przez JNDI, nie znajduje go.

<bean id="myDS" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/mydatasource"/>
</bean>

odpowiednia część mojego importowanego pliku xml

ContextResource, które konfiguruję powyżej, ma dokładnie te same parametry, których używałem w kontekście.xml czyli wdrożenie po wdrożeniu aplikacji w kontenerze tomcat. Moje zaimportowane fasolki i moja aplikacja działają poprawnie po wdrożeniu do kontenera tomcat.

Wygląda na to, że mam teraz kontekst, ale nie wygląda na to, że nazewnictwo jest właściwe. Próbowałem różnych kombinacji nazwy zasobu, ale nie wydaje się, aby wygenerować "comp" związany w tym kontekście.

Caused by: javax.naming.NameNotFoundException: Name [comp/env/jdbc/mydatasource] is not bound in this Context. Unable to find [comp].
    at org.apache.naming.NamingContext.lookup(NamingContext.java:819)
    at org.apache.naming.NamingContext.lookup(NamingContext.java:167)
    at org.apache.naming.SelectorContext.lookup(SelectorContext.java:156)
    at javax.naming.InitialContext.lookup(InitialContext.java:392)
    at org.springframework.jndi.JndiTemplate$1.doInContext(JndiTemplate.java:155)
    at org.springframework.jndi.JndiTemplate.execute(JndiTemplate.java:87)
    at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:152)
    at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:179)
    at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:95)
    at org.springframework.jndi.JndiObjectLocator.lookup(JndiObjectLocator.java:106)
    at org.springframework.jndi.JndiObjectFactoryBean.lookupWithFallback(JndiObjectFactoryBean.java:231)
    at org.springframework.jndi.JndiObjectFactoryBean.afterPropertiesSet(JndiObjectFactoryBean.java:217)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1612)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1549)
    ... 30 more
Author: 0x5a4d, 2014-07-24

6 answers

Domyślnie JNDI jest wyłączony w wbudowanym Tomcat, co powoduje NoInitialContextException. Musisz zadzwonić.Tomcat.enableNaming() aby go włączyć. Najprostszym sposobem na to jest użycie TomcatEmbeddedServletContainer podklasy:

@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
    return new TomcatEmbeddedServletContainerFactory() {

        @Override
        protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
                Tomcat tomcat) {
            tomcat.enableNaming();
            return super.getTomcatEmbeddedServletContainer(tomcat);
        }
    };
}

Jeśli zastosujesz takie podejście, możesz również zarejestrować DataSource w JNDI, nadpisując metodę postProcessContext w swojej podklasie TomcatEmbeddedServletContainerFactory.

context.getNamingResources().addResource dodaje zasób do kontekstu java:comp/env, więc jego nazwa powinna być jdbc/mydatasource, a nie java:comp/env/mydatasource.

Tomcat używa klasy thread context loader do określ, z jakim kontekstem JNDI powinno być wykonywane wyszukiwanie. Wiążesz zasób z kontekstem JNDI aplikacji sieci web, więc musisz upewnić się, że wyszukiwanie jest wykonywane, gdy program ładujący klasy aplikacji sieci web jest programem ładującym klasy kontekstu wątku. Powinieneś być w stanie to osiągnąć, ustawiając lookupOnStartup na false na jndiObjectFactoryBean. Musisz również ustawić expectedType na javax.sql.DataSource:

<bean class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/mydatasource"/>
    <property name="expectedType" value="javax.sql.DataSource"/>
    <property name="lookupOnStartup" value="false"/>
</bean>

Spowoduje to utworzenie serwera proxy dla źródła danych, w którym rzeczywiste wyszukiwanie JNDI będzie wykonywane przy pierwszym użyciu, a nie podczas uruchamiania kontekstu aplikacji.

Opisane powyżej podejście jest zilustrowane w tej próbce Spring Boot.

 60
Author: Andy Wilkinson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-23 12:09:06

[20]} ostatnio miałem wymóg używania JNDI z wbudowanym Tomcatem w wiosennym bagażniku.
Rzeczywiste odpowiedzi dają kilka ciekawych wskazówek, aby rozwiązać moje zadanie, ale to nie wystarczyło, ponieważ prawdopodobnie nie zaktualizowano Spring Boot 2.

Oto mój wkład testowany Z Spring Boot 2.0.3.Uwolnij.

Określenie źródła danych dostępnego w classpath w czasie wykonywania

Masz wiele możliwości:

  • korzystanie z DBCP 2 datasource (nie chcesz używać DBCP 1, który jest przestarzały i mniej wydajny).
  • korzystanie z Tomcat JDBC datasource.
  • korzystanie z dowolnego innego źródła danych : na przykład HikariCP.

Jeśli nie podasz żadnego z nich, przy domyślnej konfiguracji instancja źródła danych rzuci wyjątek:

Caused by: javax.naming.NamingException: Could not create resource factory instance
        at org.apache.naming.factory.ResourceFactory.getDefaultFactory(ResourceFactory.java:50)
        at org.apache.naming.factory.FactoryBase.getObjectInstance(FactoryBase.java:90)
        at javax.naming.spi.NamingManager.getObjectInstance(NamingManager.java:321)
        at org.apache.naming.NamingContext.lookup(NamingContext.java:839)
        at org.apache.naming.NamingContext.lookup(NamingContext.java:159)
        at org.apache.naming.NamingContext.lookup(NamingContext.java:827)
        at org.apache.naming.NamingContext.lookup(NamingContext.java:159)
        at org.apache.naming.NamingContext.lookup(NamingContext.java:827)
        at org.apache.naming.NamingContext.lookup(NamingContext.java:159)
        at org.apache.naming.NamingContext.lookup(NamingContext.java:827)
        at org.apache.naming.NamingContext.lookup(NamingContext.java:173)
        at org.apache.naming.SelectorContext.lookup(SelectorContext.java:163)
        at javax.naming.InitialContext.lookup(InitialContext.java:417)
        at org.springframework.jndi.JndiTemplate.lambda$lookup$0(JndiTemplate.java:156)
        at org.springframework.jndi.JndiTemplate.execute(JndiTemplate.java:91)
        at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:156)
        at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:178)
        at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:96)
        at org.springframework.jndi.JndiObjectLocator.lookup(JndiObjectLocator.java:114)
        at org.springframework.jndi.JndiObjectTargetSource.getTarget(JndiObjectTargetSource.java:140)
        ... 39 common frames omitted
Caused by: java.lang.ClassNotFoundException: org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:264)
        at org.apache.naming.factory.ResourceFactory.getDefaultFactory(ResourceFactory.java:47)
        ... 58 common frames omitted

  • Aby używać Apache JDBC datasource, nie musisz dodawać żadnych zależności, ale musisz zmienić domyślną klasę factory na org.apache.tomcat.jdbc.pool.DataSourceFactory.
    Możesz to zrobić w zasobie deklaracja : resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory"); Wyjaśnię poniżej, gdzie dodać tę linię.

  • Aby użyć DBCP 2 datasource wymagana jest zależność:

    <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-dbcp</artifactId> <version>8.5.4</version> </dependency>

Oczywiście, dostosuj wersję artefaktu zgodnie z wersją osadzoną Spring Boot Tomcat.

  • Aby użyć HikariCP, dodaj wymaganą zależność, jeśli nie jest jeszcze obecna w Twojej konfiguracji (może być, jeśli polegasz na trwałościach starterów Spring Boot), takich jak :

    <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.1.0</version> </dependency>

I określ fabrykę, z którą idzie w deklaracji zasobów:

resource.setProperty("factory", "com.zaxxer.hikari.HikariJNDIFactory");

Datasource configuration / declaration

Musisz dostosować fasolę, która tworzy instancję TomcatServletWebServerFactory.
Dwie rzeczy do zrobienia:

  • Włączanie nazw JNDI, które jest domyślnie wyłączone

  • Tworzenie i dodawanie zasobów JNDI w kontekście serwera{[22]]}

Na przykład z PostgreSQL i DBCP 2 datasource, zrób to:

@Bean
public TomcatServletWebServerFactory tomcatFactory() {
    return new TomcatServletWebServerFactory() {
        @Override
        protected TomcatWebServer getTomcatWebServer(org.apache.catalina.startup.Tomcat tomcat) {
            tomcat.enableNaming(); 
            return super.getTomcatWebServer(tomcat);
        }

        @Override 
        protected void postProcessContext(Context context) {

            // context
            ContextResource resource = new ContextResource();
            resource.setName("jdbc/myJndiResource");
            resource.setType(DataSource.class.getName());
            resource.setProperty("driverClassName", "org.postgresql.Driver");

            resource.setProperty("url", "jdbc:postgresql://hostname:port/dbname");
            resource.setProperty("username", "username");
            resource.setProperty("password", "password");
            context.getNamingResources()
                   .addResource(resource);          
        }
    };
}

Tutaj warianty dla Tomcat JDBC i HikariCP datasource.

W postProcessContext() ustaw właściwość fabryczną tak, jak wyjaśniono wcześniej dla Tomcat JDBC ds:

    @Override 
    protected void postProcessContext(Context context) {
        ContextResource resource = new ContextResource();       
        //...
        resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory");
        //...
        context.getNamingResources()
               .addResource(resource);          
    }
};

I dla HikariCP:

    @Override 
    protected void postProcessContext(Context context) {
        ContextResource resource = new ContextResource();       
        //...
        resource.setProperty("factory", "com.zaxxer.hikari.HikariDataSource");
        //...
        context.getNamingResources()
               .addResource(resource);          
    }
};

Używanie / wstrzykiwanie źródła danych

Teraz powinieneś być w stanie wyszukać JNDI ressource w dowolnym miejscu, używając standardowej instancji InitialContext:

InitialContext initialContext = new InitialContext();
DataSource datasource = (DataSource) initialContext.lookup("java:comp/env/jdbc/myJndiResource");

Możesz również użyć JndiObjectFactoryBean sprężyny, aby wyszukać źródło:

JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiName("java:comp/env/jdbc/myJndiResource");
bean.afterPropertiesSet();
DataSource object = (DataSource) bean.getObject();

Aby skorzystać z pojemnika DI możesz również zrobić DataSource wiosenną fasolkę:

@Bean(destroyMethod = "")
public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
    JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
    bean.setJndiName("java:comp/env/jdbc/myJndiResource");
    bean.afterPropertiesSet();
    return (DataSource) bean.getObject();
}

I tak możesz teraz wprowadzić źródło danych do dowolnych fasolek wiosennych, takich jak:

@Autowired
private DataSource jndiDataSource;

Zauważ, że wiele przykładów w Internecie wydaje się wyłączać wyszukiwanie zasobu JNDI przy starcie:

bean.setJndiName("java:comp/env/jdbc/myJndiResource");
bean.setProxyInterface(DataSource.class);
bean.setLookupOnStartup(false);
bean.afterPropertiesSet(); 

Ale myślę, że jest bezradny, ponieważ wywołuje tuż po afterPropertiesSet() to robi wyszukiwanie !

 17
Author: davidxxx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-02 08:58:06

W końcu dostałem odpowiedź dzięki wikisonie, najpierw fasolki:

@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
    return new TomcatEmbeddedServletContainerFactory() {

        @Override
        protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
                Tomcat tomcat) {
            tomcat.enableNaming();
            return super.getTomcatEmbeddedServletContainer(tomcat);
        }

        @Override
        protected void postProcessContext(Context context) {
            ContextResource resource = new ContextResource();
            resource.setName("jdbc/myDataSource");
            resource.setType(DataSource.class.getName());
            resource.setProperty("driverClassName", "your.db.Driver");
            resource.setProperty("url", "jdbc:yourDb");

            context.getNamingResources().addResource(resource);
        }
    };
}

@Bean(destroyMethod="")
public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
    JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
    bean.setJndiName("java:comp/env/jdbc/myDataSource");
    bean.setProxyInterface(DataSource.class);
    bean.setLookupOnStartup(false);
    bean.afterPropertiesSet();
    return (DataSource)bean.getObject();
}

Pełny kod jest tutaj: https://github.com/wilkinsona/spring-boot-sample-tomcat-jndi

 12
Author: nekperu15739,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-06 22:12:55

W SpringBoot 2.1 znalazłem inne rozwiązanie. Rozszerzenie standardowej metody gettomcatwebserver klasy fabrycznej. A potem zwróć go jako fasolę z dowolnego miejsca.

public class CustomTomcatServletWebServerFactory extends TomcatServletWebServerFactory {

    @Override
    protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
        System.setProperty("catalina.useNaming", "true");
        tomcat.enableNaming();
        return new TomcatWebServer(tomcat, getPort() >= 0);
    }
}

@Component
public class TomcatConfiguration {
    @Bean
    public ConfigurableServletWebServerFactory webServerFactory() {
        TomcatServletWebServerFactory factory = new CustomTomcatServletWebServerFactory();

        return factory;
    }

Ładowanie zasobów z kontekstu.xml jednak nie działa. Spróbuje się dowiedzieć.

 5
Author: gotozero,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-11-25 14:58:01

Uwaga zamiast

public TomcatEmbeddedServletContainerFactory tomcatFactory()

Musiałem użyć następującej metody

public EmbeddedServletContainerFactory embeddedServletContainerFactory() 
 1
Author: jerome,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-22 09:16:37

Próbowałeś @Lazy załadować źródło danych? Ponieważ inicjalizujesz osadzony kontener Tomcat w kontekście Spring, musisz opóźnić inicjalizację DataSource (dopóki nie zostaną skonfigurowane var-y JNDI).

N. B. nie miałem jeszcze okazji przetestować tego kodu!

@Lazy
@Bean(destroyMethod="")
public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
    JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
    bean.setJndiName("java:comp/env/jdbc/myDataSource");
    bean.setProxyInterface(DataSource.class);
    //bean.setLookupOnStartup(false);
    bean.afterPropertiesSet();
    return (DataSource)bean.getObject();
}

Może być również konieczne dodanie adnotacji @Lazy wszędzie tam, gdzie używane jest źródło danych. np.

@Lazy
@Autowired
private DataSource dataSource;
 0
Author: Nick Grealy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-04 07:03:27