【Spring学习】IOC容器&依赖注入基本原理&Bean加载

在这里插入图片描述

1,概念

1)IOC(Inversion of Control,控制反转)

没有IOC之前,对象A依赖于对象B需要A主动创建B或者使用已经创建的B,控制权在A上。
引入IOC之后,对象A与对象B之间不直接关联,B通过注解@Compoent将自己注入IOC容器中,运行时由IOC容器主动创建一个对象B注入到A要用到的地方。前后比对,A获得依赖B的过程由主动变成被动,控制权颠倒过来,此谓控制反转。全部对象的控制权上缴给了“IOC容器”,即好莱坞原则:

don‘t call us, we‘ll call you

2)DI(Dependency Injection,依赖注入)

由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。依赖注入是实现IOC的方法。
DI使用接口编程,在对象使用时动态注入。
有3种借本的注入方式:

  1. 构造函数注入(Contructor Injection)
    最简单的依赖注入方式.
public class MovieLister {
    private MovieFinder finder;

    public MovieLister(MovieFinder finder) {
        this.finder = finder;
    }
    ...
}
  1. setter注入
public class MovieLister {
    s...
    public void setFinder(MovieFinder finder) {
        this.finder = finder;
    }
}
  1. 接口注入
    接口注入使用接口来提供setter方法,其实现方式如下。
    首先要创建一个注入使用的接口。
public interface InjectFinder {
    void injectFinder(MovieFinder finder);
}
class MovieLister implements InjectFinder {
    ...
    public void injectFinder(MovieFinder finder) {
      this.finder = finder;
    }
    ...
}

3)IOC容器(spring管理对象)

  1. map(key,value)里存的各种对象
    在xml中配置的bean节点、@repository、@service、@controller、@component
  2. 在项目启动时读取配置文件里面的bean节点,根据全限定类名使用反射创建对象并放入map。
  3. 接下来在代码里需要用到依赖对象的时候,再通过依赖注入(autowired、resource等注解;xml里bean节点内的ref属性,项目启动时读取xml节点的ref属性根据id注入.)。

4)常用注解

给容器注入组件:包扫描+组件标注注解:

注解场景说明备注
@Component通用组件模式当组件不好归类的时候,我们可以使用这个注解进行标注。 Controller、@Service、@Repository都可以称为@Component
@ControllerWeb控制器模式控制层组件,详情见下文
@Repository数据仓储模式数据访问组件,即DAO组件
@Service服务模式业务层组件
@RestController控制层组件,包含@Controller、@ResponseBody返回体前面不需要再添加@ResponseBody就可以识别

注入bean的注解:

注解场景说明备注
@AutowiredBean依赖注入,只能按类型注入spring提供的注解,可以作用在变量(不推荐)、setter方法、构造函数上。属性required配置为false表示找不到不抛出异常正常启动,注入为null。和@Qualifier一起使用
@Inject用法和@Autowired一样,按类型注入由JSR-330提供,和@Name一起使用
@ResourceBean依赖注入,默认按名称注入(name属性),也支持按类型注入(type属性)jdk提供的注解。和@Autowired注解类似,都用来声明需要自动装配的bean,区别在于@Autowired是类型驱动的注入,而@Resource是名称驱动的注入,所以前者存在多bean问题,而后者,只要保证bean命名唯一就不会出现多bean的问题。
@Qualifier细粒度的@Autowired依赖查找当有多个同一类型的Bean时,可以用@Qualifier(“name”)来指定,与@Autowired配合使用
@Primary让spring进行自动装配的时候,默认使用首选的bean,和@Qualifier一个效果。用于一个接口有多种实现时,有此注解的类会被优先考虑。多个注解会抛异常。
@DependsOn指当前bean所依赖的bean。
@Lazy作用在bean上,表明这个bean延迟加载;作用在方法上,表示这个方法被延迟加载;作用在@Component 注释的类上,表明这个类中所有的bean 都被延迟加载。用于解决循环依赖。

1>@Autowired 有三种注入方式

  1. @Autowired注入(不推荐)
    原理:在容器启动,为对象赋值的时候,遇到@Autowired注解,会用后置处理器机制,来创建属性的实例,然后再利用反射机制,将实例化好的属性,赋值给对象。==>可能存在注入为null的情况。
@Autowired
private AService aService;
  1. 构造器注入(推荐)
    spring在4.x版本后就推荐使用构造器的方式的来注入字段。
    一个对象所有依赖的对象先实例化后,才实例化这个对象。这样就避免了注入null对象。
//Lombok提供的全构造函数; Spring4.3+之后,constructor注入支持非显示注入方式,构造函数之上不用写@Autowired 
@RequiredArgsConstructor
@RestController
public class TestController {
	//对象必须为final
	private final AService aService;
	private final BService bService;
}

