zoukankan      html  css  js  c++  java
  • Spring从认识到细化了解

    首发日期:2018-08-25
    修改日期:
    1. 2018-08-29:针对排版问题进行了修改。主要是最前面那部分排版出了问题--一些序号排序失效了。


    Spring的介绍

    • Spring框架是目前java应用最广的框架。
    • Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架 ,它为每一个层次(表现层、业务层、持久层)都提供了解决方案
      • web层:springMVC【此文不涉及springMVC
      • service层:IoC,DI,AOP
      • dao层:jdbc模板、Hibernate模板
    • spring有几个特点功能【后面将讲这几个功能的好处】:
      • IoC控制反转:将对象的生成权限交给spring,当一个对象需要其他对象时,spring会帮你生成对应的对象。
      • DI:如果使用spring生成的类需要其他类或属性来协助运行,可以使用DI来进行注入。
      • AOP切面编程:通过代理模式,spring可以对某个业务进行强化,而不影响原有的运行。
      • spring提供了与其他框架整合的组件,比如支持Hibernate开发的HibernateTemplate、支持MyBatis开发的SqlSessionTemplate。


    基本运行环境搭建

    本次环境基于spring4.3.4

    1.在官网下载:

    下载地址:http://repo.spring.io/libs-release-local/org/springframework/spring/

    2.解压压缩包:

    • 重要文件夹:
      • docs:spring的开发规范文档和api文档
      • libs:spring的依赖包、文档(后缀javadoc)和源码(后缀source)
      • schema:spring的配置文件的约束(xsd)。

    3.在libs中提取依赖包:【如果你会maven,那么你可以百度一下尝试使用maven来管理依赖

    • spring核心容器包(spring的所有模块都构建在核心容器之上,所以导入核心容器包构建的就是最基础的运行环境)
      • spring-beans-4.3.4.RELEASE.jar:
      • spring-context-4.3.4.RELEASE.jar:
      • spring-core-4.3.4.RELEASE.jar:
      • spring-expression-4.3.4.RELEASE.jar:
    • 日志接口包:commons-logging-1.2.jar【apache提供的,需要自己下载】【必须的】
    • 【如果需要扩展日志功能,才需要导入,这里使用log4j】日志包:log4j-1.2.16.jar【apache提供的,需要自己下载】



    ps:上面所引入的依赖包仅能实现基本功能,但比如spring的事务管理、aop这些还需要引入其他的包,这些包将在下面的各个模块中讲。


    由于spring很多时候都是提出解决方案,所以下面介绍spring将根据解决方案来讲。

    1. IOC【解决了对象的创建问题】
    2. DI 【解决了属性注入问题】
    3. AOP【解决了业务对象解耦问题】
    4. 模板代码【解决了jdbc代码太繁琐的问题】

    IoC

    介绍:

    • IoC全称Inversion of Control(控制反转)。
    • IoC就是把对象的生成权限交给spring(原本情况是对象需要我们手动去new,现在声明+配置,spring就可以帮我们把需要的对象自动生成)


    示例使用:

    1.导入依赖包,IoC只需要基础的依赖包。【这里省去扩展日志依赖包】

    2.创建一个用于被spring管理的类(由于仅作演示,所以创建一个简单的实体类):

    package work.domain;
    public class User {
    	private String name;
    	private int age;
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public int getAge() {
    		return age;
    	}
    	public void setAge(int age) {
    		this.age = age;
    	}
    	@Override
    	public String toString() {
    		return "User [name=" + name + ", age=" + age + "]";
    	}
    }
    

    3.在src目录下创建applicationContext.xml,在applicationContext.xml中把类配置给spring,让spring管理这个类的创建,:

    • applicationContext.xml需要xsd约束,xsd在解压文件夹spring-framework-4.2.4.RELEASEdocsspring-framework-referencehtmlxsd-configuration.html的the beans schema中。【xsd-configuration.html是spring的配置文件的所有xsd配置的参考文件,可以根据不同配置来查找到不同的xsd配置
    • applicationContext.xml需要放置在classpath可搜索路径下,通常可以放在src目录下。
    <?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,让spring管理这个类的创建,id用于标识这个类,class是类全限定名 -->
        <bean id="user" class="work.domain.User"></bean>
    </beans>
    

    4.创建测试类,获取spring创建的对象:

    package work.test;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import work.domain.User;
    public class Demo1 {
    	@Test
    	public void test1() {
    		//读取上下文
    		ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
    		//根据id来获取spring实例化对象
    		User user = (User) context.getBean("user");
    		user.setName("李雷");
    		user.setAge(9);
    		//能够使用就说明已经被实例化了
    		System.out.println(user);
    	}
    }
    

    上面的示例使用可以说明spring帮我们创建了对象。


    使用说明:

    • 如何在applicationContext.xml中配置:

      • xsd约束:

        • xsd在解压文件夹spring-framework-4.2.4.RELEASEdocsspring-framework-referencehtmlxsd-configuration.htmlthe beans schema中。【xsd-configuration.html是spring的配置文件的所有xsd配置的参考文件,可以根据不同配置来查找到不同的xsd配置】
      • bean

        • 属性

          • id:唯一标志性属性,可以用于标识spring管理的类。【在代码中,我们可以根据这个属性来获取对象】
          • name:不是唯一标志性属性,可以用于标识spring管理的类。虽然可以重复,但为了不引起歧义,通常开发中也不能重复。
          • class:需要实例化的类的路径,指明根据id或name获取类对象的时候,获取的是哪个类的。
        • 示例:

          <bean id="user" name="name_user" class="work.domain.User"></bean>
          
    • 如何在代码中获取spring创建的对象:

      • 先读取上下文,然后利用上下文的getBean方法获取实例化对象:
        • public void test1() {
          		//读取上下文
          		ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
          		//获取方法1:根据id来获取spring实例化对象
          		User user = (User) context.getBean("user");
          		//获取方法2:也可以根据name来获取spring对象
          		User user2 = (User) context.getBean("name_user");
          		user.setName("李雷");
          		user.setAge(9);
          		//能够使用就说明已经被实例化了
          		System.out.println(user);
          		System.out.println(user2);
          	}
          

    使用注意:

    • 默认情况下,生成的类是单例的,并且spring一开启就会实例化对象。也就是说,默认情况下,在不同代码中多次使用一个类的对象都是同一个对象。【可以通过配置来改变,bean的scope属性可以配置对象是单例的还是多例的】

    • applicationContext.xml的文件名是可以改变的(但默认获取的时候文件名就是这个),如果你的文件名不是这个,创建工厂的时候需要传入其他的文件名:new ClassPathXmlApplicationContext("文件名");

    • spring创建类对象的本质是通过工厂来获取类对象。

      • 老版本的工厂类:BeanFactory
        • BeanFactory:调用getBean的时候,才会生成类的实例。
      • 新版本的工厂类:ApplicationContext【现在都用这个】
        • ApplicationContext:加载配置文件的时候,就会将Spring管理的类都实例化
        • ApplicationContext常见实现类主要有两个:
          • ClassPathXmlApplicationContext :加载类路径下的配置文件
          • FileSystemXmlApplicationContext :加载文件系统下的配置文件
          • 还有一些例如XmlWebApplicationContext,AnnotationConfigApplicationContext,AnnotationConfigWebApplicationContext这些虽然也有用,但这里不涉及,所以不讲。
    • 由于本质是工厂模式,所以它是有利于进行接口化编程的,我们可以在配置文件中很方便地更改它的实现类。比如:

      • 修改前:(获取bean的时候通过id获取,存储的类型是接口,那么可以很方便地更改它的实现类)

        <bean id="calc" class="work.domain.Calc">
        
      • 修改后(只需要修改配置文件,就能给接口提供不同的实现类):

        <!-- 假如功能升级了!给它一个新的实现类。 -->
        <bean id="calc" class="work.domain.SuperCalc">
        

    Bean的实例化方式

    • spring创建的对象也可以称为bean

    • spring帮我们实例化的有几种方式,默认是调用无参构造方法来实例化。

      • 默认无参实例化(所以如果没有无参构造函数,将报错):

        • <bean id="user" name="name_user" class="work.domain.User"></bean>
          
      • 注入构造方法参数,带参数实例化:(这个涉及DI内容,具体后面讲)

        •     <bean id="user" name="name_user" class="work.domain.User">
                  <!-- 向 User(String name, int age)构造方法注入参数,然后实例化  -->
                  <constructor-arg name="name" value="lilei" ></constructor-arg>
                  <constructor-arg name="age" value="18" ></constructor-arg>
              </bean>
          
      • 调用静态实例工厂方法得到对象:

        • 静态工厂编写

          public class MyBeanFactory {
              	public static User createUser() {
              		return new User();
              	}
          }
          
        • applicationContext.xml编写

          <!-- class是工厂类路径,factory-method是静态获取对象的方法 -->
               <bean id="factory_user"  class="work.factory.MyBeanFactory" factory-method="createUser"></bean>
          
      • 调用非静态实例工厂方法得到对象:

        • 工厂编写

          public class MyBeanFactory {
              	public  User createU() {
              		return new User();
              	}
          }
          
        • applicationContext.xml编写

            <!-- 先实例化工厂 -->
             <bean id="myBeanFactory" class="work.factory.MyBeanFactory" ></bean>
             <!-- 利用工厂对象来获取对象,factory-bean是上面工厂bean的id,factory-method是获取对象的方法名  -->
             <bean id="user" factory-bean="myBeanFactory"  factory-method="createU"></bean>
          

    Bean的作用范围的配置:

    在上面说过了,默认情况下,Bean的创建是单例的,但是可以通过bean中的scope属性来配置Bean的创建方式(同时也有作用范围)。

    • scope
      • 取值:
        • singleton:单例模式,只生成一个对象。
        • prototype:多例的,每次通过上下文获取都是获取一个新的对象。
        • request:一次请求创建一个实例。
        • session:一次会话创建一个实例。
      • 示例:
        • <bean id="userAction" class="work.action.UserAction" scope="prototype"></bean>
          

    补充:

    • 这里没有讲述基于p名称空间的属性注入和spEL的属性注入【其实挺重要的,但讲细的话太占空间】,有兴趣的可以自查。


    DI:

    • 依赖注入。依赖是指某些功能可能依赖某些属性,只有有了对应的属性才能执行功能。当使用了IOC之后,可能需要把某些属性(也包括对象)注入给生成的对象,那么可以使用DI。
      • 比如你利用spring帮你新建了一个类,但是这个类需要另一个类来协助运行(好比CD机需要CD,没有CD的CD机用起来没意思),那么这时候怎么把另一个类传给这个类呢?DI就是解决这类问题的
    • DI是spring的一个很好的属性注入解决方案,如果你不使用DI的话,你可能需要使用接口或者通过继承类来完成属性注入。
      • 其他方案探究:让这个类管理他的协助类,在构造函数中传参进来或者调用某些方法设置属性,但这会造成代码耦合性很高以及难以测试(因为要确保使用协助类之前,协助类要被初始化了)。
    • 使用DI之后,当通过IoC来创建对象的时候(此时,依赖对象和被依赖对象都创建了),IoC也会同时处理依赖管理,帮助我们把协助类注入到目标类中(有构造器注入、set注入等方法)。
    • 像IoC一样,依赖注入也是有利于接口化编程的,当我们注入的属性是一个对象时,如果使用接口名来管理注入,那么我们可以很轻易地更改注入的接口实现类。



    属性注入:

    属性注入是创建对象的时候,为对象注入属性。

    • 构造方法方式注入:要求类中必须要有与注入参数对应的构造方法

      • 类的编写:

        package work.domain;
        public class User {
        	private String name;
        	private int age;
        	private Account account;
        	public User() {
        		super();
        	}
        	public User(String name, int age) {
        		super();
        		this.name = name;
        		this.age = age;
        	}
        	public String getName() {
        		return name;
        	}
        	public void setName(String name) {
        		this.name = name;
        	}
        	public int getAge() {
        		return age;
        	}
        	public void setAge(int age) {
        		this.age = age;
        	}
        	public Account getAccount() {
        		return account;
        	}
        	public void setAccount(Account account) {
        		this.account = account;
        	}
        }
        
      • applicationContext.xml的编写:

        <bean id="account" class="work.domain.Account"></bean>
            <bean id="user"  class="work.domain.User">
            	 <!--  向 User(String name, int age)构造方法注入参数,然后实例化  -->
            	 <!-- name是属性名,value是属性值,如果属性需要一个类对象,使用ref来注入,值为bean中的id或name -->
            	<constructor-arg name="name" value="lilei" ></constructor-arg>
            	<constructor-arg name="age" value="18" ></constructor-arg>
            	<constructor-arg name="account" ref="account" ></constructor-arg>
         </bean>
        
    • set方法方式,要求类中必须提供注入参数对应的setter方法

      • 类的编写:

        package work.domain;
        public class User {
        	private String name;
        	private int age;
        	private Account account;
        	public User() {
        		super();
        	}
        	public User(String name, int age) {
        		super();
        		this.name = name;
        		this.age = age;
        	}
        	public String getName() {
        		return name;
        	}
        	public void setName(String name) {
        		this.name = name;
        	}
        	public int getAge() {
        		return age;
        	}
        	public void setAge(int age) {
        		this.age = age;
        	}
        	public Account getAccount() {
        		return account;
        	}
        	public void setAccount(Account account) {
        		this.account = account;
        	}
        }
        
      • applicationContext.xml的编写:

        	<bean id="account" class="work.domain.Account"></bean>
            <bean id="user"  class="work.domain.User">
            	 <!--  使用setter来注入参数  -->
            	 <!-- name是属性名,value是属性值,如果属性需要一个类对象,使用ref来注入,值为bean中的id或name -->
            	<property name="name" value="织女"></property>
            	<property name="age" value="1000"></property>
            	<property name="account"  ref="account"></property>
            </bean>
        
    • 上面提到了基本类型属性和对象类型属性的注入,由于集合类型多,所以留到这里再讲:

      • 类的编写(包含了数组,list,set,map):需要提供setter

        • package work.domain;
          
          import java.util.List;
          import java.util.Map;
          import java.util.Set;
          
          public class CollectionBean {
          	private String[] array;
          	private List list;
          	private Set set;
          	private Map map;
          	public void setArray(String[] array) {
          		this.array = array;
          	}
          	public void setList(List list) {
          		this.list = list;
          	}
          	public void setSet(Set set) {
          		this.set = set;
          	}
          	public void setMap(Map map) {
          		this.map = map;
          	}
          	public String[] getArray() {
          		return array;
          	}
          	public List getList() {
          		return list;
          	}
          	public Set getSet() {
          		return set;
          	}
          	public Map getMap() {
          		return map;
          	}
          }
          
      • applicationContext.xml编写

        • <bean id="collecionBean" class="work.domain.CollectionBean" >
             	<!--数组类型的属性注入,name是属性名,数组的元素使用list中的value包裹,也可以使用array中的value包裹;注入的是对象的时候,可以使用ref包裹 -->
             	<property name="array">
             		<list>
             			<value>牛郎</value>
             			<value>织女</value>
             		</list>
             	</property>
             	<!-- List类型的属性注入,name是属性名,数组的元素使用list中的value包裹 -->
             	<property name="list">
             		<list>
             			<value>二郎神</value>
             			<value>华山</value>
             		</list>
             	</property>
             	<!-- Set类型的属性注入,name是属性名,数组的元素使用set中的value包裹 -->
             	<property name="set">
             		<set>
             			<value>孙悟空</value>
             			<value>白骨精</value>
             		</set>
             	</property>
             	<!-- Map类型的属性注入,name是属性名,数组的元素使用entry包裹,如果注入的是对象,那么可以使用key-ref和value-ref来注入 -->
             	<property name="map">
             		<map>
             			<entry key="爱" value="520" ></entry>
             			<entry key="一生一世" value="1314" ></entry>
             		</map>
             	</property>
             </bean>
          
    • 注入其他bean对象:上面的都是一个类对象中的普通属性,但没有说到注入一个其他bean对象时怎么注入。

      •   <bean id="productDao" class="cn.dao.ProductDao"></bean>
          <bean id="productService" class="cn.service.ProductService">
          	<!-- 注入其他bean对象,使用ref,ref是bean的id -->
          	<property name="productDao" ref="productDao"/>
          </bean>
        
    • 属性注入方式还有一种接口注入,接口注入通常使用于资源来自于外界的情况,比如当数据库连接资源来自于外部的时候就可以使用接口注入。这个知识点这里不讲,有兴趣自查。



    补充:

    • 还可以从Properties中读取数据来进行注入 ,这里不讲,有兴趣的可以自查【在事务管理中讲述了可以利用一种方式来读取】。
            <context:property-placeholder location="classpath:jdbc.properties" />
            <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            	<!-- 引用properties文件中的数据 -->
                <property name="driverClass" value="${jdbc.driverClass}" ></property>
                <property name="jdbcUrl" value="${jdbc.url}" ></property>
                <property name="user" value="${jdbc.username}" ></property>
                <property name="password" value="${jdbc.password}" ></property>
            </bean>
      ```
      


    IoC的注解方式:

    • 现在已经更趋向于使用注解来配置IOC了。
    • 注解可以减少XML配置。
    • 但XML的配置方式也有好处:
      • 统一管理、方便维护
      • 可以配置第三方组件

    依赖包

    • 除了导入基础的核心容器包,还需要spring-aop-4.3.4.RELEASE.jar

    示例使用

    • applicationContext.xml配置

      • 导入xsd:

        • 在原来的xsd的基础上,加上context的,context在spring-framework-4.3.4.RELEASE/docs/spring-framework-reference/html/xsd-configuration.html的the context schema中
            <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"> 
    
            </beans>
    
    • applicationContext.xml中配置组件扫描:

      • 在xml配置context:component-scan这个标签后,spring可以自动去扫描base-pack下面或者子包下面的java文件,如果扫描到有@Component @Controller@Service等这些注解的类,则把这些类注册为bean 。如果有多个包,可以使用,分隔。
      	<!-- 配置组件扫描,base-package是扫描的包,扫描的包下类的相关注解会把类注册成bean -->
      	<context:component-scan base-package="work.domain"></context:component-scan>
      
    • 给类加上注解@Component("person")

    package work.domain;
    
    import org.springframework.stereotype.Component;
    //添加注解
    @Component("person")  //相当于在applicationContext.xml中配置<bean id="person" class="work.domain.Person" />
    public class Person {
    	private String name;
    	private int age;
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public int getAge() {
    		return age;
    	}
    	public void setAge(int age) {
    		this.age = age;
    	}
    }
    

    上面配置完毕,就可以像XML配置方式一样使用Context上下文获取对象了。



    bean配置注解介绍:

    • @Component:修饰一个类,代表这个类的对象交给spring去生成

      • @Component("person") //相当于在applicationContext.xml中配置<bean id="person" class="....." />
      • @Component的衍生注解,下面三个注解与@Component的功能一样,不过多了层次的意义,可以使用下面的来表明是什么层次的bean。【听说后面版本下面的三个注解加了一些功能,没去了解】
        • @Controller:web层
        • @Service:service层
        • @Responsitory:dao层


    Bean的作用范围的配置

    在类上使用@Scope注解指定Bean的作用范围。

    • @Scope

      • 属性:

        • singleton:单例模式
        • prototype:多例
        • request:存入到request中
        • session:存入到session中
      • 示例:

        • @Component
          @Scope("prototype")
          



    DI的注解方式

    注解方式的属性注入:

    对于类的注解,需要开启IoC上面的组件扫描【组件扫描的时候也会扫描到用于属性注入的注解】;如果仅仅使用注解来进行属性注入,则不需要组件扫描,直接使用下面的。【很多时候,我们可能会使用xml来配置bean,注解来配置属性注入】

    <!--只会扫描属性上的注解-->
    <context:annotation-config></context:annotation-config>
    
    • 对于普通类型的属性:

      • @value("属性值")
      • 例如:
    • 对于对象类型的属性:

      • @Autowired:根据类名去进行注入
        • 歧义性问题:部分时候都是采用接口化编程,使得一个接口可能有多个实现类,那么需要使用告诉spring使用哪个类。
          • @Primary用在接口的实现类中,表明使用@Autowired来进行注入的时候,如果有多个实现类,那么优先使用这个。
          • @Qualifier与@Autowired一起使用,@Qualifier("xxx")的时候,代表使用xxx实现类注入。
      • @Resource(name = "....") :根据id去进行注入(对于注解式的,@Component("xxx")中的xxx就是id)【由于@Autowired有歧义问题,所以通常使用@Resource】
    • 对于集合类型的属性,我觉得使用注解就不太清晰了。有兴趣的可以自查,它通常使用@Resource来注入。

    • DI注解的使用:如果有setter方法,就把注解添加到set方法上;如果没有,那么添加到属性上;两者都有的时候建议添加到set方法上。




    AOP

    • AOP全称Aspect Oriented Programming (面向切面编程)。
    • AOP可以做到在不改变程序原代码的情况下对程序进行增强。
      • 诸如日志、事务管理和安全这种服务经常用来配合业务,如果把这些服务的代码写在各个业务中,当需要修改,你需要修改很多地方,除此还会可能造成业务逻辑混乱。(业务混乱是指影响了业务实体,增加了不必要的概念,好比某个业务的运行需要先初始化日志对象,这样就影响了本来业务的执行。)
      • AOP通过声明把这类服务应用到它们需要影响的业务中(不需要改变业务代码)。
    • AOP有点类似于拦截器,拦截器是发起请求之后先到拦截器,再到业务逻辑,出去再经过拦截器,拦截器也是脱离了业务逻辑的。
    • 在声明之后,AOP会对目标方法所在的类生成一个代理类对象,这个代理类对象中的目标方法是使用AOP加强过了的。
    • 以事务的理念来谈一下AOP:通常来说,我们的增删改操作需要事务的帮助,我们可能需要在各处代码中都写下开启事务的代码。这样代码可能就很赘余了,而且事务的代码变成了必须品。如果使用了AOP,那么调用方法之前,AOP帮你开启事务,这样你就不必在代码里写下开启事务了。


    动态代理技术简单演示

    spring AOP的底层实现技术就是动态代理技术。为了帮助了解AOP底层原理,这里基于jdk动态代理对动态代理技术简单演示。【仅作理解,不写太严谨的代码】

    1.创建一个接口

          public interface SchoolPerson {
          	public void read();
          	public void save();
          }
    

    2.定义一个实现接口的类

         package work.domain;
         public class Student implements SchoolPerson {
         	public void read() {
         		System.out.println("read");
         	}
         	public void save() {
         		System.out.println("save");
         	}
         }
    

    3.创建一个代理器,实现InvocationHandler接口,创建createProxy方法--负责获取代理对象,创建invoke方法--负责对方法进行增强。

          package work.utils;
          
          import java.lang.reflect.InvocationHandler;
          import java.lang.reflect.Method;
          import java.lang.reflect.Proxy;
          
          import work.domain.SchoolPerson;
          
          public class JDKProxy implements InvocationHandler {
          	//被代理的对象
          	private SchoolPerson sp;
          	//把对象传给代理器,让它生成一个代理对象
          	public JDKProxy(SchoolPerson sp) {
          		this.sp = sp;
          	}
          	//生成代理对象,并绑定代理方法
          	public SchoolPerson createProxy() {
          		SchoolPerson proxyStu = (SchoolPerson) Proxy.newProxyInstance(sp.getClass().getClassLoader(), sp.getClass().getInterfaces(), this);
          		return proxyStu;
          	}
          	@Override
          	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          		if(method.getName().equals("save")) {
          			System.out.println("增强功能执行---。");
          			return  method.invoke(sp, args);
          		}
          		return method.invoke(sp, args);
          	}
          }
    

    4.测试:

          package work.test;
          import org.junit.Test;
          import work.domain.SchoolPerson;
          import work.domain.Student;
          import work.utils.JDKProxy;
          
          public class Demo2 {
          	@Test
          	public void test1() {
          		SchoolPerson sp=new Student();
          		JDKProxy proxy = new JDKProxy(sp);
          		SchoolPerson createProxy = proxy.createProxy();
                  //调用方法,测试是否被增强
          		createProxy.save();
          		/*测试结果:
          		 * 增强功能执行---。
          			save
          		 */
          	}
          }
    

    上述例子可以看出,student对象原有的save方法被增强了,而且我们并没有修改过student的代码。



    AOP相关术语:

    • Aspect:切面,切面是指贯穿多个业务的环境。比如拦截器就贯穿了业务逻辑。

    • Advice:通知、增强。是指增强的方法,根据逻辑和顺序可以区分成多个通知类型(下面讲)

    • Joinpoint:连接点,可拦截点,判断一个点是否是切点。

    • Pointcut:切点,被切面拦截的点就是一个拦截点。

    • Target:目标,被增强的对象

    • Weaving:织入,生成代理对象的过程

    • Proxy:代理对象



    通知类型:

    • 前置通知before:在目标方法之前执行通知里面的操作

    • 正常返回通知after-returning:目标方法正常执行完成之后,进行通知里面的操作

    • 环绕通知around:目标方法执行之前和目标方法执行完成之后,进行通知里面的操作

    • 异常抛出通知after-throw:发生异常时,进行通知里面的操作

    • 后置(最终)通知after:运行完成时,无论是否发生异常,都进行通知里面的操作

    • 五种通知的执行顺序为: 前置通知→环绕通知→后置通知→正常返回通知/异常抛出通知



    使用示例:

    • 导入依赖包:

      • 基本包:
      • AOP依赖包
        • 两个spring自带的:
          • spring-aop-4.3.4.RELEASE.jar
          • spring-aspects-4.3.4.RELEASE.jar
        • 三个需要下载的:
          • aopalliance-1.0.jar
          • aspectjrt-1.7.2.jar
          • aspectjweaver-1.7.2.jar
    • 编写一个接口:

      • package work.dao;
        public interface StudentDao {
        	public void read();
        	public void save();
        }
        
    • 编写一个接口的实现类:

      • package work.dao.impl;
        import work.dao.StudentDao;
        public class StudenDaoImpl implements StudentDao {
        	@Override
        	public void read() {
        		System.out.println("read");
        	}
        	@Override
        	public void save() {
        		System.out.println("save");
        	}
        }
        
    • 编写一个切面类,用于调用切面类的方法对目标对象增强:

      • package work.utils;
        
        import org.aspectj.lang.ProceedingJoinPoint;
        
        public class MyAspect {
        	public void before() {
        		System.out.println("before。。。");
        	}
        	public void after() {
        		System.out.println("after。。。");
        	}
        	public void afterReturn() {
        		System.out.println("afterReturn。。。");
        	}
        	public void around(ProceedingJoinPoint pj) throws Throwable {
        		System.out.println("around in。。。");
        		pj.proceed();
        		System.out.println("around out。。。");
        	}
        	public void afterThrow() {
        		System.out.println("afterThrow。。。");
        	}
        }
        
    • 配置applicationContext.xml文件

      • 导入xsd,他在xsd-configuration.html的the aop schema中(如果你有其他功能,注意进行保留,在原来的基础上增加aop的xsd)

        • <?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
                  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
          </beans>
          
      • 把接口实现类交给Spring去管理

        • <!-- 把目标实现类交给spring管理 -->
             <bean id="studentDao" class="work.dao.impl.StudenDaoImpl"></bean>
          
    • 把切面交给Spring去管理

      • <!-- 把切面类交给spring管理 -->
          <bean id="myAspect" class="work.utils.MyAspect"></bean>
        
    • 配置AOP

      •     <!-- 配置AOP -->
            <aop:config>
                	<!-- 配置切点,配置哪些类的哪些方法需要增强 -->
                	<aop:pointcut expression="execution(* work.dao.impl.StudenDaoImpl.*(..))" id="pointcut1"/>
                	<!-- 配置切面,配置通知与切点的关系 -->
                	<aop:aspect ref="myAspect">
                		<aop:before method="before" pointcut-ref="pointcut1"/>
                		<aop:after method="after"  pointcut-ref="pointcut1" />
                		<aop:after-returning method="afterReturn"  pointcut-ref="pointcut1"  />
                		<aop:around method="around"  pointcut-ref="pointcut1" />
                	</aop:aspect>
            </aop:config>
        
    • 编写测试方法:

      • //测试AOP
        	@Test
        	public void test2() {
        		ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
        		StudentDao studentDao = (StudentDao) context.getBean("studentDao");
        		studentDao.read();
        	}
        


    使用详解:

    AOP配置结构

    •     <!-- aop:config是配置AOP的顶层标签 -->
          <aop:config>
              	<!-- aop:pointcut配置切点,配置哪些类的哪些方法需要增强 -->
              	<aop:pointcut expression="..." id="..."/>
              	<!-- aop:aspect配置切面 -->
              	<aop:aspect ref="myAspect"><!-- ref引用的是切面类的bean -->
                      <!--配置对于切点怎么增强 -->
              		<aop:before method="..." pointcut-ref="..."/>
              		<aop:after method="..."  pointcut-ref="..." />
              		<aop:after-returning method="..."  pointcut-ref="..."  />
              		<aop:around method="..."  pointcut-ref="..." />
              	</aop:aspect>
          </aop:config>
      


    切点:

    在xml中,用<aop:pointcut expression="..." id="..."/>来声明一个切点,使得增强可以针对一个切点来进行。其中id是用于标识一个切点的;expression是用来表示切点的位置的,表示增强用于哪个类的哪个方法上。

    • expression的写法
      • 语法格式:expression="execution(访问修饰符 返回类型 包名.类名.方法名(参数) )"
      • execution()代表方法执行时触发
      • 访问修饰符(public之类的)是可选的。
      • 返回类型是必需的,可以使用*来代表任意返回值类型的
      • 包名.类名 ,可以使用*来代表任意包任意类,但注意要符合层次比如work.demo.dao可以用*.*.*代替
      • 方法名,可以使用*来代表任意方法
      • 参数中填写的是参数类型,(..)表示任意个数任意类型参数,(.)表示有一个参数 ,(*,String)表示第一个参数为任意,第二个为String类型
    • expression的示例:
      • public void com.domain.Custom.save(..)
      • * *.*.*.save(..)
    • expression的语法类似于正则表达式,这里仅仅讲了一些基础的,足够你基本使用的。如果想了解更多,可以自查。


    切面:

    在xml中,用<aop:aspect ref="..."></aop:aspect>来声明一个切面,声明切面怎么对切点增强。

    • aop:aspect 的属性:
      • ref用来引用spring管理的切面类的bean。
      • order:用与多个切面处理同一个切点的时候,决定它们之间的顺序。没有的时候,多个切面处理一个切点的时候,顺序是乱的,如果想有顺序的,需要使用order。
    • 通知类型【aop:aspect 下使用各种通知类型来定义什么时候对切点进行增强】
      • 前置通知:<aop:before method="调用的方法" pointcut-ref="切点id"/>
      • 后置通知:<aop:after method="调用的方法" pointcut-ref="切点id" />
      • 环绕通知:<aop:around method="调用的方法" pointcut-ref="切点id" />
      • 返回通知:<aop:after-returning method="调用的方法" pointcut-ref="切点id" />
      • 异常抛出通知:<aop:after-throwing method="调用的方法" pointcut-ref="切点id" />


    切面类:

    切面类是用来对切点增强的,在xml中配置的通知类型的method就是切面类中的方法名。

    通知类型有不同的作用,所以切面类也需要规范写法。

    • 前置通知before可以用来获取切点信息,在方法中需要定义一个形参JoinPoint pj,这个形参会自动传入。

      • public void before(JoinPoint jp) {
        		System.out.println("before。。。"+jp);
        	}
        
    • 正常通知after-returning既可以用来获取切点信息,也可以获取目标方法的返回值。如果在目标方法中进行了返回,可以在xml中使用returning属性来指明存储到哪个变量中。

      • xml中:<aop:after-returning method="afterReturn" pointcut-ref="pointcut1" returning="result" />【returning中的值是通知中的形参的名字。通知中通过形参来获取目标方法的返回值】
      • 当获取返回值的时候要注意环绕通知的使用,获取返回值需要在环绕通知中使用return pj.proceed();
    • 环绕通知可以用来拦截方法,所以它需要“放行”才能正常环绕,在方法中需要定义一个形参ProceedingJoinPoint pj(会自动传入值),然后pj.proceed()就可以放行了。

      • public void around(ProceedingJoinPoint pj) throws Throwable {
        		System.out.println("around in。。。");
        		Object obj=pj.proceed();
        		System.out.println("around out。。。");
                return obj;
        	}
        
    • 异常抛出通知可以获取目标方法的异常信息

    补充

    • 还可以对通知类型传入参数,这样在切面的类的方法中可以根据参数来执行。这里不讲,有兴趣自查。

    AOP的注解方式:

    • AOP的注解方式不需要引入额外的其他依赖包,只需要引入前面的核心容器包和aop包即可。

    使用示例

    开启自动代理

    • 在xml中开启AOP自动代理【其实也可以全注解配置--可以创建一个java类来进行配置,但这里不讲。】
      • <aop:aspectj-autoproxy></aop:aspectj-autoproxy><!-- 这个标签与bean同级,不要配错了。 -->
        

    创建切面并定义通知

    使用@Aspect来注解一个类,Spring就会认为这个类是一个切面类。使用@Before来注解一个方法,Spring就会把这个方法当成切面类的前置通知。

    package work.utils;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    //注解,标明这个是一个切面类
    @Aspect
    public class MyAspect {
    	//前置通知
    	@Before(value="execution(* work.dao.impl.StudenDaoImpl.*(..))")
    	public void before() {
    		System.out.println("before。。。");
    	}
    }
    

    这样之后,获取的对象就是被AOP前置增强了的。



    使用详解:

    通知类型:

    • 前置通知:@Before
    • 正常返回通知:@AfterReturning
    • 环绕通知:@Around
    • 异常抛出通知:@AfterThrowing
    • 后置通知:@After


    ### 切点:
    • 当在通知中同时声明一个切点的时候,使用的是切点表达式,可以参考上面XML中的切点表达式的写法。

    • 除了切点表达式,还可以使用注解声明切点,然后在通知是使用切点名即可。

      • 使用@Pointcut注解来声明切点,它使用在方法上,这个方法是没有意义的,可以为空方法,方法名相当于切点的ID。Pointcut的值为切点表达式。可以在通知中使用 类名.方法名() 来指向一个切点。

        • package work.utils;
          
          import org.aspectj.lang.annotation.Aspect;
          import org.aspectj.lang.annotation.Before;
          import org.aspectj.lang.annotation.Pointcut;
          //注解,标明这个是一个切面类
          @Aspect
          public class MyAspect {
          	@Pointcut(value="execution(* work.dao.impl.StudenDaoImpl.*(..))")
          	public void pointcut1() {}
          //	@Before(value="execution(* work.dao.impl.StudenDaoImpl.*(..))")
          	@Before(value="MyAspect.pointcut1()")
          	public void before() {
          		System.out.println("before。。。");
          	}
          }
          


    补充:

    • 多个切面对一个切点增强时,如果想要按顺序处理,可以在切面类上使用注解@Order,Order的值影响它们的顺序。

    Spring与数据库:

    • 对于非框架式的JDBC数据库编程,Spring提供jdbc模板来简化jdbc的使用
    • 对于Hibernate框架,Spring提供了Hibernate模板来简化Spring整合Hibernate时对Hibernate的操作。
    • 对于MyBatis框架,Spring没有提供很好的MyBatis模板,通常使用MyBatis社区开发了MyBatis-Spring整合包,其中SqlSessionTemplate就相当于MyBatis模板。
    • 对于Hibernate和MyBatis框架,这里不涉及框架整合知识,所以这里只介绍一下jdbc模板。[整合框架的内容迟一点写]


    JDBC模板使用示例

    • JDBC模板可以说是Spring进行jdbc的封装,它的使用就像DbUtils。

    1. 引入依赖包:

    • spring核心容器基本包
    • 数据库驱动包:mysql-connector-java-5.1.7-bin.jar
    • Spring的JDBC包:spring-jdbc-4.3.4.RELEASE.jar
    • Spring的事务包(需要事务时才导入):spring-tx-4.3.4.RELEASE.jar

    2. 创建数据连接资源(这里以简单数据库连接池为例)

    		SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
    		dataSource.setDriverClass(com.mysql.jdbc.Driver.class);
    		dataSource.setUrl("jdbc:mysql://localhost:3306/spring");
    		dataSource.setUsername("root");
    		dataSource.setPassword("123456");
    

    3. 获取Jdbc模板

    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

    4.使用jdbc模板操作数据库

    这里以保存数据为例:

    @Test
    	public void test3() {
    		//创建简单连接池
    		SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
    		dataSource.setDriverClass(com.mysql.jdbc.Driver.class);
    		dataSource.setUrl("jdbc:mysql://localhost:3306/spring");
    		dataSource.setUsername("root");
    		dataSource.setPassword("123456");
    		//获取jdbc模板
    		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    		//操作数据库
    		jdbcTemplate.update("insert into userinfo values(null,?,?) ","李雷",20);
    	}
    


    使用详解

    jdbc模板的使用

    • 新增、修改、删除:jdbc模板的的增删改主要是通过update方法来与数据库交互的。

      • public void test4() {
        		//获取jdbc模板
        		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        		JdbcTemplate jdbcTemplate = (JdbcTemplate) context.getBean("jdbcTempldate");
        		//操作数据库
        		jdbcTemplate.update("insert into userinfo values(null,?,?) ","韩雷",20);
        		jdbcTemplate.update("delete from userinfo where id=3");
        		jdbcTemplate.update("update userinfo set name='地雷' where id=?", 4);
        	}
        


    • 查询:

      • 查询主要依靠query方法和queryForObject方法,他们的使用与DbUtils中的查询使用类似,都是需要传入一个结果集处理对象

      • 下面只给出一个小例子(这个例子是使用了spring注入生成template的结果,配置方法在下下份代码中),基本跟DbUtils的用法没什么区别,所以你也可以自己把它封装到对象中

        	@Test
        	public void test5() {
        		//获取jdbc模板
        		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        		JdbcTemplate jdbcTemplate = (JdbcTemplate) context.getBean("jdbcTempldate");
        		//操作数据库
        		jdbcTemplate.query("select * from userinfo", new RowMapper() {
        			@Override
        			public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
        				String name=rs.getString("name");
        				int age=rs.getInt("age");
        				System.out.println(name+":"+age);
        				return null;
        			}
        		});
        	}
        
      • queryForObject可以用来获取单个数据,也可以用来像query一样用结果集处理对象来处理数据

        @Test
        	public void test5() {
        		//获取jdbc模板
        		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        		JdbcTemplate jdbcTemplate = (JdbcTemplate) context.getBean("jdbcTempldate");
        		
        		Long num = jdbcTemplate.queryForObject("select count(*) from userinfo",Long.class);
        		System.out.println(num);
        	}
        
    • 上面讲了jdbc模板的使用涉及spring管理jdbc模板,了jdbc模板也可以交给spring去管理。

      • <!-- class里面写dbcp的类,使用dataSource获取的时候就是dbcp连接池对象 -->
        	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        		<!-- 由于里面有很多setter,可以直接属性注入 -->
        		<property name="driverClassName" value="com.mysql.jdbc.Driver" ></property>
        		<property name="url" value="jdbc:mysql://localhost:3306/spring" ></property>
        		<property name="username" value="root" ></property>
        		<property name="password" value="123456" ></property>
        	</bean>
          
            <bean id="jdbcTempldate" class="org.springframework.jdbc.core.JdbcTemplate">
            	    <!-- jdbc模板需要数据库连接池,这里注入一个dbcp的连接池对象 -->
                	<property name="dataSource" ref="dataSource"></property>
            </bean>
        


    配置数据库资源

    如果你之前学过第三方数据库连接池的配置的话,你应该了解到他们都是DataSource接口的实现类,再想想上面的IoC,所以其实Spring中配置数据库资源就是将连接池类交给Spring去管理。(如果是在代码中创建数据库连接资源的话,像普通的编写即可,跟Spring没关系)

    • 配置DBCP连接池:
      • 引入dbcp依赖包:
        • commons-dbcp-1.4.jar
        • commons-pool-1.5.6.jar
      • 配置DBCP:
        • 让spring管理dataSource对象。
    <!-- class里面写dbcp的类,使用dataSource获取的时候就是dbcp连接池对象 -->
    	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    		<!-- 由于里面有很多setter,可以直接属性注入 -->
    		<property name="driverClassName" value="com.mysql.jdbc.Driver" ></property>
    		<property name="url" value="jdbc:mysql://localhost:3306/spring" ></property>
    		<property name="username" value="root" ></property>
    		<property name="password" value="123456" ></property>
    	</bean>
    
    • 配置C3P0连接池:
      • 引入依赖包:
        • c3p0-0.9.1.2.jar
      • 配置连接池,把连接池交给spring管理:
    <!-- class里面写c3p0的类,使用dataSource获取的时候就是c3p0连接池对象 -->
    	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    		<!-- 由于里面有很多setter,可以直接属性注入 -->
    		<property name="driverClass" value="com.mysql.jdbc.Driver" ></property>
    		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring" ></property>
    		<property name="user" value="root" ></property>
    		<property name="password" value="123456" ></property>
    	</bean>
    

    引入外部配置文件:

    在上面的连接池配置过程中,你可能会发现一个问题,以往会使用配置文件来解决在代码中把参数写成固定值的问题,在上面中也是使用了固定值,那么怎么使用外部配置文件来解决这个问题呢?

    1.引入Property配置文件:【注意一下下面标签是以context开头的,所以它需要什么xsd,你懂的】

          <context:property-placeholder location="classpath:jdbc.properties" />
    

    2.根据配置文件中的key【要求key是点分的多单词,测试过单个单词无法获取】,引用配置文件中的参数:

          <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
          		<property name="driverClass" value="${jdbc.driverClass}" ></property>
          		<property name="jdbcUrl" value="${jdbc.url}" ></property>
          		<property name="user" value="${jdbc.username}" ></property>
          		<property name="password" value="${jdbc.password}" ></property>
          	</bean>
    

    给一下配置文件的写法:

    jdbc.driverClass=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/spring
    jdbc.username=root
    jdbc.password=123456
    



    事务管理

    • 在上面中讲述了Spring对数据库操作的支持,但没有涉及事务管理,并且实质上jdbc模板是没有自带事务管理的。
    • Spring对事务的管理是通过PlatformTransactionManager管理的。

    Spring用于管理事务的几个类

    • PlatformTransactionManager:一个接口,spring用于管理事务

      • DataSourceTransactionManager:PlatformTransactionManager的实现类,可用于jdbc模板和MyBatis框架
      • HibernateTransactionManager:PlatformTransactionManager的实现类,主要针对Hibernate框架
    • TransactionDefinition:事务定义信息,包含了一些关于事务管理的变量(隔离级别、传播行为等等),spring会把方法类中配置的事务相关属性装载到TransactionDefinition中。

    • TransactionStatus:事务的状态

    • 平台事务管理器PlatformTransactionManager会根据事务定义信息TransactionDefinition的信息来进行事务管理。事务管理中产生的状态信息存储到TransactionStatus中。



    声明式事务管理

    xml方式【XML的方式有很多种,例如拦截器方式、通知方式,这里介绍通知方式】:

    1.导入事务管理的xsd:

          <?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:aop="http://www.springframework.org/schema/aop"
                  xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="
                  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
                  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> 
          
          </beans>
    

    2.配置事务管理器

         <!--使用事务管理器,必须注入一个dataSource -->
         <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
         	<property name="dataSource" ref="dataSource" />
         </bean>
    

    3.配置事务的增强,把事务管理器当成切面类来对方法增强,相当于给方法配置一个事务管理增强。

    •  <!-- 把spring管理的transactionManager赋给transaction-manager属性 -->
           <tx:advice id="txAdvice" transaction-manager="transactionManager" >
      		<tx:attributes>
      			<!-- name写要进行事务管理的方法名 -->
      	        <tx:method name="transfer"  />
      	    </tx:attributes>
      	 </tx:advice>
      

    4.AOP配置,把上面的事务增强配置给哪个切点,这些切点就是需要事务管理的地方:

    	 <aop:config>
    	 	<!-- 这里的切点注意要写把多个数据库操作集合起来的方法 -->
    	 	<aop:pointcut expression="execution(* work.service.impl.AccountServiceImpl.*(..))" id="pointcut1"/>
    	 	<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
    	 </aop:config>
    

    5.测试类的编写:

    注解式

    1.导入事务管理tx和aop的xsd

    2.配置事务管理器

    • <!--使用事务管理器,必须注入一个dataSource -->
      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      	<property name="dataSource" ref="dataSource" />
      </bean>
      

    3.开启注解式事务

    • <tx:annotation-driven transaction-manager="transactionManager"/>
      

    4.在业务层增加@Transactional注解:

    • 使用在类上,代表类的所有方法都使用事务管理
    • 也可以仅使用在某个方法上。


    使用详解:

    • 事务管理器配置

      • spring管理事务依靠事务管理器,所以事务管理器是必须的,无论注解式还是XML式都需要。对于MyBatis和JDBC可以使用DataSourceTransactionManager作为实现类,对于Hibernate可以使用HibernateTransactionManager作为实现类

      • 事务管理器必须注入一个dataSource

      • <!--使用事务管理器,必须注入一个dataSource -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        	<property name="dataSource" ref="dataSource" />
        </bean>
        
    • XML式中事务增强的配置

      • method配置:

        • name属性是增强的方法名。另外,可以使用*作为后缀,代表匹配某一些特定前缀开头的方法,比如save*可以匹配saveCustomer,saveStudent;单写*的时候的,代表匹配所有方法。
      •  <!-- 把spring管理的transactionManager赋给transaction-manager属性 -->
             <tx:advice id="txAdvice" transaction-manager="transactionManager" >
        		<tx:attributes>
        			<!-- name写要进行事务管理的方法名 -->
        	        <tx:method name="transfer"  />
        	    </tx:attributes>
        	 </tx:advice>
        
    • 注解式事务管理的开启,使用注解式事务管理,除了创建事务管理器,还需要开启事务管理的注解管理,<tx:annotation-driven transaction-manager="transactionManager"/>

    • 注解式中@Transactional的位置

      • 当它使用在类上时,代表类中的所有方法都使用事务管理
      • 使用在方法上时,代表对某个方法使用事务管理。
    • 隔离级别配置:【隔离级别分别解决了不同的读写问题,由于是SQL的内容,所以这里不介绍,只讲用法】

      • 隔离级别有:Read uncommitted、Read committed 、Repeatable read、Serializable
      • 对于注解方式,对注解@Transactional添加属性isolation即可,例如@Transactional(isolation=Isolation.DEFAULT)
      • 对于XML方式(这里指上面那种,XML有很多方式),在tx:method中增加isolation属性即可,例如<tx:method name="transfer" isolation="DEFAULT" />
    • 传播行为配置:(传播行为用来处理多个事务时的叠加问题--当方法相互调用时可能会发生,某个方法有了事务,另一个方法也有事务,那么用哪个事务管理?)

      • 传播行为类型:【以A、B两个方法为例】
        • 保证方法都在一个事务中(A+B)
          • REQUIRED:A调用B时,如果A中已经有了事务,那么B使用A中的事务(A+B);如果A中没有事务,那么A创建一个事务,然后包裹B(A+B)。【默认值】
          • SUPPORTS:A调用B时,如果A中已经有了事务,那么B使用A中的事务(A+B);如果A中没有事务,那么B不使用事务。
          • MANDATORY:A调用B时,如果A中已经有了事务,那么B使用A中的事务(A+B);如果A中没有事务,那么报错。
        • 保证方法都不在一个事务中
          • REQUIRES_NEW:A调用B时,无论A是否有事务(有事务则挂起事务),B都重新创建一个事务来包裹B。
          • NOT_SUPPORTED:A调用B时,无论A是否有事务(有事务则挂起事务),B都不使用事务。
          • NEVER:A调用B时,如果A中有事务,则报错。
        • 嵌套事务
          • NESTED:A调用B时,A事务中嵌套B事务,如果B事务报错,只回滚B事务,不回滚A事务。
      • 对于XML方式,在tx:method中增加propagation属性即可,例如<tx:method name="*" propagation="REQUIRED" />
      • 对于注解方式,对注解@Transactional添加属性propagation即可,例如@Transactional(propagation=Propagation.REQUIRED)
    • jdbcTemplate注入问题:你可能会发现每一个Dao你都要注入一次jdbcTemplate,那样子貌似太麻烦了。spring提供了一个JdbcDaoSupport,我们的Dao可以继承这个类,这个类中已经包含了jdbcTemplate。除此之外,如果给这个类注入一个连接池,它会自动根据连接池来生成jdbcTemplate(这样我们就不需要把jdbcTemplate交给spring管理了,直接在dao中注入一个dataSource即可)。



    补充:

    • 其实处理事务,还可以使用编程式事务(就是利用事务模板transactionTemplate来手动写),但编程式事务没有声明式事务简单好用,而且声明式事务基于AOP特性,所以这里只讲声明式事务。
    • 有时候使用@Transactional会失效,通常有几个情况
        1. 由于事务管理是基于AOP,所以不能用于静态和非public的方法。
        2. 自调用问题:调用自身类的方法,传播行为是不生效的,具体可以参考这个老哥的博文,讲的比较详细了,https://blog.csdn.net/jiesa/article/details/53438342

    分模块开发问题:

    有时候你可能不想在一个applicationContext.xml中配置所有的属性,你想建立多个applicationContext.xml来配置不同的属性。

    • 配置文件名默认名字是applicationContext.xml,其实你可以改成别名字。你可能留意到new ClassPathXmlApplicationContext()时需要给一个名字,所以其实你可以改成其他的。

    • 在new ClassPathXmlApplicationContext()中其实可以读取多个配置文件

      • ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml","applicationContext2.xml");
        
    • 如果你不想在ew ClassPathXmlApplicationContext()时导入多个,也可以在一个配置文件中引入多个配置文件,例如在applicationContext.xml中使用import就可以引入别的配置文件

      • <import resource="applicationContext2.xml"/>
        

    框架整合问题:

    • 这里不讨论怎么与其他框架整合,只提一个常见的问题--这是与其他框架整合时都会遇到的问题。
    • 问题:当你把spring使用到web中的时候,在没有提前创建的基础上,每次请求都需要创建一个Spring的工厂(每次都ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");),工厂是很耗费资源的,一个项目只有一个Spring的工厂,下面讨论这个问题的解决方案。

    解决方案其实我们都知道,重点是提前创建,然后存储起来,需要的时候再获取。

    解决方案1:

    使用ServletContextListener 监听ServletContext 对象的创建和销毁。

    • 在ServletContextListener 的ContetInitialized中创建工程,并存储到ServletContext 中,每次获取工厂都从ServletContext中获取。【重点不是这个,所以不讲述那么多。】


    标准解决方案:

    配置spring核心监听器,让监听器提前创建工厂。监听器会帮我们把工厂存储到ServletContext 中。

    • 引入jar包:spring-web.jar

    • 在web.xml中配置监听器:

      •   <listener>
          		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
          </listener>
          <!-- 配置参数,告诉核心过滤器读取哪个文件来创建工厂 -->
          <context-param>
        	  	<param-name>contextConfigLocation</param-name>
        	  	<param-value>classpath:applicationContext.xml</param-value>
          </context-param>
        
    • 获取ServletContext ,然后利用WebApplicationContext context= WebApplicationContextUtils.getWebApplicationContext(sc);获取工厂 :【这里为了帮助了解,使用基础的servlet,而不是框架】

      • public class HelloServelet extends HttpServlet {
        	@Override
        	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        		//获取ServletContext
        		ServletContext sc=req.getServletContext();
        		//传入ServletContext,获取工厂
        		WebApplicationContext context= WebApplicationContextUtils.getWebApplicationContext(sc);
        		//通过工厂获取对象
        		Person person = (Person) context.getBean("person");
        		person.setName("丁典");
        		//能使用就说明获取成功
        		System.out.println(person);
        	}
        }
        

    写在最后

    1. 这个讲述顺序是我想了好久才琢磨出来的顺序,希望能帮助了解
    2. spring框架也是个大块头,虽然这篇博文已经很长了,但还是不能涉及所有内容,而我是一个比较喜欢系统化的人,对于一些知识,我可能会整合到别的博文中去,但如果某天发现不适合整合到别的博文中去时,我可能会回来整合到这篇博文中。
    3. 希望这篇博文能帮助到你。

  • 相关阅读:
    AB测试原理及样本量计算的Python实现
    数据分析-A/B test
    数据分析-分类分析
    数据分析-漏斗模型(AARRR模型)
    置信区间的I型错误和II型错误
    tableau 计算字段
    tableau数据分层、数据组、数据集
    tableau 地图
    tableau 进阶
    tableau 基础
  • 原文地址:https://www.cnblogs.com/progor/p/9534626.html
Copyright © 2011-2022 走看看