Spring学习笔记

一、Spring框架

1.1 Spring框架是什么

  • Spring是一种容器框架,用于配置各个组件(bean)并且维护各个组件bean之间的关系
  • Spring框架可以管理Web层,业务层,DAO层,持久层。
  • Spring提倡面向接口编程,配合DI技术可以实现层与层的解耦(主要是WEB层和业务层)
  • Spring框架图


    Spring架构.jpg

1.2 一个简单的spring项目

  • 目录结构

    • 引入spring的开发包(最小配置spring.jar以及日志包common-logging.jar)
    • 创建spring的一个核心文件ApplicationContext.xml, 该文件一般放在src目录下,该文件中引入xsd文件,可以自定义文件名。
      • hibernate核心文件 hibernate.cfg.xml
      • struts核心文件 struts-config.xml
      TestSpring
          |---src
              |---com.netease
              |          |---Service
              |                |---HelloService.java
              |                |---ByeService.java
              |          |---Test
              |                |---TestService.java
              |---ApplicationContext.xml
      External Libraries
          |--- spring.jar
          |--- common-logging.jar
          |--- ...
    
  • 代码详情

    package com.netease.Service;
    public class HelloService {
        private String name;
        private ByeService byeService;      /** 引用了一个ByeService*/
    
        public String getName() { return name;}
        public void setName(String name) { this.name = name;}
    
        public ByeService getByeService() { return byeService;}
        public void setByeService(ByeService byeService) { this.byeService = byeService;}
    
        public void sayHello() {
            System.out.println("hello "+name);
        }
    }
    
    package com.netease.Service;
    public class ByeService {
        private String name;
    
        public String getName() { return name;}
        public void setName(String name) {this.name = name;}
    
        public void sayBye() {
            System.out.println("Bye " + name);
        }
    }
    
    package com.netease.Test;
    import com.netease.Service.HelloService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class TestService {
        public static void main(String[] args) {
            /*通过反射机制,在Spring容器中生成对象*/
            /*如果%%.xml放在某个包下,则就变为<包名+文件名>*/
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
            HelloService helloService = (HelloService) applicationContext.getBean("helloService");
            helloService.sayHello();
            helloService.getByeService().sayBye();
        }
    }
    
  • Spring核心容器文件ApplicationContext.xml

    • 在容器文件中配置Bean(Bean有可能是以下各种类型service/dao/domain/action/数据源)
    • Spring框架加载时,会阅读该容器文件,自动创建一个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"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
                    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
    
        <!--注意Spring如何配置Bean以及如何维护Bean之间的依赖-->
        <bean id="helloService" class="com.netease.Service.HelloService">
            <!--注入属性值-->
            <property name="name">
                <value>Today</value>
            </property>
            <!--维护Bean之间依赖,ref指向下一个Bean-->
            <property name="byeService" ref="byeService"/>
        </bean>
    
        <bean id="byeService" class="com.netease.Service.ByeService">
            <property name="name">
                <value>Yesterday</value>
            </property>
        </bean>
    </beans>
    

1.3 Spring框架运行原理图

  • Spring框架什么时候被加载,Spring中配置的bean怎样被创建,Bean与Bean之间的关系如何维护

1.4 IOC与DI是什么

  • IOC(Inverse Of Controll ) 控制反转: 所谓控制反转就是把创建对象(bean),和维护对象(bean)的关系的权利从程序中转移到spring的容器(applicationContext.xml),而程序本身不再维护.
  • DI(Dependency Injection) 依赖注入: 实际上DI和IOC是同一个概念,Spring设计者认为DI更准确表示Spring核心技术

二、装配Bean

2.1 容纳Bean

  • ApplicationContext方式
    • ApplicationContext ac = new ClassPathXmlApplicationContext("com/netease/bean.xml")
    • 这句话执行时,不仅实例化了该容器,其中配置的所有scope为singleton的bean全部通过反射机制被实例化,scope为prototype的bean不会被实例化
    • 其他三种加载方式
      1. ClassPathXmlApplicationContext:从类路径中加载。
      2. FileSystemXmlApplicationContext:从文件系统加载,需要全路径
      3. XmlWebApplicationContext:从web系统中加载。
    • 好处:预先加载,速度快;缺点:耗内存
    • Bean的作用域,即scope


      scope.png
  • Bean工厂方式
    • BeanFactory factory = new XmlBeanFactory(new ClassPathResource("com/netease/bean.xml"))
    • 这句话执行时,仅实例化了该容器,容器中的bean不被实例化,只有当你使用factory.getBean("***")获取某个bean时,才实例化该bean对应的对象。类似于延迟实例化
    • 好处:节约内存;缺点:速度慢
  • 一般没有特殊要求,都采用ApplicationContext方式实例化(90%),移动端可采用Bean工厂方式

