IOC control reversal

Posted by shamil on Mon, 07 Feb 2022 13:38:57 +0100

preface

Before understanding IOC, we need to know the following knowledge

Servlet development in Java Web

Coupling / dependency

Dependency injection DI / control inversion IOC

Servlet development in Java Web

Servlet is the basis of Java Web, that is, network programming. The bottom layer of most frameworks we use now is servlet.

Servlet development 1.0

As shown in the figure, there are many servlets in the server. For a server, a Servlet will occupy one thread (resource), so reducing servlets appropriately is the goal in the future

Disadvantages: there are too many servlets, which takes up a lot of resources

Servlet Development 2.0

Implementation method:

When the front end sends a request, it will attach a parameter, that is http://localhost:8080:/pro?operate=add

After receiving the request, the FruitServlet uses request Getparameter ("operate") gets the string add,

Judge by if and perform the subsequent add method.

@WebServlet("/fruit.do")
public class FruitServlet extends ViewBaseServlet {
    private FruitDAO fruitDAO = new FruitDAOImpl();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //Set code
        request.setCharacterEncoding("UTF-8");

        String operate = request.getParameter("operate");//Get the parameter of Url, operate
        if(StringUtil.isEmpty(operate)){
            operate = "index" ;
        }

        switch(operate){
            case "index":
                index(request,response);
                break;
            case "add":
                add(request,response);
                break;
            case "del":
                del(request,response);
                break;
            case "edit":
                edit(request,response);
                break;
            case "update":
                update(request,response);
                break;
            default:
                throw new RuntimeException("operate Illegal value!");
        }
    }

    private void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");

        String fname = request.getParameter("fname");
        Integer price = Integer.parseInt(request.getParameter("price")) ;
        Integer fcount = Integer.parseInt(request.getParameter("fcount"));
        String remark = request.getParameter("remark");

        Fruit fruit = new Fruit(0,fname , price , fcount , remark ) ;

        fruitDAO.addFruit(fruit);

        response.sendRedirect("fruit.do");

    }
}

Advantages: it solves the resource occupation of multiple servlets in a server

Disadvantages: when there are too many services, there are too many switch case codes, which are bloated and difficult to maintain

Servlet development 2.5

Using reflection method to solve the code bloated problem of switch case

We delete the switch case and change it to the reflection method,

Know the currently required Servlet class + method name through the URL;

Get the Servlet class using reflection and run the method;

package com.atguigu.servlet;

import java.lang.reflect.Method;

public class reflect {
    public static void main(String[] args) throws Exception {
        String a = "say";
        Class clazz = Class.forName("com.atguigu.servlet.Person");// Get runtime class
        Object o = clazz.newInstance();// Instantiate class 
        Method[] declaredMethod = o.getClass().getDeclaredMethods();// Get its internal method
        for (Method method : declaredMethod) {
            System.out.println(method.getName());
        }
    }
}
class Person {
    public void say() {
        System.out.println("im a good boy");
    }
    public void bye() {
        System.out.println("im a good boy");
    }
}

Advantages: reduces the amount of switch case code

Disadvantages: when I have multiple servlets, such as FruitServlet and UserServlet, I will have hundreds or thousands of the same reflection calling methods, resulting in code redundancy

Servlet development 3.0

Due to servlet2 Each controller in 5 has a reflection code, which has high code redundancy. We passed a

Dispatcher / Servlet / central controller / core controller merge the reflection code

Implementation method:

  • Prepare a configuration file: ApplicationContext XML, which records the information (name, directory) of multiple controller s
<?xml version="1.0" encoding="utf-8"?>
<beans>
    <!-- this bean The function of the label is the future servletpath The names referred to in correspond to fruit,Then you have to FruitController This class handles -->
    <bean id="fruit" class="com.atguigu.fruit.controllers.FruitController"/>
    <bean id="user" class="com.atguigu.fruit.controllers.UserController"/>
    <bean id="order" class="com.atguigu.fruit.controllers.OrderController"/>
    <bean id="product" class="com.atguigu.fruit.controllers.ProductController"/>
