接上一篇:(四) 控制反转(IOC)/ 依赖注入(DI)

spring 核心功能:beans、core、context、expression
在这里插入图片描述
Spring设计理念
Spring是面向Bean的编程

Spring三个核心组件(Core、Context、Beans)。如果再再他们三个中选一个核心来,那就非Beans莫属了,为何这样说?其实Spring就是面向Bean的变成(BOP,Bean Oriented Programming),Bean才是Spring中的真正主角。Spring就是面向Bean的编程,在Spring中所有对象都可以看成一个Bean。Bean在Spring 中作用就像Object对OOP的意义一样,没有对象的概念就没有面向对象编程,Spring中没有Bean也就没有Spring存在意义。就像一次演出舞台都准备好了但是却没有演员一样。为什么要Bean这种角色?或者说Bean在Spring中如此重要,这都是由Spring框架的设计目标决定的,Spring为何如此流行,我们使用Spring的原因是什么?思考下,你会发现原来Spring解决了一个非常关键的问题,他可以让你把对象之间的关系转而使用配置文件来管理,也就是他的依赖注入机制,而这个注入关系在一个叫Ioc的容器中管理。Spring正是通过把对象包装在Bean中从而达到对这些对象管理以及一系列额外操作的目的。他的这种设计策略完全类似于Java实现OOP的设计理念,当然Java本身的设计要比Spring复杂太多太多,但是都是构建一个数据结构,然后根据这个数据结构设计他的生存环境,并让在这个环境后总按照一定得规律在不停的运动,在他们的不停运动中高设计一系列与环境或者其他个体完成信息交换。这样想来,我们用到过的其他框架都是大概类似的设计理念那这些核心组件如何协同工作?前面说Bean是Spring中的关键因素,那Context和Core又有何作用?前面把Bean比作一场演出中的演员的话,那么Context就是这场演出的舞台背景,而Core就是应该是演出的道具了。只有他们在一起才能具备演出一场好戏的最基本条件。当然有最基本的条件还不能使这场演出脱颖而出,还要他表演的节目足够的精彩,这些节目就是Spring提供的特色功能了。那它是怎么管理这些Bean的呢?Spring把所有的Bean及它们之间的依赖关系以配置文件的方式组装起来,在一个叫IoC(Inversion of Control)的容器中进行管理,这也就是Spring的核心设计思想之一依赖注入机制,Spring的另一个核心设计思想叫做AOP,这两个概念在后续会有详细讲解,这里就不在赘述。通过集成实现的接口就知道他具有spring里面经典的工厂方法,还有对国际化支持的Message,以及配置信息的Resource,还有spring支持的发布和监听事件功能。一个Context基本上把spring具有的核心功能都包裹起来了,那么这就是spring框架运行需要的环境,也就是常说的上下文。任何一个框架运行都通过一个类来进行描述它执行时的环境,ServletContext也是一样,就是Servlet环境信息。可以将context理解为一个框架执行信息的载体,可以理解问一个框架的门面(门面模式),将框架内部的各个组件信息都通过一个context暴露给外部。 上下文模块建立在由核心和bean模块提供的坚实基础上,他是访问定义和配置的任何对象的媒介。ApplicationContext接口是上下文模块的重点。

Spring核心技术控制反转(IoC:Inversion of Control ) /依赖注入(DI:Dependency Injection )

IoC和DI的关系其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器来配置依赖对象”。

4.1 控制反转(IOC)

控制反转(IoC:Inversion of Control )。这是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看看找到自己喜欢的,然后打听她们的兴趣爱好、qq号、电话号………,想办法认识她们,投其所好送其所要……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个),使用完之后还要将对象销毁,对象始终会和其他的接口或类耦合起来。

那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个要求的列表,告诉它我想找个什么样的女朋友,然后婚介就会按照我们的要求,提供一个女孩,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

4.2 Spring容器

spring容器可以理解为生产对象(OBJECT)的地方,在这里容器不只是帮我们创建了对象那么简单,它负责了对象的整个生命周期–创建、装配、销毁。而这里对象的创建管理的控制权都交给了Spring容器,所以这是一种控制权的反转,称为IOC容器

BeanFactory和ApplicationContext是Spring的两大核心接口,而其中ApplicationContext是BeanFactory的子接口。它们都可以当做Spring的容器

BeanFactory

BeanFactory不常用,这是最简单的容器,只能提供基本的DI功能;它在构建容器时,采用延迟加载的方式创建bean对象。

ApplicationContext

继承了BeanFactory后派生而来的ApplicationContext(应用上下文),它能提供更多企业级的服务,例如解析配置文本信息等等,这也是ApplicationContext实例对象最常见的应用场景。它在构建容器时,采用立即加载的方式创建bean对象。
classpath: https://blog.csdn.net/u011095110/article/details/76152952