2.2 Bean生命周期

  • 完整生命周期步骤如下(通常只用到加粗的几步):

    1. 实例化:程序加载ApplicationContext文件,并把bean(scope=singleton)实例化到内存
    2. 设置属性:调用set方法设置bean中指定的属性
    3. 如果你实现了BeanNameAware接口, 则可以通过BeanName
    4. 如果你实现了BeanFactoryAware接口,则可以获取BeanFactory
    5. 如果你实现了ApplicationContextAware接口,则可以获取ApplicationContext
    6. 如果bean和一个后置处理器关联,则会自动去调用 postProcessBeforeInitialization()方法
    7. 如果你实现InitializingBean接口,则会调用afterPropertiesSet()方法
    8. 如果设置了<bean init-method=”init” />,则可以在bean文件中定义自己的初始化方法init.
    9. 如果bean和一个后置处理器关联,则会自动去调用 postProcessAfterInitialization()方法
    10. 使用bean
    11. 容器关闭
    12. 可以通过实现DisposableBean接口来调用方法 destory,用来关闭资源等
    13. 可以在<bean destory-method=”destroy”/> 调用定制的销毁方法destroy
  • ApplicationContext方式

    Context.png
  • Bean工厂方式

    Factory.png

2.3 装配Bean

2.3.1 什么是装配Bean

  • 告诉容器有哪些Bean以及容器如何使用依赖注入将它们配合在
    一起。

2.3.2 使用XML装配

  • 在XML文件中配置,spring会自动读取该文件加载配置好的Bean

2.3.3 添加一个Bean

  • 基本配置:一个bean ID + 全称类名

    <beans>
       <bean id="foo" class="...Foo"/>
       <bean id="bar" class="...Bar"/>
    </beans>
    
  • scope属性

    <beans>
      <bean id="foo" scope="prototype/singleton" class="...Foo"/>
      <bean id="bar" class="...Bar"/>
    </beans>
    
    • 注意尽量使用默认的singleton,以防占用太大内存,影响程序性能
  • init-method 和destroy-method属性

    • 用途:在Spring实例化或者销毁bean时做一些处理工作
    • 使用方法:
      1. 声明方式:在XML文件中先声明该Bean拥有的初始化和销毁方法的函数名。在bean.java中实现initdestroy方法,函数名可自定义
         <bean id="foo" init-method="init" destory-method="destroy" class="...Foo" >
        
      2. 标签方式:不需要在xml文件中声明初始化和销毁方法,在bean.java中采用标签方式声明初始化和销毁方法
        @PostConstruct
        public void ini(){…}
        @PreDestroy
        public void destroy(){…}
        
    • 其他进行初始化和销毁处理的方法:
      • Spring还提供了两个接口供bean.java使用,来进行实例化或者销毁时的处理,但不推荐

2.3.4 通过set方法注入属性和依赖

通过<bean>元素的<property>子元素注入属性和依赖

  • 注入简单属性(基本类型和String)
    <bean id="foo" class="...Foo">
          <property name="name">
                <value>tom</value>
          </property>
    </bean>
    
  • 注入依赖
    • 引用其他的Bean
      <bean id="foo" class="...Foo">
            <property name="name">
                  <ref bean="bar">
            </property>
      </bean>
      <bean id="bar" class="...Bar">
      </bean>
      
    • 内部bean
      <bean id="foo" class="...Foo">
        <property name="bar">
              <bean class="...Bar">...</bean>
        </property>
      </bean>
      
  • 注入集合属性(数组,List,Set,Map)
    1. 设置null
      <property name="barlist">
             <null/>
      </property>
      
    2. 数组
      <!-- 给数组注入值 -->
      <property name="empName">
        <list>
          <value>小明</value>
          <value>李雷</value>
          <value>韩梅梅</value>
        </list>
      </property>
      
    3. List
        <!-- 给list注入值 list 中可以有相等的对象 -->
        <property name="empList">
           <list>
               <ref bean="emp2" />
               <ref bean="emp1"/>
               <ref bean="emp1"/>
           </list>
        </property>
      
    4. Set
        <!-- 给set注入值 set不能有相同的对象 -->
        <property name="empsets">
           <set>
               <ref bean="emp1" />
               <ref bean="emp2"/>
               <ref bean="emp2"/>
           </set>
        </property>
      
    5. Map
      <!-- 给map注入值只要key不同即可 -->
        <property name="empMaps">
           <map>
               <entry key="11" value-ref="emp1" />
               <entry key="22" value-ref="emp2"/>
               <entry key="33" value-ref="emp1"/>
           </map>
       </property>
      
    6. property
      <property name="pp">
        <props>
            <prop key="pp1">abcd</prop>
            <prop key="pp2">hello</prop>
        </props>
      </property>
      
  • Bean的继承
    1. public class Student有name和age两个属性
    2. public class Gradate extends Student 有degree属性
    3. 在ApplicationContext文件中体现配置
      • 即student中配置过的属性graduate可以不配置
      • 如果配置,则会覆盖父类的属性
        <!-- 配置一个学生对象 -->
        <bean id="student" class="com.hsp.inherit.Student">
          <property name="name" value="顺平" />
          <property name="age" value="30"/>
        </bean>
        <!-- 配置Grdate对象 -->
        <bean id="grdate" parent="student" class="com.hsp.inherit.Gradate">
          <!-- 如果自己配置属性name,age,则会替换从父对象继承的数据  -->
          <property name="name" value="小明"/>
          <property name="degree" value="学士"/>
        </bean>
      

