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