Implementing Simple IOC and AOP by Imitating Spring

1. Background

When I was a senior intern, I began to contact with J2EE development work. I also came into contact with and studied Spring framework at the same time. It has been almost two years now. Nevertheless, Spring IOC and AOP have not been copied before, but they have a certain understanding of the principles of Spring IOC and AOP on the macro level. So in order to further understand Spring IOC and AOP principles. In my spare time, I refer to some information and code, and implement a simple IOC and AOP, and achieve the following functions:

  1. Load the related bean s according to the xml configuration file

  2. Support bean s of BeanPostProcessor type

  3. Support bean s of BeanFactoryAware type

  4. Implementation of AOP based on JDK dynamic proxy

  5. Integrating IOC and AOP makes them work well together.

Before I implement my own IOC and AOP, my idea is to implement a very simple IOC and AOP, even if it is implemented in dozens of lines of code. After that, it feels interesting. But that implementation is too simple, far from Spring IOC and AOP. Later on, I thought that I couldn't just satisfy the simple implementation, so I had this copying project. Comparatively speaking, the imitation code is a little more complicated, has a little more functions, and looks a little bit like it. Although the copying project is still toy level, but in the process of copying, we have learned something. Generally speaking, the harvest is still great. In the next article, I will also implement different versions of IOC and AOP from easy to difficult. Okay, let's not say much. Start working.

2. Simple IOC and AOP implementation

2.1 Simple IOC

Starting with a simple IOC container implementation, the simplest IOC container can be implemented in only four steps, as follows:

  1. Load the xml configuration file and traverse the < bean > tag

  2. Get the id and class attributes in the < bean > tag, load the classes corresponding to the class attributes, and create the beans

  3. Traverse the < property > tag in the < bean > tag, get the attribute value, and fill the attribute value into the bean

  4. Register beans into bean containers

As shown above, it only takes four steps. Does it feel simple? All right, Talk is cheap, Show me the code. Next, we need to add the code. But the guest officer is not in a hurry. Before I go to the code, let me give a brief introduction to the code structure.

SimpleIOC     // The implementation class of IOC implements the four steps mentioned above.
SimpleIOCTest    // IOC Test Class
Car           // Beans for IOC testing
Wheel         // Ditto 
ioc.xml       // bean configuration file

Container implementation SimpleIOC-like code:

public class SimpleIOC {

    private Map<String, Object> beanMap = new HashMap<>();

    public SimpleIOC(String location) throws Exception {
        loadBeans(location);
    }

    public Object getBean(String name) {
        Object bean = beanMap.get(name);
        if (bean == null) {
            throw new IllegalArgumentException("there is no bean with name " + name);
        }

        return bean;
    }

    private void loadBeans(String location) throws Exception {
        // Load the xml configuration file
        InputStream inputStream = new FileInputStream(location);
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        Document doc = docBuilder.parse(inputStream);
        Element root = doc.getDocumentElement();
        NodeList nodes = root.getChildNodes();

        // Traversing the < bean > tag
        for (int i = 0; i < nodes.getLength(); i++) {
            Node node = nodes.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                String id = ele.getAttribute("id");
                String className = ele.getAttribute("class");
                
                // Loading beanClass
                Class beanClass = null;
                try {
                    beanClass = Class.forName(className);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                    return;
                }

                   // Create bean s
                Object bean = beanClass.newInstance();

                // Traverse < property > labels
                NodeList propertyNodes = ele.getElementsByTagName("property");
                for (int j = 0; j < propertyNodes.getLength(); j++) {
                    Node propertyNode = propertyNodes.item(j);
                    if (propertyNode instanceof Element) {
                        Element propertyElement = (Element) propertyNode;
                        String name = propertyElement.getAttribute("name");
                        String value = propertyElement.getAttribute("value");

                            // Use reflection to set access rights to bean-related fields as accessible
                        Field declaredField = bean.getClass().getDeclaredField(name);
                        declaredField.setAccessible(true);

                        if (value != null && value.length() > 0) {
                            // Fill attribute values in related fields
                            declaredField.set(bean, value);
                        } else {
                            String ref = propertyElement.getAttribute("ref");
                            if (ref == null || ref.length() == 0) {
                                throw new IllegalArgumentException("ref config error");
                            }
                            
                            // Fill references in related fields
                            declaredField.set(bean, getBean(ref));
                        }

                        // Register beans into bean containers
                        registerBean(id, bean);
                    }
                }
            }
        }
    }

    private void registerBean(String id, Object bean) {
        beanMap.put(id, bean);
    }
}

bean code for container testing:

public class Car {
    private String name;
    private String length;
    private String width;
    private String height;
    private Wheel wheel;
    
    // Eliminate other unimportant code
}

public class Wheel {
    private String brand;
    private String specification ;
    
    // Eliminate other unimportant code
}

The bean configuration file ioc.xml content:

<beans>
    <bean id="wheel" class="com.titizz.simulation.toyspring.Wheel">
        <property name="brand" value="Michelin" />
        <property name="specification" value="265/60 R18" />
    </bean>

    <bean id="car" class="com.titizz.simulation.toyspring.Car">
        <property name="name" value="Mercedes Benz G 500"/>
        <property name="length" value="4717mm"/>
        <property name="width" value="1855mm"/>
        <property name="height" value="1949mm"/>
        <property name="wheel" ref="wheel"/>
    </bean>
