среда, 14 декабря 2011 г.

Возможность изменения строк java и их кэширование

Поговорим об изменении неизменяемых классов Java и немного о кэшировании строк.
Для тех, кто знает эти механизмы здесь не будет ничего нового. Для тех, кто не задумывался о том, как они уживаются вместе - немного уличной магии.
Итак, есть код:
public class SecureClass {
    
    private String msg = "Fail";
    
    public String getMessage() {
        return msg;
    }    
}
public class Main {

    public static void main(String[] args) {
        SecureClass secureClass = new SecureClass();        
        System.out.println(secureClass.getMessage());
    }   
}
После выполнения в консоли по вполне очевидным причинам будет:
>>Fail
Но мы то хотим выиграть, а не проиграть?
При этом внести исправления в SecureClass не можем или не хотим? Тогда немного уличной магии для класса Main:
public class Main {

    public static void main(String[] args) throws Exception {
        SecureClass secureClass = new SecureClass();  
        Field valueField = String.class.getDeclaredField("value");
        valueField.setAccessible(true);
        valueField.set("Fail", new char[] {'W', 'i', 'n', '!'});
        System.out.println(secureClass.getMessage());
    }   
}
Для большей наглядности и чтобы не перегружать заметку обработкой всех исключений, которые нужно обрабатывать при использовании reflect, сократил код, написав throws Exception. Запускаем ещё раз и видим:
>>Win!
Ну прекрасно, так и должно быть!

Небольшие пояснения:
В локальной безымянной переменной метода main с помощью reflect меняем значение внутреннего поля "value". Таким образом эта локальная переменная уже содержит в себе другую строку (вот вам и невозможность изменить класс String). Эти изменения отражаются и на переменной из класса SecureClass, потому что java старается хранит строки в пуле, когда это возможно и когда программист этому явно не воспрепятствовал. Получается, что в обоих классах строковые переменные "смотрели" на один и тот же объект. Попытаемся намекнуть java, что мы не хотим своими reflect-операциями изменять строки в пуле, указав new String("Fail") вместо "Fail":
public class Main {

    public static void main(String[] args) throws Exception {
        SecureClass secureClass = new SecureClass();  
        Field valueField = String.class.getDeclaredField("value");
        valueField.setAccessible(true);
        valueField.set(new String("Fail"), new char[] {'W', 'i', 'n', '!'});
        System.out.println(secureClass.getMessage());
    }   
}
Запускаем и видим:
>>Fail
Потому что теперь переменные указывают на разные объекты. Меняя один - не оказываем никакого влияния на другой.

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

воскресенье, 11 декабря 2011 г.

Про ошибки

Вспомнились пословицы о том, что на ошибках учатся. Интересно, кто их придумал? А кто вообще придумывает пословицы?

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

Как тут не согласиться с мнениями кучи знаменитых программистов о том, что ошибки проектирования куда сильнее сказываются на сроках сдачи проекта и авральности работы, чем непосредственные ошибки кодинга.
Вот только многие недели до этого я всё так же особенного внимания не уделял проектированию. А в результате оказывался прав, и экономил кучу времени на обсуждениях. Конечно, это всё были достаточно локальные проблемы и риски малы: в самом худшем случае я терял бы не более 1,5-2 дней, поэтому мог себе позволить такую роскошь как отсутствие заранее продуманной полной модели, и просто полагался на чутьё. В этот раз не удалось. Я расстроен? Очень. Сейчас, в свой выходной, я всё ещё не могу заставить себя прекратить думать над проблемой. Но учитывая всю динамику проекта - считаю верным принимать не самые ключевые решения быстро. На длительной дистанции это всё равно даёт существенный плюс в скорости. И чем больше опыта - тем более весомым оказывается плюс.

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

Неудачная попытка вызова метода @Stateless сервиса

Сегодня предлагаю рассмотреть RuntimeException при вызове метода сервиса:
javax.ejb.EJBException: Illegal non-business method access on no-interface view
Как ошибка получается?
Пусть есть крайне полезный сервис:
@Stateless
public class MyService {

    public String getAnswer() {
        return "42";
    }

    protected String getQuestion() {
        return "20 + 22 = ?";
    }
}
И также есть сервис (расположенный в том же пакете), обращающийся к первому в процессе выполнения какой-то своей логики:
@Stateless
public class MyClientService {

    @Inject
    private MyService myService;

