https://github.com/segg3r/epam_presentation
Why:
    ServiceLocator
    PropertiesLocator
    Injection
Application model:
public class FirstService
    @Autowired
    public Singleton singleton;
    @Autowired
    public Prototype prototype;
}
public class SecondService
    @Autowired
    public Singleton singleton;
    @Autowired
    public Prototype prototype;
}
public class Singleton {
}
public class Prototype {
}
public class FactoryBean  {
}
public class Factory {
    public static FactoryBean factoryMethod() {
        return new FactoryBean();
    }
}
xml-config
    
     // constructor-arg
    
    
    
    
AppCtx-s:
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-10.xml");
FirstService firstService = ctx.getBean(FirstService.class);
annotation-config:
@Component("mixedBean")
public class MixedConfigurationBean {
    public int value;
    public MixedConfigurationBean() {
    }
    public MixedConfigurationBean(int value) {
        this.value = value;
    }
}
    
    
In this case - XML-config wins. Flawless victory
INFO: Overriding bean definition for bean 'mixedBean' with a different definition:
1
java-config:
@Configuration
public class ScopesConfig11 {
    @Bean
    public FirstService firstService() {
        return new FirstService();
    }
    @Bean
    public SecondService secondService() {
        return new SecondService();
    }
    @Bean
    public Singleton singleton() {
        return new Singleton();
    }
    @Bean
    @Scope(BeanDefinition.SCOPE_PROTOTYPE)
    public Prototype prototype() {
        return new Prototype();
    }
}
ApplicationContext ctx = new AnnotationConfigApplicationContext(ScopesConfig11.class);
...
Lookup 1 (dirty)
@Autowired
private ApplicationContext ctx;
private UserAction getUserAction(String email) {
    UserAction result = ctx.getBean(UserAction.class);
    result.email = email;
    return result;
}
Lookup 2
private UserAction getUserAction(String email) {
    UserAction result = buildUserAction();
    result.email = email;
    return result;
}
public UserAction buildUserAction() { return null; }
    
