Spring

Spring介绍

Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益
简单来说,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。

Spring好处

方便解耦,简化开发:
  • Spring就是一个大工厂,专门负责生成Bean,可以将所有对象创建和依赖关系维护由Spring管理
AOP编程的支持:
  • Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能
声明式事务的支持:
  • 只需要通过配置就可以完成对事务的管理,而无需手动编程
方便程序的测试:
  • SpringJunit4支持,可以通过注解方便的测试Spring程序
    方便集成各种优秀框架:
Spring不排斥各种优秀的开源框架
  • 其内部提供了对各种优秀框架(如:StrutsHibernateMyBatisQuartz等)的支持
降低JavaEE API的使用难度Spring:
  • JavaEE开发中一些难用的APIJDBCJavaMail、远程调webservice用等),都提供了封装,使这些API应用难度大大降低

Spring体系结构

Spring 框架是一个分层架构,,它包含一系列的功能要素并被分为大约20个模块。这些模块分为Core ContainerData Access/IntegrationWebAOP(Aspect Oriented Programming)Instrumentation和测试部分,如下图所示:

Spring架构

Spring在项目中的架构
  • web层:Struts,SpringMVC
  • dao层:Hibernate,mybatis
    Spring在项目中的架构

Spring IOC的底层实现原理

Spring IOC的底层实现原理

Spring核心jar包

  • spring-core-3.2.2.RELEASE.jar
    包含Spring框架基本的核心工具类,Spring其它组件要都要使用到这个包里的类,是其它组件的基本核心。

  • spring-beans-3.2.2.RELEASE.jar
    所有应用都要用到的,它包含访问配置文件、创建和管理bean
    以及进行Inversion of Control(IoC) / Dependency Injection(DI)操作相关的所有类

  • spring-context-3.2.2.RELEASE.jar
    Spring提供在基础IoC功能上的扩展服务,此外还提供许多企业级服务的支持,
    如邮件服务、任务调度、JNDI定位、EJB集成、远程访问、缓存以及各种视图层框架的封装等。

  • spring-expression-3.2.2.RELEASE.jar
    Spring表达式语言

  • com.springsource.org.apache.commons.logging-1.1.1.jar
    第三方的主要用于处理日志

Spring IOC/DI

  • IOC Inverse of Control反转控制的概念, 就是将原本在程序中手动创建对象的控制权, 交给Spring框架管理, 简单的说, 就是将创建对象控制权被翻转到了Spring框架
  • DI Dependency Injection依赖注入的概念, 就是在Spring创建这个对象的过程中, 将这个对象所依赖的属性注入进去

简单的Spring框架使用

创建一个UserService接口

public void sayHello();

和它的实现类UserServiceImpl

public void sayHello {
    private String name;
    public String getName() {
          return name;
     }
    public void setName(name) {
          this.name = name;
    }
    public void sayHello() {
          System.out.println("Spring hello" + name)
    }
    
}
  • 传统使用UserService
public void demo() {
    UserService userService = new UserServiceImpl();
    userService.setName("李四");
    userService.sayHello();
}

输出结果

Spring hello李四
  • 使用Spring
    在resource里创建一个applicationContext.xml文件, 里面写入
// IOC
<bean id = "userService" class="com.rui.ioc.demo.UserServiceImpl>
  <property name = "name" value="李四" />
</bean>
public void demo () {
    // 创建Spring工厂, 加载classPath下的配置文件
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 通过工厂获得类
    UserService userService = (UserService) applicationContext.getBean("userService");
    userService.sayHello();
}

输出结果

Spring hello李四
  • IOC=>
使用`Spring`工厂获得类叫做`IOC`, 即将创建对象的权利交给Spring
  • DI =>
<property name = "name" value="李四" />

叫做依赖注入, 就是在Spring创建这个对象的过程中, 将这个对象所依赖的属性注入进去

Spring的工厂类

Spring的工厂类
  • ClassPathXmlApplicationContextBeanFactory的区别

    • BeanFactoryspring中比较原始,比较古老的Factory。因为比较古老,所以BeanFactory无法支持spring插件,例如:AOPWeb应用等功能。
    • 如果使用ApplicationContext,如果配置的beansingleton,那么不管你有没有或想不想用它,它都会被实例化。好处是可以预先加载,坏处是浪费内存。
    • BeanFactory,当使用BeanFactory实例化对象时,配置的bean不会马上被实例化,而是等到你使用该bean的时候(getBean)才会被实例化。好处是节约内存,坏处是速度比较慢。多用于移动设备的开发。
    • 没有特殊要求的情况下,应该使用ApplicationContext完成。因为BeanFactory能完成的事情,ApplicationContext都能完成,并且提供了更多接近现在开发的功能。
  • 加载某个位置下的配置文件