    public String getPageHeader() {
        return myService.getQuestion() + myService.getAnswer();
    }
}
Чем нам так нравятся последние версии EJB?
Например, не нужно делать лишнюю и утомительную работу по написанию никому не нужных home-интерфейсов для сервисов. Объявляем сервис @Stateless, контейнер вставляет его куда надо с помощью @Inject/@EJB и вперёд. Смотрите сами: приведённый код достаточно ясен и краток. И даже компилируется... Вот только не выполняется, выбрасывая обозначенную выше ошибку.

Почему?
А вот не надо забывать, что механизмы работы EJB особенно не поменялись. Все эти позволения не писать лишнего - некоторый синтаксический сахар (хотя и очень классный). И на самом деле интерфейс для сервиса всё равно будет создан. И всё равно прокси-объект "над нашим сервисом" будет реализовывать этот автоматически созданный интерфейс. И войдут в него только public-методы. Другими словами, если грубо, то получится следующее:
public interface MyServiceIntf extends EJBHome {

  public String getAnswer();
}
Как видно, метода getQuestion() здесь нет. И именно этот интерфейс будет иметь переменная myService в MyClientService. А потому и происходит RuntimeException, когда мы пытаемся сделать вызов
myService.getAnswer()
При этом компилятор не показывает ошибок: с его точки зрения всё верно: вызов protected-методов внутри одного пакета разрешён.

среда, 26 октября 2011 г.

Автоматический update, выполняемый hibernate

Сегодня столкнулся со следующей проблемой в j2ee приложении, использующем jpa (hibernate):
Обычный поисковый select-запрос, реализованный с помощью Criteria API, после своего выполнения отсылал update-запрос базе. В большинстве случаев в этом не было ничего критичного, ну разве что перфоманс слегка страдал. Вот никто и не замечал... Сегодня же select выполнялся ко view, поэтому следующий за ним update вызвал ошибку.
Caused by: java.sql.SQLSyntaxErrorException: ORA-01031: insufficient privileges
Caused by: org.hibernate.exception.SQLGrammarException: could not update
Объяснение эффекта достаточно простым оказалось, но тем не менее.Всё работает абсолютно так, как должно быть: в классе был метод @PostLoad, проверяющий определённые строковые поля на null и заменяющий их на пустые строки. После окончания работы с данными Hibernate проверял объект, видел изменения и считал необходимым сделать аналогичные изменения в БД. Отсюда и update, отсюда и ошибка шла. Ещё чаще подобные "нежелательные" апдейты встречаются, когда orm работает через методы-аксессоры, а не через поля, а программисты спасаются от NullPointerException, делая вот такие getter'ы:
public String getStr() {
    return str == null ? "" : str;
}
Вообще, любые изменения приводят к такому вот эффекту... Например замена "Ё" на "Е", поэтому надо помнить об этой особенности.

среда, 22 июня 2011 г.

Ещё 1 проблема автобоксинга


Историческое превью:
Так уж повелось, что Long и long - совершенно разные вещи с абсолютно разным поведением. Ну всякие там возможности/невозможности хранить null-значения, корректность сравнения через оператор == и т.п. В java 1.5 появилась такая фича как автобоксинг. То есть теперь свободно можно писать нечто в стиле:
Long l = 4L;
if (l != 10L) {...}

В то время как раньше приходилось пользовать такой код:
Long l = new Long(4L);
if (l.longValue() != 10L) {...}

По сути, автобоксинг - автоматическое преобразование между классами и примитивными типами. Работает как в одну, так и в другую сторону.

пятница, 3 июня 2011 г.

CriteriaBuilder. Проблема в count-запросе сущностей с составным первичным ключом

Проблема:
Если запрос строится на Criteria API, то существует баг, воспроизводимый при попытке count-запроса для сущности с составным первичным ключом (composite primary key).
Ошибка при этом звучит так:
Caused by: java.sql.SQLException: ORA-00907: missing right parenthesis

понедельник, 23 мая 2011 г.

Многопоточность в java. Пример

Удобство
Да, многопоточность в яве действительно сделана хорошо. Инструменты удобны и если хочется получить какой-то высокоуровневый инструмент для многопоточной программы, то он существует (а если нет, то один из существующих наверняка лучше того, который хотелось увидеть).
Простой пример:
private static String[] sites = 
    {"www.google.com", "www.ya.ru", "www.yahoo.com"};