</beans>
  • Store the entity objects in multiple bean tags into a container for plug and play

    Step 1 read the configuration file ApplicationContext xml

    InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml");
    //1. Create DocumentBuilderFactory
    DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
    //2. Create DocumentBuilder object
    DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder() ;
    //3. Create Document object
    Document document = documentBuilder.parse(inputStream);
    

    Step 2 get all bean nodes and instantiate them into the container

    private Map<String,Object> beanMap = new HashMap<>();
    //4. Get all bean nodes
    NodeList beanNodeList = document.getElementsByTagName("bean");
    for(int i = 0 ; i<beanNodeList.getLength() ; i++){
        Node beanNode = beanNodeList.item(i);
        if(beanNode.getNodeType() == Node.ELEMENT_NODE){
            Element beanElement = (Element)beanNode ;
            String beanId =  beanElement.getAttribute("id");
            String className = beanElement.getAttribute("class");
            Class controllerBeanClass = Class.forName(className);
            Object beanObj = controllerBeanClass.newInstance() ;
            beanMap.put(beanId , beanObj) ;// Parse the XML file to obtain all objects in the configuration file
        }
    }
    
  • Obtain the Controller through the value passed from the URL parameter

@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //Set code
    request.setCharacterEncoding("UTF-8");
    //Suppose the url is: http://localhost:8080/pro15/hello.do ? operate=add
    //Then servletPath is: / Hello do
    // My idea is:
    // Step 1: / Hello Do - > hello or / fruit do  -> fruit
    // Step 2: Hello - > hellocontroller or fruit - > fruitcontroller
    String servletPath = request.getServletPath();
    servletPath = servletPath.substring(1);
    int lastDotIndex = servletPath.lastIndexOf(".do") ;
    servletPath = servletPath.substring(0,lastDotIndex);
    Object controllerBeanObj = beanMap.get(servletPath);
  • Call the method in the current Controller through the operate parameter

    Traverse all methods, and lock the method to be executed through method name + method parameter + method parameter type

    Execute this method with invoke(Object obj, Object... args)

Method[] methods = controllerBeanObj.getClass().getDeclaredMethods();
for(Method method : methods){
    if(operate.equals(method.getName())){
        //1. Obtain request parameters uniformly
        //1-1. Get the parameters of the current method and return the parameter array
        Parameter[] parameters = method.getParameters();
        //1-2.parameterValues is used to carry the value of the parameter
        Object[] parameterValues = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            String parameterName = parameter.getName() ;
            //If the parameter names are request, response and session, then it is not the way to obtain parameters in the request
            if("request".equals(parameterName)){
                parameterValues[i] = request ;
            }else if("response".equals(parameterName)){
                parameterValues[i] = response ;
            }else if("session".equals(parameterName)){
                parameterValues[i] = request.getSession() ;
            }else{
                //Get parameter value from request
                String parameterValue = request.getParameter(parameterName);
                String typeName = parameter.getType().getName();

                Object parameterObj = parameterValue ;

                if(parameterObj!=null) {
                    if ("java.lang.Integer".equals(typeName)) {
                        parameterObj = Integer.parseInt(parameterValue);
                    }
                }

                parameterValues[i] = parameterObj ;
            }
        }
        //2. Method call in controller component
        method.setAccessible(true);
        Object returnObj = method.invoke(controllerBeanObj,parameterValues);

        //3. View processing
        String methodReturnStr = (String)returnObj ;
        if(methodReturnStr.startsWith("redirect:")){        //For example: redirect: fruit do
            String redirectStr = methodReturnStr.substring("redirect:".length());
            response.sendRedirect(redirectStr);
        }else{
            super.processTemplate(methodReturnStr,request,response);    // For example: "edit"
        }
    }
}

IOC implementation

Coupling / dependency

When dependency refers to, the successful operation of class a requires the use of a method in class B. In this way, class a depends on class B. That is, if class B is not in class A, it cannot be implemented.

This dependency is equivalent to coupling

Our pursuit is high cohesion and low coupling

As shown in the figure:

The Controller Service Dao is interdependent. This is

MVC mode

MVC: model, View, Controller
View layer: an interface for data presentation and user interaction
Control layer: it can accept the request of the client, and the specific business functions still need to be completed with the help of model components
Model layer: there are many kinds of models: simple pojo/vo(value object), business model components and data access layer components

pojo/vo : Value object
AO : Data access object
BO : Business object

