Статьи

Spring Integration — Использование канальных адаптеров RMI

1. Введение

В этой статье объясняется, как отправлять и получать сообщения через RMI с помощью адаптеров канала RMI Spring Integration. Он состоит из следующих разделов:

  • Реализация сервиса: первый раздел посвящен созданию и представлению сервиса.
  • Реализация клиента: показывает, как вызвать службу с помощью класса MessagingTemplate.
  • Абстрагирование логики SI: Наконец, я добавил еще один раздел, объясняющий, как реализовать тот же клиент, абстрагирующий весь код Spring Integration, оставляя клиента сосредоточенным на его бизнес-логике.

Вы можете получить исходный код на GitHub .

2. Реализация сервиса

Эта первая часть довольно проста. Сервис определяется с помощью аннотаций, поэтому он будет автоматически определяться при сканировании компонента. В него встроен репозиторий, который получает данные из встроенной базы данных, как будет показано в этом же разделе:

01
02
03
04
05
06
07
08
09
10
@Service("defaultEmployeeService")
public class EmployeeServiceImpl implements EmployeeService {
    @Autowired
    private EmployeeRepository employeeRepository;
 
    @Override
    public Employee retrieveEmployee(int id) {
        return employeeRepository.getEmployee(id);
    }
}

Репозиторий выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Repository
public class EmployeeRepositoryImpl implements EmployeeRepository {
    private JdbcTemplate template;
    private RowMapper<Employee> rowMapper = new EmployeeRowMapper();
    private static final String SEARCH = "select * from employees where id = ?";
    private static final String COLUMN_ID = "id";
    private static final String COLUMN_NAME = "name";
 
    @Autowired
    public EmployeeRepositoryImpl(DataSource dataSource) {
        this.template = new JdbcTemplate(dataSource);
    }
 
    public Employee getEmployee(int id) {
        return template.queryForObject(SEARCH, rowMapper, id);
    }
 
    private class EmployeeRowMapper implements RowMapper<Employee> {
        public Employee mapRow(ResultSet rs, int i) throws SQLException {
            Employee employee = new Employee();
            employee.setId(rs.getInt(COLUMN_ID));
            employee.setName(rs.getString(COLUMN_NAME));
 
            return employee;
        }
    }
}

Следующая конфигурация предоставляет сервис через RMI:

Сервер-config.xml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<context:component-scan base-package="xpadro.spring.integration"/>
 
<int-rmi:inbound-gateway request-channel="requestEmployee"/>
 
<int:channel id="requestEmployee"/>
 
<int:service-activator method="retrieveEmployee" input-channel="requestEmployee" ref="defaultEmployeeService"/>
 
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
 
<!-- in-memory database -->
<jdbc:embedded-database id="dataSource">
    <jdbc:script location="classpath:db/schemas/schema.sql" />
    <jdbc:script location="classpath:db/schemas/data.sql" />
</jdbc:embedded-database>

Давайте сосредоточимся на строках с пространством имен ‘int’:

Функция шлюза состоит в том, чтобы отделить систему обмена сообщениями от остальной части приложения. Таким образом, это скрыто от бизнес-логики. Шлюз является двунаправленным, поэтому у вас есть:

  • Входящий шлюз: переносит сообщение в приложение и ожидает ответа.
  • Исходящий шлюз: вызывает внешнюю систему и отправляет ответ обратно приложению.

В этом примере мы используем входящий шлюз RMI . Он получит сообщение через RMI и отправит его на канал requestEmployee, который также определен здесь.

Наконец, активатор службы позволяет вам подключать пружинный компонент к каналу сообщений. Здесь он подключен к каналу requestEmployee. Сообщение поступит на канал, и активатор службы вызовет метод retrieveEmployee. Учтите, что атрибут ‘method’ необязателен, если у bean-компонента есть только один открытый метод или метод, аннотированный @ServiceActivator.

Ответ будет отправлен на канал ответа. Поскольку мы не определили этот канал, он создаст временный канал ответа.

temporaryChannel

3. Реализация клиента

Клиент, которого мы собираемся реализовать, будет вызывать службу для получения сотрудника. Для этого он будет использовать класс MessagingTemplate :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:xpadro/spring/integration/test/config/client-config.xml"})
public class TestRmiClient {
    @Autowired
    MessageChannel localChannel;
 
    @Autowired
    MessagingTemplate template;
 
    @Test
    public void retrieveExistingEmployee() {
        Employee employee = (Employee) template.convertSendAndReceive(localChannel, 2);
 
        Assert.assertNotNull(employee);
        Assert.assertEquals(2, employee.getId());
        Assert.assertEquals("Bruce Springsteen", employee.getName());
    }
}

Клиент использует messagingTemplate для преобразования объекта Integer в Сообщение и отправки его на локальный канал. Как показано ниже, есть исходящий шлюз, подключенный к локальному каналу. Этот исходящий шлюз отправит сообщение с запросом через RMI.

1
2
3
4
5
<int-rmi:outbound-gateway request-channel="localChannel" remote-channel="requestEmployee" host="localhost"/>
 
<int:channel id="localChannel"/>
 
<bean class="org.springframework.integration.core.MessagingTemplate" />

4. Абстрагирование логики СИ

В предыдущем разделе вы, возможно, заметили, что клиентский класс, который обращается к службе, имеет специальную логику Spring Integration, смешанную с его бизнес-кодом:

  • Он использует MessagingTemplate, который является классом SI.
  • Он знает о локальном канале, который специфичен для системы обмена сообщениями

В этом разделе я реализую этот же пример, абстрагируя логику обмена сообщениями, поэтому клиент будет заботиться только о своей бизнес-логике.

Во-первых, давайте посмотрим на нового клиента:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:xpadro/spring/integration/test/config/client-gateway-config.xml"})
public class TestRmiGatewayClient {
    @Autowired
    private EmployeeService service;
 
    @Test
    public void retrieveExistingEmployee() {
        Employee employee = service.retrieveEmployee(2);
 
        Assert.assertNotNull(employee);
        Assert.assertEquals(2, employee.getId());
        Assert.assertEquals("Bruce Springsteen", employee.getName());
    }
}

Теперь мы видим, что клиент просто реализует свою бизнес-логику, не используя ни каналы сообщений, ни шаблоны сообщений. Он просто вызовет интерфейс сервиса. Все определения сообщений находятся в файле конфигурации.

1
2
3
4
5
6
<int-rmi:outbound-gateway request-channel="localChannel" remote-channel="requestEmployee" host="localhost"/>
 
<int:channel id="localChannel"/>
 
<int:gateway default-request-channel="localChannel"
    service-interface="xpadro.spring.integration.service.EmployeeService"/>

клиент-шлюз-config.xml

Здесь мы добавили шлюз, который будет перехватывать вызовы к интерфейсу сервиса EmployeeService. Spring Integration будет использовать класс GatewayProxyFactoryBean для создания прокси вокруг интерфейса службы. Этот прокси будет использовать шаблон обмена сообщениями для отправки вызова на канал запроса и ожидания ответа.

gatewayProxyFactoryBean

5. Заключение

Мы видели, как использовать Spring Integration для доступа к сервису через RMI. Мы также видели, что мы можем не только явно отправлять сообщения, используя MessagingTemplate, но и делать это прозрачно с GatewayProxyFactoryBean.