官方推荐理由

  1. 单一职责
    当使用构造函数注入的时候,你会很容易发现参数是否过多,这个时候需要考虑你这个类的职责是否过大,考虑拆分的问题;而当使用@Autowired注入field的时候,不容易发现问题
  2. 依赖不可变
    只有使用构造函数注入才能注入final
  3. 依赖隐藏
    使用依赖注入容器意味着类不再对依赖对象负责,获取依赖对象的职责就从类抽离出来,IOC容器会帮你自动装备。这意味着它应该使用更明确清晰的公用接口方法或者构造器,这种方式就能很清晰的知道类需要什么和到底是使用setter还是构造器
  4. 降低容器耦合度
    依赖注入框架的核心思想之一是托管类不应依赖于所使用的DI容器。换句话说,它应该只是一个普通的POJO,只要您将其传递给所有必需的依赖项,就可以独立地实例化。这样,您可以在单元测试中实例化它,而无需启动IOC容器并单独进行测试(使用一个可以进行集成测试的容器)。如果没有容器耦合,则可以将该类用作托管或非托管类,甚至可以切换到新的DI框架。
  1. setter注入
    原理:使用set方法依赖注入时,Spring首先实例化对象,然后才实例化所有依赖的对象。如果合作者无效或不存在spring会抛出异常,这样spring保证一个对象的合作者都是可用的。
@RestController
public class TestController {
 
    private final AService aService;
    private final BService bService;
 
    @Autowired
    public void setAService(AService aService){
        this.aService = aService;
    }
  
    @Autowired
    public void setBService(BService bService){
        this.bService = bService;
    }
}

2>@Lazy

主要用于解决循环依赖。对应@Autowired的三种用法:

  1. @Autowired注入(不推荐)
  2. 构造器注入(推荐)
public DictController(@Lazy AService aService){
}
  1. setter注入(推荐)

3>@Bean注解

注解场景说明备注
@Scope设置Spring容器如何新建Bean实例方法上,得有@Bean
说明
Singleton单例,一个Spring容器中只有一个bean实例,默认模式。 Bean 放入 Spring IoC 的缓存池中,BeanFactory管理它的生命周期
Protetype为每一个Bean提供一个实例,即每次注入都新建一个bean。 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean
Requestweb项目中,给每个http request新建一个bean
Sessionweb项目中,给每个http session新建一个bean,session过期则失效
GlobalSession给每一个 global http session新建一个Bean实例,全局作用域,和Portlet相关,几乎不用了。

其它:

application: 定义在ServletContext的生命周期中复用的一个单例对象;
websocket:bean定义在websocket的生命周期中复用的单例对象。
session:表示一个会话。(从客户端浏览器连接服务器开始,到客户端浏览器与服务器断开,这个过程就是一个会话)

  1. Singleton 作为单例是线程安全的吗?
    不是。static不安全的,尽量放threadLocal中。==》如Spring的事务管理器,使用ThreadLocal为不同线程保存不同的connection(有状态的)。

有状态的:会保存数据。这样就会受线程安全影响。

Bean的生命周期及执行顺序:

序号阶段具体说明
1处理BeanDefinitionBeanDefinition解析扫描类路径,确定类定义:
1. XML资源、
2. Properties资源、
3. Java注解
BeanDefinition注册注册到BeanDefinitionRegistry
BeanDefinition合并变为RootBeanDefinition
2Bean实例化(Instantiation)Bean实例化前对象已实例化,但是属性还未设置,都是null
Bean实例化
Bean实例化后解析@Autowired@Value@Resource@Inject@PostConstruct@PreDestroy等,并将元信息缓存下来
3Bean属性赋值赋值前
赋值依赖注入:查找被@Autowired@Value@Resource标注的方法或属性的元信息并注入
4Bean初始化(Initialization)Bean Aware接口回调阶段回调Aware方法,比如BeanNameAware、BeanFactoryAware:
1. 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
2. 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
3. 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
Bean初始化前1. BeanPostProcessor # postProcessAfterInitialization
2. 执行@PostConstruct标注的方法
Bean初始化阶段执行InitializingBean # afterPropertiesSet()
Bean初始化后执行BeanPostProcessor # postProcessAfterInitialization(Spring 的 AOP 在此处加工实现)
Bean初始化完成启动阶段,初始化所有非延迟单例Bean完成后
执行SmartInitializingSingleton # afterSingletonsInstantiated,只调用一次
5Bean销毁Bean销毁前1. 执行DestructionAwareBeanPostProcessor # postProcessBeforeDestruction
2. 执行@PreDestroy标注的方法
Bean销毁阶段1. 执行DisposableBean # destory()
2. 执行自定义销毁方法
不会立刻gc

5)BeanPostProcessor

用来定制Bean,在Bean初始化阶段执行。
如果有多个BeanPostProcessor ,通过Ordered接口或者@Order注解来指定运行顺序。

场景:

  1. 通过BeanPostProcessor 实现工厂模式
  2. AOP的实现。