//создание пула потоков    
ExecutorService service = Executors.newCachedThreadPool();
List<Future<String>> futures = 
    new ArrayList<Future<String>>();
for (int i = 0; i < sites.length; ++i) {
    //запуск нового потока с параметром из массива
    Future<String> future = 
        service.submit(new ThreadWorker(sites[i]));
    futures.add(future);
    }
//на этом этапе все потоки запущены
System.out.println("threads started");
for (Future<String> future : futures) {
    //забираем результат выполнения потока
    //если поток ещё не завершился, то происходит ожидание
    System.out.println("get result from Future: " + future.get());
}
//сообщаем пулу о том, что закончили пользоваться потоками
service.shutdown();
Подробнее?

пятница, 20 мая 2011 г.

Форматированный вывод вещественных чисел

Постоянно если сталкиваюсь, то пытаюсь вспомнить, что ж там надо писать в строке формата String.format, чтобы вывести в том виде, в котором нужно. И постоянно приходится или угадывать, или гуглить. Даже не знаю, что быстрее. Поэтому стоит записать, как же получить нормальное строковое представление для float/double немного альтернативным способом:
NumberFormat format = new DecimalFormat("#.##", 
    new DecimalFormatSymbols(Locale.US));
String result = format.format(doubleParam);

Второй параметр у конструктора можно не указывать. По умолчанию используется локаль системы. Мне было неудобно, т.к. нужна была точка в качестве разделителя целой и дробной части, а русская локаль ставит запятую.
Как это сработает?
1.4 "1.4"
1.333"1.33"
1.89999"1.9"
16.23"16.23"

Тесты

Нет, заметка не о jUnit, хоть я и люблю их.
Хотел поделиться новостью: http://www.quizful.net снова работает.
Кстати, на прошлую заметку меня натолкнул тест именно на этом ресурсе

Численные методы на службе прикладного программиста

Точность чисел
На определённые исследования по этой теме меня сподвиг вопрос теста:
long l1 = Long.MAX_VALUE;
long l2 = Long.MAX_VALUE-1;
double d1 = Long.MAX_VALUE;
System.out.println("l1 == d1: " + (l1 == d1));
System.out.println("l2 == d1: " + (l2 == d1));
Чтобы никто не мучился, сразу скажу, вывод даст:
l1 == d1: true
l2 == d1: true

Почему?

вторник, 19 апреля 2011 г.

Массив в коллекцию, пожалуйста. И чтобы покороче

