Spring

定义

Spring是分层的JavaSE/EE full-stack(一站式) 基于容器轻量级开源框架,是为了解决企业应用程序开发复杂性而创建的。核心特性可以用于开发任何 Java 应用程序。Spring的出现是为了取代EJB(Enterprise JavaBean)的臃肿、低效、脱离现实。

SUN提供的EE的三层结构:

  • web层。Struts2是web层基于MVC设计模式框架.
  • 业务层。
  • 数据访问层(持久层,集成层)。Hibernate是持久的一个ORM的框架.

Spring框架有对三层的一站式解决方案:

  • web层:Spring MVC
  • 持久层:JDBC Template
  • 业务层:Spring的Bean管理

Spring的核心

Spring容器

Spring有两个核心接口:BeanFactory和ApplicationContext,其中ApplicationContext是BeanFactory的子接口。他们都可代表Spring容器,Spring容器是生成Bean实例的工厂,并且管理容器中的Bean
基于 Spring 的应用中,所有的对象(即bean)生存于 Spring 容器,Spring 负责创建、装配、配置并管理这些bean的整个生命周期。

DI依赖注入

DI(依赖注入)也称为IoC(控制发转)用于管理Java对象之间的依赖关系.
其基本概念是:不创建对象,但是描述创建它们的方式。在代码中不直接与对象和服务连接,但在配置文件中描述哪一个组件需要哪一项服务。容器 (在 Spring 框架中是 IOC 容器) 负责将这些联系在一起。

AOP(面向切片编程)

AOP用于解耦业务代码和公共服务代码(如日志,安全,事务等)。

AOP术语

  • 通知(Advice) :就是你想要的功能,用方法实现的功能,也就是上说的安全、事物、日子等。你先把功能用方法给先定义好,然后再想用的地方用一下。包含Aspect的一段处理代码。
  • 连接点(JoinPoint) :就是spring允许你把通知(Advice)放在的地方,那可就真多了,基本每个方法的前、后(两者都有也行),或抛出异常是时都可以是连接点,spring只支持方法连接点。其他如AspectJ还可以让你在构造器或属性注入时都行,不过那不是咱们关注的,只要记住,和方法有关的前前后后都是连接点。
  • 切入点(Pointcut): 是上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对吧,但是你并不想在所有方法附件都使用通知(使用叫织入,下面再说),你只是想让其中几个,在调用这几个方法之前、之后或者抛出异常时干点什么,那么就用切入点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
  • 切面(Aspect) :是通知和切入点的结合。现在发现了吧,没连接点什么事,连接点就是为了让你好理解切点搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的befor,after,around等就能知道),二切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
  • 引入(introduction) :允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗。
  • 目标(target) :引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咋们织入切面。二自己专注于业务本身的逻辑。
  • 代理(proxy) :怎么实现整套AOP机制的,都是通过代理,这个一会儿给细说。
  • 织入(weaving): 把切面应用到目标对象来创建新的代理对象的过程。

Spring配置方式

通过配置Spring告诉容器需要加载哪些Bean和如何装配这些Bean。

通过配置文件

  • Spring的核心框架提供了10个命名空间配置
  • Spring的属性注入有两种方式:构造器注入和set方法注入

配置文件含义

关于图片的补充:

  • 为类的非集合类型成员变量进行注入:有构造器注入和set注入(需要有set方法)两种方法。其中构造器注入通过constructor-arg标签,set注入通过property标签。
  • 创建Bean:可以通过构造函数,也可以通过设置bean标签的factory-method属性,通过静态方法创建。
//使用factory-method属性:允许我们调用一个指定的静态方法代替构造函数创建一个实例。
<bean> id="duke" 
	   class="com.springinaction.springidol.Juggler">
	   factory-method="getInstance"/> 
</bean>
--------------------------------------------------------------------
 //<constructor-arg> 元素:构造函数注入,让Spring调用对应的构造器实现成员变量赋值,否则使用默认空参构造器
<bean> id="duke" class="com.springinaction.springidol.Juggler">
	<constructor-arg value="15">