public void demo () {
  // 创建Spring工厂
  ApplicationContext applicationContext = new FileSystemXmlApplicationContext("c:\\applicationContext.xml");
  // 通过工厂获得类
  UserService userService = (UserService) applicationContext.getBean("userService");
  userService.sayHello();
}

Spring的Bean管理方式

  • XML

    • 三种实例化Bean的方式

      • 使用类构造器实例化(默认无参数)
    // 对象
    public class Bean1 {
        public Bean1() {
            System.out.println("Bean1被实例化了...");
        }
    }
    
    // Spring配置文件
    <bean id="bean1" class="com.rui.demo.Bean1" />
    
    @Test
    public void demo () {
      // 创建Spring工厂, 加载classPath下的配置文件
      ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
      Bean1 bean1 = (bean1) applicationContext.getBean("bean1");
    }
    
    输出结果为
    Bean1被实例化了...
    
    • 使用静态工厂实例化(简单工厂模式)
    // 对象
    public class Bean2 {
        public Bean2() {
            System.out.println("Bean2被实例化了...");
        }
    }
    
     // Bean2的静态工厂
    public class Bean2Factory {
        public static Bean2 createBean2() {
            System.out.println("Bean2被实例化了...");
            return new Bean2();
        }
    }
    
    <bean id="bean2" class="com.rui.demo.Bean2Factory"  factory-method="createBean2" />
    
    public void demo () {
      // 创建Spring工厂, 加载classPath下的配置文件
      ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
      Bean2 bean2 = (bean2) applicationContext.getBean("bean2");
    }
    
    输出结果为
    Bean2被实例化了...
    
    • 使用实例工厂方法实例化(工厂方法模式)
    // 对象
    public class Bean3 {
        public Bean3() {
            System.out.println("Bean3被实例化了...");
        }
    }
    
     // Bean3的实例化工厂方法
    public class Bean3Factory {
        public Bean3 createBean3() {
            System.out.println("Bean3被实例化了...");
            return new Bean3();
        }
    }
    
    <bean id="bean3Factory" class="com.rui.demo.Bean3Factory" />
    <bean id="bean3" actory-bean="bean3Factory"  factory-method="createBean3" />
    
    public void demo () {
      // 创建Spring工厂, 加载classPath下的配置文件
      ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
      Bean3 bean3 = (bean3) applicationContext.getBean("bean3");
    }
    
    输出结果为
    Bean3被实例化了...
    
    • Bean的常用配置

      • id和name
        • 一般情况下, 装配一个Bean的时候, 通过一个id属性作为Bean的名称
        • id属性在IOC容器里必须是唯一的
        • 如果Bean的名称中含有特殊字符, 就需要使用name属性
      • class
        • class用于设置一个类的完全路径名称, 主要作用是IOC容器生成类的实例
      • Bean的作用域Scope
      类别 说明
      singleton(默认) SpringIOC容器中仅存在一个Bean实例, Bean以单例的方式存在
      prototype 每次调用getBean()时都返回一个新的实例
      request 每次Http请求都会创建一个新的Bean, 该作用域仅适用于WebApplicationContext环境
      Session 同一个HTTP Session共享一个Bean, 不同的HTTP Session使用不同的Bean, 该作用域仅适用于WebApplicationContext环境
    • Spring容器中Bean的生命周期

      Spring初始化bean或销毁bean的时候, 有时候需要做一些处理工作, 因此Spring可以在创建和销毁bean的时候调用bean的两个声明周期方法

      类别 说明
      init-method 初始化方法
      destory-method 销毁方法, 仅在scope=singleton有效
        // 当bean被载入到容器的时候调用init, 当bean从容器中删除的时候调用destory(仅在`scope=singleton有效)
          <bean id="xxx" class="xxxx" init-method="init" destory-method="destory" />
      
      Spring容器中Bean的生命周期
      1. instantiate bean对象实例化
      2. populate properties 封装对象
      3. 如果Bean实现BeanNameAware执行setBeanName
      4. 如果Bean实现BeanFactoryAware或者ApplicationContextAware设置工厂
      5. 如果存在类实现BeanPostProcessor(后处理Bean), 执行postProcessBeforeInitialization
      6. 如果Bean实现InitializingBean执行afterPropertiesSet
      7. 调用<bean init-method="init" />指定初始化init
      8. 如果存在类实现BeanPostProcessor(处理Bean), 执行postProcessAfterInitiazation
      9. 执行业务逻辑
      • Spring中beanPostProcessor使用案例
        • applicationContext.xml
        <bean class="com.rui.ioc.MyBeanPostProcessor" />
        <bean id="userDao" class="com.rui.ioc.UserDaoImpl" />
        
        • UserDao
          public interface UserDao {
              public void findAll();
              public void save();
              public void update();
              public void delete();
           }  
        
        • UserDaoImpl
         public class UserDaoImpl implements UserDao {
          @Override
          public void findAll() {
          System.out.println("查");
          }
          @Override
           public void save() {
              System.out.println("存");
            }
          @Override
          public void update() {
              System.out.println("改");
           }
          @Override
          public void delete() {
              System.out.println("删");
          }
        }
        
        • MyBeanPostProcessor
           public class MyBeanPostProcessor implements BeanPostProcessor {
             @Override
             public Object postProcessBeforeInitialization(Object bean, String beanName)
           throws BeansException {
                   return bean;
           }
              @Override
              public Object postProcessAfterInitialization(final Object bean, String beanName)
             throws BeansException {
                 if ("userDao".equals(beanName)) {
                       Object proxy =
                   java.lang.reflect.Proxy.newProxyInstance(bean.getClass().getClassLoader(),
                           bean.getClass().getInterfaces(), new InvocationHandler() {
                               public Object invoke(Object proxy, Method method, Object[] args)
                                       throws Throwable {
                                   if ("save".equals(method.getName())) {
                                       System.out.println("权限调用");
                                       return method.invoke(bean, args);
                                   }
                                   return method.invoke(bean, args);
                               }
                           });
                   return proxy;
               } else {
                   return bean;
             }
           }
        }
        
        • 输出
           第八步2222
           查
           权限调用
           存
           改
           删
          
    • Spring的属性注入

      • 构造函数注入, 在对象中, 必须实现对应的构造函数参数为设置的属性
        <bean id="user" class="com.rui.demo.User">
           <contructor-arg name="name" value="1223">
           <contructor-arg name="age" value="23">
        </bean>
        
      • 属性setter方法注入, 在Spring配置文件中, 通过<property>设置注入的属性, 在对象中必须实现对应的参数的setter方法
        <bean id="user" class="com.rui.demo.User">
           <property name="name" value="1223">
           <property name="age" value="23">
        </bean>
         ```
         ```
        // User对象中含有一个对象Cat
        <bean id="user" class="com.rui.demo.User">
           <property name="name" value="1223">
           <property name="age" value="23">
           <property name="cat" ref="cat"> // ref引入别的bean的id
        </bean>
        <bean id="cat" class="com.rui.demo.Cat">
           <property name="name" value="ketty">
        </bean>     
         ```
        
      • P名称空间注入
         <!--xmlns:p="http://www.springframework.org/schema/p"         需要引入-->
         <bean id="userDao" class="com.rui.ioc.UserDaoImpl" p:age="12"
         p:name="shsj" />
        
      • SpEL注入
          <bean id="catogery" class="com.rui.ioc.Category">
              <property name="name" value="#{'服装'}" />
          </bean>
          <bean id="productInfo" class="com.rui.ioc.ProductInfo" />
          <bean id="productInfo" class="com.rui.ioc.Product">
               <property name="name" value="#{'男装'}" />
               <!--        <property name="price" value="#{'1999'}" />-->
               <property name="price" value="#{productInfo.caculatePrice}" />
              <property name="category" value="#{catogery}" />
          </bean>
        
      • 复杂类型的属性注入(一般用于整合其他框架)
      // 数组类型
      <property name="arrs">
          <list>
              <value>aaa</value>
          </list>
      </property>
      // list类型
      <property name="list">
          <list>
              <value>aaa</value>
          </list>
      </property>
      // 数组类型
      <property name="arrs">
          <list>
              <value>aaa</value>
          </list>
      </property>
      // set类型
      <property name="set">
          <set>
              <value>aaa</value>
          </set>
      </property>
      // Map类型
      <property name="map">
          <set>
              <value>aaa</value>
          </set>
      </property>
      // Map类型
      <property name="arrs">
          <map>
              <entry key="aaa" value="bbb" />
          </map>
      </property>
      // properties类型
      <property name="properties">
          <props>
              <prop key="username">root</prop>
              <prop key="password">12345</prop>
          </props>
      </property>
      

Spring的Bean注解方式

  • Bean的管理

applicationContext.xml文件里, 引入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.rui.ioc"/>
</beans>

就会自动扫描ioc下所有的类, 在类上标注@Component/@Service/@Repository/@Controller

1、@controller 控制器(注入服务)
2、@service 服务(注入dao
3、@repository dao(实现dao访问)
4、@component (@component("xxx"))(把普通pojo实例化到spring容器中,相当于配置文件中的<bean id="xxx" class=""/>

  • 属性的注入-注解方式

    • 简单属性注入
       @value("coding")
       private String something; // something输出值为coding
      
    • @Autowired进行自动注入
      • @Autowired默认按照类型进行注入
        • 如果两个相同的Bean类型相同, 则按照名称进行注入
      • 通过@Autowiredrequired属性, 设置一定要找到匹配的Bean
      • 使用@Qualifier指定注入Bean的名称
        同时使用@Autowired@Qualifier可以指定固定的Bean, 效果和使用@Resource相同
    • @PostConstruct: 初始化
    • @PreDestroy: 销毁
    • @Scope("prototype"): 多例, 默认为单例

Spring AOP

  • 什么是AOP

    AOP Aspect Oriented Programing 面向切面编程
    AOP采取横向抽取机制, 取代了传统纵向继承体系重复性代码(性能监视, 事务管理, 安全检查, 缓存)

  • AOP术语

    AOP术语
  • JDK动态代理

UserDao

package com.rui.aop;

public interface UserDao {
    public void findAll();
    public void save();
    public void update();
    public void delete();
}

UserDaoImpl

package com.rui.aop;
public class UserDaoImpl implements UserDao {
    @Override
    public void findAll() {
        System.out.println("查找用户~");
    }
    @Override
    public void save() {
        System.out.println("保存用户~");
    }
    @Override
    public void update() {
        System.out.println("更新用户~");
    }
    @Override
    public void delete() {
        System.out.println("删除用户~");
    }
}

MyJdkProxy

package com.rui.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyJDKProxy implements InvocationHandler {
    private UserDao userDao;
    public MyJDKProxy(UserDao userDao) {
        this.userDao = userDao;
    }
    public Object createProxy() {
        Object proxy = Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao
                .getClass().getInterfaces(), this);
        return proxy;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("save".equals(method.getName())) {
            System.out.println("保存用户之前的操作");
            return method.invoke(userDao, args);
        }
        return method.invoke(userDao, args);
    }
}

aopTest

package com.rui.aop;
import org.junit.Test;
public class aopTest {
    @Test
    public void demo1() {
        UserDao userDao = new UserDaoImpl();
        userDao = (UserDao)new MyJDKProxy(userDao).createProxy();
        userDao.findAll();
        userDao.delete();
        userDao.save();
        userDao.update();
    }
}

输出结果

查找用户~
删除用户~
保存用户之前的操作
保存用户~
更新用户~
  • 使用CGLIB生成代理

MyCglibProxy

public class MyCglibProxy implements MethodInterceptor {
    private ProductDao productDao;

    public MyCglibProxy(ProductDao productDao) {
        this.productDao = productDao;
    }
    public Object createProxy() {
        // 1. 创建核心类
        Enhancer enhancer = new Enhancer();
        // 2. 设置父类
        enhancer.setSuperclass(productDao.getClass());
        // 3. 设置回调
        enhancer.setCallback(this);
        // 4.生成代理
        Object proxy = enhancer.create();
        return proxy;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        if ("save".equals(method.getName())) {
            System.out.println
                    ("============================权限校验==========================");
            return methodProxy.invokeSuper(o, objects);
        }
        return methodProxy.invokeSuper(o, objects);
    }
}

ProductDao

public class ProductDao {
    public void findAll() {
        System.out.println("查找用户~");
    }
    public void save() {
        System.out.println("保存用户~");
    }
    public void update() {
        System.out.println("更新用户~");
    }
    public void delete() {
        System.out.println("删除用户~");
    }
}

Test

@Test
public void demo1() {
     ProductDao productDao = new ProductDao();
     ProductDao proxy = (ProductDao)new MyCglibProxy(productDao).createProxy();
     proxy.findAll();
     proxy.delete();
     proxy.save();
     proxy.update();
}

输出

查找用户~
删除用户~
============================权限校验==========================
保存用户~
更新用户~

代理相关

  • Spring在运行期, 生成动态代理对象, 不需要特殊的编译器
  • Spring AOP的底层就是通过JDK动态代理或CGLib动态代理技术为目标Bean执行横向织入
    • 若目标对象实现了若干接口, Spring使用JDKjava.lang.reflect.Proxy类代理
    • 若目标对象没有实现任何接口, spring使用cglib库生成目标对象的子类
  • 程序中应该优先对接口创建代理, 便于程序解耦维护
  • 标记为final的方法, 不能被代理, 因为无法覆盖
    • JDK动态代理, 是针对接口生成子类, 接口中方法不能使用final修饰
    • CGLib是针对目标类生产子类, 因此类或方法, 不能使用final
  • Spring只支持方法连接点, 不提供属性连接点

Spring AOP增强类型

  • AOP联盟为通知Advice定义了org.aopallinance.aop.Interface.Advice
  • Spring按照通知Advice在目标类方法的链接点位置, 可以分为5类
    • 前置通知 org.springframework.aop.MethodBeforeAdvice
      • 在目标方法执行前实施增强
    • 后置通知 org.springframework.aop.AfterReturningAdvice
      • 在目标方法执行后实施增强
    • 环绕通知 org.springframework.intercept.MethodInterceptor
      • 在目标方法执行前后都实施增强
    • 异常抛出通知 org.springframework.ThrowsAdvice
      • 在方法抛出异常后实施增强
    • 引介通知 org.springframework.IntroductionInterceptor
      • 在目标类中添加一些新的方法和属性

Spring AOP切面类型

  • Advisor: 代表一般切面, Advice本身就是一个切面, 对目标类所有方法进行拦截
  • PointcutAdvisor: 代表具有切点的切面, 可以指定拦截目标类哪些方法
  • IntroductionAdvisor: 代表引介切面, 针对引介通知而使用切面

自动创建代理

  • BeanNameAutoProxyCreator 根据Bean名称创建代理
  • DefaultAdvisorAutoProxyCreator 根据Advisor本身包含信息创建代理
  • AnnotationAwareAspectJAutoProxyCreator 基于Bean中的AspectJ注解进行自动代理

AspectJ AOP

  • AspectJ是一个基于java语言的AOP框架
  • Spring 2.0以后新增了对AspectJ切点表达式的支持

1.Spring开启Aspectj

 <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:aop="http://www.springframework.org/schema/aop"
    xsi:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/bea      ns  http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
 
   <!--    开启aspectj的注解开发-->
   <aop:aspectj-autoproxy/>
 </beans>

2.@AspectJ提供不同的通知类型

  • @Before 前置通知, 相当于BeforeAdvice
  • @AfterReturning 后置通知, 相当于AfterReturningAdvice
  • @Around 环绕通知, 相当于MethodInterceptor
  • @AfterThrowing 异常跑出通知, 相当于ThrowAdvice
  • @After 最终final通知, 不管是否异常, 该通知都会执行
  • @DeclareParents 引介通知, 相当于IntroductionInterceptor

3. 在通知中通过value属性定义切点

  • 通过excution函数, 可以定义切点的方法切入
  • 语法
excution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
  • eg.
- 匹配所有类public方法 excution(public * *(...))
- 匹配指定包下所有类方法 excution(* com.imooc.dao.*(...)) 不包含子包
- 匹配指定包下所有类方法 excution(* com.imooc.dao..*(...)) 包含子包
- 匹配指定类所有方法 excution(* com.imooc.service.UserService.*(..))
- 匹配实现特定接口所有类方法 excution(* com.imooc.service.dao.GenericDAO+.*(..))
- 匹配所有save开头的方法 excution(* save*(..))

4. @Before/@AfterReturning/@Around/AfterThrowing/After

  • 切面类
@Aspect
public class MyAspect {
  @Pointcut(value="execution(* com.rui.aspectj.ProductDao.save(..))")
  private void save() {}

  @Pointcut(value = "execution(* com.rui.aspectj.ProductDao.update(..))")
  private void update() {}

  @Pointcut(value = "execution(* com.rui.aspectj.ProductDao.delete(..))")
  private void delete() {}

  @Before(value="save()")
  public void before(JoinPoint joinPoint) {
      System.out.println("前置通知============" + joinPoint);
  }
  @AfterReturning(value="update()",
  returning = "result")
  public void afterReturning(Object result) {
      // result为返回值
      System.out.println("后置通知============" + result);
  }
  @Around(value="delete()")
  public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
      System.out.println("环绕前通知=====================");
      Object obj = joinPoint.proceed(); // 执行目标方法
      System.out.println("环绕后通知=====================");
      return obj;
  }
  @AfterThrowing(value="execution(* com.rui.aspectj.ProductDao.findOne(..))",
  throwing = "e")
  public void afterThrowing(Throwable e) {
      System.out.println("异常抛出通知===========" + e.getMessage());
  }
  @After(value = "execution(* com.rui.aspectj.ProductDao.findAll(..))")
  public void after() {
      System.out.println("最终通知!================");
  }
}
  • Dao
public class ProductDao {
  public void save() {
      System.out.println("保存商品...");
  }
  public String update() {
      System.out.println("更新商品...");
      return "hello world";
  }
  public void delete() {
      System.out.println("删除商品...");
  }
  public void findOne() {
      System.out.println("查找一个商品...");
  }
  public void findAll() {
      System.out.println("查找所有商品...");
  }
}
  • Spring 配置
<!--    开启aspectj的注解开发-->
  <aop:aspectj-autoproxy/>
<!--    目标类-->
  <bean id="productDao" class="com.rui.aspectj.ProductDao" />
  <!--定义切面-->
  <bean class="com.rui.aspectj.MyAspect" />
  • 测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AspectTest {
  @Resource(name = "productDao")
  private ProductDao productDao;
  @Test
  public void demo1() {
      productDao.save();
      productDao.delete();
      productDao.findAll();
      productDao.findOne();
      productDao.update();
  }
}
  • output
前置通知============execution(void com.rui.aspectj.ProductDao.save())
保存商品...
环绕前通知=====================
删除商品...
环绕后通知=====================
查找所有商品...
最终通知!================
查找一个商品...
异常抛出通知===========/ by zero

JDBC Template

使用

  • Mysql驱动
  • Spring组件(Core, beans, context, aop)
  • JDBCTemplate(jdbc, tx)
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://local........." />
    <property name="username" value="root" />
    <property name="password" value="root" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="datasource" ref="datasource" />
</bean>

具体方法

  • update方法
// 对数据进行增删改操作
int update(String sql, Object[] args)
int update(String sql, Object... args)
public void testUpdate() {
    String sql = "insert into student(name, sex) values(?,?)";
    jdbcTemplate.update(sql, new Object[]{"zhagsan", "man"});
    // 或
    jdbcTemplate.update(sql, "zhagsan", "man");
}
  • batchUpdate方法
// 对数据进行批量增删改操作
int [] batchUpdate(String [] sql)
int [] batchUpdate(String sql, List<Object[]> args)
public void testBatchUpdate() {
    String[] sqls = {
              "insert into student(name, sex) values ('关羽', '男')",
              "insert into student(name, sex) values ('张飞', '男')"
    };
    jdbcTemplate.batchUpdate(sqls);
}
public void testBatchUpdate() {
    String sql = insert into student(name, sex) values (?, ?)";
    List<Object []> list = new ArrayList<Object[]>();
    list.add(new Object[]{"关羽", "男"});
    list.add(new Object[]{"张飞", "男"});
    jdbcTemplate.batchUpdate(sql, list);
}
  • 查询简单数据项

    • 获取一个
    T queryForObject(String sql, Class<T> type)
    T queryForObject(String sql, Object[] args, Class<T> type)
    T queryForObject(String sql, Class<T> type, Object... args)
    
    public void testQuerySimple() {
        String sql = "select count(*) from student";
        int count = jdbcTemplate.queryForObject(sql, Integer.class);
    }
    
    • 获取多个
    List<T> queryForList(String sql, Class<T> type)
    List<T> queryForList(String sql, Object[] args, Class<T> type)
    List<T> queryForList(String sql, Class<T> type, Object... args)
    
    public void testQuerySimple() {
        String sql = "select name from student where sex=?";
        List<String> names = jdbcTemplate.queryForList(sql, String.class, "男");
    }
    
  • 查询复杂对象(封装为Map)

    • 获取一个
    Map queryForMap(String sql)
    Map queryForMap(String sql, Object[] args)
    Map queryForMap(String sql, Object... args)
    
    public void testMap() {
        String sql = "select * from student where id = ?";
        Map<String, Object> stu = jdbcTemplate.queryForMap(sql, 1003);
    }
    
    • 获取多个
    List<Map<String, Object>> queryForList(String sql)
    List<Map<String, Object>>queryForList(String sql, Object[] args)
    List<Map<String, Object>>queryForList(String sql, Object... args)
    
    public void testMap() {
        String sql = "select * from student";
        Map<String, Object> stu = jdbcTemplate.queryForMap(sql);
    }
    
  • 查询复杂对象(封装为实体对象)

    • RowMapper接口
      • 获取一个
      T queryForObject(String sql, RowMapper<T> mapper)
      T queryForObject(String sql, Object[] args, RowMapper<T> mapper)
      T queryForObject(String sql, RowMapper<T> mapper, Object... arg)
      
      public void testQueryEntity() {
          String sql = "select * from student where id = ?";
          Student stu = jdbcTemplate.queryForObject(sql, new RowMapper<Student>() {
              public Student mapRow(ResultSet resultSet, int i) throw SQLException {
                  Student stu = new Student();
                  stu.setId(resultSet.getInt("id"));
                  stu.setName(resultSet.getInt("name"));
                  stu.setSex(resultSet.getInt("Sex"));
                  stu.setBorn(resultSet.getInt("born"));
                  return stu;
              }
          }, 1004)
      }
      
      • 获取多个
      List<T> query(String sql, RowMapper<T> mapper)
      List<T> query(String sql, Object[] args, RowMapper<T> mapper)
      List<T> query(String sql, RowMapper<T> mapper, Object... arg)
      
      public void testQueryEntityList() {
          String sql = "select * from student";
          List<Student> stus = jdbcTemplate.query(sql, new RowMapper<Student>() {
              public Student mapRow(ResultSet resultSet, int i) throw SQLException {
                  Student stu = new Student();
                  stu.setId(resultSet.getInt("id"));
                  stu.setName(resultSet.getInt("name"));
                  stu.setSex(resultSet.getInt("Sex"));
                  stu.setBorn(resultSet.getInt("born"));
                  return stu;
              }
          })
      }
      
      在日常开发中, 匿名类的形式比较冗余, 一般进行封装, 将RowMapper单独实现
      private class StudentRowMapper implements RowMapper<Student> {
              public Student mapRow(ResultSet resultSet, int i) throw SQLException {
                  Student stu = new Student();
                  stu.setId(resultSet.getInt("id"));
                  stu.setName(resultSet.getInt("name"));
                  stu.setSex(resultSet.getInt("Sex"));
                  stu.setBorn(resultSet.getInt("born"));
                  return stu;
              }
      }
      
      使用如下:
      public void testQueryEntityList() {
          String sql = "select * from student";
          List<Student> stus = jdbcTemplate.query(sql, new StudentRowMapper());
      
      但是在实际开发中, 每个类都要实现一个RowMapper会让开发变得极其繁琐, 一般实现一个通用的工具类来做转换的功能:
  • JDBCTemplate优缺点

    • 优点
      • 简单
      • 灵活
    • 缺点
      • sql和java代码牵扯的比较杂乱
      • 功能不够丰富, 比如分页的功能没有等

Spring事务管理

什么是事务

  • 事务就是正确执行一系列的操作(或者动作), 使数据库从一种状态转换成另一种状态且保证操作全部成功, 或者全部失败

事务的特点

  • 原子性(Atomicity)
    • 即不可分割性, 事务要么全部被执行, 要么就全部不执行
  • 一致性(Consistency)
    • 事务的执行使得数据库从一种正确的状态转换成另一种正确的状态
  • 隔离性(Isolation)
    • 在事务正确提交之前, 它可能的结果不应该显示给任何其他事务
  • 持久性(Durability)
    • 事务正确提交以后, 其结果将永久保存在数据库中

Java事务的产生

  • 程序操作数据库的需要. 在Java编写的程序或者系统中, 实现ACID的操作
  • Java事务实现范围
    • 通过jdbc相应方法间接来实现对数据库的增删改查, 把事务转移到Java程序代码中进行控制
    • 确保事务, 要么全部执行成功, 要么撤销不执行
  • Java事务机制和原理就是确保数据库操作的ACID特性

Java事务实现模式

  • Java事务的实现
    • 通过Java代码来实现对数据库的事务性操作
  • Java事务类型
    • JDBC事务: 用Connection对象控制, 包括手动模式和自动模式;
    • JTA(Java Transaction API)事务: 与实现无关, 与协议无关的API;
    • 容器事务: 应用服务器提供的, 且大多是基于JTA完成(通常基于JNDI的, 相当复杂的API实现)

三种事务的差异

  • JDBC事务: 控制的局限性在一个数据库连接内, 但是其使用简单
  • JTA事务: 功能强大, 可跨越多个数据库或者多个DAO, 使用比较复杂
  • 容器事务: 主要指的是J2EE应用服务器提供的事务管理, 局限于EJB

事务接口

  • 事务接口架构


    事务接口架构

事务读取类型

- 脏读: 事务没提交, 提前读取
- 不可重复读: 多次读取到的数据不一致
- 幻读: 事务不是独立执行时发生的一种非预期现象

事务隔离级别

隔离级别

事务传播行为

事务传播行为

事务超时

  • 事务超时就是事务的一个定时器, 在特定时间内事务如果没有执行完毕, 那么久会自动回滚, 而不是一直等待其结束

事务回滚

  • 默认情况下, 事务只有遇到运行期异常才会回滚, 而在遇到检查型异常时不会回滚
  • 自定义回滚策略
    • 生命失误在遇到特定的检查型异常时像遇到运行期异常那样回滚
    • 声明事务遇到特别的异常不回滚, 即使这些异常时运行期异常

Spring事务状态

  • 事务接口
    • 通过事务管理器获取TransactionStatus实例;
    • 控制事务在回滚或提交的时候需要应用对应的事务状态;
    • Spring事务接口
    // Spring事务状态接口
    // 通过调用PlatformTransactionManager的getTransaction()
    // 获取事务状态实例
    public interface TransactionStatus {
        boolean isNewTransaction(); // 是否是新的事务
        boolean hasSavepoint(); // 是否有恢复点
        boolean setRollbackOnly(); // 设置为只回滚
        boolean isRollbackOnly(); // 是否为只回滚
        boolean isCompleted(); // 是否已完成
    }
    

编程式事务实现方式

事务管理器(PlatformTransactionManager)方式

  • 类似应用JTA UserTransaction API方式, 但异常处理更简洁;
  • 核心类为: Spring事务管理的三个接口类以及JdbcTemplate
  • 步骤:
    • 获取事务管理器
    • 创建事务属性对象
    • 获取事务状态对象
    • 创建JDBC模板对象
    • 业务数据操作处理

模板事务(TransactionTemplate)的方式

  • 此为Spring官方团队推荐的编程式事务管理方式
  • 主要工具为JdbcTemplate

声明式事务管理

声明式事务实现原理

  • 基于AOP模式机制, 对方法前后进行拦截

声明式事务管理的配置类型:

  • 5种类型: 独立代理; 共享代理, 拦截器, tx拦截器, 全注释(注解方式) (前三类2.0版本以后不推荐使用)

声明式事务管理配置实现方式:

  <!--    引入配置文件-->
  <bean id="propertyConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
      <property name="order" value="2"/>
      <property name="ignoreUnresolvablePlaceholders" value="true"/>
      <property name="locations">
          <list>
              <!--配置属性文件-->
              <value>classpath:datasource.properties</value>
          </list>
      </property>
      <property name="fileEncoding" value="utf-8"/>
  </bean>

  <!--配置连接池-->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
      <property name="driverClassName" value="${db.driverClassName}"/>
      <property name="url" value="${db.url}"/>
      <property name="username" value="${db.username}"/>
      <property name="password" value="${db.password}"/>
      <!-- 连接池启动时的初始值 -->
      <property name="initialSize" value="${db.initialSize}"/>
      <!-- 连接池的最大值 -->
      <property name="maxActive" value="${db.maxActive}"/>
      <!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
      <property name="maxIdle" value="${db.maxIdle}"/>
      <!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
      <property name="minIdle" value="${db.minIdle}"/>
      <!-- 最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制 -->
      <property name="maxWait" value="${db.maxWait}"/>
      <!--#给出一条简单的sql语句进行验证 -->
      <!--<property name="validationQuery" value="select getdate()" />-->
      <property name="defaultAutoCommit" value="${db.defaultAutoCommit}"/>
      <!-- 回收被遗弃的(一般是忘了释放的)数据库连接到连接池中 -->
      <!--<property name="removeAbandoned" value="true" />-->
      <!-- 数据库连接过多长时间不用将被视为被遗弃而收回连接池中 -->
      <!--<property name="removeAbandonedTimeout" value="120" />-->
      <!-- #连接的超时时间,默认为半小时。 -->
      <property name="minEvictableIdleTimeMillis" value="${db.minEvictableIdleTimeMillis}"/>

      <!--# 失效检查线程运行时间间隔,要小于MySQL默认-->
      <property name="timeBetweenEvictionRunsMillis" value="40000"/>
      <!--# 检查连接是否有效-->
      <property name="testWhileIdle" value="true"/>
      <!--# 检查连接有效性的SQL语句-->
      <property name="validationQuery" value="SELECT 1 FROM dual"/>
  </bean>
  <!-- 使用@Transactional进行声明式事务管理需要声明下面这行 -->
  <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
  <!-- 事务管理 -->
  <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource"/>
      <property name="rollbackOnCommitFailure" value="true"/>
  </bean>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容