Особенности Arrays.asList(T... elements)
Arrays.asList(T... elements) и Collection.toArray() являются стандартным (так говорят javadoc'и) мостом между библиотеками, работающими с массивами и коллекциями. Звучит красиво, что уж там.
Немногие дочитывают до того места, в котором говорится, что полученный List является fixed-size. А это означает, например, что оттуда нельзя удалить элемент. Этот баг можно быстро найти и понять, но тем не менее. В общем, я был неприятно удивлён. Что побудило Sun реализовать метод именно так я не смог понять (поэтому мне сложно решить, пометить запись меткой "фича" или "кривота"...выберу обе).
Как лечится?
Например, так:
List<Integer> list = new ArrayList<Integer>(Arrays.asList(1, 2, 3));

понедельник, 11 апреля 2011 г.

Переименовать таблицу или колонку

Парочка мелких вещей в SQL, которые меня слегка удивили.
  • В БД при изменении имени таблиц все constraints автоматически подстраиваются под это изменение. То есть никаких ошибок не возникнет (входит в стандарт и работает на абсолютном большинстве БД).
    ALTER TABLE MyTable RENAME TO MY_TABLE;
    
  • Но Oracle пошёл и того дальше. Он умеет даже переименовывать колонки. При этом даже если это Primary Key. Даже если на колонку навешены другие индексы, даже если на неё ссылается Foreign Key из другой таблицы. К сожалению, эта фишка не из стандарта, поэтому в других БД может и не быть (в MySQL нет, например).
    ALTER TABLE MyTable RENAME COLUMN id TO identifier;
    

воскресенье, 3 апреля 2011 г.

Асимметрия

Вопрос:
Может ли функция Math.abs(int) вернуть отрицательное число?

Ответ:
Как же хочется написать "нет", улыбнуться и подумать, что я всех обманул таким вопросом. На самом деле может. Так уж повелось, что отрицательных чисел больше, чем положительных. Так уж повелось, что если byte, то диапазон -128..127, ну и так далее с увеличением количества байт на хранение. К чему это приводит?
int minValue = Integer.MIN_VALUE;
int abs = Math.abs(minValue);
System.out.println(abs);
Закрываем, трясём, смотрим (©):
> -2147483648

воскресенье, 27 марта 2011 г.

Кириллица в идентификаторах

        final String строка = "Неужели они разрешены?";
        for (int бегунок = 0; бегунок < строка.length(); ++бегунок) {
            
            System.out.print(строка.charAt(бегунок));
        }

И весь ужас в том, что это компилится и работает...
Для знатоков кодировок: буквы "И", "ш" тоже работают корректно.

суббота, 19 марта 2011 г.

Поиск по всем таблицам в Oracle

Задача: полнотекстовый поиск в Oracle
То есть, мы знаем значение какой-то колонки какой-то таблицы. И ничего больше не знаем, ни названия таблицы, ни названия колонки. Такое может быть нужным, когда надо определить, в какую таблицу БД попадают определённые данные с веб-формы. Вызвано это, например, может быть тем, что у нас есть доступ к БД, но нет исходников приложения. А таблицы кто-то называл в стиле TABLE_1, TABLE_2, OT_1 и так далее.
К счастью, БД была Oracle, поэтому можно использовать тёмные стороны PL/SQL.
Думаю, смысл действа понятен, поэтому сразу к коду:

create or replace procedure whereIsValue(valueParam varchar2)
AS
  TYPE VALCUR IS REF CURSOR;
  cursor tabl is select table_name from user_tables;
  cursor col (tablename varchar2) is select column_name 
                                     from user_tab_columns
                                     where table_name like tableName;
  valueCursor VALCUR;
  tableName varchar2(50);
  columnName varchar2(50);
  columnValue varchar2(500);
  qq number(3);
begin
  open tabl;
  LOOP
    fetch tabl into tableName;
    EXIT WHEN tabl%NOTFOUND;
    OPEN col(tableName);
    LOOP     
      fetch col into columnName;
      EXIT WHEN col%NOTFOUND;
        OPEN valueCursor for 'select ' || columnName 
                         || ' from ' || tableName;
        LOOP
          BEGIN
            fetch valueCursor into columnValue;        
            EXIT WHEN valueCursor%NOTFOUND;
            if (columnValue like valueParam) then
              dbms_output.put_line(tableName);
              exit;
            end if;
          EXCEPTION
            WHEN OTHERS then
              qq := 4;
          END;
        END LOOP;
        CLOSE valueCursor;
    END LOOP;
    CLOSE col;
  END LOOP;

end;

Вкратце о том, что же происходит:

  • проходим по всем таблицам схемы;
  • для каждой таблицы проходим по всем её колонкам;
  • для каждой колонки забираем список значений и сравниваем с тем, что передано в параметре процедуры;
  • если совпало, то печатаем имя таблицы в DBMS_OUTPUT;

пятница, 18 марта 2011 г.

Autogenerated FK in Hibernate

Страдаете от Hibernate'овского identifier too long? Тогда мы идём к вам!

Hibernate даёт свои автогенеренные названия для Foreign Key по одним разработчикам понятному принципу.
Вообще говоря, ничего необычного: 'FK' и ещё 16 символов - по 8 от hashcode имени каждой из участвующих таблиц (там не совсем hashCode, но и неважно).
Что получается, когда сущности (@Entity) наследуются? FK разрастается на 8 символов за каждый уровень наследования. То есть, для одиночного наследования длина FK будет уже не 18, а 18+8=26 символов, а при добавлении в эту иерархию ещё 1 сущности уже 18+2*8=34.
Самое время вспомнить о всеми любимом ограничении Oracle (и некоторых ещё СУБД) на длину идентификаторов в 30 символов.

Решение
Баг в Hibernate в открытом состоянии уже 3 года. Видимо, не сильно мешает, т.к. исправить ситуацию довольно просто: нужно дать своё имя для FK аннотацией в стиле:
@ForeignKey(name="myFKname")

суббота, 12 марта 2011 г.

EJB: используем TimerService. Часть 1

Пример использования Singleton

В прошлой заметке был показан механизм создания Singleton, используя возможности EJB 3.1. Однако созданный сервис не выполнял никакой полезной работы, поэтому смысл действа казался несколько туманным.

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


Как это сделать?
Открываем заготовку Singleton из прошлой заметки и наполняем её содержанием:


  • Необходим специальный интерфейс TimerService, позволяющий создавать различные виды таймеров.
    @Resource
    private TimerService timerService;

  • В метод инициализации, выполняемый сразу после инстанцирования объекта добавляем задание. Этим мы добьёмся того, что каждые 60 секунд будет срабатывать таймер и пытаться вызвать метод, помеченный аннотаций @Timeout.
    @PostConstruct
    public void init() {
    System.out.println("Инициализируем проверку почты");

    long interval = 60000L; // 1 minute
    timerService.createIntervalTimer(0L,
    interval, new TimerConfig(null, false));
    }

  • Наконец, нужно описать само действие, которое должно выполняться раз в минуту:
    @Timeout
    public void process(Timer timer) {
    checkMail();
    }


Собственно, это весь код, необходимый для организации событий, которые бы срабатывали с определённой частотой. Можно лишь добавить, что TimerService предоставляет несколько разных видов таймеров. В этом примере использовался createIntervalTimer, когда события возникают всегда через одинаковый интервал времени. Помимо этого доступны одноразовые таймеры (сработает единожды через определённое количество секунд, или же в определённое время), таймеры на основе CRON (с возможностью задать временное условие вида "каждый будний день в 9:00").


В следующий раз закончим тему таймеров, рассмотрев возможность параметризировать возникающее при достижении "часа Х" событие.

среда, 9 марта 2011 г.

Singleton стандартными средствами EJB

Создание Singleton в j2ee-приложениях
EJB позволяет быстро и просто реализовать Singleton, выполняющийся в отдельном потоке. Фактически, мы получаем тот же Stateful-bean, но с постоянным временем жизни. Так как это обычный bean, в нашем распоряжении оказываются механизмы Dependency Injection и аннотации @PostConstuct, @PreDestroy.


Итак, создим простой Singleton:


@Singleton
@Startup
public class ApplicationWatchDog implements Serializable {

@PostConstruct
public void init() {
System.out.println("Singleton успешно стартовал");
}
}



Что же мы сделали?

Аннотации над классом говорят сами за себя:


  • описываемый класс является Синглтоном (присутствует лишь в единственном экземпляре);

  • экземпляр создастся сразу же после развёртывая (deploy) приложения на сервере.

Метод, аннотированный @PostConstruct вызывается сразу же после создания экземпляра класса и обычно служит для настроек деятельности этого сервиса.



В следующей главе рассмотрим применение данного механизма для решения реальных задач.

суббота, 5 марта 2011 г.

CDI Events

Для чего нам может быть нужен Observer?
Первое, что приходит в голову: логирование операций пользователя. Сохраняет/изменяет человек какие-то данные на портале - одновременно с сохранением требуемой информации нужно ещё и сделать запись в лог, что такой-то пользователь в такое-то время изменил такую-то информацию.

Как реализуется?
Для начала необходимо определить тип (название) события, которое мы хотим ловить. Для этого создаём аннотацию:


@Qualifier

@Target({FIELD, PARAMETER, METHOD, TYPE})

@Retention(RetentionPolicy.RUNTIME)

public @interface DataChangeEvent {   

}

Теперь необходимо создать класс, для передачи всех необходимых данных от возбудителя события к его подписчикам.

public class DataChangeInfo implements Serializable {

    public User user;

    public Data data;

    public DataChangeInfo(User user, Data data) {

        this.user = user;

        this.data = data;

    }

}

В сервисе, ответственном за сохранение, с помощью Dependency Injection определяем объект-событие:

@Inject 
@DataChangeEvent

private Event<DataChangeInfo> eventHandler;


И теперь в методу сохранения можно создать данной событие:
public void saveData(Data data, User user) {

    entityManager.merge(data);

    eventHandler.fire(new DataChangeInfo(user,data));

}

Осталось лишь реализовать подписчиков на это событие:

@Stateless

public class DataChangeSubscriber {

   

    public void dataWasChanged(@Observes @DataChangeEvent DataChangeInfo info) {

        Date date = new Date();

        User user = info.user;

        Data data = info.data;

        //Сохраняем data, user, date в лог.

    }

}

Вот и всё, что нужно для функционирования механизма.