</bean>
--------------------------------------------------------------------
<beans>的default-init-method和default-destroy-method属性为所有Bean设置了共同的初始化和销毁方法。
--------------------------------------------------------------------
<bean>内部可以包含内部Bean,可以通过构造器和set方法注入:
1、通过set方法注入
     <bean id="saxophone" class="com.spring.test.setter.Saxophone"/>
     </bean>
     <bean id="kenny" class="com.spring.test.setter.Instrumentalist">
	     <property name="age" value="25" />
         <property name="instrument">
             <bean class="com.spring.test.setter.Saxophone"/>//将bean赋给instrument,这个bean专属于Bean kenny,和上面id="saxophone"的Bean不是一个Bean
         </property>
     </bean>
2、通过构造器注入
     <bean id="kenny" class="com.spring.test.setter.Instrumentalist">
	     <constructor-arg value="30"/>
         <constructor-arg>
             <bean class="com.spring.test.setter.Saxophone"/>
         </constructor-arg>
     </bean>
     上面的构造器注入对应的构造函数是:
     public Instrumentalist(int age,Instrument instrument){
        this.age = age;
        this.instrument = instrument;
    }

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

装配集合属性

元素描述可以装配的类型
<list>允许重复任何 java.util.Collection 的实现或数组
<set>不允许重复任何 java.util.Collection 的实现或数组
<msp>名称和值可以是任意类型java.util.Map
<props>名称和值必须是String类型java.util.Properties
  • 具体配置代码可以参见上面的图,另外补充:<msp> 中key为Bean引用的时候,需要用key-ref指定,而非key。

通过SpEL(Spring表达式)装配Bean的属性

有些属性在运行时才知道,此时需要运行期执行的表达式将值装配到Bean的属性或构造函数中。通过SpEL可以完成传统Spring装配方式难以做到的事情:

  • 调用方法和访问对象的属性;
  • 对值进行算数、关系和逻辑运算;
  • 正则表达式匹配;
  • 集合操作。

SpEL表达式要放到#{ }之中,具体而言:

  • 表示字面值
<bean id = "yoona" class = "com.sjf.bean.Student">
	<property name="age" value="#{24}"/>
	<property name="school" value="#{'西电'}"/>//字面量为字符串时,里面单引号外面双引号,反之亦可
	<property name="name" value="my name is #{'yoona'}"/>
	<property name="weight" value="#{120.4}"/>
	<property name="sex" value="#{true}"/>
</bean>
  • 引用Bean、属性和方法
//引用Bean---------------------------------------------
<property name="school" ref="xidianSchool"/>
//上面用SpEL可以表示如下。注意,用的还是value!
<property name="school" value="#{xidianSchool}"/>

//引用Bean属性---------------------------------------------
property name="address" value="#{xidianSchool.location}"/>

//引用Bean方法---------------------------------------------
<property name="address" value="#{xidianSchool.getLocation()}"/>
//如果getLocation() 返回一个null 值, 那么SpEL 表达式求值时会抛出一个NullPointerException 异常。
//为避免抛出空指针异常,使用null-safe 存取器(?.)。如果如果getLocation() 返回一个null 值,SpEL不会再调用getCity()方法。
<property name="address" value="#{xidianSchool.getLocation()?.getCity()}"/> 
  • 操作类
//在SpEL 中,使用T() 运算符返回类对象,从而调用类的静态方法和常量
<property name="pi" value="#{T(java.lang.Math).PI}"/> 
<property name="randomNumber" value="#{T(java.lang.Math).random()}"/> 
  • 运算
    这里写图片描述
  • 在SpEL 中筛选集合
  • 访问集合成员[]
<property name="school" value="#{schools[数字常量或者表示式]}"/>
  • 查询集合成员.?[] 或 .^[] 或 .$[]
//从学校集合中查询位于西安的学校
<property name="likeSchool" value="#{schools.?[location == '西安']}"/>
//从集合中查询出第一个匹配项
<property name="school" value="#{schools.^[location == '山东']}"/>
//从集合中查询出最后一个匹配项
<property name="school" value="#{schools.$[location == '山东']}"/>
  • 投影集合.![]