2,BeanFactory

BeanFactory是所有Spring Bean的容器根接口,给IOC提供了一套完整的规范。BeanFactory实际上是实例化、配置和管理众多bean的容器。
有诸多实现:如 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等。

常用方法:

方法内容
getType(String name)获取Bean的Class类型
getBean(String name)从Spring容器中获取对应Bean对象
containsBean(String name)判断Spring容器中是否存在该对象
isSingleton(String name)判断Bean对象是否为单例对象

1)和ApplicationContext区别

ApplicationContext是BeanFactory的子接口。

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
}

1>ApplicationContext功能

  1. 继承MessageSource,因此支持国际化;
  2. 统一的资源文件访问方式;
  3. 提供再监听器中注册bean的事件;
  4. 同时加载多个配置文件;
  5. 载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用的web层。

2>区别

  1. BeanFactory采用延迟加载形式来注入Bean==>调用时才对Bean进行加载实例化==>第一次使用调用getBean方法才会抛出异常。ApplicationContext在容器启动时,一次性创建了所有的Bean==>容器启动时就能发现依赖是否注入。==》程序启动较慢,占用更多的内存空间。运行较快。
  2. BeanFactory需要手动注册,ApplicationContext可以自动注册。

2)和FactoryBean区别

FactoryBean:是一个创建bean的工厂,又是一个bean。
其作用类似于@Bean注解,但比其能实现更多复杂的功能,重要方法:getObject(),返回一个bean。
源码如下:

public interface FactoryBean<T> {

	
	String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

	//获取bean对象
	@Nullable
	T getObject() throws Exception;

	//bean对象的类型
	@Nullable
	Class<?> getObjectType();

	//生产的bean是否为单例
	default boolean isSingleton() {
		return true;
	}

}

使用场景:
在Spring中有两种类型的bean,一种是普通Bean,一种是工厂Bean,即FactoryBean,一般用于创建比较复杂的bean。

public class UserFactoryBean implements FactoryBean<User> {

    @Override
    public User getObject() throws Exception {
        return new User();
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}
在application.xml中注册一个UserFactoryBean<bean id="userFactory" class="com.yaliyao.pojo.testBeanPojo.UserFactoryBean"/>

    @Test
    public void testFactoryBean(){
        XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("application.xml"));
        User user1 = xmlBeanFactory.getBean(User.class);
        User user2 = xmlBeanFactory.getBean(User.class);
        Object user3 = xmlBeanFactory.getBean("userFactory");
        //要获取FactoryBean本身,要在前面加&
        Object userFactory = xmlBeanFactory.getBean("&userFactory");
        
        System.out.println(user1);
        System.out.println(user2);
        System.out.println(user3);
        System.out.println(userFactory);
        System.out.println(user1==user2);
    }
运行结果:    
User(id=0, username=null, password=null)
User(id=0, username=null, password=null)
User(id=0, username=null, password=null)
com.yaliyao.pojo.testBeanPojo.UserFactoryBean@77846d2c
false

当我们使用第三方框架或者库时,有时候是无法去new一个对象的,
比如静态工厂,对象是不可见的,只能通过getInstance()之类方法获取,
此时就需要用到FactoryBean,通过实现FactoryBean接口的bean重写getObject()方法,
返回我们所需要的bean对象。

3,Aware接口

Bean一般不需要感知到Spring容器的存在,但是在一些场景需要用到容器的特殊功能,通过Spring提供的很多Aware接口可以拿到容器信息,从而代替通过@Autowired注解注入BeanFactory或ApplicationContext,直接使用Bean。

常见场景:

  1. 同一个类的方法互相调用,让第二个@Transaction修饰的方法注解生效
  2. AOP方法直接调用导致方法失效

1)实现ApplicationContextAware接口

源码:

public interface ApplicationContextAware extends Aware {
    void setApplicationContext(ApplicationContext var1) throws BeansException;
}

使用方式:

  1. 启动类 implements ApplicationContextAware 并实现
 @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
		//将ApplicationContext 存储起来
        SpringContextUtils.setContext(applicationContext);
    }
  1. 全局对象存储ApplicationContext ,并封装调用方法

import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

@Component("myServiceSpringContextUtils")
public class SpringContextUtils {
    /**
     * 上下文对象实例
     */
    private static ApplicationContext applicationContext;

    public static void setContext(ApplicationContext context) {
        SpringContextUtils.applicationContext = context;
    }

    /**
     * 获取applicationContext
     *
     * @return
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 通过name获取 Bean.
     *
     * @param name
     * @return
     */
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    /**
     * 通过class获取Bean.
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    /**
     * 通过name,以及Clazz返回指定的Bean
     *
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }

}
  1. 调用:通过getBean方法获取想要的Bean对象
//调用
SynchService synchService = SpringContextUtils.getBean(SynchService .class);
synchService.methodA();

2)实现BeanFactoryAware接口

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值