4.3 getBean方法的三种签名

  1. 按照bean的名字获取
    beanfactory.getBean("helloWorld");
  2. 按照类型获取
    beancontext.getBean(HelloWorld.class);
  3. 按照bean的名字加类型获取(推荐)
    context.getBean("helloWorld",HelloWorld.class);

常见错误
错误1:根据不存的bean的名子去获取bean对象导致NoSuchBeanDefinitionException异常

错误2:多个bean的名字或类型一样,导致NoUniqueBeanDefinitionException异常

4.4 Bean的基本配置

spring 中的id 和name 的区分

  1. id 和name 属性作用上一样,推荐使用id;
  2. id取值要求严格些,必须满足XML的命名规范。id是唯一的,配置文件中不允许出现两个id相同的。
  3. name取值比较随意,甚至可以用数字开头。在配置文件中允许出现两个name相同的,在用getBean()返回实例时,后面一个Bean被返回。
  4. 如果没有id,name,则用类的全名作为name,如,可以使用getBean(“test.Test”)返回该实例。
  5. id的时候用分号(“;”)、空格(“ ”)或逗号(“,”)分隔开就只能当成一个标识,name的时候用分号(“;”)、空格(“ ”)或逗号(“,”)分隔开就要当成分开来的多个标识(相当于别名 alias 的作用)。如:name=“1 2 3 4”等同于 name=“1,2,3,4” 这样写相当于有 1 2 3 4(4个)个标识符标识当前beanid=“1 2 3 4” 这样写相当于有 “1 2 3 4”(1个)个标识符标识当前bean。
  6. 如果既配置了 id ,也配置了 name ,则两个都生效。
  7. 如果存在多个id和name都没有指定,且实例类都一样的,如:
    <bean class="com.stamen.BeanLifeCycleImpl"/>
    <bean class="com.stamen.BeanLifeCycleImpl"/>
    <bean class="com.stamen.BeanLifeCycleImpl"/>

则第一个bean通过getBean(“com.stamen.BeanLifeCycleImpl”)获得,第二个bean通过getBean(“com.stamen.BeanLifeCycleImpl#1”)获得,第三个bean通过getBean(“com.stamen.BeanLifeCycleImpl#2”)获得,以此类推。

Spring中引入其他配置文件
<import resource=" " />元素

4.5 Spring测试框架

Spring 4版本里面配置如下:

@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration({"classpath:applicationContext.xml","classpath:spring_mvc.xml"})public class Test{    @org.junit.Test	public void test1(){	}}

Spring 5版本里面配置如下:

@SpringJUnitConfig(locations = {"classpath:bean.xml"})public class TestApp {	@org.junit.jupiter.api.Test	public void test() {	}}//注:如果配置xml文件是以当前测试类为前缀加-context.xml结尾,(TestApp-context.xml)并且配置文件放置在同级目录时,locations属性可以忽略写

4.6 Bean的实例化方式

1.通过无参构造方式(也可以通过有参的方式)

调用构造方法创建Bean是最常用的一种情况Spring容器通过new关键字调用构造器来创建Bean实例,通过class属性指定Bean实例的实现类,也就是说,如果使用构造器创建Bean方法,则元素必须指定class属性,其实Spring容器也就是相当于通过实现类new了一个Bean实例。调用构造方法创建Bean实例,通过名字也可以看出,我们需要为该Bean类提供无参数的构造器。下面是一个通过构造方法创建Bean的最简单实例

如果一个类没有提供无参构造则导致bean对象无法创建。
案例:

// 演示一个类提供了无参构造和没提供无参构造的情况
<bean id=" " class=" "></bean>

2. 通过静态工厂方式

public class StaticFactory {
public static IUserService getUserService() {		
	return new UserServiceImpl();
	}
}
<bean id="userService" class=" x.x.StaticFactory" factory-method="getUserService">	</bean>

3. 通构普通工厂(实例工厂)方式

public class InstanceFactory {
public IUserService getUserService() {
	return new UserServiceImpl();
}}
<bean id="instanceFactory" class="x.x.InstanceFactory" />
<bean id="userService" factory-bean="instanceFactory" factory-method="getUserService" ></bean>

4. 实现FactoryBean接口方式(它就是第3中方式的变化写法)

BeanFactory与FactoryBean区别
BeanFactory是Spring中Bean工厂的顶层接口,也是我们常说的SpringIOC容器,它定下了IOC容器的一些规范和常用方法并管理着Spring中所有的Bean,今天我们不讲它,我们看一下后面那个FactoryBean。

FactoryBean:首先它是一个Bean,但又不仅仅是一个Bean。它是一个能生产或修饰对象生成的工厂Bean,类似于设计模式中的工厂模式和装饰器模式。它能在需要的时候生产一个对象,且不仅仅限于它自身,它能返回任何Bean的实例。

public class CreateBeanFactory implements FactoryBean<IUserService> { 	@Override	public IUserService getObject() throws Exception {
	return new UserServiceImpl()
} 	
@Override
public Class<?> getObjectType() {
	// TODO Auto-generated method stub	
	return IUserService.class;
}}
<bean id="createBeanFactory" class="cn.bdqn.createbean.CreateBeanFactory" />

4.7 Bean作用域

作用域scope配置项
作用域限定了Spring Bean的作用范围,在Spring配置文件定义Bean时,通过声明scope配置项,可以灵活定义Bean的作用范围。分别如下:

  1. Singleton 在Spring IOC 容器仅存在一个Bean实例,Bean以单例方式存在,这个是默认值。
  2. prototype 每次从容器调用bean时,都会返回一个新的实例,也就是每次调用getBean()时都会实例化一个新的bean。
  3. request 每次HTTP请求都会创建一个新的Bean,该作用于仅适用于web环境
  4. session 每个HTTP Session共享一个Bean,不同的Session使用不同的Bean,同样只适用于web环境。
  5. Global Session 一般作用于Portlet应用环境,只作用于Web环境。当不是集群环境时,它等价于session.

五种作用域中,其中request、session、global session三种作用域仅适用于web环境。

4.8 依赖注入(DI)

DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,分析一下:

  1. 谁依赖于谁:应用程序依赖于IoC容器;
  2. 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
  3. 谁注入谁: IoC容器注入应用程序某个对象,应用程序依赖的对象;
  4. 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

spring框架为我们提供了三种注入方式分别是set注入,构造方法注入,接口注入。接口注入不作要求,下面介绍前两种方式。

注入的数据有三种:

  1. 成员变量: String/int/…
  2. 引用对象: Object
  3. 集合数据:List/set/map/Properties…

通过XML配置装配

1、set注入

采用属性的set方法进行初始化,就成为set注入。
1)给普通字符类型赋值。

 public class User{    
 private String username;   
 public String getUsername() {
          returnusername;
       } 
 publicvoid setUsername(String username) {
       this.username= username;   
        } 
  }  

