com.kciray
), and the Main.java
class inside it. The second package is org.springframework. Yes, we will duplicate the structure of the packages of the original spring, the name of its classes and their methods. There is such an interesting effect - when you create something of your own, it starts to seem simple and clear. Then, when you work in large projects, it will seem to you that everything is created on the basis of your workpiece. Such an approach can have a very positive effect on the understanding of the operation of the system as a whole, its improvement, correction of bugs, problem solving, and so on.ProductFacade
and PromotionService
. Now imagine that you want to connect these classes with each other, but so that the classes themselves do not know about each other (Pattern DI). We need a separate class that will manage all these classes and determine the dependencies between them. Let's call it a container. Create a class Container
... But no, wait! In Spring, there is no single container class. We have many container implementations, and all of these implementations can be divided into 2 types - bean factories and contexts. The bean factory creates bins and binds them together (dependency injection, DI), and the context does roughly the same thing, plus adds some additional functions (for example, internationalizing messages). But we do not need these additional functions now, so we will work with the bins factory.BeanFactory
class and place it in the org.springframework.beans.factory
package. Let the Map<String, Object> singletons
stored inside this class, in which the id
bean is mapped onto the bean itself. Add the Object getBean(String beanName)
method to it, which pulls the beans by ID. public class BeanFactory { private Map<String, Object> singletons = new HashMap(); public Object getBean(String beanName){ return singletons.get(beanName); } }
BeanFactory
and FactoryBean
are different things. The first is the bins factory (container), and the second is the bin factory, which sits inside the container and also produces bins. Factory inside the factory. If you are confused between these definitions, you can remember that in English the second noun is the leading one, and the first is something like an adjective. In the word Bean Factory The main word is the factory, and in the Factory Bean - bin.ProductService
and PromotionsService
classes. ProductService
will return the product from the database, but before that you need to check whether any discounts (Promotions) apply to this product. In e-commerce, work with discounts is often allocated to a separate class-service (and sometimes to a third-party web service). public class PromotionsService { } public class ProductService { private PromotionsService promotionsService; public PromotionsService getPromotionsService() { return promotionsService; } public void setPromotionsService(PromotionsService promotionsService) { this.promotionsService = promotionsService; } }
BeanFactory
) discover our classes, create them for us and inject one into the other. Operations of type new ProductService()
must be inside the container and be done for the developer. Let's use the most modern approach (scanning classes and annotations). To do this, we need to use pens to create the @Component
annotation ( org.springframework.beans.factory.stereotype
). @Retention(RetentionPolicy.RUNTIME) public @interface Component { }
RetentionPolicy.CLASS
). We changed this behavior through a new retention policy ( RetentionPolicy.RUNTIME
).@Component
before the ProductService
classes and before the PromotionService
. @Component public class ProductService { //... } @Component public class PromotionService { //... }
BeanFactory
scan our package ( com.kciray
) and find in it classes that are annotated with @Component
. This task is not trivial at all. In Java Core there is no ready solution , and we should make a crutch most. Thousands of applications in spring use component scanning through this crutch. You have learned the terrible truth. You will have to extract ClassLoader
names from ClassLoader
and check ClassLoader
they end in ".class" or not, and then build their full name and pull class objects out of it!BeanFactory
and call it in Main
: //BeanFactory.java public class BeanFactory{ public void instantiate(String basePackage) { } } //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray");
ClassLoader
. It is responsible for loading the classes, and it’s pretty simple to mine: ClassLoader classLoader = ClassLoader.getSystemClassLoader();
List<URL>
(paths in your file system that can be used to search for class files). String path = basePackage.replace('.', '/'); //"com.kciray" -> "com/kciray" Enumeration<URL> resources = classLoader.getResources(path);
Enumeration<URL>
is not a List<URL>
. What is it all about? Oh horror, this is the old progenitor Iterator
, available since the days of Java 1.0. This is the legacy we have to deal with. If you can walk through Iterable
with for (all collections implement it), then in the case of Enumeration
you will have to do a walk around with handles, through while(resources.hasMoreElements())
and nextElement()
. And yet there is no possibility to remove items from the collection. Only 1996, only hardcore. Oh yes, in Java 9, we added the Enumeration.asIterator()
method, so you can work through it. while (resources.hasMoreElements()) { URL resource = resources.nextElement(); File file = new File(resource.toURI()); for(File classFile : file.listFiles()){ String fileName = classFile.getName();//ProductService.class } }
lastIndexOf(".")
: if(fileName.endsWith(".class")){ String className = fileName.substring(0, fileName.lastIndexOf(".")); }
Class
: Class
): Class classObject = Class.forName(basePackage + "." + className);
@Component
annotation: if(classObject.isAnnotationPresent(Component.class)){ System.out.println("Component: " + classObject); }
Component: class com.kciray.ProductService Component: class com.kciray.PromotionsService
new ProductService()
, but for each bin we have our own class. Reflection in Java provides us with a universal solution (the default constructor is called): Object instance = classObject.newInstance();//=new CustomClass()
Map<String, Object> singletons
. To do this, select the name of the bean (its id). In Java, we call variables like classes (only the first lowercase letter). This approach can be applicable to bins too, because Spring is a Java framework! Transform the name of the bin so that the first letter is small, and add it to the map: String beanName = className.substring(0, 1).toLowerCase() + className.substring(1); singletons.put(beanName, instance);
instantiate()
and the name of the method classObject.newInstance();
have a common root. Moreover, instantiate()
is part of the bin's life cycle. In Java, everything is interconnected! //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); ProductService productService = (ProductService) beanFactory.getBean("productService"); System.out.println(productService);//ProductService@612
org.springframework.beans.factory.stereotype.Service
annotation. It performs exactly the same function as @Component
, but is called differently. The whole point is in the title - you demonstrate that the class is a service, not just a component. This is something like conceptual typing. In the certification for the spring there was a question “What annotations are stereotyped? (of the listed). So, stereotypical annotations are those that are in the stereotype
package.newInstance()
). The next step is the cross injection of the bins (dependency injection, also known as control inversion (IoC)). You need to go through the properties of the bins and understand exactly what properties you need to inject. If you call productService.getPromotionsService()
, you will get null
, because dependency not yet added.org.springframework.beans.factory.annotation
package and add the @Autowired
annotation to @Autowired
. The idea is to mark with this annotation those fields that are dependencies. @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { }
@Component public class ProductService { @Autowired PromotionsService promotionsService; //... }
BeanFactory
find these annotations and inject dependencies on them. Add a separate method for this, and call it from Main
: public class BeanFactory { //... public void populateProperties(){ System.out.println("==populateProperties=="); } }
singletons
map, and for each bean go through all its fields ( object.getClass().getDeclaredFields()
returns all fields, including private ones). And check if the field has @Autowired
annotation: for (Object object : singletons.values()) { for (Field field : object.getClass().getDeclaredFields()) { if (field.isAnnotationPresent(Autowired.class)) { } } }
for (Object dependency : singletons.values()) { if (dependency.getClass().equals(field.getType())) { } }
promotionsService
using reflection directly. But spring doesn't work that way. After all, if a field has a private
modifier, we will first have to set it as public
, then write our value, then set it to private
again (to preserve integrity). Sounds like a big crutch. Let's make a small crutch instead of a large crutch (we will form the name of the setter and call it): String setterName = "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);//setPromotionsService System.out.println("Setter name = " + setterName); Method setter = object.getClass().getMethod(setterName, dependency.getClass()); setter.invoke(object, dependency);
productService.getPromotionsService()
our bean is returned instead of null
.javax.annotation.Resource
). It differs in the fact that instead of the field type, its name will be extracted, and according to it - dependence from mapy. It's all the same, even in something easier. I recommend that you experiment and create some kind of your own bin, and then inject it with @Resource
and extend the populateProperties()
method.@InjectName
and sculpt it into fields of type String. But this solution will be too general and allows you to shoot yourself in the foot many times (place this annotation on the fields of unsuitable types (not String), or try to inject the name into several fields in the same class). There is another solution, more accurate - to create a special interface with one setter method. All the bins that implement it get their name. Create a BeanNameAware
class in the org.springframework.beans.factory
package: public interface BeanNameAware { void setBeanName(String name); }
PromotionsService
implement it: @Component public class PromotionsService implements BeanNameAware { private String beanName; @Override public void setBeanName(String name) { beanName = name; } public String getBeanName() { return beanName; } }
public void injectBeanNames(){ for (String name : singletons.keySet()) { Object bean = singletons.get(name); if(bean instanceof BeanNameAware){ ((BeanNameAware) bean).setBeanName(name); } } }
BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); beanFactory.populateProperties(); beanFactory.injectBeanNames(); //... System.out.println("Bean name = " + promotionsService.getBeanName());
InitializingBean
interface, and put the signature of the void afterPropertiesSet()
method in it. The implementation of this mechanism is absolutely similar to that presented for the BeanNameAware
interface, so the solution is under the spoiler. Practice and do it yourself in a minute: //InitializingBean.java package org.springframework.beans.factory; public interface InitializingBean { void afterPropertiesSet(); } //BeanFactory.java public void initializeBeans(){ for (Object bean : singletons.values()) { if(bean instanceof InitializingBean){ ((InitializingBean) bean).afterPropertiesSet(); } } } //Main.java beanFactory.initializeBeans();
afterPropertiesSet()
method), and the other after. Now let's think about the methods themselves - what parameters should they have? Obviously, there must be a bin itself ( Object bean
). For convenience, in addition to the bean, you can pass the name of this bean. You remember that bin itself does not know its name. And we do not want to force all beans to implement the BeanNameAware interface. But, at the post processor level, the name of the bean can be very useful. So we add it as the second parameter. package org.springframework.beans.factory.config; public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object bean, String beanName); Object postProcessAfterInitialization(Object bean, String beanName); }
//BeanFactory.java private List<BeanPostProcessor> postProcessors = new ArrayList<>(); public void addPostProcessor(BeanPostProcessor postProcessor){ postProcessors.add(postProcessor); }
initializeBeans
method so that it takes into account post-processors: public void initializeBeans() { for (String name : singletons.keySet()) { Object bean = singletons.get(name); for (BeanPostProcessor postProcessor : postProcessors) { postProcessor.postProcessBeforeInitialization(bean, name); } if (bean instanceof InitializingBean) { ((InitializingBean) bean).afterPropertiesSet(); } for (BeanPostProcessor postProcessor : postProcessors) { postProcessor.postProcessAfterInitialization(bean, name); } } }
public class CustomPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { System.out.println("---CustomPostProcessor Before " + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) { System.out.println("---CustomPostProcessor After " + beanName); return bean; } }
//Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.addPostProcessor(new CustomPostProcessor());
@PostConstruct (javax.annotation.PostConstruct)
annotation @PostConstruct (javax.annotation.PostConstruct)
. It provides an alternative method of initialization (having roots in Java, and not in the spring). Its essence is that you place an annotation on a method, and this method will be called BEFORE standard spring initialization (InitializingBean).@PostConstruct
is implemented exactly this way, through the post-processor CommonAnnotationBeanPostProcessor. But do not peek there, write your implementation.void close()
to the class BeanFactory
and work out two more mechanisms. The first is the abstract @PreDestroy (javax.annotation.PreDestroy)
, intended for methods that must be called when the container is closed. The second is the interface org.springframework.beans.factory.DisposableBean
that contains the method void destroy()
. All bins that execute this interface will be able to destroy themselves (free up resources, for example). //DisposableBean.java package org.springframework.beans.factory; public interface DisposableBean { void destroy(); } //PreDestroy.java package javax.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface PreDestroy { } //DisposableBean.java public void close() { for (Object bean : singletons.values()) { for (Method method : bean.getClass().getMethods()) { if (method.isAnnotationPresent(PreDestroy.class)) { try { method.invoke(bean); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } if (bean instanceof DisposableBean) { ((DisposableBean) bean).destroy(); } } }
BeanFactory
. But, besides the basic functions (DI), it also adds some cool features. One of these features is sending and processing events between bins.org.springframework.context
, ApplicationContext
. BeanFactory
. , close()
. public class ApplicationContext { private BeanFactory beanFactory = new BeanFactory(); public ApplicationContext(String basePackage) throws ReflectiveOperationException{ System.out.println("******Context is under construction******"); beanFactory.instantiate(basePackage); beanFactory.populateProperties(); beanFactory.injectBeanNames(); beanFactory.initializeBeans(); } public void close(){ beanFactory.close(); } }
Main
, , : ApplicationContext applicationContext = new ApplicationContext("com.kciray"); applicationContext.close();
close()
, « » - . , : package org.springframework.context.event; public class ContextClosedEvent { }
ApplicationListener
, . , ( ApplicationListener<E>
). , Java-, . , , : package org.springframework.context; public interface ApplicationListener<E>{ void onApplicationEvent(E event); }
ApplicationContext
. close()
, , . ApplicationListener<ContextClosedEvent>
, onApplicationEvent(ContextClosedEvent)
. , ? public void close(){ beanFactory.close(); for(Object bean : beanFactory.getSingletons().values()) { if (bean instanceof ApplicationListener) { } } }
bean instanceof ApplicationListener<ContextClosedEvent>
. Java. (type erasure) , <T> <Object>. , ? , ApplicationListener<ContextClosedEvent>
, ? for (Type type: bean.getClass().getGenericInterfaces()){ if(type instanceof ParameterizedType){ ParameterizedType parameterizedType = (ParameterizedType) type; } }
Type firstParameter = parameterizedType.getActualTypeArguments()[0]; if(firstParameter.equals(ContextClosedEvent.class)){ Method method = bean.getClass().getMethod("onApplicationEvent", ContextClosedEvent.class); method.invoke(bean, new ContextClosedEvent()); }
@Service public class PromotionsService implements BeanNameAware, ApplicationListener<ContextClosedEvent> { //... @Override public void onApplicationEvent(ContextClosedEvent event) { System.out.println(">> ContextClosed EVENT"); } }
//Main.java void testContext() throws ReflectiveOperationException{ ApplicationContext applicationContext = new ApplicationContext("com.kciray"); applicationContext.close(); }
Source: https://habr.com/ru/post/419679/
All Articles