Lookup (java-config)
... getUserAction ...
@Lookup
public UserAction buildUserAction() { return null }
@Config
// !!! lookup methods cannot get replaced on beans, returned from factory methods where we cannot dynamically provide a sublass for them
@ComponentScan("...beans.userservice")
public class ScopesConfig21 {
    @Bean
    public UserService userService() {
        return new UserService();
    }
    @Bean
    @Scope(BeanDefinition.SCOPE_PROTOTYPE)
    public UserAction userAction() {
        return new UserAction();
    }
}
...
@Component
public class UserService {
    ...
}
init-n:
* Reading bean def-s and populating them into BeanFactory
* Calling BeanFactoryPostProcessors
* Instantiating bean object
* Calling BeanPostProcessor::postProcessBeforeInitialization
* Initializating object (init-method, @PostConstruct)
* Calling BeanPostProcessor::postProcessAfterInitialization
public class HelloWorldBPP implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeanException {
        System.out.println("hello pre : " + beanName + " is " + bean.getClass());
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeanException {
        System.out.println("hello post : " + beanName + " is " + bean.getClass());
        return bean;
    }
}
// Procies instances of given class and intercepting its methods
public class ProxyBPP implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeanException {
        System.out.println("Post : " + beanName + " is " + bean.getClass());
        if (Service.class.isAssignableFrom(bean.getClass())) {
            return (Service) Proxy.newProxyInstance(
                Service.class.getClassLoader(),
                new Class [] { Service.class },
                new InvocationHandler() {
                    @Override
                    public Object invode(Object proxy, Method method, Object [] args) throws Throwable {
                        System.out.println("Calling method " + method.getName());
                        return method.invoke(bean, args);
                    }
                });
        }
        return bean;
    }
}
public class ServiceImpl implements Service {
    @Override
    public void printSomething() {
        System.out.println("Something");
    }
}
@Configuration
public class BPPConfig2 {
    @Bean
    public BeanPostProcessor proxyBPP() {
        return new ProxyBPP();
    }
    @Bean
    public BeanPostProcessor helloWorldBPP() {
        return new HelloWorldBPP();
    }
    @Bean
    public Service printService() {
        return new PrintServiceImpl();
    }
}
pre: pringService is class ...beans.ServiceImpl
hello pre: pringService is class ...beans.ServiceImpl
Post: pringService is class ...beans.ServiceImpl
hello post: pringService is class ...beans.ServiceImpl
Calling method printSomething
Something
// Multiple annotation postprocessors
public class DashboardService {
    @LogThisMethod
    @SendEmail
    public void saveDashboard() {
    }
}
@Aspect
public class SendEmailAspect {
    @Before("@annotation(SendEmail)")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Sending email");
    }
}
public class LogThisBPP implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeanException {
        for (Method method : bean.getClass().getDeclaredMethods()) {
            if (method.isAnnotationPresent(LogThisMethod.class)) {
                ProxyFactory factory = new ProxyFactory();
                factory.setSuperClass(bean.getClass());
                factory.setFilter(new MethodFilter() {
                    @Override
                    public boolean isHandled(Method m) {
                        return m.isAnnotationPresent(LogThisMethod.class);
                    }
                });
                Class> clazz = factory.createClass();
                MethodHandler handler = new MethodHandler() {
                    @Override
                    public Object invoke(Object self, Method overridden, Method forwarder, Object [] args) throws Throwable {
                        System.out.println("Logging method");
                        Method beanMethod = bean.getClass().getMethod(
                            overridden.getName(),
                            overridden.getParameterTypes());
                        return beanMethod.invode(bean, args);
                    }
                };
                Object intance = clazz.newInstance();
                ((ProxyObject) instance).setHandler(handler);
                return instance;
            }
        }
        return bean;
    }
}
// Multiple annotation postprocessors
@Configuration
@EnableAspectJAutoProxy
public class BPPConfig4 {
    @Bean
    public SendEmailAspect sendEmailAspect() {
        return new SendEmailAspect();
    }
    @Bena
    public LogThisBPP logThisBPP() {
        return new LogThisBPP();
    }
    @Bena
    public DashboardService dashboardService() {
        return new DashboadService();
    }
}
// output
Sending email
// problem
Email sent, but there is no logging
// Runner
ApplicationContext ctx = new AnnotationConfigApplicationContext(BPPConfig4.class);
DashboardService service = ctx.getBean(DashboardService.class);
service.saveDashboard();
// for (Method method : bean.getClass().getDeclaredMethods()) {
bean.getClass() -> ...beans.annotationprocessing.DashboardService$$EnhancerBySpringCGLIB$$....
// This method does not have annotation in proce class
bean.getClass().getMethod("saveDashboard", new Class>[]{}).getAnnotations()
[]
Hot to fix???
// Bean caching
private Map beansForLogging = Maps.newHashMap();
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    for (Method method : bean.getClass().getDeclaredMethods()) {
        if (method.isAnnotationPresent(LogThisMethod.class)) {
            beansForLogging.put(beanName, bean);
        }
    }
    return bean;
}
// The fix is:
Object originalBean = beansForLogging.get(beanName); // Get cached bean
if (originalBean == null)
    return bean;
ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(originalBean.getClass()); // Get original bean class
factory.setFilter(new MethodFilter() {
    @Override
    public boolean isHandled(Method m) {
        return m.isAnnotationPresent(LogThisMethod.class);
    }
});
Class> clazz = factory.createClass();
MethodHandler handler = new MethodHandler() {
    @Override
    public Object invoke(Object self, Method originalBeanMethod, Method proxyMethod, Object [] args) throws Throwable {
        System.out.println("Logging method");
        Method beanMethod = bean.getClass().getMethod(originalBeanMethod.getName(), originalBeanMethod.getParameterTypes()); // Call the method of proxied bean
        return beanMethod.invoke(bean, args);
    }
};
Object instance = clazz.newInstance();
((ProxyObject) instance).setHandler(handler);
return instance;
// Conclusion (for BeanPostProcessors)
1. BPPs are needed for postprocessing beans themselves (self-injection, proxying)
2. Only create proxies in "postProcessAfterInitialization"
3. Save original bean in "postProcessBeforeInitialization", if needed
4. Always use Class::isAssibnableFrom instead of Class::equals
5. Do not use "bean.getClass()" to parse annotations in "postProcessAfterInitialization", cache beans instead
// HelloWorldBeanFactoryPostProcessor
public class HelloWorldBFPP implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition beanDefinition = beanFactoryh.getBeanDefinition(beanDefinitionName);
            System.out.println("BFPP : " + beanDefinitionName + "; " + beanDefinition.getClass());
        }
    }
}
// BeanFactoryPostProcessor should be static in appropriate config !
// BFPP should be created very early in lifecycle of ApplicationContext
@Configuration
public class BFPPConfig1 {
    @Bean
    public static BeanFactoryPostProcessor helloWorldBFPP() {
        return new HelloWorldBFPP();
    }
    @Bean
    public BeanPostProcessor helloWorldBPP() {
        return new HelloWorldBPP();
    }
    @Bean
    public Service printService() {
        return new ServiceImpl();
    }
}
// ExtensionBeanFactoryPostProcessor
public class ExtensionDeployer {
    @Autowired
    private List extensions;
    public void deployExtensions() {
        extensions.forEach((extension) -> {
            System.out.println("Deploying " + extension.getClass());
        });
    }
}
// We want our users to provide their own implementations of Extension class
public class ExtensionBFPP implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansExtension {
        List> extensionClasses = parseClasspath();
        for (Class extends Extension> extensionClass : extensionClasses) {
            try {
                beanFactory.registerSingleton(extensionClass.getName(), extensionClass.newInstance());
            } catch (Exception e) {
                System.out.println("Failed to register extension " + extensionClass.getName());
                e.printStackTrace();
            }
        }
    }
    @SuppressWarning("unchecked")
    private List> parseClasspath() {
        return Lists.newArrayList(SimpleExtension.class);
    }
    public static class SimpleExtension extends Extension {
    }
}
// Runner
ApplicationContext ctx = new AnnotationConfigApplicationContext(BFPPConfig2.class)
ExtensionDeployer extensionDeployer = ctx.getBean(ExtensionDeployer.class);
extensionDeployer.deployExtensions()
//Output
Deploying class ...spring.postprocessor.ExtensionBFPP$SimpleExtension
// Conclusion
1. BFPPs are needed for BeanDefinition metadata postprocessing
    * addmin method overrides
    * change bean definition properties
    * etc
2. BFPPs are needed for customizing list of beans before their creation
!!! check scopes.txt here !!!