掘金 后端 ( ) • 2024-04-11 15:52

highlight: a11y-dark

作为一个以项目为驱动的程序员,我以为学习一项技术时,很少在使用它之前就系统地学习过这项技术,深刻地理解这项技术的由来。所以快速学习如何使用技术成为程序员的基本操作,但如果只有基本操作无疑提升很慢,想要进步,还是要根据应用继续学习原理。因此学习顺序从上学时候的“是什么-为什么-怎么做”,变成了“怎么用-为什么-是什么”。

所以今天从怎么用容器开始理解。

怎么用

new一个对象

在我们还不懂什么是“面向对象”时,就学会了创建对象。一个类中想调用另一个类中的方法,当然要先new一个对象。TestController中想要使用TestService的方法,需要先初始化一个TestServiceImpl的对象,在哪一步调用,就在哪一步创建对象,因此这两个方法中各创建了一个对象。

package com.example.demo.controller;

import com.example.demo.service.TestService;
import com.example.demo.service.impl.TestServiceImpl;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping("/test1")
    public void test1() {
        TestService testService = new TestServiceImpl();
        testService.test1();
    }

    @GetMapping("/test2")
    public void test2() {
        TestService testService = new TestServiceImpl();
        testService.test2();
    }

}
package com.example.demo.service.impl;

import com.example.demo.service.TestService;

public class TestServiceImpl implements TestService {

    @Override
    public void test1() {
        System.out.println("this is test1");
    }

    @Override
    public void test2() {
        System.out.println("this is test2");
    }
}

如果有更多的方法,每个方法都需要这个类的对象,那么更好的方法是把这个对象变成一个公共对象。

package com.example.demo.controller;

import com.example.demo.service.TestService;
import com.example.demo.service.impl.TestServiceImpl;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {

    TestService testService = new TestServiceImpl();

    @GetMapping("/test1")
    public void test1() {
        testService.test1();
    }

    @GetMapping("/test2")
    public void test2() {
        testService.test2();
    }

}

把testService变成一个成员对象后,就省去了每次调用前先创建对象的步骤。但如果别的类中也需要一个TestService的对象呢?

package com.example.demo.controller;

import com.example.demo.service.TestService;
import com.example.demo.service.impl.TestServiceImpl;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test2")
public class TestTwoController {

    TestService testService = new TestServiceImpl();

    @GetMapping("/test1")
    public void test1() {
        testService.test1();
    }

    @GetMapping("/test2")
    public void test2() {
        testService.test2();
    }

}

创建全局对象

这样每个需要TestService的类中都需要创建对象,但其实我只需要一个对象,怎么让这个对象再公共一点呢?我想到一个好方法,一个简单的单例模式

package com.example.demo.util;

import com.example.demo.service.TestAService;
import com.example.demo.service.TestBService;
import com.example.demo.service.TestService;
import com.example.demo.service.impl.TestAServiceImpl;
import com.example.demo.service.impl.TestBServiceImpl;
import com.example.demo.service.impl.TestServiceImpl;

public class ObjectUtil {

    private static TestService testService = new TestServiceImpl();

    private static TestAService testAService = new TestAServiceImpl();

    private static TestBService testBService = new TestBServiceImpl();

    public static TestService getTestService() {
        return testService;
    }

    public static TestAService getTestAService() {
        return testAService;
    }

    public static TestBService getTestBService() {
        return testBService;
    }

}

这样每个需要引用这个对象的地方,只需要获取就可以了

TestService testService = ObjectUtil.getTestService();

TestAService testAService = ObjectUtil.getTestAService();

TestBService testBService = ObjectUtil.getTestBService();

写到这里,还有一些问题需要解决,比如,每一个需要全局对象的类都要创建,一个项目中那么多类,写的完吗?比如,初始化对象时如果需要参数怎么办?比如,每个对象有没有作用域之分?

这就是Spring容器要解决的问题了。

@Component注解的使用

在web项目中,controller层要用@RestController的注解,service层要用@Service的注解,配置要用@Configuration,这些注解都是由一个@Component衍生来的。@Component就是需要创建对象的信号了。

package com.example.demo.service.impl;

import com.example.demo.service.TestService;
import org.springframework.stereotype.Service;

@Service
public class TestServiceImpl implements TestService {

    @Override
    public void test1() {
        System.out.println("this is test1");
    }

    @Override
    public void test2() {
        System.out.println("this is test2");
    }
}
package com.example.demo.controller;

import com.example.demo.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    TestService testService;

    @GetMapping("/test1")
    public void test1() {
        testService.test1();
    }

    @GetMapping("/test2")
    public void test2() {
        testService.test2();
    }

}

首先在TestServiceImpl上加@Service的注解,然后在需要用到TestService对象的地方加上@Autowired的注解,这两个注解配合,在项目中就完全省去了创建对象这个步骤。

至此,虽然还不完全理解容器,但已经会使用容器了。

为什么

关于为什么这么用,以上述简单的操作可以得到浅显的认知:每次都要创建对象,管理对象太麻烦,对象又要占用堆内存。我想要一个工具,能帮我实现管理对象,包括对象的初始化、对象的保存、对象的使用、对象的销毁等等。并且这个工具最好让我写代码时能无感使用,我不需要关心每个对象的具体创建过程,直接能使用每个对象的核心方法。

是什么

那么容器到底是什么?

概念层面看,管理对象的东西就叫容器。 代码层面看,以上操作中的ObjectUtil,就是我们自己写的容器。Spring中的容器叫ApplicationContext,而Spring容器所管理的对象,就是bean。

以上操作从我们自己创建对象变成了由容器创建对象,这就是控制反转,即Inversion of Control (IoC)。