SpringBoot中Spring IOC的运用

维基百科上对IOC的描述:

早在2004年,Martin Fowler就提出了“哪些方面的控制被反转了?”这个问题。他总结出是依赖对象的获得被反转了,因为大多数应用程序都是由两个或是更多的类通过彼此的合作来实现业务逻辑,这使得每个对象都需要获取与其合作的对象(也就是它所依赖的对象)的引用。如果这个获取过程要靠自身实现,那么这将导致代码高度耦合并且难以维护和调试。

上一篇:
《Spring&SpringMVC&SpringBoot》

(一)项目准备

来继续深造这个项目,通过这个项目来了解一下。

.com.shousidaima.truede.
					   -
                       -controller.
                         -HelloController.java
                       -dao.
                         -.impl.
                         	-HelloDaoJDBCImpl.java
                         -HelloDao.java
                       -entity.
                       	 -Hello.java
                       -service.
                       	 -HelloService.java
                         -impl.
    						-HeloServiceImpl.java

具体如下图:

spring boot ioc容器面试题 springboot的ioc容器_java

com.shousidaima.truede.HelloDao.java:

package com.shousidaima.truede.dao;


import com.shousidaima.truede.entity.Hello;

//数据访问层
public interface HelloDao {

    Hello getHello(int code,String msg);

}

com.shousidaima.truede.HelloJDBCImpl.java

我们使用java类来模拟JDBC的链接;
通常我们都是使用Mybatis来绑定dao的。

package com.shousidaima.truede.dao.Impl;

import com.shousidaima.truede.dao.HelloDao;
import com.shousidaima.truede.entity.Hello;
import org.springframework.stereotype.Repository;

@Repository
public class HelloJDBCImpl implements HelloDao {

    @Override
    public Hello getHello(int code, String msg) {
        Hello hello = new Hello();
        hello.setCode(code);
        hello.setMsg(msg);
        return hello;
    }
}

com.shousidaima.truede.Hello.java

package com.shousidaima.truede.entity;

public class Hello {

    private String msg;

    private int code;

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    @Override
    public String toString() {
        return "Hello{" +
                "msg='" + msg + '\'' +
                ", code=" + code +
                '}';
    }
}

com.shousidaima.truede.HelloService.java

package com.shousidaima.truede.service;

import com.shousidaima.truede.entity.Hello;



public interface HelloService {

    Hello getHello(int code, String msg);

}

com.shousidaima.truede.HelloServiceImpl.java

package com.shousidaima.truede.service.impl;

import com.shousidaima.truede.dao.HelloDao;
import com.shousidaima.truede.entity.Hello;
import com.shousidaima.truede.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
public class HelloServiceImpl implements HelloService {

    @Autowired
    HelloDao helloDao;

    @Override
    public Hello getHello(int code, String msg) {
        return helloDao.getHello(code,msg);
    }

}

(二)编写测试单元&验证

package com.shousidaima.truede;

import com.shousidaima.truede.entity.Hello;
import com.shousidaima.truede.service.HelloService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class TruedeApplicationTests {

	@Autowired
	HelloService helloService;

	@Test
	void contextLoads() {
		Hello hello = helloService.getHello(200, "访问成功啦!");
		System.out.println(hello);
	}

}

启动测试单元后:

spring boot ioc容器面试题 springboot的ioc容器_java_02

(三)分析哪些对象被BeanFactory管理了?

@Repository
public class HelloJDBCImpl implements HelloDao { }


@Service
public class HelloServiceImpl implements HelloService { }

可以看到这两个类的头部都加了一个注解,@Repository和@Service。

@Repository:标识这个类是一个数据访问层的代码;
@Service:标识这个类是业务层的代码;

其实这两个是没有本质的区别的,代码一摸一样,只是注解的名字不同:

这样做的目的也是为类区分你这个类是什么类型的类对象实例。

spring boot ioc容器面试题 springboot的ioc容器_spring_03

重点是这两个类上都有@Component这个注解,这个注解就是Spring用来注入bena的其中的一种方式。

再会过头来看HelloController.java对象:

@Controller
@RequestMapping(value = "hello")
public class HelloController {

    @RequestMapping(value = "getHello",method = RequestMethod.GET)
    @ResponseBody
    public String getHello(){
        return "HelloController hello.";
    }

}

@Controller注解的源代码如下:

是不是很惊讶,与@Repository和@Service的源代码都是一样的,同样是为了标识这个类是什么类型的。
加上@Controller后就标志着这是一个控制层的java类,那么访问的时候,会访问这个对象(当然了,还会对其进行一系列的处理)。

spring boot ioc容器面试题 springboot的ioc容器_java_04

(四)为什么实体类没有被BeanFactory管理?

因为一个实体类我们在一个项目中可能会创建很多次&使用很多次,数据也是都不相同的,完全是没必要被BeanFactory管理的。