//从集合的每一个成员中选择特定的属性放入一个新的集合中
//将所有学校的姓名作为成员,放入visitedSchool集合中
<property name="visitedSchool" value="#{schools.![name]}"/>

通过注解

Bean的作用域

当通过Spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下5种作用域:

  • singleton:默认,单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例

  • prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例

  • request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效

  • session:对于每次HTTP Session,使用session定义的Bean都将产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效

  • globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效

p命名空间和c命名空间

使用p-namesapce和c-namespace可以简化bean的定义。

  • p-namespace的作用就是使用xml中的元素属性取代节点来定义bean的属性。

    //普通set注入:
    <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="person" class="Person">
            <property name="name" value="Tom"/>
            <property name="age" value="20"/>
        </bean>
        <bean id="messageHandler" class="MessageHandler">
            <property name="messageService">
                <ref bean="messageService" />
            </property>
        </bean>
    </beans>
    
    //使用p-namespace
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p"               //增加了一行声明
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="person" class="Person" p:name="Tom" p:age="20"/>         //简化为一行
        <bean id="messageHandler" class=“MessageHandler” p:messageService-ref=“messageService”/> //加上-ref后缀即表示是对一个bean的引用
    </beans>
    
  • 类似的,c-namespace的作用就是简化构造器方式注入方式

    //普通构造器注入
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:c="http://www.springframework.org/schema/c"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="person" class="Person">
            <constructor-arg name="name">
                <value>Tom</value>
            </constructor-arg>
            <constructor-arg name="age" value="20"/>
        </bean>
    	    <bean id="messageHandler" class="MessageHandler">                  //一般类型赋值
            <constructor-arg name="messageService" ref bean="messageService"/> //对象赋值
        </bean>
    </beans>
    
    //使用c-namespace
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:c="http://www.springframework.org/schema/c"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
            
    	<bean id="person"  c:name="Tom" c:age="20"/>
        <bean id="messageHandler" class="MessageHandler" c:messageService-ref="messageService"/>
    </beans>
    

如何使用Bean

  • 创建上下文
  • 使用getBean方法获取bean

最小化Spring XML配置

随着应用的不断发展,我们将不得不编写越来越复杂的XML配置,为此Spring提供了自动装配和自动检测来帮助我们减少XML的配置数量。

Bean的自动装配

让Spring自动识别如何装配Bean的依赖关系,有助于减少甚至消除配置<property>元素和<constructor-arg>元素

  • 4中类型的自动装配:
    利用<beans>属性default-autowire为所有Bean设置自动装配类型,默认为none,表示都不使用自动装配。
    利用<bean>元素的autowire属性来覆盖<beans>的默认自动装配策略,设置某个Bean的自动装配类型。
    即使设置了自动装配,我们还是可以通过显式配置来指定装配的值,而忽略自动装配。

  • byName——把与Bean的属性名具有相同名字(或者id)的其他Bean自动装配到Bean的对应属性中。

    如果没有跟属性的名字相匹配的Bean,则该属性不进行装配。

    	//手动装配
    	<bean id="saxophone" class="com.roger.spring.beans.Saxophone" />
    	<bean id="kenny" class="com.roger.spring.beans.Instrumentalist">
    	    <property name="song" value="Jingle Bells" />
    	    <property name="instrument" ref="saxophone" />
    	</bean>
    	//自动装配:自动装配 kenny 的 instrument 属性
    	<bean id="instrument" class="com.roger.spring.beans.Saxophone" />
    	<bean id="kenny" class="com.roger.spring.beans.Instrumentalist"
    	    autowire="byName">
    	    <property name="song" value="Jingle Bells" />
    	</bean>
    
  • byType——把与Bean的属性具有相同类型的其他Bean自动装配到Bean的对应属性中。

    如果没有跟属性的名字相匹配的Bean,则该属性不进行装配;
    如果找到多个匹配的Bean则抛出异常。即应用只允许存在一个Bean与需要自动装配的属性类型相匹配。

    • 解决装配歧义的问题:
      设置一个首选: 将想选的Bean的<bean>中primary属性设为true(所有Bean 默认为true)
      取消其他符合条件的Bean的资格:将其他的Bean的<bean>中primary属性设为false
  • constructor——把与Bean的构造器入参具有相同类型的其他Bean自动装配到Bean构造器的对应入参中。在Bean声明中,不需要<constructor-arg>元素了。

    具有和byType自动装配相同的局限和解决办法。

  • autodetect——首先尝试使用constructor进行自动装配。如果没有发现与构造器相匹配的Bean时,再尝试使用byType进行自动装配。