</beans>

IOC test class SimpleIOCTest:

public class SimpleIOCTest {
    @Test
    public void getBean() throws Exception {
        String location = SimpleIOC.class.getClassLoader().getResource("spring-test.xml").getFile();
        SimpleIOC bf = new SimpleIOC(location);
        Wheel wheel = (Wheel) bf.getBean("wheel");
        System.out.println(wheel);
        Car car = (Car) bf.getBean("car");
        System.out.println(car);
    }
}

Test results:

The above is the whole content of a simple IOC implementation. It's not too difficult, and the code is not too difficult to understand. I won't talk more about it here. Let's talk about the implementation of simple AOP.

2.2 Simple AOP Implementation

AOP implementation is based on the proxy model, which I believe you should all know. The proxy mode is the foundation of AOP implementation. It is not difficult to understand the proxy mode. It will not take much time to introduce the proxy mode here. Before introducing the implementation steps of AOP, we first introduce some concepts in Spring AOP, and then we will use these concepts.

Advice

    Notification defines the logic to weave into the target object and the timing of execution.
    Spring corresponds to five different types of notifications:
    · Before: Execute the notification before the target method is executed
    · After: After the target method is executed, the notification is executed, regardless of what the result of the target method is returned.
    · After-returning: After the target method is executed, the notification is executed
    · After-throwing: Execute the notification after the target method throws an exception
    · Around notification: The target method is notified of the package, and the notification is invoked before and after the execution of the target method

Pointcut

    If a notification defines when to execute a notification, then a pointcut defines where to execute the notification. So the function of the tangent point is
    By matching rules to find the appropriate join points, AOP weaves notifications into those join points.

Aspect

   Cutting bread contains notification and tangent points, which together define what a tangent is, when and where to perform the tangent logic. 

After that, let's talk about the steps of simple AOP implementation. AOP is based on JDK dynamic proxy and can be accomplished in only three steps.

  1. Define an object that contains aspect logic, assuming logMethodInvocation

  2. Define an Advice object (implements the InvocationHandler interface) and pass in the logMethod Invocation and the target object above

  3. Pass the above divce object and target object to the JDK dynamic proxy method to generate the proxy for the target object

The above steps are relatively simple, but in the implementation process, there are still some difficulties, here we need to introduce some auxiliary interfaces to achieve. Next, I will introduce the code structure of simple AOP.

MethodInvocation Interface  // Implementation classes contain aspect logic, such as logMethodInvocation above
Advice Interface        // InvocationHandler interface inherited
BeforeAdvice class    // Implementing the Advice interface is a pre-notification
SimpleAOP class       // Generating proxy classes
SimpleAOPTest      // SimpleAOP from test class
HelloService Interface   // Target Object Interface
HelloServiceImpl   // Target object

Method Invocation Interface Code:

public interface MethodInvocation {
    void invoke();
}

Advice interface code:

public interface Advice extends InvocationHandler {}

BeforeAdvice implementation code:

public class BeforeAdvice implements Advice {
    private Object bean;
    private MethodInvocation methodInvocation;

    public BeforeAdvice(Object bean, MethodInvocation methodInvocation) {
        this.bean = bean;
        this.methodInvocation = methodInvocation;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // Call notification before execution of target method
        methodInvocation.invoke();
        return method.invoke(bean, args);
    }
}

SimpleAOP implementation code:

public class SimpleAOP {
    public static Object getProxy(Object bean, Advice advice) {
        return Proxy.newProxyInstance(SimpleAOP.class.getClassLoader(), 
                bean.getClass().getInterfaces(), advice);
    }
}

HelloService interface and its implementation class code:

public interface HelloService {
    void sayHelloWorld();
}

public class HelloServiceImpl implements HelloService {
    @Override
    public void sayHelloWorld() {
        System.out.println("hello world!");
    }
}

SimpleAOPTest code:

public class SimpleAOPTest {
    @Test
    public void getProxy() throws Exception {
        // 1. Create a MethodInvocation implementation class
        MethodInvocation logTask = () -> System.out.println("log task start");
        HelloServiceImpl helloServiceImpl = new HelloServiceImpl();
        
        // 2. Create an Advice
        Advice beforeAdvice = new BeforeAdvice(helloServiceImpl, logTask);
        
        // 3. Generating Agents for Target Objects
        HelloService helloServiceImplProxy = (HelloService) SimpleAOP.getProxy(helloServiceImpl,beforeAdvice);
        
        helloServiceImplProxy.sayHelloWorld();
    }
}

Output results:

The above is a simple implementation of IOC and AOP. The implementation process is relatively simple. At the same time, IOC container and AOP can only run independently, but can not be integrated together. So functionally, the IOC and AOP implemented in this version are still too weak. In the next article, I will implement a more complex IOC and AOP, if you are interested to see. Okay, this article is over. What's wrong with it is welcome to point out. Thank you.


This work adopts Knowledge Sharing Signature - Non-commercial Use - Prohibition of Deduction of 4.0 International Licensing Agreement License.

Keywords: Java Spring xml JDK Attribute

Added by raymie7 on Sun, 02 Jun 2019 23:44:54 +0300