2.3.5 通过构造器注入依赖

通过<bean>元素的<constructor-arg>子元素注入属性

<bean id="test" class ="...Test">
  <constructor-arg index="0" type="java.lang.String" value="顺平" />
  <constructor-arg index="1" type="int" value="20"/>
  <constructor-arg  index="2" type="double" value="34.5" />
</bean>

2.3.6 自动装配

不推荐使用,但需要了解

  • 通过autowire属性装配
  • <bean id="foo" class="...Foo" autowire="autowire type">
  • 有四种自动装配类型:
    1. byName寻找和属性名相同的bean,若找不到,则装不上。(属性名和bean id名必须相同)
    2. byType:寻找和属性类型相同的bean,找不到,装不上,找到多个抛异常。
    3. constructor:查找和bean的构造参数一致的一个或多个bean,若找不到或找到多个,抛异常。按照参数的类型装配
    4. autodetect: (3)和(2)之间选一个方式。不确定性的处理与(3)和(2)一致。
    5. defualt : 这个需要在<beans defualt-autorwire=“指定” />
    6. no : 不自动装配,这是autowrite的默认值。


      autowire.png
  • default说明
    • 需要在<beans defualt-autorwire=“指定” />
      • 如果在<beans>指定了default-atuowrite后,所有的bean的默认的autowire就是<beans>中指定的装配方法;
      • 如果没有在<beans>指定defualt-autorwire,则默认是defualt-autorwire=”no”,所有的bean则默认不自动装配,除非自己配置autowire

2.3.7 通过标签自动装配(待跟进)

  • spring2.5提供了<context:annotation-config />配置,即只需要在ApplicationContext.xml中写入这句话,可激活在类中探测到的各种注解,
  • 常用注解: @Required @Autowire @PostConstrct @PreDestroy @Resource @EJB @PersistenceContext @WebServiceRef
  • 非常有用

2.3.8 分散配置

通过在ApplicationContext.xml文件中配置context:property-placeholder 引入属性文件,有多个需要使用','号间隔.

  • 步骤如下:
    1. 配置属性文件db.property
       name=scott
       drivername=oracle:jdbc:driver:OracleDirver
       url=jdbc:oracle:thin:@127.0.0.1:1521:hsp
       pwd=tiger
      
    2. 在ApplicationContext.xml文件中引入db.properties文件,两种方式
      • 第一种

        <context:property-placeholder location = "classpath:com/netease/db.properties,classpath:com/netease/db2.properties,..."/>

      • 第二种

        <bean id="dbPropertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="locations">
                 <list>
                 <value>xx/yy/db.properties</value>
                 <value>xx/yy/db2.properties</value>
                 </list>
              </property>
        </bean>
        
    3. 在ApplicationContext.xml文件中配置bean,采用占位符$方式
      <bean id="dbutil" class="com.hsp.dispatch.DBUtil">
      <property name="name" value="${name}" />
      <property name="drivername" value="${drivername}" />
      <property name="url" value="${url}" />
      <property name="pwd" value="${pwd}" />
      </bean>
      

三、AOP原理剖析

Aspect Oriented Programming(AOP),即面向切面编程。对所有对象或一类对象编程。
在不增加代码的基础上,还增加新的功能

3.1 AOP原理

3.1.1 AOP术语介绍

  1. 切面(aspect):要实现的交叉功能,是系统模块化的一个切面或领域。如日志记录。
  2. 通知:切面的实际实现,他通知系统新的行为。如在日志通知包含了实现日志功能的代码,如向日志文件写日志。通知在连接点插入到应用系统中。
  3. 连接点:应用程序执行过程中插入切面的地点,可以是方法调用,异常抛出,或者要修改的字段。
  4. 切入点:定义了通知应该应用在哪些连接点,通知可以应用到AOP框架支持的任何连接点。
  5. 引入:为类添加新方法和属性。
  6. 目标对象:被通知的对象。既可以是你编写的类也可以是第三方类。
  7. 代理对象:将通知应用到目标对象后创建的对象,应用系统的其他部分不用为了支持代理对象而改变。
  8. 织入:将切面应用到目标对象从而创建一个新代理对象的过程。织入发生在目标对象生命周期的多个点上:
    • 编译期:切面在目标对象编译时织入,这需要一个特殊的编译器。
    • 类装载期:切面在目标对象被载入JVM时织入,这需要一个特殊的类载入器。
    • 运行期:切面在应用系统运行时织入。

