вторник, 28 февраля 2017 г.

Вызов Rest сервиса в Java

Рассмотрим способо обращения к Rest-сервису, на примере микросервисов. Так же в скользь будет рассмотрен способ избежения дублирования кода в микросервисах. Я использую данную статью, позже, планирую ее перевести.

Первым делом реализуем библиотечную часть, которая будет использоваться во всех частях микросервиса для обмена.
package com.example.user.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by bartoszjedrzejewski on 08/06/2016.
 */
@JsonIgnoreProperties(ignoreUnknown = true)
public class UserView {

    private long id;
    private String forename;
    private String surname;
    private String organisation;
    private List notifications;
    private long points;

    public UserView(){

    }

    public long getId() {
        return id;
    }

    public String getForename() {
        return forename;
    }

    public String getSurname() {
        return surname;
    }

    public String getOrganisation() {
        return organisation;
    }

    public List getNotifications() {
        return notifications;
    }

    public long getPoints() {
        return points;
    }
}

и pom-файл для мавена:

    4.0.0

    com.example
    user-client-libs
    0.0.1-SNAPSHOT
    
        
            com.fasterxml.jackson.core
            jackson-annotations
            2.6.6
        
    



Чтобы была возможность использовать данный класс, необходимо выполнить в консоли mvn clean install


Теперь рассмотрим микросервис, предоставляющий информацию о пользователе.

Пользователь:
package com.example;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by bartoszjedrzejewski on 08/06/2016.
 */
public class User {

    private final long id;
    private final String forename;
    private final String surname;
    private final String organisation;
    private final List notifications;
    private final long points;
    //Friends are deprecated and should not be used
    private final List friends;

    public User(int id) {
        String[] forenames = {"Alice", "Manjula", "Bartosz", "Mack"};
        String[] surnames = {"Smith", "Salvatore", "Jedrzejewski", "Scott"};
        String[] organisations = {"ScottLogic", "UNICEF"};

        forename = forenames[id%3];
        surname = surnames[id%4];
        organisation = organisations[id%2];
        notifications= new ArrayList<>();
        notifications.add("You have been promoted!");
        notifications.add("Sorry, disregard the previous notifaction- wrong user");
        points = id * 31 % 1000;

        //You have no friends
        friends = new ArrayList<>();

        this.id = id;
    }

    public long getId() {
        return id;
    }

    public String getForename() {
        return forename;
    }

    public String getSurname() {
        return surname;
    }

    public String getOrganisation() {
        return organisation;
    }

    public List getNotifications() {
        return notifications;
    }

    public long getPoints() {
        return points;
    }

    public List getFriends() {
        return friends;
    }
}

Обычный Rest-контроллер:
package com.example;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.atomic.AtomicLong;

/**
 * Created by bartoszjedrzejewski on 08/06/2016.
 */
@RestController
public class UserController {

    @RequestMapping("/user")
    public User getUser(@RequestParam(value="id", defaultValue="1") int id) {
        return new User(id);
    }

}
И запускалочка от SpringBoot:
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class UserServiceApplication {

 public static void main(String[] args) {
  SpringApplication.run(UserServiceApplication.class, args);
 }
}

Для настройки порта используется файл application.properties.Укажем в нем: server.port = 9001

pom-файл:

 4.0.0

 com.example
 user-service
 0.0.1-SNAPSHOT
 jar

 user-service
 Demo user-service with Spring Boot

 
  org.springframework.boot
  spring-boot-starter-parent
  1.3.5.RELEASE
   
 

 
  UTF-8
  1.8
 

 
  
   org.springframework.boot
   spring-boot-starter-web
  
  
   org.springframework.boot
   spring-boot-starter-test
   test
  
  
   com.fasterxml.jackson.core
   jackson-databind
   2.5.0
  
 


 
  
   
    org.springframework.boot
    spring-boot-maven-plugin
   
  
 
 




Простенький Rest-контроллер, который обращается к выше описанному микросервису:
package com.example;

import com.example.user.dto.UserView;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * Created by bartoszjedrzejewski on 09/06/2016.
 */
@RestController
public class UserDashboardController {

    @RequestMapping("/dashboard")
    public String getUser(@RequestParam(value="id", defaultValue="1") int id) {
        RestTemplate restTemplate = new RestTemplate();
        UserView user = restTemplate.getForObject("http://localhost:9001/user?id="+id, UserView.class);
        return "USER DASHBOARD 
" +
                "Welcome " + user.getForename() +" "+user.getSurname()+"
"+
                "You have " +user.getPoints() + " points! Good job!
"+
                "
"+
                "
"+user.getOrganisation();
    }

}
Запускалочка:
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class UserDashboardApplication {

 public static void main(String[] args) {
  SpringApplication.run(UserDashboardApplication.class, args);
 }
}

порт: server.port = 9002

