среда, 28 сентября 2011 г.

RESTful веб-сервис на Jersey + Spring 3

Пишем простой REST веб-сервис.

Как известно, начиная с третьей версии у Spring появилась возможность легко создавать REST веб сервис с помощью Spring MVC (на аннотациях @RequestMapping). Но я хочу рассмотреть классический Jax-RS с помощью Jersey. Итак, начнем.

Для начала определим maven зависимости:




    com.sun.jersey
    jersey-server
    1.9.1




    com.sun.jersey.contribs
    jersey-spring
    1.9.1
    
        
            org.springframework
            spring
        
        
            org.springframework
            spring-core
        
        
    



    com.sun.xml.bind
    jaxb-impl
    2.1.9

exclusions в зависимости jersey+spring необходимы потому, что библиотека требует зависимости спринга версий 2 или 2.5. А мы ей их дать не можем, тк используем третью версию. В исключениях требуется указать все библиотеки Spring, которые у нас есть в проекте.

Теперь можно писать сам сервис. Будем доставать продукты по их id:
@Path("/product/{id}")
@Component
@Scope("prototype")
public class ProductRSController {

    @Autowired
    private ProductService srv;

    @GET
    @Produces(MediaType.APPLICATION_XML)
    public Product getDescription(@PathParam("id") String productId) {
        Product product = srv.getProduct(productId);
        return product;
    }
}
Пометив метод с помощью аннотаций @GET и @Produces(MediaType.APPLICATION_XML), мы указали, что url вида http://myserver.com/myapp/rs/product/123 (где myserver.com - наш сервер, myapp - имя развернутого приложения), будет возвращать в ответ сериализованный обьект Product в xml.

Класс ProductService представляет собой сервис бин, с методом Product getProduct(String productId) {...}

