programing

Embedded Tomcat Container를 사용한Spring Boot에서의 JNDI 컨텍스트 작성 방법

lastmemo 2023. 3. 22. 20:38
반응형

Embedded Tomcat Container를 사용한Spring Boot에서의 JNDI 컨텍스트 작성 방법

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

                    }
                });
            }
        }
    };
}

}

스프링 부트를 사용하여 데이터 소스의 JNDI 컨텍스트를 만드는 내장 Tomcat을 사용하여 시작하려고 합니다.

    <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)에서 데이터 소스를 볼 수 있습니다.데이터 소스 -> jdbc/mydatasource)

또한 MBeans가 JConsole을 통해 여기에 표시됩니다(Tomcat -> DataSource -> /> localhost -> javax.sql).데이터 소스 -> jdbc/mydatasource)

그러나 실제로 JNDI를 통해 mydatasource를 찾고 있는 것을 Import Resource에서 검색해도 찾을 수 없습니다.

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

가져온 xml 파일의 관련 부분

위에서 설정하는 ContextResource는 context.xml에서 사용하던 파라미터와 완전히 동일합니다.이 파라미터는 어플리케이션이 Tomcat 컨테이너에 전개될 때 전개됩니다.Import한 콩과 어플리케이션은 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

가 디세블로 있기 「Tomcat」의 있습니다.NoInitialContextException활성화하려면 전화해야 합니다.이를 위한 가장 쉬운 방법은 다음과 같습니다.TomcatEmbeddedServletContainer★★★★★★★★★★★★★★★★★★:

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

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

하면, 「」를 .DataSource에서 JNDI를 postProcessContextTomcatEmbeddedServletContainerFactory서브클래스