我们只需要提供属性的set方法,然后去属性文件中去配置好让框架能够找到applicationContext.xml文件的beans标签。标签beans中添加bean标签, 指定id,class值,id值不做要求,class值为对象所在的完整路径。bean标签再添加property 标签,要求,name值与User类中对应的属性名称一致。value值就是我们要给User类中的username属性赋的值。

<bean id="userAction"class="com.lsz.spring.action.User" >  
	<property name="username" value="admin"></property> 
</bean>  

2)给对象赋值
同样提供对象的set方法

public class User{ 
private UserService userservice; 
public UserServicegetUserservice() {   
	 return  user;
	}

public void setUserservice(UserService userservice){    	 
	this.userservice= userservice;
	 }
 }

配置文件中要增加UserService的bean标签声明及User对象对UserService引用。

 <!--对象的声明-->  
 <bean id="userService" class="com.lsz.spring.service.UserService"></bean> 
 <bean id="userAction"class="com.lsz.spring.action.User" > 
 <property name="userservice" ref="userService"></property>  
 </bean>  

这样配置,框架就会将UserService对象注入到User类中。

3)给list集合赋值
同样提供set方法

 public class User{ 
 private List<String> username;
 public List<String> getUsername() {  
  	return username; 
   }
 public void setUsername(List<String> username) {  
    this.username= username;  
  }  
 }  
<bean id="userAction"class="com.lsz.spring.action.User" >
 <property ="username"> 
     
	<list><value>zhang,san</value>          
 	<value>lisi</value> 
 	<value>wangwu</value></list>                                            
 </property> 
 </bean>  

4)给属性文件中的字段赋值

public class User{  
private Properties props ;
public Properties getProps() {
          returnprops;   
    }
public void setProps(Properties props) {
         this.props= props; 
     } 
  }  
 <bean>  
   <property name="props"> 
    <props> 
    <prop key="url">jdbc:oracle:thin:@localhost:orl</prop>
    <prop key="driverName">oracle.jdbc.driver.OracleDriver</prop>
    <prop key="username">scott</prop>
     <prop key="password">tiger</prop> 
      </props>
      </property> 
   </bean>  

标签中的key值是.properties属性文件中的名称
注意:
无论给什么赋值,配置文件中标签的name属性值一定是和对象中名称一致。

