Синопсис
Иногда очень часто бины выполняют достаточно общие операции, это может быть обращение к базе данных или вывод чего-нибудь куда-нибудь, в общем в разных бинах приходится реализовывать схожую логику. В этом случае, если в разных приложениях, запускаемые в разных контейнерах, будет схожий функционал, то нам придется переписывать одинаковый код для разных бинов в разных приложениях. Поэтому хорошим решением будет написать некий общий бин, который будет использоваться во всех приложениях один раз и везде, где он будет требоваться просто подключать к этому приложению библиотеку с этим бином. В этом подходе есть только один недостаток, если бин аннотирован спринговыми аннотациями, а приложение, которому требуется логика из этого бина построено с использованием контейнера Google Guice, то такой бин становится не совместимым с этим приложением. Такой проблемы не будет, если этот бин будет старого типа, то есть POJO класс. В этом случае достаточно прописать этот бин в конфигурационном файле или в конфигурационном классе. Но если этот бин с аннотациями то его можно использовать только в том контейнере, который эти аннотации поддерживает. Чтобы было возможно использовать аннотированный бин в разных контейнерах, то его аннотации должны соответствовать аннотациям, которые поддерживаются всеми контейнерами. Одним из таких стандартов можно называть JSR-330 который мы рассмотрим в этом посте.
Пример проекта
Создадим 3 разных проекта. Два проекта будут запускаться в IoC контейнерах, которые будут называться Spring и Google guice и будут запускаться соответственно в spring и google guice контейнерах. Тритий проект будет называться JSR330 в котором будут создаваться независимые от контейнера бины. Каждое приложение, запущенное на spring или google guice будет зависеть от того бина, которой создается в проекте JSR330.
Схема проекта
Ниже представлены структуры всех трех проектов, так же показано какой класс от какого зависит:
Проект JSR-330
jsr330 ├─src │ └─main │ └─java │ └─com │ └─dev │ └─blogs │ └─jsr330 ┌─────────────────────────────┐ │ └─service ↓ │ │ ├─MessageService.java<────────────────────┼────────┐ │ └─impl │ │ │ └─MessageServiceImpl.java │ │ └─pom.xml │ │
Проект Spring
spring │ │ ├─src │ │ │ └─main Класс ServiceConsumer обращается │ │ │ ├─java к классу MessageServiceImpl │ │ │ │ └─com через интерфейс MessageService │ │ │ │ └─dev │ │ │ │ └─blogs │ │ │ │ └─spring │ │ │ │ ├─App.java │ │ │ │ └─ServiceConsumer.java────────────────────────┘ │ │ └─resources │ │ └─lookup.xml │ └─pom.xml │
Проект Google guice
google-guice │ ├─src │ │ └─main │ │ └─java │ │ └─com │ Класс ServiceConsumer обращается │ └─dev │ к классу MessageServiceImpl │ └─blogs │ через интерфейс MessageService │ └─google │ │ └─guice │ │ ├─App.java │ │ ├─Configurer.java │ │ └─ServiceConsumer.java─────────────────────────────┘ └─pom.xml
Обратим внимание, что у контейнеров spring и google guice разные инфраструктуры, и, соответственно, они по разному работают с сёрд пати бином. Приложение на спринге использует для этого конфигурационный xml файл в котором прописывается информация из какого класса создается бин. Так же это делается в приложении гугл гуйс, но для этого используется конфигурационный класс Configurer. При этом проекту JSR-330 все равно, какой контейнер и как будет создавать бин из класса, который он предоставляет.
Проект JSR-330
Интерфейс MessageService.java
package com.dev.blogs.jsr330.service; public interface MessageService { public String getMessage(); }
Класс MessageServiceImpl.java
package com.dev.blogs.jsr330.service.impl; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import com.dev.blogs.jsr330.service.MessageService; @Named("messageService") @Singleton public class MessageServiceImpl implements MessageService { @Inject @Named("message") private String message = "Default message"; public String getMessage() { return message; } }
Конфигурационный файл pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.dev.blogs</groupId> <artifactId>jsr330</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>jsr330</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> </project>
Проект Spring
Класс ServiceConsumer.java
package com.dev.blogs.spring; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import com.dev.blogs.jsr330.service.MessageService; @Named("serviceConsumer") @Singleton public class ServiceConsumer { @Inject private MessageService messageService; public void print() { System.out.println(messageService.getMessage()); } }
Класс App.java
package com.dev.blogs.spring; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.dev.blogs.jsr330.service.MessageService; public class App { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("lookup.xml"); context.refresh(); ServiceConsumer serviceConsumer = context.getBean("serviceConsumer", ServiceConsumer.class); serviceConsumer.print(); } }
Конфигурационный файл lookup.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd"> <context:component-scan base-package="com.dev.blogs"/> <bean id="message" class="java.lang.String"> <constructor-arg value="Spring. Test message"/> </bean> </beans>
В билд файл добавлена зависимость на джарник созданный в проекте JSR-330 строчки 19-23:
Конфигурационный файл pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.dev.blogs</groupId> <artifactId>spring</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>spring</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>3.2.13.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>com.dev.blogs</groupId> <artifactId>jsr330</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.2.1</version> <executions> <execution> <goals> <goal>java</goal> </goals> </execution> </executions> <configuration> <mainClass>com.dev.blogs.spring.App</mainClass> <arguments> <argument>foo</argument> <argument>bar</argument> </arguments> </configuration> </plugin> </plugins> </build> </project>
Здесь с помощью тега build мавен подтягивает плагин с помощью которого мы позже запустим джарник как обычное приложение, то есть этот плагин позволят запустить мэйн метод.
Проект Google guice
Класс ServiceConsumer.java
package com.dev.blogs.google.guice; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import com.dev.blogs.jsr330.service.MessageService; @Named("serviceConsumer") @Singleton public class ServiceConsumer { @Inject private MessageService messageService; public void printMessage() { System.out.println(messageService.getMessage()); } }
Класс Configurer.java
package com.dev.blogs.google.guice; import com.dev.blogs.jsr330.service.MessageService; import com.dev.blogs.jsr330.service.impl.MessageServiceImpl; import com.google.inject.AbstractModule; import com.google.inject.name.Names; import com.google.inject.util.Providers; public class Configurer extends AbstractModule { @Override protected void configure() { bind(MessageService.class).to(MessageServiceImpl.class); bind(String.class).annotatedWith(Names.named("message")).toProvider(Providers.<String>of("Google guice. Test message")); } }
Класс App.java
package com.dev.blogs.google.guice; import com.google.inject.Guice; import com.google.inject.Injector; public class App { public static void main(String[] args) { Injector injector = Guice.createInjector(new Configurer()); ServiceConsumer serviceConsumer = injector.getInstance(ServiceConsumer.class); serviceConsumer.printMessage(); } }
В билд файл добавлена зависимость на джарник созданный в проекте JSR-330 строчки 19-23:
Конфигурационный файл pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.dev.blogs</groupId> <artifactId>google.guice</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>google.guice</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <google.guice.version>3.0</google.guice.version> </properties> <dependencies> <dependency> <groupId>com.dev.blogs</groupId> <artifactId>jsr330</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.google.inject</groupId> <artifactId>guice</artifactId> <version>${google.guice.version}</version> </dependency> <dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.2.1</version> <executions> <execution> <goals> <goal>java</goal> </goals> </execution> </executions> <configuration> <mainClass>com.dev.blogs.google.guice.App</mainClass> <arguments> <argument>foo</argument> <argument>bar</argument> </arguments> </configuration> </plugin> </plugins> </build> </project>
Так же как для проекта Spring тег build подтягивает плагин, с помощью которого мы запустим мэйн метод.
Сборка и запуск
Для того, чтобы проекты Spring и Google-guice можно было собрать и запустить им нужна зависимость на джарник из проекта JSR-330. По умолчанию, после сборки проекта JSR-330 его джарник положится в локальный мавенский репозиторий. А так как все проекты собираются на той же машине и на любую зависимость в локальном репозитории установлен класспасс, то проекты, которым нужен джарник JSR-330 соберутся без проблем, нужно только в помчике прописать зависимость на JSR-330:
<dependency> <groupId>com.dev.blogs</groupId> <artifactId>jsr330</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
Но если проект JSR-330 был собран на другой машине, затем мы взяли собранный джарник и перенесли его на текущую машину (то есть на машину, где собираются проекты spring и google-guice), то для того, чтобы собрать эти проекты (spring и google-guice, которые зависят от JSR-330), этот джарник (JSR-330) нужно положить в локальный репозиторий вручную. Положить вручную это не значит, что просто взять его туда и скопировать, нужно положить его с помощью мавена, тогда мавен будет знать о нём. Кладется сёрд пати джарник в локальный репозиторий с помощью мавенской команды install:install-file. Чтобы положить наш получившийся после сборки проекта JSR-330 джарник нужно выполнить такую команду:
mvn install:install-file -Dfile=C:\practice\google-guice\jsr330\target\jsr330-0.0.1-SNAPSHOT.jar -DgroupId=com.dev.blogs -DartifactId=jsr330 -Dversion=0.0.1-SNAPSHOT -Dpackaging=jar
После выполнения этой команды в локальном репозитории появится джарник jsr330-0.0.1-SNAPSHOT.jar и класспас, для мавенских сборок, будет ссылаться на этот джарник. Вот тогда можно будет собирать любые проекты зависящие от этой зависимости. Еще раз повторю, делать так надо только в том случае, если проект JSR-330 был собран на другой машине.
А теперь соберем все имеющиеся у нас проекты, начнем с проекта JSR-330:
Сборка и запуск проекта JSR-330
cd /path/to/JSR-330 mvn install
Если будет BUILD SUCCESS, то джарник собран и положен в локальный репозиторий. Теперь соберем остальные проекты, которые от него зависят:
Сборка и запуск проекта Spring
cd /path/to/Spring mvn install mvn exec:java -Dexec.mainClass="com.dev.blogs.spring.App"
Результатом должна быть строка Spring. Test message:
[INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building spring 0.0.1-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] >>> exec-maven-plugin:1.2.1:java (default-cli) > validate @ spring >>> [INFO] [INFO] <<< exec-maven-plugin:1.2.1:java (default-cli) < validate @ spring <<< [INFO] [INFO] --- exec-maven-plugin:1.2.1:java (default-cli) @ spring --- Здесь будет всякий спринговый мусорSpring. Test message [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1.650 s [INFO] Finished at: 2015-05-20T16:44:05+03:00 [INFO] Final Memory: 6M/134M [INFO] ------------------------------------------------------------------------
Сборка и запуск проекта Google-guice
cd /path/to/Google-guice mvn install mvn exec:java -Dexec.mainClass="com.dev.blogs.google.guice.App"
Результатом должна быть строка Google guice. Test message:
[INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building google.guice 0.0.1-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] >>> exec-maven-plugin:1.2.1:java (default-cli) > validate @ google.guice >>> [INFO] [INFO] <<< exec-maven-plugin:1.2.1:java (default-cli) < validate @ google.guice <<< [INFO] [INFO] --- exec-maven-plugin:1.2.1:java (default-cli) @ google.guice ---Google guice. Test message [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 0.939 s [INFO] Finished at: 2015-05-20T16:39:47+03:00 [INFO] Final Memory: 9M/155M [INFO] ------------------------------------------------------------------------
Весь проект можно взять с gitHub: https://github.com/dev-blogs/jsr330