目录

首发日期:2018-08-25


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/spring work/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- -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只需要基础的依赖包。【这里省去扩展日志依赖包】

  1. 创建一个用于被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 + \"]\";
    }
}
  1. applicationContext. 中把类配置给spring,让spring管理这个类的创建,:
    • applicationContext. 需要xsd约束,xsd在解压文件夹spring- work-4.2.4.RELEASEdocsspring- work-referencehtmlxsd-configuration.html的the beans schema中。【xsd-configuration.html是spring的配置文件的所有xsd配置的参考文件,可以根据不同配置来查找到不同的xsd配置】
    • applicationContext. 需要放置在classpath可搜索路径下,通常可以放在src目录下。
<?  version=\"1.0\" encoding=\"UTF-8\"?>
<beans  ns=\"http://www.spring work.org/schema/beans\"
     ns:xsi=\"http://www.w3.org/2001/ Schema-instance\"
    xsi:schemaLocation=\"
        http://www.spring work.org/schema/beans http://www.spring work.org/schema/beans/spring-beans.xsd\">
    <!-- 配置bean,让spring管理这个类的创建 -->
    <bean id=\"user\" class=\"work.domain.User\"></bean>
</beans>
  1. 创建测试类,获取spring创建的对象:

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

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

使用说明:

  • 如何在applicationContext. 中配置:

    • xsd约束:

      • xsd在解压文件夹spring- work-4.2.4.RELEASEdocsspring- work-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 ClassPath ApplicationContext(\"applicationContext. \");
              //获取方法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一开启就会实例化对象。也就是说,默认情况下,在不同代码中多次使用一个类的对象都是同一个对象。【可以通过配置来改变】

  • applicationContext. 的文件名是可以改变的,但默认获取的时候文件名就是这个,考虑到遵循规范,这里不讲解怎么更改获取的文件名。

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

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

    • 修改前:

      <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. 编写

        <!-- 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. 编写

          <!-- 先实例化工厂 -->
           <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. 的编写:

          <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. 的编写:

          <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):

      • 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. 编写

      • <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>
  • 属性注入方式还有一种接口注入,接口注入通常使用于资源来自于外界的情况,比如当数据库连接资源来自于外部的时候就可以使用接口注入。这个知识点这里不讲,有兴趣自查。

补充:

  • 还可以从Properties中读取数据来进行注入 ,这里不讲,有兴趣的可以自查【在事务管理中讲述了可以利用一种方式来读取】。

IoC的注解方式:

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

依赖包

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

示例使用

  • applicationContext. 配置

    • 导入xsd:

      • 在原来的xsd的基础上,加上context的,context在spring- work-4.3.4.RELEASE/docs/spring- work-reference/html/xsd-configuration.html的the context schema中
    • applicationContext. 中配置组件扫描:

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

package work.domain;

import org.spring work.stereotype.Component;
//添加注解
@Component(\"person\")  //相当于在applicationContext. 中配置<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;
    }
}

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

bean配置注解介绍:

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

    • @Component("person") //相当于在applicationContext. 中配置<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上面的组件扫描【组件扫描的时候也会扫描到用于属性注入的注解】;如果仅仅使用注解来进行属性注入,则不需要组件扫描,直接使用下面的。【很多时候,我们可能会使用 来配置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来注入。
  • 注解的使用:如果有setter方法,就把注解添加到set方法上;如果没有,那么添加到属性上;两者都有的时候建议添加到set方法上。

AOP

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

