среда, 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-методов внутри одного пакета разрешён.