3.1.2 AOP原理图

aop原理图.png

原理解释

  • 代理对象:仅需要配置在XML配置文件中即可,用于将目标对象和通知进行绑定;
  • 通知:切面编程的实现,只要在代理对象中配置了目标对象,该通知就可以作用到目标对象上;
  • 目标对象:需要使用通知的对象,例如想要通过通知来写日志,做安全检查等

通知类型

通知类型.png

3.1.3 举个栗子

本例中,

  • 有两个接口HelloServiceInterfaceByeServiceInterface,其中类SayingService实现了这两个接口中的方法。
  • 配置文件中,为类SayingService的具体对象配置了四种通知(严格说是五种),分别在该对象调用每一个方法时为其执行这几个通知
  • 注意!!!通知针对的是对象,对象调用几个方法,通知就会执行多少遍,除非配置切入点,使得通知绑定到具体的方法上;
  • 其他解释见代码注释
  • 目录结构
      TestSpring
          |---src
              |---com.netease
              |          |---Advice
              |                |---MyBeforeAdvice (前置通知,实现了接口MethodBeforeAdvice)
              |                |---MyAfterReturningAdvice (后置通知,实现了接口AfterReturningAdvice)
              |                |---MyInterceptAdvice (环绕通知,实现了接口MethodInterceptor)
              |                |---MyThrowableAdvice (异常通知,实现了接口ThrowsAdvice)
              |          |---Service
              |                |---ByeServiceInterface.java (包含方法sayBye)
              |                |---HelloServiceInterface.java (包含方法sayHello)
              |                |---SayingService.java (实现了以上两个接口)
              |          |---Test
              |                |---TestService.java
              |---ApplicationContext.xml
      External Libraries
          |--- spring.jar
          |--- common-logging.jar
          |--- ...
    
  • 代码描述
    public class TestService {
        public static void main(String[] args) {
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
            /*注意这里必须获取代理对象!!否则就白配置了*/
            /*得到的对象为什么可以是HelloServiceInterface --> 这里可以理解为,代理类实际上实现了这个接口,因此可以通过强转得到*/
            HelloServiceInterface helloService = (HelloServiceInterface) applicationContext.getBean("proxyFactoryBean");
            helloService.sayHello();
            /*同理,由于代理类同样实现了ByeServiceInterface接口*/
            ByeServiceInterface byeService = (ByeServiceInterface) helloService;
            byeService.sayBye();
        }
    }
    
  • 配置文件
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns=....省略>
    
        <!--配置通知-->
        <bean id="myBeforeAdvice" class="com.netease.Advice.MyBeforeAdvice"/>
        <bean id="myAfterReturningAdvice" class="com.netease.Advice.MyAfterReturningAdvice"/>
        <bean id="myInterceptAdvice" class="com.netease.Advice.MyInterceptAdvice"/>
        <bean id="myThrowableAdvice" class="com.netease.Advice.MyThrowableAdvice"/>
    
        <!--配置切入点(可选)-->
        <!--这里表示将myBeforeAdvice这个通知绑定到sayHello方法,对其他方法失效-->
        <bean id="nameMatchMethodPointcutAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
            <property name="advice" ref="myBeforeAdvice"/>
            <property name="mappedName" value="sayHello"/>
        </bean>
    
    
        <!--配置目标对象-->
        <bean id="helloService" class="com.netease.Service.App">
            <property name="name">
                <value>zhujie</value>
            </property>
        </bean>
    
        <!--配置代理对象-->
        <bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
            <!--配置被代理的接口集合,表示该代理对象服务于哪些接口,即需要实现哪几个接口-->
            <property name="proxyInterfaces">
                <list>
                    <value>com.netease.Service.HelloServiceInterface</value>
                    <value>com.netease.Service.ByeServiceInteface</value>
                </list>
            </property>
            <!--配置被代理的通知-->
            <property name="interceptorNames">
                <list>
                    <!--<value>myBeforeAdvice</value>-->
                    <!--注意这里写的是我们配置的切入点id,如果仍然写前置通知的id,则切入点配置就无效了-->
                    <value>nameMatchMethodPointcutAdvisor</value>
                    <value>myBeforeAdvice</value>
                    <value>myAfterReturningAdvice</value>
                    <value>myInterceptAdvice</value>
                    <value>myThrowableAdvice</value>
                </list>
            </property>
            <!--配置被代理的对象-->
            <!--表示该对象调用方法时会被注入通知-->
            <property name="target" ref="helloService"/>
        </bean>
    </beans>
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259

推荐阅读更多精彩内容