使用注解装配Bean

使用注解方式允许更细粒度的自动装配,可以选择性地标注某一个属性来对其应用自动装配。

  • Spring容器默认禁用注解装配,所以需要先在xml配置中启用注解。最简单的启用方式为利用context命名空间中<context:annotation-config>元素:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context:"http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http:..www.springframework.org/schema/context
            http:..www.springframework.org/schema/context/spring-context.xsd">
        
        <context:annotation-config />    
    </beans>
    
  • Spring支持集中不同的用于自动装配的注解

  • Spring自带的@Autowired注解(默认是通过byType方式进行自动装配)

    1、标注属性:可以配置任何成员变量,包括私有的
    @Autowired
    private Instrument instrument;
    2、标注方法:可以配置任何成员方法,包括没有set方法的一般方法和构造器
    @Autowired
    public void setInstrument (Instrument instrument){
    	this.instrument = instrument
    }
    3、一些补充
    a、默认情况下,这个注解标识的属性或参数必须是可装配的。没有Bean装配到,就会报NoSuchBeanDefinitionException。这个时候可以设置required=false来设置自动装配是可选的。没有匹配到Bean就会为null。
       @@Autowired(required=false)
    b、有可能spring找到了多个满足条件的Bean,通过@Qualifier注解将范围缩小。相当于把byType转为了byName。
       @Qualifier("guita")//尝试将id为"guita"的bean注入
    
  • JSR-330的@Inject注解

    • 这个注解和@Autowired一样可以自动装配,可以装配属性,方法,构造器。但是没有required属性所以装配的Bean必须存在,不存在会报错。
    • @Autowired的@Qualifier与@Inject的@Named对应。 @Qualifier注解缩小Bean的选择范围(默认使用Bean的id),@Named通过Bean的Id标识可选择的Bean
  • JSR-250的@Resource注解
    @Resource装配顺序

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

    @Value("jack")//装配静态值
    private String name;
    @Value("#{abc.name}")//动态装配
    private String name;
    

Bean的自动检测

比自动装配更进一步,让Spring能够自动识别哪些类需要被配置成Spring Bean,从而减少对<bean>元素的使用。

  • 需要先启用注解:<context:component-scan>。还可以通过<context:component-scan>元素的base-package属性指定的包及其所有子包。
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context:"http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http:..www.springframework.org/schema/context
            http:..www.springframework.org/schema/context/spring-context.xsd">
        
        <context:component-scan
            base-package="com.roger.spring.beans">
        </context:component-scan>
        
    </beans>
    
  • 默认情况下,<context:component-scan>查找使用构造型(stereotype)注解所标注的类。这些特殊注解如下:
    • @Component——通过的构造型注解,标识该类为Spring组件

      • Spring扫描发现@Component注解所标注的类,并自动地将它注册为Spring Bean,Bean的ID默认为无限定类名(如:Person Bean的id为person)
      • 也可以使用@Component(“Roger”)方式来指定Bean的id。
    • @Controller——标识将该类定义为Spring MVC controller

    • @Repository——标识将该类定义为数据仓库

    • @Service——标识将该类定义为服务

    • 使用@Component标注的任意自定义注解

      • <context:component-scan>下面还有子元素<context:include-filter>和<context:exclude-filter>。

      • 只是用<onctext:component-scan>的情况下,spring只会去找基于注解的组件。但是还要包含一个没有注解的接口的所有实现类要怎么办。就是用<context:include-filter>

        <context:component-scan base-package="">
        	//派生与Instrument的所有类自动注册为SpringBean
             <context:include-filter type="assignable" expression="com.springinaction.springidol.Instrument"/>
        </context:component-scan>
        
        • type的5中类型
          1.annotation:扫面指定注解所标注的类,通过expression指定注解。
          2.assignable:扫面派生于expression指定类型的哪些类。
          3.aspectj:扫面与expression指定的aspectJ表达式匹配的类
          4.custom:指定expression使用的自定义TypeFilter实现类。
          5.regex:正则匹配的类。
      • 除了使用<context:include-filter>来告知<onctext:component-scan>哪些类需要注册为Spring Bean以外,还可以使用<context:exclude-filter>告知<onctext:component-scan>哪些类不需要注册为Spring Bean。