pom-файл:

 4.0.0

 com.example
 user-dashboard
 0.0.1-SNAPSHOT
 jar

 user-dashboard
 Demo user-dashboard service with Spring Boot

 
  org.springframework.boot
  spring-boot-starter-parent
  1.3.5.RELEASE
   
 

 
  UTF-8
  1.8
 

 
  
   org.springframework.boot
   spring-boot-starter-web
  
  
   org.springframework.boot
   spring-boot-starter-test
   test
  
  
   com.example
   user-client-libs
   0.0.1-SNAPSHOT
  
 
 
 
  
   
    org.springframework.boot
    spring-boot-maven-plugin
   
  
 
 



Вот и все, что нужно для обращения к Rest сервису. Все за нас делает объект RestTemplate. Конечно же можно выполнять не только Get запросы, но и post (postForObject(URI url, Object request, Class responseType )), put, delete. Подробности здесь.

пятница, 24 февраля 2017 г.

Дебаг Soap

Чтобы получить тело сообщения в лог необходимо:

  1. для класса вебсервиса добавить аннотацию @HandlerChain с параметром file="handler.xml"
  2. в handler.xml нужно указать на класс-хэндлер, который и будет выводить сообщение в лог:
    
      
        
          org.Handler
          org.Handler
    
  3. И его реализация:
    public class Inflate implements SOAPHandler {
    
        public boolean handleMessage(SOAPMessageContext mc) {
            try {
                SOAPMessage message = mc.getMessage();
                message.writeTo(System.out);
                return true;
            } catch (SOAPException e) {
                return false;
            }
        }
    }
    
Взято здесь

воскресенье, 5 февраля 2017 г.

Кастомные аннотации в спринг

Попробуем написать свою простенькую аннтацию для спринга. Она будет выводить аргументы вызываемой функции. Подопытным классом будет калькулятор, который умеет складывать и вычитать два числа, вот его интерфейс:

package com.calculator;

public interface Calculator {
    int sum(int a, int b);
    int sub(int a, int b);
}


И его реализация:
package com.calculator;
import org.springframework.stereotype.Component;

@Component
@Profiling
public class CalculatorImpl implements Calculator {
    @Override
    public int sum(int a, int b) {
        return a + b;
    }

    @Override
    public int sub(int a, int b) {
        return a - b;
    }
}


Нас интересует новая аннотация @Profiling, которую мы и будем рализовывать. Для этого создадим следующий интерфейс:

package com.calculator;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Profiling {
}

Если у аннотации должны быть какие то параметры - создаются обычные филды, но это не наш случай. Так же необходимо обратить внимание на аннотацию @Retention(RetentionPolicy.RUNTIME) . Ее основное назначение - указать, как долго необходимо хранить данную аннотацию. Бывают следующие значния:

  • RetentionPolicy.SOURCE - аннотация используется на этапе компиляции и должна отбрасываться компилятором.
  • RetentionPolicy.CLASS - аннтоация будет записана в class-файл компилятором, но не должна быть доступна во время выполнения.
  • RetentionPolicy.RUNTIME - аннотация будет записана в class-файл и доступна во время выполнения.
Для данной аннотации значение по умолчанию CLASS. Для нас подходит только RUNTIME. 
Аннотация готова, но про нее ничего не знает сам спринг. Чтобы их познакомить необходимо создать BeanPostProcessor, для обработки данной аннотации.

package com.calculator;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@Component
public class ProfilingAnnotationBeanPostProcessor implements BeanPostProcessor {
    private Map<String, Class>map = new HashMap<>();
    @Override
    public Object postProcessBeforeInitialization(Object bean, String s) throws BeansException {
        if (bean.getClass().isAnnotationPresent(Profiling.class)){
            map.put(s,bean.getClass());
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String s) throws BeansException {
        Class beanClass = map.get(s);
        if (beanClass != null){
            return Proxy.newProxyInstance(beanClass.getClassLoader(), beanClass.getInterfaces(),
                    (proxy, method, args) -> {
                        System.out.println("method:" + method.getName());
                        System.out.println("params:" + Arrays.toString(args));

                        return method.invoke(bean, args);
            });
        }
        return bean;
    }
}

в BeforeInitialization мы запоминаем те бины, которые помечены аннотацией, а в AfterInitialization, проверяем, если это запомненный бин, добавляем логирование. Ну и осталось создать контекст спринга, в котором нет ничего особенного:



и запуск:
package com;

import com.calculator.Calculator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;


public class App2 {
    public static void main(String[] arg){
        ApplicationContext context = new GenericXmlApplicationContext("context.xml");
        Calculator calc = context.getBean(Calculator.class);
        System.out.println(calc.sum(5,4));
        System.out.println(calc.sub(5,4));

    }


}

следующий вовод:
Connected to the target VM, address: '127.0.0.1:61942', transport: 'socket'
log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
Disconnected from the target VM, address: '127.0.0.1:61942', transport: 'socket'
method:sum
params:[5, 4]
9
method:sub
params:[5, 4]
1

Process finished with exit code 0


Process finished with exit code 0