Spring&Proxy, или куда деваются мои классы?

Анна Харитонова, Jet Infosystems
Разминка.
История одного бага
public interface BookDao {
public void save();
}
@Repository
class BookDaoImpl {
@Override
@Transactional
public void save() {
...
}
public void flush() {
...
}
}
@Service
class BookServiceImpl {
@Autowired
private BookDao bookDao;
@Override
public void strangeAction() {
((BookDaoImpl) bookDao).flush();
}
}
???
Что такое прокси
@Component
class DataConsumerComponent {
@Autowired
DataProvider dataProvider; //dataProvider.getClass() ???
}

И для чего оно нужно
- Нельзя использовать реальный объект
- Добавление функциональности: cross-cutting concerns
- Добавление функциональности: динамическая реализация интерфейса
- Управление получением бина
Нельзя использовать реальный объект: Remoting
- RMI
- EJB
- Web Services
- JMS
- ...

Нельзя использовать реальный объект: Remoting
public interface BookService {
public BookInfo getBookInfo(String id);
public void addBook(BookInfo bookInfo);
}
<bean id="booksWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
<property name="serviceInterface" value="ru.springproxy.BookService"/>
<property name="wsdlDocumentUrl" value="http://localhost:8888/BookServiceEndpoint?WSDL"/>
<property name="namespaceUri" value="http://springproxy/"/>
<property name="serviceName" value="BookService"/>
<property name="portName" value="BookServiceEndpointPort"/>
</bean>
public class MyBookStoreServiceImpl {
@Autowired
BookService bookService;
public void getBookInfo(String id) {
bookService.getBookInfo(id);
}
}
Добавление функциональности: АОП
- Транзакции
- Security
- Мониторинг
- ...
Добавление функциональности: АОП
public class SmartLogger {
public Object doLog(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("Started for method " + pjp.getSignature().getName());
try {
return pjp.proceed();
} finally {
System.out.println("Ended for method " + pjp.getSignature().getName());
}
}
}
<aop:config>
<aop:aspect id="logger" ref="smartLogger">
<aop:pointcut id="someOperation"
expression="execution(* ru.annaalkh.springproxy.aspect.*.*(..))"/>
<aop:around
pointcut-ref="someOperation"
method="doLog"/>
</aop:aspect>
</aop:config>
Добавление интерфейса: mixins
- Заставить бин реализовывать интерфейс через конфигурацию



catProxy implements Cat,
Flyable, Swimable
CatImpl
FlyableAspect
SwimableAspect



Proxy
Управление получением бинов
- Pooling
- Prototype
- HotSwapping
- ...
Управление получением бинов
@Component
@Scope(value = "singleton")
public class SingletonBean {
@Autowired
private PrototypeBeanInterface prototypeBean;
public void firstCall() {
prototypeBean.makeCall();
}
public void secondCall() {
prototypeBean.makeCall();
}
}
@Component
@Scope(value = "prototype")
public class PrototypeBean implements
PrototypeBeanInterface {
private int id = new Random().nextInt();
@Override
public void makeCall() {
System.out.println("Bean with id " +
+ id + " is called");
}
}
Bean with id -553917404 is called
Bean with id -553917404 is called
Управление получением бинов
@Component
@Scope(value = "singleton")
public class SingletonBean {
@Autowired
private PrototypeBeanInterface prototypeBean;
public void firstCall() {
prototypeBean.makeCall();
}
public void secondCall() {
prototypeBean.makeCall();
}
}
@Component
@Scope(value = "prototype",
proxyMode = ScopedProxyMode.INTERFACES)
public class PrototypeBean implements
PrototypeBeanInterface {
private int id = new Random().nextInt();
@Override
public void makeCall() {
System.out.println("Bean with id " +
+ id + " is called");
}
}
Bean with id 1681508081 is called
Bean with id 241944767 is called
Как он это делает?

AutoProxyCreator.postProcessAfterInitialization
Типы прокси
- JDK Dynamic Proxy
- CGLIB Proxy

@Autowired
Cat cat;
JDK Proxy

@Autowired
@Flying(proxy="jdk")
Cat cat;
class FlyingInvocationhandler implements Invocationhandler {
private Object target;
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
throws Throwable {
System.out.println("It can fly!");
return method.invoke(target, args)
}
}
Invocationhandler handler = new FlyingInvocationhandler(cat)
Cat flyingCat = (Cat) Proxy.newProxyInstance(Cat.class.getClassLoader(),
new Class[] { Cat.class },
handler);
CGLib Proxy

@Autowired
@Flying(proxy="cglib")
Cat cat;
- Генерация класса прокси на основе наследования
- Обязательно наличие target класса
- Класс должен быть не final
Почему?
public class NotVerySmartLogger {
public Object doLog(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("Some method execution started");
return pjp.proceed();
}
}
Some method execution started
Some method execution started
Internal service message 1
Some method execution started
Internal service message 2
Выбор алгоритма
- Если целевой класс - интерфейс или прокси, использовать JDK Proxy
-
Если нет подходящих для проксирования интерфейсов - CGLIB прокси:
- Не callback интерфейс Spring и JVM
- Не наследник SpringProxy
- Содержит хотя бы один метод
- Если один из параметров proxyTargetClass, preserveTargetClass или optimize равен true - CGLIB прокси
- Во всех остальных случаях - JDK Proxy
Странный баг
@Repository
public class BookDaoImpl {
@Transactional
public void createNewBook() {
updateMethodInvocationCounter();
...
}
@Transactional
private void updateMethodInvocationCounter() {
...
}
}
Еще более странный баг
@Service
public void Service1 {
public void method1() {
...
}
...
}
@Service
public void Service2 {
public void method1() {
...
}
...
}
@Component
public void Client1 {
@Autowired
Service1 service;
...
}
@Component
public void Client2 {
@Autowired
Service2 service;
...
}
public interface Service {
public void method1;
...
}
@Service
public void Service1 implements Service {
@Override
public void method1() {
...
}
...
}
Главный ответ
<aop:aspectj-autoproxy proxy-target-class="true">
<aop:include name="someNotImportantAspect"/>
</aop:aspectj-autoproxy>
Вопросы?

Spring&Proxy
By annaalkh
Spring&Proxy
- 934