context.getNamingResources().addResource 자원을 합니다.java:comp/env을 """로 합니다.jdbc/mydatasourcejava: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>

이렇게 하면 실제 JNDI 조회가 응용 프로그램 컨텍스트 시작 시가 아닌 처음 사용 시 수행되는 DataSource 프록시가 생성됩니다.

위의 접근방식은 이 스프링부트의 샘플에 설명되어 있습니다.

「스프링 부트」 「Tomcat」 「JNDI」 「Spring Boot」 「JNDI」 「Tomcat」
실제 답변은 작업을 해결하기 위한 몇 가지 흥미로운 힌트를 제공하지만 Spring Boot 2에 대해 업데이트되지 않았을 수 있기 때문에 충분하지 않았습니다.

다음은 Spring Boot 2.0.3에서 테스트한 성과입니다.풀어주다.

런타임에 클래스 경로에서 사용할 수 있는 데이터 원본 지정

여러 가지 선택지가 있습니다.

  • DBCP 2 데이터 소스를 사용합니다(오래되어 효율성이 떨어지는 DBCP 1은 사용하지 않습니다).
  • Tomcat JDBC 데이터 소스를 사용합니다.
  • using other data source : 예를 들어 HikariCP.

이들 중 누구도 지정하지 않으면 기본 설정으로 데이터 소스의 인스턴스화에 의해 예외가 발생합니다.

원인: javax.naming.Naming Exception:리소스 팩토리 인스턴스를 생성할 수 없습니다.org.disc.naming.factory에 있습니다.ResourceFactory.getDefaultFactory(ResourceFactory.java:50)org.disc.naming.factory에 있습니다.FactoryBase.getObjectInstance(FactoryBase).자바:90)javax.naming.spi에 있습니다.Naming Manager.getObjectInstance(NamingManager.java:321)org.disc.naming에서요.Naming Context.lookup(Naming Context.java:839)org.disc.naming에서요.Naming Context.lookup(Naming Context.java:159)org.disc.naming에서요.Naming Context.lookup(Naming Context.java:827)org.disc.naming에서요.Naming Context.lookup(Naming Context.java:159)org.disc.naming에서요.Naming Context.lookup(Naming Context.java:827)org.disc.naming에서요.Naming Context.lookup(Naming Context.java:159)org.disc.naming에서요.Naming Context.lookup(Naming Context.java:827)org.disc.naming에서요.Naming Context.lookup(Naming Context.java:173)org.disc.naming에서요.Selector Context.lookup(Selector Context.java:163)javax.naming에서.Initial Context.lookup(Initial Context.java:417)org.springframework.jndi에 있습니다.JndiTemplate.lambda$lookup$0(JndiTemplate).Java: 개요)org.springframework.jndi에 있습니다.JndiTemplate.execute(JndiTemplate).자바:91)org.springframework.jndi에 있습니다.JndiTemplate.lookup(JndiTemplate).Java: 개요)org.springframework.jndi에 있습니다.JndiTemplate.lookup(JndiTemplate).Java: 개요)org.springframework.jndi에 있습니다.JndiLocator 지원.룩업(JndiLocator 지원.java:96)org.springframework.jndi에 있습니다.JndiObjectLocator.lookup(JndiObjectLocator.java:114)org.springframework.jndi에 있습니다.JndiObjectTargetSource.getTarget(JndiObjectTargetSource.java:140)... 39개의 공통 프레임 생략원인: java.lang.ClassNotFoundException: org.apache.tomcat.dbcp.dbcp2.Basic Data Source Factoryjava.net 에 접속해 주세요.URLClassLoader.find Class(URL Class Loader.java:381)를 참조해 주세요.ClassLoader.loadClass(ClassLoader.java:424)해질녘에.misc에.Launcher$AppClassLoader.loadClass(Launcher.java:331)를 참조해 주세요.ClassLoader.loadClass(ClassLoader.java:357)를 참조해 주세요.Class.forName0(네이티브 메서드)를 참조해 주세요.Class.forName(Class.java:264)
org.disc.naming.factory에 있습니다.ResourceFactory.getDefaultFactory(ResourceFactory.java:47)... 58개의 공통 프레임 생략
  • 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.HikariJNDIFactory");

데이터 소스 구성/선언

커스터마이즈해야 합니다.TomcatServletWebServerFactory사례.
다음 두 가지 작업:

  • 기본적으로 비활성화되어 있는 JNDI 명명 사용

  • 서버 컨텍스트에서 JNDI 리소스 생성 및 추가

예를 들어 Postgre를 사용하는 경우SQL 및 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);          
    }
};

데이터 소스 사용/주입

이제 표준을 사용하여 어디서든 JNDI 리소스를 조회할 수 있습니다.InitialContext인스턴스:

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

를 사용할 수도 있습니다.JndiObjectFactoryBean자원 조회를 위한 Spring:

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

또한 DI 컨테이너를 활용하기 위해DataSource봄콩:

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

이제 Data Source를 다음과 같은 스프링 콩에 삽입할 수 있습니다.

@Autowired
private DataSource jndiDataSource;

인터넷상의 많은 예에서는 기동시에 JNDI 자원의 조회가 무효가 되어 있는 것에 주의해 주세요.

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

하지만 나는 그것이 바로 뒤에 호출되기 때문에 속수무책이라고 생각한다.afterPropertiesSet()그것은 검색을 합니다!

결국 나는 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

Spring Boot 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에서 리소스를 로드하는 것은 작동하지 않습니다.알아보겠습니다.

대신 주의해 주세요.

public TomcatEmbeddedServletContainerFactory tomcatFactory()

나는 다음과 같은 메서드 서명을 사용해야 했다.

public EmbeddedServletContainerFactory embeddedServletContainerFactory() 

해보셨어요?@Lazy데이터 소스를 로드하시겠습니까?Spring 컨텍스트 내에서 임베디드 Tomcat 컨테이너를 초기화하고 있기 때문에 이 컨테이너의 초기화를 지연시켜야 합니다.DataSource(JNDI var.

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데이터 소스를 사용하는 모든 위치에 주석을 달 수 있습니다(예:

@Lazy
@Autowired
private DataSource dataSource;

언급URL : https://stackoverflow.com/questions/24941829/how-to-create-jndi-context-in-spring-boot-with-embedded-tomcat-container

반응형