Подтвердить что ты не робот

Как создать контекст JNDI в Spring Загрузка с встроенным контейнером 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);

                    }
                });
            }
        }
    };
}

}

Я использую загрузку spring и пытаюсь запустить с помощью встроенного tomcat, который создает контекст JNDI для моих источников данных:

    <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>

Если я удалю @ImportResource, мое приложение запустится просто отлично. Я могу подключиться к экземпляру tomcat. Я могу проверить все конечные точки привода. Используя JConsole, я могу подключиться к приложению, я могу видеть свой источник данных в MBeans (Catalina → Resource → Context → "/" → localhost → javax.sql.DataSource → jdbc/mydatasource)

У меня также есть MBeans, показывающие через JConsole здесь (Tomcat → DataSource → /- > localhost → javax.sql.DataSource → jdbc/mydatasource)

Однако, когда я @ImportResource, что действительно ищет mydatasource через JNDI, он не находит его.

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

Соответствующая часть импортированного XML файла

Контекстный ресурс, который я настраиваю выше, имеет те же параметры, которые я использовал в контексте .xml, который развертывается при развертывании приложения в контейнере tomcat. Мой импортированный beans, и мое приложение работает правильно, когда оно развернуто в контейнер tomcat.

Итак, похоже, что у меня есть контекст сейчас, но не кажется, что именование правильное. Я пытался использовать различные комбинации имени ресурса, но в этом контексте не может генерировать "comp".

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
4b9b3361

Ответ 1

По умолчанию JNDI отключен во встроенном Tomcat, который вызывает NoInitialContextException. Чтобы включить его, вам необходимо вызвать Tomcat.enableNaming(). Самый простой способ сделать это - подкласс TomcatEmbeddedServletContainer:

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

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

Если вы примете этот подход, вы также можете зарегистрировать DataSource в JNDI, переопределив метод postProcessContext в подклассе TomcatEmbeddedServletContainerFactory.

context.getNamingResources().addResource добавляет ресурс в контекст java:comp/env, поэтому имя ресурса должно быть jdbc/mydatasource not java:comp/env/mydatasource.

Tomcat использует загрузчик классов контекста потока, чтобы определить, с каким контекстом JNDI должен выполняться поиск. Вы привязываете этот ресурс к контексту JNDI веб-приложения, поэтому вам нужно убедиться, что поиск выполняется, когда загрузчик класса веб-приложения является загрузчиком классов контекста потока. Вы должны быть в состоянии достичь этого, установив lookupOnStartup в false на jndiObjectFactoryBean. Вам также необходимо установить expectedType в 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>

Это создаст прокси для DataSource, при котором фактический поиск JNDI выполняется при первом использовании, а не при запуске контекста приложения.

Описанный выше подход иллюстрируется этим Spring образцом загрузки.

Ответ 2

В конце концов, я получил ответ благодаря Wikisona, сначала бобам:

@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();
}

полный код это здесь: https://github.com/wilkinsona/spring-boot-sample-tomcat-jndi

Ответ 3

Недавно у меня было требование использовать JNDI со встроенным Tomcat в Spring Boot.
Фактические ответы дают некоторые интересные подсказки для решения моей задачи, но этого было недостаточно, поскольку, вероятно, не обновлены для Spring Boot 2.

Вот мой вклад, протестированный с Spring Boot 2.0.3.RELEASE.

Указание источника данных, доступного в пути к классам во время выполнения

У вас есть несколько вариантов:

  • использование источника данных DBCP 2 (вы не хотите использовать устаревший и менее эффективный DBCP 1).
  • используя источник данных Tomcat JDBC.
  • используя любой другой источник данных: например, HikariCP.

Если вы не укажете никого из них, при конфигурации по умолчанию создание источника данных вызовет исключение:

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

  • Чтобы использовать источник данных Apache JDBC, вам не нужно добавлять никаких зависимостей, но вы должны изменить класс фабрики по умолчанию на org.apache.tomcat.jdbc.pool.DataSourceFactory.
    Вы можете сделать это в объявлении ресурса: resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory"); Я объясню ниже, где добавить эту строку.

  • Для использования источника данных DBCP 2 требуется зависимость:

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

Конечно, адаптируйте версию артефакта в соответствии с вашей встроенной версией Spring Boot Tomcat.

  • Чтобы использовать HikariCP, добавьте необходимую зависимость, если она еще не присутствует в вашей конфигурации (это может быть, если вы полагаетесь на постоянные загрузчики Spring Boot), такие как:

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

и укажите фабрику, которая используется в объявлении ресурса:

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

Конфигурация источника данных/декларация

Вы должны настроить bean-компонент, который создает экземпляр TomcatServletWebServerFactory.
Две вещи, которые нужно сделать:

  • включение именования JNDI, которое по умолчанию отключено

  • создание и добавление ресурсов JNDI в контексте сервера

Например, с PostgreSQL и источником данных DBCP 2:

@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);          
        }
    };
}

Вот варианты для источника данных Tomcat JDBC и HikariCP.

В postProcessContext() установите заводское свойство, как объяснялось ранее для 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);          
    }
};

и для HikariCP:

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

Использование/Внедрение источника данных

Теперь вы сможете найти InitialContext JNDI в любом месте, используя стандартный экземпляр InitialContext:

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

Вы также можете использовать JndiObjectFactoryBean of Spring для поиска ресурса:

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

Чтобы воспользоваться преимуществами контейнера DI, вы также можете сделать DataSource компонентом Spring:

@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();
}

И теперь вы можете внедрить DataSource в любые компоненты Spring, такие как:

@Autowired
private DataSource jndiDataSource;

Обратите внимание, что многие примеры в Интернете, по-видимому, отключают поиск ресурса JNDI при запуске:

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

Но я думаю, что это беспомощно, так как он вызывается сразу после afterPropertiesSet() который выполняет поиск!

Ответ 4

Обратите внимание, что вместо

public TomcatEmbeddedServletContainerFactory tomcatFactory()

Мне пришлось использовать следующую сигнатуру метода

public EmbeddedServletContainerFactory embeddedServletContainerFactory() 

Ответ 5

Вы пробовали @Lazy загрузку источника данных? Поскольку вы инициализируете встроенный контейнер Tomcat в контексте Spring, вам нужно отложить инициализацию вашего DataSource (пока не будут установлены JNDI-вары).

N.B. Я еще не успел протестировать этот код!

@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();
}

Вам также может потребоваться добавить аннотацию @Lazy везде, где используется DataSource. например.

@Lazy
@Autowired
private DataSource dataSource;

Ответ 6

В весенней загрузке 2.1 я нашел другое решение. Расширьте стандартный метод фабричного класса getTomcatWebServer. А затем верните его как боб из любого места.

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;
    }

Загрузка ресурсов из context.xml не работает, хотя. Постараюсь выяснить.