2、构造方法注入

1)构造方法一个参数

public class User{
private String usercode;  
public User(String usercode) { 
   this.usercode=usercode; 
    }  
 }  
<bean id="userAction"class="com.lsz.spring.action.User">             		  <constructor-arg  value="admin"></ constructor-arg >                           </bean>  

2)构造函数有两个参数时
当参数为非字符串类型时,在配置文件中需要制定类型,如果不指定类型一律按照字符串类型赋值。
当参数类型不一致时,框架是按照字符串的类型进行查找的,因此需要在配置文件中制定是参数的位置

 <constructor-arg value="admin" index="0"></constructor-arg>                   <constructor-arg value="23" type="int" index="1"></constructor-arg>  

这样制定,就是构造函数中,第一个参数为string类型,第二个参数为int类型

XML自动装配
通过bean提供的autowire属性自动装配(并不会使用,所以只需要了解即可)

自动装配的方式:
autowire属性里指定自动装配的模式
1.no:当autowire设置为no的时候,Spring就不会进行自动注入,相当于不给引 用属性赋值。
2.byType(根据类型自动装配):若IOC容器中有多个与目标Bean类型一致的Bean,在这种情况下Spring将无法判断哪个Bean最适合该属性,所以不能执行自动装配.
3.byName(根据名称自动装配):必须将目标Bean的名称和属性名设置的完全相同.
4.Constructor(通过构造器自动装配,不推荐使用):当Bena中存在多个构造器时.这种自动装配的方式将会很复杂
5.default:全局配置default相当于no,局部的default表示使用全局配置设置

XML配置里的Bean自动装配的缺点:

  1. 在Bean配置文件里设置autowire属性进行自动装配将会装配Bean的所有属性,然而,若希望装配个别属性时,aotuwire属性就不够灵活了
  2. Autowire属性要么根据类型自动装配,要么根据名称自动装配,不能两者兼而有之.一般情况下,在实际的项目中很少使用自动装配功能,因为和自动装配功能所带来的好处比起来,明确清晰的配置文档更有说服力一些.

注解自动装配
DI注解
@AutoWired
Spring 规范提供

  1. 可以让Spring自动的把属性需要的对象找出来,并注入到对象
  2. 可以放置在字段和settet方法上
  3. 默认情况下AutoWired注解必须要能找到对应的对象,否则报错,但是可以通过@AutoWired(required=false)避免
  4. 第三方程序:在Spring3.0之前,需要手动配置DI注解的解析器:
<context:annotation-config />

如果Spring版本高于3.0,则在测试环境下可以不写也能自动解析,但如果是在Web环境下“必须”配置解析器。
@AutoWired注解寻找bean的方式(重要)1) 首先依照依赖对象的类型去找,找到就是用setter或者字段直接注入2) 如果在Spring上下文中找到多个匹配的类型,再按照名字去找,如果没有找到直接报错3) 可以通过使@Qualifier(‘ohter’)注解标签规定依赖对象按照bean的id和type的结合方式去找;注:@Qualifier一般用于当接口有多个实现类时,指明@Autowired具体注入哪个实现类。

@Resource
@Resource装配顺序

  1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
  2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
  3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
  4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;

@AutoWired和@Resource区别:
5. @Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。
6. @Autowired默认按类型装配(这个注解是属业spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用,如下:@Autowired()@Qualifier(“baseDao”)privateBaseDao baseDao;
7. Resource(这个注解属于J2EE的),默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

@Value

  1. 可以直接对bean的字段设置值,例如:@Value(“123”)
  2. 可以获取properties文件的key,获取方式: @Value(“${key}”)
    注:需要把properties文件加载至spring容器里面,通过
 <context:property-placeholder location="classpath:xxxx.properties"/>

IOC注解

<context:component-scan base-package="xx.xx.xx" />

IOC注解需要配置IOC注解解析器(扫描器),通过扫描某些包下的注解,才可使用以下注解,因为它的作用其实就是把标识了这些注解的类以bean的形式放入Spring容器。

Spring 能够从 classpath 下自动扫描, 侦测和实例化具有特定注解的组件.特定的组件包括以下几个:

@Component:基本组件 标识了一个受spring 管理的组件,组件管理的通用形式,如果当前类不属于以下3种组件范畴的类,可以用它来标识。

@Repository:标识持久层(Dao)组件

@Service :标识服务层组件

@Controller:标识表现层组件 (会在Spring MVC知识点,详细讲解)

对于这些组件spring有默认的命名策略, 一般是首字母小写,也可以通过注解中value 属性命名

辰鬼丫
原文地址:https://www.cnblogs.com/Acechengui/p/13635469.html