(五)哪些实体类应该被管理?

单例对象应该被管理:
(1)统一资源类;
(2)N次使用同一个的对象;

在Spring或者SpringBoot或者Mybatis,或者一些和Spring相关的开源框架中,基本上离不开IOC(依赖注入,也就是BeanFactory托管对象)。

(1)例如:我们配置server.port=8081

实际上传递的参数是被ServerProperties.java文件给接收了,然后管理起来了。
SpringBoot在启动过的时候,就会直接拿ServerProperties.java中的参数port参数。

ServerProperties.java源代码如下:

@ConfigurationProperties(
    prefix = "server",
    ignoreUnknownFields = true
)
public class ServerProperties {
    private Integer port;
    private InetAddress address;
    ......
}

(2)例如:我开源的一个框架中的配置文件数据就这样托管的

因为使用配置文件配置一些信息会比较方便。

我开发的框架中也采用的大多数开源框架的思想。

我统一的入口:SwaggerPluginConfig.java中的一段代码

/**
 * Spring IOC统一管理对象
 */
@Configuration
public class SwaggerPluginConfig {

	//从配置文件中获取swagger-plugin开头的信息
    @ConfigurationProperties(prefix = "swagger-plugin")
    @Bean(name = "swaggerPluginConfigBean")
    public SwaggerPluginConfigBean getSwaggerPluginConfigBean(){
        return new SwaggerPluginConfigBean();
    }

}

那么使用我框架的项目中需要在配置文件(application.yml)中:

#swagger 配置
swagger-plugin:
    #配置要扫描的controller层
    scan:
        path: com.springbootswagger1.controller
    #是否开启swagger-plugin框架的debug模式
    debug: true

那么我在框架中就可以直接获取:

@Autowired
  private SwaggerPluginConfigBean swaggerPluginConfigBean;

使用的时候直接调用:

try {
    ....
} catch (CannotCompileException e) {
    //从配置文件中拿到的,如果开启的debug模式,就打印日志
    if(swaggerPluginConfigBean.isDebug()){
        logger.warn("找不到了1:"+e.getMessage());
    }
} catch (NotFoundException e) {
    if(swaggerPluginConfigBean.isDebug()){
        logger.warn("找不到了2:"+e.getMessage());
    }
}

这就是IOC的好处。

(六)获取Spring IOC管理的Bean

package com.shousidaima.truede;

import com.shousidaima.truede.entity.Hello;
import com.shousidaima.truede.service.HelloService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationContextFactory;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.web.context.WebApplicationContext;

@SpringBootTest
class TruedeApplicationTests implements ApplicationContextAware {

	@Autowired
	HelloService helloService;

	@Test
	void contextLoads() {
		Hello hello = helloService.getHello(200, "访问成功啦!");
		System.out.println(hello);
	}

	private  ApplicationContext applicationContext;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

		if (this.applicationContext == null) {
			this.applicationContext = applicationContext;
		}

	}

	@Test
	public void testGetBean(){
		HelloService bean = applicationContext.getBean(HelloService.class);
		System.out.println(bean.getHello(100,"你好呀"));
	}

}

当我们执行testGetBean()测试单元的时候,就会打印:

Hello{msg='你好呀', code=100}

所以是有效的。

还可以根据不同的需求来设置

@Test
public void testGetBean(){
    //通过具体的类的class获取
    HelloService bean = applicationContext.getBean(HelloService.class);
    System.out.println(bean.getHello(100,"你好呀"));

    //通过名字+class 获取
    HelloService helloService = applicationContext.getBean("helloServiceImpl", HelloService.class);
    System.out.println(helloService.getHello(1022,"哈哈哈"));

    //通过名字获取
    //默认的名字是有规律的:java名:HelloServiceImpl -->bean名:helloServiceImpl(首字母大写变小写)
    HelloService helloService1 = (HelloService) applicationContext.getBean("helloServiceImpl");
    System.out.println(helloService.getHello(666,"66666"));

}

结果:

Hello{msg='你好呀', code=100}
Hello{msg='哈哈哈', code=1022}
Hello{msg='66666', code=666}

也可以指定bean的名字,不同的注入方式有不同的方法

例如业务实现层:

在@Service注解中指定名字就可以

@Service("helloimpl")
public class HelloServiceImpl implements HelloService {...}

那么获取的时候:

@Test
public void test1(){
    HelloService helloimpl = applicationContext.getBean("helloimpl", HelloService.class);
    System.out.println(helloimpl.getHello(10086,"hhhhh"));
}

SpringBoot中Spring AOP的运用

为了给读者更好的阅读体验,特此分开来写,关注我,见下一篇。

Spring IoC的原理

为了给读者更好的阅读体验,特此分开来写,关注我,见下一篇。

Spring AOP的原理

为了给读者更好的阅读体验,特此分开来写,关注我,见下一篇。