Spring基于Java的配置

让我们使用极少量的XML语言而是用Java语言实现配置。

  • 启用Java配置

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context:"http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http:..www.springframework.org/schema/context
            http:..www.springframework.org/schema/context/spring-context.xsd">
            
        // <context:component-scan>会自动加载使用@Configuration注解所标注的类。
        <context:component-scan base-package="com.roger.spring.beans">
        </context:component-scan>
        
    </beans>
    
  • 定义一个配置类
    在基于java的配置里使用@Configuration注解java类,等价于xml配置中的<beans>元素。它告知Spring:这个类将包含一个或多个Spring Bean的定义,这些Bean的定义时使用@Bean注解所标注的方法

    import org.springframework.context.annotation.Configuration;
    @Configuration
    public class SpringConfig {   
        //@Bean告知Spring这个方法将返回一个对象,该对象应该被注册为Spring应用上下文中的一个Bean,方法名作为Bean的id。
        @Bean
        public Person roger() {
            return new Teacher();
        }
    }
    
  • 使用Spring基于java的配置进行注入

    //简单值注入
    
    @Bean
    public A get(){
          return new B(12);
    }
    @Bean
    public A get(){
          B b =  new B();
          b.setName("jack");
           return B;
    }
    //引用注入
    //注意:通过@Bean注解标注address()方法,要在其他Bean的声明方法中引用这个方法时,
    //Spring都会拦截方法的调用,并尝试在应用上下文中查找该Bean,而不是让方法创建一个新的实例。
    @Bean
    public A get(){
          return new B();
    }
    @Bean
    public C set(){
          return new C(get());
    }
    

Spring JDBS框架

Spring 事物管理


#参考文献
https://www.zhihu.com/question/21142149 —什么是Spring
https://www.ibm.com/developerworks/cn/java/wa-spring1/index.html —Spring
http://wiki.jikexueyuan.com/project/spring/ —Spring
https://blog.csdn.net/qq_35246620/article/details/54704656 —Spring MVC
https://blog.csdn.net/LPlanguage/article/details/70598501 —Spring MVC
https://blog.csdn.net/qq_19865749/article/details/70155529 —Spring容器
https://blog.csdn.net/chenssy/article/details/8188570 —Spring容器
https://blog.csdn.net/lutianfeiml/article/details/51731219 —Spring入门第1天–IOC快速入门
https://blog.csdn.net/u012049463/article/details/11807587 —Spring配置文件
https://my.oschina.net/itblog/blog/203436 Spring容器中Bean的作用域
https://www.cnblogs.com/xing901022/p/4248674.html 内部Bean
https://www.cnblogs.com/huang0925/p/3624526.html 使用p-namesapce和c-namespace简化bean的定义
https://blog.csdn.net/sunnyyoona/article/details/50638957 SpEL表达式
https://blog.csdn.net/u014034934/article/details/60579887 自动装配
https://my.oschina.net/u/1020238/blog/502930 自动装配
https://blog.csdn.net/yuanye348623610/article/details/8823429 AOP术语

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