Не забудем настроить web.xml:

    contextConfigLocation/WEB-INF/spring-context.xml


    org.springframework.web.context.ContextLoaderListener



    Jersey Web Application
    com.sun.jersey.spi.spring.container.servlet.SpringServlet
    1



    Jersey Web Application
    /rs/*

и spring-context.xml:




Теперь можно разворачивать приложение и зайти по URL http://myserver.com/myapp/rs/product/123. В случае успеха мы увидим полученный XML, если конечно у нас есть Product с id = 123.

Теперь можно создавать клиент веб-сервиса. Для удобства работы, возьмем клиент от Jersey.

зависимости:

    com.sun.xml.bind
    jaxb-impl
    2.1.9



    com.sun.jersey
    jersey-client
    1.9.1


сам клиент будет выглядеть так:
public class RESTClient {

    public Product recieveObject() {
        Client c = Client.create();
        WebResource r = client.resource("http://myserver.com/myapp/rs/");
        Product product = response = r.path("product/123").
                accept(MediaType.APPLICATION_XML_TYPE).
                get(Product.class);

        return product;
    }
    
    public String recieveXML() {
        Client c = Client.create();
        WebResource r = client.resource("http://myserver.com/myapp/rs/");
        String product = response = r.path("product/123").
                accept(MediaType.APPLICATION_XML_TYPE).
                get(String.class);

        return product;
    }
}
метод recieveObject() возвращает десериализованный обьект Product, а метод recieveXML() возвращает XML.

С помощью Generics можно сделать клиент универсальным:
public class RESTClient {

    private static final Client client;

    private String resource = "http://myserver.com/myapp/rs/";

    static {
        client = Client.create();
    }

    public RESTClient() {
    }

    public RESTClient(String resource) {
        this.resource = resource;
    }

    public void recieve() {

        //object
        Product product = recieve(Product.class, "product/", "123");
        System.out.println("response.getProductId: " + product.getProductId());
        
        //string 
        String xml = recieve(String.class, "product/", "123");
        System.out.println("response: " + xml);
    }

    private <T> T recieve(Class<T> type, String URI, String id) {
        WebResource r = client.resource(resource);

        T response = r.path(URI + id).
                accept(MediaType.APPLICATION_XML_TYPE).
                get(type);

        return response;
    }
}


Для настройки вывода данных в формате JSON необходимо проделать следующее:

Добавить зависимость:

            com.sun.jersey
            jersey-json
            1.10
        

Создать класс JAXBContextResolver в том же package, где лежит контроллер
@Provider
public final class JAXBContextResolver implements ContextResolver<jaxbcontext> {

    private final JAXBContext context;

    private final Set<class> types;

    private final Class[] cTypes = {MyEntity1.class, MyEntity1.class};

    public JAXBContextResolver() throws Exception {
        this.types = new HashSet(Arrays.asList(cTypes));
        this.context = new JSONJAXBContext(JSONConfiguration.natural().build(), cTypes);
    }

    @Override
    public JAXBContext getContext(Class<?> objectType) {
        return (types.contains(objectType)) ? context : null;
    }
}
где MyEntity1, MyEntity2 - отображаемые в JSON обьекты


и в контроллере указывать формат данных @Produces("application/json; charset=utf-8")

среда, 7 сентября 2011 г.

Scala под windows 7. ошибка при запуске компилятора.

После установки Scala 2.9.0 на win 7 столкнулся с проблемой. (С предыдущими версиям scala все нормально)

при запуске команды scalac -version неожиданно вываливается Эксепшн:

Exception in thread "main" java.lang.NoClassDefFoundError: scala/tools/nsc/MainGenericRunner
Caused by: java.lang.ClassNotFoundException: scala.tools.nsc.MainGenericRunner
        at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
Could not find the main class: scala.tools.nsc.MainGenericRunner.  Program will exit.

Начал разбираться в чем проблема. Оказалось, что при запуске scala.bat тоже самое.

Путем трейса файлов scalac.bat и scala.bat установил переменная _SCALA_HOME
в них неверная:
_SCALA_HOME c:\Program Files\Files\scala\bin\..

Хотя Переменная среды проставлена правильно:
c:\Program Files\scala\bin\..

В итоге пришлось в файлах scala.bat и scala.sh исправить строчки:

:set_home
  set _BIN_DIR=
  rem for %%i in (%~sf0) do set _BIN_DIR=%_BIN_DIR%%%~dpsi
  rem set _SCALA_HOME=%_BIN_DIR%..
  set _SCALA_HOME=%~dps0..
goto :eof

здесь заккоментировал:
for %%i in (%~sf0) do set _BIN_DIR=%_BIN_DIR%%%~dpsi
set _SCALA_HOME=%_BIN_DIR%..

и добавил
set _SCALA_HOME=%~dps0..

суббота, 27 августа 2011 г.

AspectJ + Maven + Spring + IDEA

Как заставить работать AspectJ с Maven и Spring.

Подцепим зависимости maven:

    org.springframework
    spring-aop
    ${spring.version}
    
        
            org.aspectj
            aspectjweaver
        
    



    org.springframework
    spring-aspects
    ${spring.version}
    
        
            org.aspectj
            aspectjweaver
        
    





    org.aspectj
    aspectjrt
    ${aspectj.version}


Для выполнения aspectj компиляции добавим плагин в maven:

    org.codehaus.mojo
    aspectj-maven-plugin
    1.1
    
        1.6
        1.6
        true
        ignore
        
            
                org.springframework
                spring-aspects
            
        
        false
    
    
        
            
                compile
                test-compile
            
        
    


Все, проект можно собирать.
Для выполнения приложения прямо из IDEA надо поставить галку "Run Maven Goal" в Run -> Edit Configurations -> Before Launch и выбрать в ней плагин aspectj:compile

суббота, 9 июля 2011 г.

Singleton и Prototype scope в Spring

Singleton scope означает что экземпляр бина создается один раз, при инициализации контекста. Не стоит сравнивать этот scope со stateless bean в EJB. EJB контейнер при каждом обращении выдает новый экземпляр бина. Поэтому в stateless бине состояние не сохраняется. Таким образом Singleton scope в Spring - тоже statefull. В случае со scope Prototype, можно создавать его новые экземпляры, когда это надо, с помощью фабрики. (statefull)

В обьявлении этих бинов нет ничего сложного:



Но проблема появляется, когда нужна зависимость SingletonBean от PrototypeBean. В этом случае можно поступить несколькими способами:
ApplicationContextAware или BeanNameAware method injection; Lookup method injection; Arbitrary method replacement.
Расскажу о самом простом (по крайней мере для меня) из них - аналоге BeanNameAware.

PrototypeBean может представлять из себя что угодно. В данном контексте нам это не важно, так что будем рассматривать его просто как POJO класс:
public class PrototypeBean {
...
}

а в SingletonBean используем фабрику org.springframework.beans.factory.ObjectFactory:
public class SingletonBean {

  private ObjectFactory factory;

  public void createPrototypeBean() {
     //здесь можем получить сколько угодно экземпляров PrototypeBean
     PrototypeBean bean = factory.getObject();
  }

  ...

  public void setFactory(ObjectFactory beanFactory) throws BeansException {
     this.factory = beanFactory;
  }
}

Чтобы явно указать ObjectFactory экземпляр какого бина создавать, в конфигурации пропишем ObjectFactory как property Singleton-бина:



  
    
      
        
      
    
  

суббота, 25 июня 2011 г.

Установка Tomcat под Ubuntu

Для начала установливаем openjdk вместе с пакетом ubuntu-restricted-extras:
sudo apt-get install ubuntu-restricted-extras
установленная версия: 1.06_22

качаем томкат:
wget http://apache.cyberuse.com/tomcat/tomcat-7/v7.0.16/bin/apache-tomcat-7.0.16.tar.gz

распаковываем:
tar xvzf apache-tomcat-7.0.16.tar.gz

копируем в нужную папку:
sudo mv apache-tomcat-7.0.16 /usr/local/tomcat

также необходимо прописать переменные окружения JAVA_HOME и JDK_HOME. Для этого добавляем в файле /etc/environment:

JDK_HOME="/usr/lib/jvm/java-6-openjdk"
JAVA_HOME="/usr/lib/jvm/java-6-openjdk"

запускаем томкат:
sudo /usr/local/tomcat/bin/catalina.sh start

редактируем server.xml:
sudo nano /usr/local/tomcat/conf/server.xml

ищем следующий текст:

меняем порт на стандартный для http - 80, и указываем кодировку для URI:

создаем файл автозагрузки
sudo nano /etc/init.d/tomcat

и вставляем следующее:
# Tomcat auto-start
#
# description: Auto-starts tomcat
# processname: tomcat
# pidfile: /var/run/tomcat.pid

export JAVA_HOME=/usr/lib/jvm/java-6-openjdk

case $1 in
start)
       sh /usr/local/tomcat/bin/startup.sh
       ;;
stop)  
       sh /usr/local/tomcat/bin/shutdown.sh
       ;;
restart)
       sh /usr/local/tomcat/bin/shutdown.sh
       sh /usr/local/tomcat/bin/startup.sh
       ;;
esac   
exit 0

далее сделаем этот скрипт запускаемым:
sudo chmod 755 /etc/init.d/tomcat

для проверки работы запустим tomcat:
sudo /etc/init.d/tomcat start

сервер должен запуститься на 80 порту. Для остановки используем следующую команду:
sudo /etc/init.d/tomcat stop

создадим ссылки на скрипт для автоматического запуска и останова:
sudo update-rc.d tomcat defaults

перезагружаем сервер:
sudo shutdown -r now

в конце надо не забыть создать пользователя tomcat с ролями admin-gui и manager-gui в conf\tomcat-users.xml:



четверг, 9 июня 2011 г.

Double checked locking

Шаблон проектирования "Блокировка с двойной проверкой" используется в многопоточном программировании. Это один из излюбленных вопросов на собеседованиях. Звучит он обычно как просьба написать синглетон, корректно работающий в многопоточном режиме.

Рассмотрим принцип паттерна на примере Синглетона.
// Не работает в Java 1.4 и более ранних версиях из-за семантики volatile
class Foo {
  private volatile Helper helper = null;  

  public Helper getHelper() {
    if (helper == null) {          //блок проверки, для ускорения производительности
      synchronized(this) {         //блок синхронизации
        if (helper == null)        //если обьект еще не создан, то создаем его
          helper = new Helper();
      }
    }
    return helper;
  }
}

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

Модификатор volatile появился начиная с версии Java 1.5. Он позволяет корректно обработать запись в переменную в многопоточном режиме.

среда, 11 мая 2011 г.

Typesafe Enum pattern или как обойтись без Enum-ов.

Часто нужда в использовании Enum возникает в случае, если необходимо передать в метод одно из значений, например "green", "red" или "blue". Первое что приходит в голову - это принимать String:
public void method(String param) {...}

Но в таком случае в качестве параметра может быть принята любая строка. В соответствии с принципом, что все потенциальные ошибки по возможности надо переносить на момент компиляции, такой вариант неприемлем. Тут и приходит на помощь Enum:

public enum TypesafeEnum {GREEN, RED, BLUE};

А можно ли обойтись без него? Конечно можно, ведь Enum появились только с java 1.5. Тут нам может помочь паттерн Typesafe Enum:
public class TypesafeEnum {

  private final String name;

  private TypesafeEnum(String name) {
    this.name = name;
  }

  public static final TypesafeEnum GREEN = new TypesafeEnum("green");
  public static final TypesafeEnum RED = new TypesafeEnum("red");
  public static final TypesafeEnum BLUE = new TypesafeEnum("blue");

  public String toString() {
    return name;
  }
}
Теперь можно обьявить наш метод таким образом:
public void method(TypesafeEnum param) {...}
соответственно его вызов:
someObject.method(TypesafeEnum.RED);


Подробнее о паттерне здесь