【Spring Framework】(1)控制反转与依赖注入

基本问题

class A{}
class B{
	B(){this.a = new A(...)}//紧耦合
}
class C{
	C(A a){this.a = a}//松耦合
}
  1. 依赖:如果B类需要使用到A类,那么类B就依赖于A。上面例子中,B依赖于A,C依赖于A。
  2. 耦合:存在依赖,就存在耦合。
    – 上面例子中,A与B存在耦合,A与C存在耦合。
    – 但耦合是有松紧的,当依赖的类发生改变时,当前类需要做的改动越少,那么耦合程度就越低。
    – 上面例子中,如果A的构造方法发生变化,那么B需要进行修改,C不需要进行修改,前者耦合程度低于后者的耦合程度。如果在实际中,大量的类都需要依赖A类,那么这些类全部都要修改,者带来很大的工作量。

那么我们应该采取怎样的设计理念来降低耦合度?

控制反转与依赖注入思想

  • 上面例子中,紧耦合来自B类需要控制A类的初始化,我们可以将A类的初始化交给第三方对象来处理,然后保持对第三方对象的引用即可,比如一个简单的思路如下:
class B{
	B(){
		this.a = AFactory.newInstance();
	}
}

使用工厂模式后,B减少响应A的修改,B和A是松耦合。

  • 控制反转(Inversion Of Control,IoC)就要把B类对A类的控制权抽离出来,交给一个第三方去做,把控制权反转给第三方
  • 第三方可以设计为一个容器,我们只需要将设计好的对象放入容器中,使用时从容器中获取即可。这样Ioc容器将控制所有放进去的对象,这种思想就是依赖注入。
  • 依赖注入(Dependency Injection, DI)是IoC最典型的实现方法。由第三方(我们称作IoC容器)来控制依赖,把IoC中的对象通过构造函数、属性或者工厂模式等方法,注入到需要改对象的类中。

Spring的IoC容器

1. IoC容器和Bean概述

  • IoC容器相关的类位于org.springframework.beansorg.springframework.context这两个包下;
  • BeanFactory是所有IoC容器实现类的根接口,提供了配置框架和基本功能;
  • ApplicationContext接口继承于BeanFactory,增加了更多关于企业的功能;
  • Bean的定义:在Spring应用中,构成应用程序主干并且被IoC容器管理的对象称为Bean。
  • 运行流程
    在这里插入图片描述
    首先生成IoC容器,为容器提供原始对象和配置对象的元数据(即如何构造对象),然后使用IoC容器获取装配好的对象。

2. 装配Bean

  • 有3种装配机制:在XML中显示配置在Java中显示配置自动装配,使用优先级为3>2>1。

2.1 自动装配

  1. @Component
  • 在类上使用@Component注解,表明该类是组件类,Spring会将该类放入IoC容器管理,该类成为了容器中的一个Bean。
  • @Component代表通用的组件类,还可以使用@Service@Controller@Repository来表示服务组件、控制器组件、持久化组件,这3个注释只是添加了语义化,没有添加其他功能
  • 可以带参数为Bean指定id,@Component("beanId")
  • IoC容器默认调用的是Bean的无参构造方法,如果有参数,需要使用Value注解。
@Component
class Student{
	String name;
	Student(@Value("${name}")String name){
		this.name = name;
	}
}
  1. @ComponentScan
  • 该注解放在配置类的类注解上,表示启用组件扫描,默认扫描与配置类相同的包及子包,如果查找到某个类带有@Component注解,那么会将该类放入IoC容器管理。
  • 可以带参数,指定需要扫描的基础包@Component(basePackages={})
  1. @Autowired
  • 使用这个注释在使用的地方自动装配Bean,可以在成员变量、构造方法、构造方法参数、一般方法、setter方法等位置

例:在B类注入A实例

//成员变量
@Autowired
A a;

//构造方法
@Autowired
public B(A a){
	//use a do something
}

//构造方法参数
public B(@Autowired A a){
	//use a do something
}

//一般方法,该方法由容器调用,一般指定为setter方法
@Autowired
public void f(A a){
	//use a do something
}

//Autowired注释方法参数时,只能注释构造方法才有效,下面这个注入失败
public void g(@Autowired A a){
	//这里a为null
}
  • 可以带一个参数required,默认为true。当@Autowired(required=true)时,如果IoC容器没找到需要的Bean,就会抛出NoSuchBeanDefinitionException;当@Autowired(required=false)时,IoC容器没找到不会抛出异常,但注入的值为null,因此需要自己手动判断是否为空,否则容易造成NullPointerException
  • 注意:如果一个类位于容器内注入另一个Bean,那么可以使用Autowired;如果一个类位于容器外,那么需要使用到容器的相关引用(如ApplicationContext等)才能获取容器内的Bean。
  1. ApplicationContext
  • 使用这个类创建IoC容器,两种创建方式
  • 第一种,使用类生成,可以传配置类组件类一般类,配置类将会解析其中的注解配置,组件类就直接生成Bean定义,一般类(没有加Component注解)也会纳入容器作为Bean管理。