动态代理技术简单演示

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

  1. 创建一个接口

    1. public interface SchoolPerson {
        public void read();
        public void save();
      }
  2. 定义一个实现接口的类

    1. package work.domain;
      public class Student implements SchoolPerson {
        public void read() {
            System.out.println(\"read\");
        }
        public void save() {
            System.out.println(\"save\");
        }
      }
  3. 创建一个代理器

    1. 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   invoke(  proxy, Method method,  [] args) throws Throwable {
            if(method.getName().equals(\"save\")) {
                System.out.println(\"增强功能执行---。\");
                return  method.invoke(sp, args);
            }
            return method.invoke(sp, args);
        }
      }
  4. 测试:

    1. 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
             */
        }
      }

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. 文件

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

      • <?  version=\"1.0\" encoding=\"UTF-8\"?>
        <beans  ns=\"http://www.spring work.org/schema/beans\"
             ns:xsi=\"http://www.w3.org/2001/ Schema-instance\"
             ns:aop=\"http://www.spring work.org/schema/aop\" xsi:schemaLocation=\"
                http://www.spring work.org/schema/beans http://www.spring work.org/schema/beans/spring-beans.xsd
                http://www.spring work.org/schema/aop http://www.spring work.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  =\"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 ClassPath ApplicationContext(\"applicationContext. \");
              StudentDao studentDao = (StudentDao) context.getBean(\"studentDao\");
              studentDao.read();
          }

使用详解:

AOP配置结构

  •     <!-- aop:config是配置AOP的顶层标签 -->
        <aop:config>
          <!-- aop:pointcut配置切点,配置哪些类的哪些方法需要增强 -->
          <aop:pointcut  =\"...\" id=\"...\"/>
          <!-- aop:aspect配置切面 -->
          <aop:aspect ref=\"myAspect\">
                <!--配置对于切点怎么增强 -->
              <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>

切点:

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

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

切面:

在 中,用<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" />

切面类:

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

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

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

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

    • 中:<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。。。\");
                obj=pj.proceed();
              System.out.println(\"around out。。。\");
              return obj;
          }
  • 异常抛出通知可以获取目标方法的异常信息

补充

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

AOP的注解方式:

  • AOP的注解方式不需要引入其他依赖包。

使用示例

开启自动代理

  • 在 中开启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

切点:

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

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

    • 使用@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. 创建数据连接资源(这里以简单数据库连接池为例)

3. 获取Jdbc模板

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 ClassPath ApplicationContext(\"applicationContext. \");
              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方法和queryFor 方法,他们的使用与DbUtils中的查询使用类似,都是需要传入一个结果集处理对象

    • 下面只给出一个小例子,基本跟DbUtils的用法没什么区别,所以你也可以自己把它封装到对象中

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

      @Test
          public void test5() {
              //获取jdbc模板
              ApplicationContext context = new ClassPath ApplicationContext(\"applicationContext. \");
              JdbcTemplate jdbcTemplate = (JdbcTemplate) context.getBean(\"jdbcTempldate\");
      
              Long num = jdbcTemplate.queryFor (\"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.spring work.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,你懂的】

    1. <context:property-placeholder location=\"classpath:jdbc.properties\" />
  2. 根据配置文件中的key【要求key是点分的多单词,测试过单个单词无法获取】,引用配置文件中的参数:

    1. <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中。

声明式事务管理

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

  1. 导入事务管理的xsd:

    1. <?  version=\"1.0\" encoding=\"UTF-8\"?>
      <beans  ns=\"http://www.spring work.org/schema/beans\"
               ns:xsi=\"http://www.w3.org/2001/ Schema-instance\"
               ns:aop=\"http://www.spring work.org/schema/aop\"
               ns:tx=\"http://www.spring work.org/schema/tx\" xsi:schemaLocation=\"
              http://www.spring work.org/schema/beans http://www.spring work.org/schema/beans/spring-beans.xsd
              http://www.spring work.org/schema/tx http://www.spring work.org/schema/tx/spring-tx.xsd
              http://www.spring work.org/schema/aop http://www.spring work.org/schema/aop/spring-aop.xsd\"> 
      
      </beans>
  2. 配置事务管理器

    • <!--使用事务管理器,必须注入一个dataSource -->
      <bean id=\"transactionManager\" class=\"org.spring work.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  =\"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.spring work.jdbc.datasource.DataSourceTransactionManager\">
         <property name=\"dataSource\" ref=\"dataSource\" />
      </bean>
  3. 开启注解事务

    • <tx:annotation-driven transaction-manager=\"transactionManager\"/>
  4. 在业务层增加@Transactional注解:

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

使用详解:

  • 事务管理器配置

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

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

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

    • 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的位置

    • 当它使用在类上时,代表类中的所有方法都使用事务管理
    • 使用在方法上时
收藏 打印