IOC - control reversal

  1. Previously, in the Servlet, we created a service object, fruitservice. Fruitservice = new fruitserviceimpl();
    If this sentence appears inside a method in the servlet, the scope (life cycle) of the fruitService should be the method level;
    If this sentence appears in the servlet class, that is, fruitService is a member variable, the scope (life cycle) of this fruitService should be the servlet instance level
  2. ApplicationContext after us The fruitService is defined in XML. Then, by parsing the XML, the fruitService instance is generated and stored in the beanMap, which is in a BeanFactory
    Therefore, we transferred (changed) the life cycle of the previous service instances, dao instances, and so on. Control is transferred from the programmer to BeanFactory. This phenomenon is called inversion of control

DI - dependency injection

  1. Previously, we found the code in the control layer: fruitservice, fruitservice = new fruitserviceimpl();
    The coupling layer and the control layer exist.
  2. After that, we modify the code to fruitservice, fruitservice = null; Then, configure in the configuration file:


code implementation

configuration file

First write the configuration file. In the configuration file, write the class to be used, and declare its name + class path.

// application.xml
<?xml version="1.0" encoding="utf-8"?>
<beans>
    <bean id="fruitDAO" class="com.alex.fruit.dao.impl.FruitDAOImpl"/>
    <bean id="fruitService" class="com.alex.fruit.service.impl.FruitServiceImpl">
        <!-- property Tags are used to represent attributes; name Indicates the attribute name; ref Indicates a reference to another bean of id value-->
        <property name="fruitDAO" ref="fruitDAO"/>
    </bean>
    <bean id="fruit" class="com.alex.fruit.controllers.FruitController">
        <property name="fruitService" ref="fruitService"/>
    </bean>
</beans>

Controller layer

As shown below, you need to use FruitService in FruitController

public class FruitController {
    private FruitService fruitService = null ;
    private String update(Integer fid , String fname , Integer price , Integer fcount , String remark ){
        //3. Execute update
        fruitService.updateFruit(new Fruit(fid,fname, price ,fcount ,remark ));
        return "redirect:fruit.do";
    }
    private String edit(Integer fid , HttpServletRequest request){
        if(fid!=null){
            Fruit fruit = fruitService.getFruitByFid(fid);
            request.setAttribute("fruit",fruit);
            return "edit";
        }
        return "error" ;
    }
}

Service layer

Write a Service interface to facilitate future maintenance

public interface FruitService {
    //View specified inventory records by id
    Fruit getFruitByFid(Integer fid);
    //Modify specific inventory records
    void updateFruit(Fruit fruit);
}

FruitServiceImpl is an implementation class of a Service

public class FruitServiceImpl implements FruitService {

    private FruitDAO fruitDAO = null ;
    @Override
    public Fruit getFruitByFid(Integer fid) {
        return fruitDAO.getFruitByFid(fid);
    }
    @Override
    public void updateFruit(Fruit fruit) {
        fruitDAO.updateFruit(fruit);
    }
}

DAO layer

FruitDAOImpl inherits the JDBC baseDAO and implements the FruitDAO interface at the same time

public class FruitDAOImpl extends BaseDAO<Fruit> implements FruitDAO {
    @Override
    public Fruit getFruitByFid(Integer fid) {
        return super.load("select * from t_fruit where fid = ? " , fid);
    }
    @Override
    public void updateFruit(Fruit fruit) {
        String sql = "update t_fruit set fname = ? , price = ? , fcount = ? , remark = ? where fid = ? " ;
    }
}

BeanFactory

BeanFactory is used to parse XML files and obtain objects declared in any XML file.

public interface BeanFactory {
    Object getBean(String id);
}

The implementation class of BeanFactory is DOM operation

Step 1 get Document object

Read configuration file

InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml");
//1. Create DocumentBuilderFactory
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
//2. Create DocumentBuilder object
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder() ;
//3. Create Document object
Document document = documentBuilder.parse(inputStream);

Step 2 store the objects in the configuration file into the container

Parse Document

Through bean tag id attribute class attribute + reflection = instantiate object + store in container m

//4. Get all bean nodes
NodeList beanNodeList = document.getElementsByTagName("bean");
for(int i = 0 ; i<beanNodeList.getLength() ; i++){
    Node beanNode = beanNodeList.item(i);
    if(beanNode.getNodeType() == Node.ELEMENT_NODE){
        Element beanElement = (Element)beanNode ;
        String beanId =  beanElement.getAttribute("id");
        String className = beanElement.getAttribute("class");
        Class beanClass = Class.forName(className);
        //Create bean instance
        Object beanObj = beanClass.newInstance() ;
        //Save the bean instance object to the map container
        beanMap.put(beanId , beanObj) ;
        //So far, it should be noted that the dependency between bean s has not been set
    }
}