new AnnotationConfigApplicationContext(A.class,B.class,Config.class...);
  • 第二种,传递基础包,自动扫描配置类和组件类,一般类会被忽略
new AnnotationConfigApplicationContext("test");
  1. 例子
//在B类,声明为一个组件/Bean
@Component
class B{}
//在Config类可以注入B类实例
@Configuration//表明这是一个配置类
@ComponentScan
class Config{
	//使用上面几种autowired都可以
	@Autowired B b;
}
//在Main方法
public static void main(String[] args){
	ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
}//声明容器,Config自动成为一个Bean
Config config = applicationContext.getBean(Config.class);//获取Bean
assertNotNull(config.b);//这里b已经成功注入了

2.2 通过Java代码装配

  • 如果想注入第三方库中的组件,没法在其类上添加@Component注解,因此只能显式装配,显式装配包括Java代码装配和XML装配。
  1. @Bean
  • 即声明一个Bean,Bean的id默认为方法名, 也可以使用name属性指定
//C类被声明为Bean;id为cName,默认为方法名c
@Bean(name="cName")
public C c() {
	//这里可以做很多自定义操作来初始化C
    return new C();
}

2.3 通过XML装配

  • 这是比较传统的装配方式,与使用Java注释装配达到的最终效果是一致的。
  • 在XML文件中配置Bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="c" class="test.C"/>

</beans>
  • 在代码中使用
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:config.xml");
C c = applicationContext.getBean(C.class);
System.out.println(c);

3. Bean限定符

3.1 @primary

  • 在同一个类有多个Bean的候选时,可以指定其中一个为主要的Bean
@Bean
//@Primary
public D d1(){
	return new D();
}
@Bean
public D d2(){
	return new D();
}
//如果不加primary,那么下面两种情况会报错
//1.autowired
@Autowired D d;
//2.appContext.getBean
appContext.getBean(D.class);
//但这样不会报错
@Autowired D d1;

3.2 @Qualifier

  • 限定符,消除Bean的歧义
  • 在Bean的定义上添加这个注解
@Component
@Qualifier("d")
class D{}

@Bean
@Qualifier("d")
publlic D d(){
	return new D();
}
  • 使用
@Autowired
@Qualifier("d")
D d;

4. Bean的作用域(Scope)

4.1 6种作用域

  • 单例(singleton)
    整个应用只创建bean的1个实例
  • 原型(Prototype)
    每次获取时,都创建一个新的实例
  • 会话(Session)
    在Web应用中,为每个会话创建一个实例
  • 请求(Request)
    在Web应用中,为每个请求创建一个实例
  • 应用(Application)
    在Web应用中,为每个Servlet创建一个实例
  • Socket
    在Web应用中,为每个Socket创建一个实例

4.2 使用@Scope

在Bean定义的时候标记

@Component
@Scope("prototype")
public class A{}

@Bean
@Scope("prototype")
public A a(){
}

5. 条件化地创建Bean

5.1 @Conditional

以生产和开发环境为例,例如数据库连接对象等,在生产开发环境中需要不同的对象

//生产环境需要创建这个Bean
@Bean
@Conditional(ProdCondition.class)
C c1() {
    return new C();
}

//开发环境创建这个Bean
@Bean
@Conditional(DevCondition.class)
C c2() {
    return new C();
}

5.2 Condition接口

class ProdCondition implements Condition{
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return "prod".equals(context.getEnvironment().getProperty("env"));
    }
}
class DevCondition implements Condition{

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return "dev".equals(context.getEnvironment().getProperty("env"));
    }
}

matches方法返回是否需要创建这个Bean

6. 运行时注入外部值

6.1 @PropertySource

  • 可以在配置类声明外部值来源的文件
@PropertySource("classpath:application.properties")

文件内容为key=value的形式

name=myName
key=value

6.2 Environment接口

  • 使用Environment接口获取值
  • 可以直接注入Environment对象
@Autowired Environment env;
  • 也可以通过context获取
appContext.getEnvironment();
  • 然后获取值
env.getProperty("key");

6.3 @Value

  • 可以注入Spring表达式(SpEL)的值,包括字面量和外部的值。具体SpEL后面会写到。
  • 主要注释字段和构造方法,为对象赋予初始值
  • 直接赋予字面量值,内容可以为Spring表达式
@Value("myName")
private String name;
  • 也可以赋予外部值
@Value("${name}")
private String name;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值