Step3 dependencies between assembly objects (dependency injection)

In the previous step, we instantiated the object through bean tag id attribute, class attribute + reflection = instantiation

However, as shown below, the property tag in the XML file has not been parsed, indicating that we have not carried out dependency injection

// application.xml
<?xml version="1.0" encoding="utf-8"?>
<beans>
    <bean id="fruitDAO" class="com.alex.fruit.dao.impl.FruitDAOImpl"/>
    <bean id="fruitService" class="com.alex.fruit.service.impl.FruitServiceImpl">
        <!-- property Tags are used to represent attributes; name Indicates the attribute name; ref Indicates a reference to another bean of id value-->
        <property name="fruitDAO" ref="fruitDAO"/>
    </bean>
    <bean id="fruit" class="com.alex.fruit.controllers.FruitController">
        <property name="fruitService" ref="fruitService"/>
    </bean>
</beans>

Dependency injection

Parsing Document files through DOM operations

Use if to judge whether the bean tag contains the property tag,

If yes, inject attributes through reflection

for(int i = 0 ; i<beanNodeList.getLength() ; i++){
    Node beanNode = beanNodeList.item(i);
    if(beanNode.getNodeType() == Node.ELEMENT_NODE) {
        Element beanElement = (Element) beanNode;
        String beanId = beanElement.getAttribute("id");
        NodeList beanChildNodeList = beanElement.getChildNodes();
        for (int j = 0; j < beanChildNodeList.getLength() ; j++) {
            Node beanChildNode = beanChildNodeList.item(j);
            if(beanChildNode.getNodeType()==Node.ELEMENT_NODE && "property".equals(beanChildNode.getNodeName())){
                Element propertyElement = (Element) beanChildNode;
                String propertyName = propertyElement.getAttribute("name");
                String propertyRef = propertyElement.getAttribute("ref");
                //1) Find the instance corresponding to propertyRef
                Object refObj = beanMap.get(propertyRef);
                //2) Set refObj to the property property property of the corresponding instance of the current bean
                Object beanObj = beanMap.get(beanId);
                Class beanClazz = beanObj.getClass();
                Field propertyField = beanClazz.getDeclaredField(propertyName);
                propertyField.setAccessible(true);
                propertyField.set(beanObj,refObj);// Injection by reflection
            }
        }
    }
}

Summary:

  1. Write Servlet classes one by one to realize a basic function

  2. There are too many Servlet classes. Merge the servlets of a class of entities and use switch case to realize functions

  3. **Get the method by reflecting * * instead of using switch case, which reduces the amount of code

  4. Since multiple servlets each have to write reflection code (fruit, employee and customer), resulting in code redundancy, the code is simplified by extracting the common part

    This public part (dispatcher) needs to judge which class and method to call through URL parameters. When the judgment is successful, it will call the method directly. That is, there is a dispatcher between the controller and the client, and the dispatcher is responsible for calling the methods in the controller.

  5. Service is introduced to further refine the whole process. As shown above, many operation related codes are still stored in the controller. These methods are extracted and placed in a single layer, that is, the service layer.

  6. So far, we can find that we now have many layers of Controller, Service and DAO. There is a problem with these layers: too many dependencies and too high coupling.

    Solution: create a factory, BeanFactory, through the configuration file, which instantiates each object for us.

    Core idea: change the life cycle of dependent classes.

    Original: FruitService fruitService = new FruitServiceImpl(); Write in a class

    Now: FruitService fruitService = null, Assign this object by reflection,

    Original: the life cycle of the fruitService changes with a class or a method (mainly depending on where the new comes out). When a class is destroyed, the fruitService is also destroyed

    Now: fruitService is placed in a Map container, and its life cycle will not change with the objects that depend on it. It is only controlled by the Map container.

    Benefits: it is conducive to code maintenance,

    In the future, if a large number of dependent objects pass fruitservice, fruitservice = new fruitserviceimpl();

    So if I change fruitserviceimpl() - > fruitserviceimpl2(), I need to change all fruitservices. Fruitservice = new fruitserviceimpl();

    Using the configuration file, I only need to change the bean tag in the configuration file,

    Greatly reduce the cost of code maintenance.

Topics: Java Front-end server