IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 超全超细的Spring学习笔记 -> 正文阅读

[Java知识库]超全超细的Spring学习笔记

Spring简介

Spring 是一个开源框架,是一个分层的 JavaEE 一站式框架。

所谓一站式框架是指 Spring 有 JavaEE 开发的每一层解决方案。

  • WEB层:SpringMVC
  • Service层:Spring的Bean管理,声明式事务
  • DAO层:Spring的JDBC模板,ORM模板

优点:

  • IOC:方便解耦合
  • AOP:对程序进行扩展
  • 轻量级框架
  • 方便与其他框架整合

Spring使用

Spring开发包解压后的目录介绍:

  • docs: Spring 开发规范和API
  • libs: Spring jar 包和源代码
  • schema: Spring 配置文件的约束

在这里插入图片描述

DataAccess 用于数据访问,WEB 用于页面显示,核心容器也就是IOC部分。

控制反转(IOC)

控制反转(Inversion of Control)是指将对象的创建权反转(交给)Spring。

使用IOC就需要导入IOC相关的包,也就是上图中核心容器中的几个包:beans,context,core,expression四个包。

实现原理

传统方式创建对象

UserDao userDao = new UserDao();	

若进一步面向接口编程的话,可以多态:

UserDao userDao = new UserDaoImpl();

这种方式的缺点是接口和实现类高耦合,切换底层实现类时,需要修改源代码。程序设计应该满足OCP元祖,在尽量不修改程序源代码的基础上对程序进行扩展。

此时,学过设计模式的同学可能会想到用工厂模式对接口及其实现类进行解耦,我们使用工厂模式:

class BeanFactory{
    public static UserDAO getUserDAO(){
        return new UserDAOImpl();
    }
}

此种方式虽然在接口和实现类之间没有耦合,但是接口和工厂之间存在耦合。

使用工厂+反射+配置文件的方式,实现解耦,这也是 Spring 框架 IOC 的底层实现。

//xml配置文件
//<bean id="userDAO" class="xxx.UserDAOImpl"></bean>
class BeanFactory{
    public static Object getBean(String id){
        //解析XML
        //反射
        Class clazz=Class.forName();
        return clazz.newInstance();
    }
}

IOC与传统方式的比较

获取对象方式:传统通过 new 关键字主动创建一个对象。

IOC 方式中,将对象的生命周期交给 Spring 管理,直接从 Spring 获取对象。也就是控制反转————将控制权从自己手中交到了 Spring 手中

IOC XML 开发

在 docs 文件中包含了 xsd-configuration.hmtl 文件。其中定义了 beans schema。

<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
    <bean id="userService" class="x.y.UserServiceImpl">
    </bean>
</beans>

调用类:

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService)applicationContext.getBean("userService");
userService.save();

Bean标签配置

告诉spring创建对象

声明bean,就是告诉spring要创建某个类的对象

id:对象的自定义名称,唯一值,不能出现特殊字符。spring通过这个名称找到对象

class:类的全限定名称(不能是接口,因为spring是反射机制创建类,必须使用类)

spring就完成SomeService someService = new SomeServiceImpl();

spring是把创建好的对象放入到map中,spring框架有一个map存放对象的

? springMap.put(id的值,对象);

? 例如:springMap.put("someService",new SomeServiceImpl());

一个bean标签声明一个对象

生命周期

  • init-method: bean被初始化的时候执行的方法
  • destroy-method: bean被销毁的时候执行的方法

作用范围

scope:bean的作用范围,有如下几种,常用的是前两种

  1. singleton: 默认使用单例模式创建
  2. prototype: 多例
  3. request: 在web项目中,spring 创建类后,将其存入到 request 范围中
  4. session: 在web项目中,spring 创建类后,将其存入到 session 范围中
  5. globalsession: 在web项目中,必须用在 porlet 环境

Spring工厂类

  • BeanFactory: 老版本的工厂类,在调用getBean()方法时,才会生成类的实例。

  • ApplicationContext: 在加载配置文件的时候,就会将 Spring 管理的类都实例化。有两个实现类:

    1. ClassPathXmlApplicationContext: 加载类路径下的配置文件
    2. FileSystemXmlApplicationContext: 加载磁盘下的配置文件

IOC和DI

DI 指依赖注入,其前提是必须有 IOC 的环境,Spring 管理这个类的时候将类的依赖的属性注入进来。

例如,在UserServiceImpl.java中:

public class UserServiceImpl implements UserService{
  private String name;
  public void setName(String name){
    this.name=name;
  }
  public void save(){
    System.out.println("save "+name);
  }
}

在配置文件中:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
     http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans.xsd"
    <bean id="userService" class="spring.demo1.UserServiceImpl">
    <!--配置依赖的属性-->
    <property name="name" value="tony"/>
    </bean>
</beans>

测试代码:

@Test
public void demo2(){
  //创建Spring工厂
  ApplicationContext applicationContext=
  new ClassPathXmlApplicationContext("applicationContext.xml");

  UserService userService=
  (UserService)applicationContext.getBean("userService");

  userService.save();
}

运行结果:

save tony

可以看到,在配置文件中配置的属性,在 Spring 管理该类的时候将其依赖的属性成功进行了设置。如果不使用依赖注入,则无法使用接口,只能使用实现类来进行设置,因为接口中没有该属性。

普通类型(简单类型)的set注入

在Spring中规定,Java的基本数据类型和String类型都是简单类型

set注入(设置注入):spring调用类的set方法,可以在set方法中完成属性赋值

简单类型的set注入:

<bean id="xx" class="xx">
    <property name="属性名字" value="此属性的值"/>
    一个property只能给一个属性赋值
</bean>

我们以Student类为例

在Student.java中:

public class Student {
    private String name;
    private int age;

    private School school;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setSchool(School school) {
        this.school = school;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

在applicationContext.xml文件中:

<bean id="myStudent" class="com.jiawen.package1.Student">
    <property name="name" value="lisi"/>
    <property name="age" value="20"/>
</bean>

测试:

@Test
public void test01(){
    String config = "package1/applicationContext.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);

    Student myStudent = (Student) applicationContext.getBean("myStudent");
    System.out.println(myStudent);
}

运行结果:

Student{name='lisi', age=20}

引用类型的set注入

引用类型的set注入:spring调用类的set方法

<bean id="xxx" class="xxxx">
    <property name="属性名称" ref="bean的id(对象的名称)"/>
</bean>

以学校和学生为例

在School.java中:

public class School {
    private String name;
    private String address;

    public void setName(String name) {
        this.name = name;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

在Student.java中:

public class Student {
    private String name;
    private int age;

    private School school;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setSchool(School school) {
        this.school = school;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

在applicationContext.xml中:

<bean id="myStudent" class="com.jiawen.package2.Student">
        <property name="name" value="lisi"/>
        <property name="age" value="22"/>
        <property name="school" ref="mySchool"/>
    </bean>

<!--    声明school对象-->
    <bean id="mySchool" class="com.jiawen.package2.School">
        <property name="name" value="bj"/>
        <property name="address" value="hdq"/>
     </bean>

运行结果:

Student{name='lisi', age=22, school=School{name='bj', address='hdq'}}

构造注入

构造注入:spring调用类的有参构造方法,在创建对象的同时,在构造方法中属性赋值

构造注入使用<constructor-arg>标签

<constructor-arg>标签:一个<constructor-arg>表示构造方法的一个参数

<constructor-arg>标签属性:

? name:表示构造方法的形参名

? index:构造方法参数的位置,参数从左往右位置是0、1、2…的顺序

? value:构造方法的形参类型是简单类型的,使用value

? ref:构造那个方法的形参类型是引用类型的,使用ref

以学校和学生为例

在School.java中:

public class School {
    private String name;
    private String address;

    public void setName(String name) {
        this.name = name;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

在Student.java中:

public class Student {
    private String name;
    private int age;

    private School school;

    public Student() {
    }

    public Student(String name, int age, School school) {
        this.name = name;
        this.age = age;
        this.school = school;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setSchool(School school) {
        this.school = school;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

在applicationContext.xml中:

<bean id="myStudent" class="com.jiawen.package3.Student">
    <constructor-arg name="name" value="zhangsan"/>
    <constructor-arg name="age" value="20"/>
    <constructor-arg name="school" ref="mySchool"/>
</bean>

<bean id="mySchool" class="com.jiawen.package3.School">
    <property name="name" value="bj"/>
    <property name="address" value="bjhd"/>
</bean>

测试:

@Test
public void test03(){
    String config = "package3/applicationContext.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
    Student myStudent = (Student) applicationContext.getBean("myStudent");
    System.out.println(myStudent);
}

运行结果:

Student{name='zhangsan', age=20, school=School{name='bj', address='bjhd'}}

引用类型的自动注入

引用类型的自动注入:spring框架根据某些规则可以给引用类型赋值
使用规则:byName,byType

byName(按名称注入)

byName(按名称注入):Java类中引用类型的属性名和spring容器中(配置文件)的id名称一样,且数据类型是一样的
这样的容器中的bean,spring能够赋值给引用类型
语法:

<bean id="xx" class="xx" autowire="byName">
	简单类型属性赋值
</bean>

以学校和学生为例

在School.java中:

public class School {
    private String name;
    private String address;

    public void setName(String name) {
        this.name = name;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

在Student.java中:

public class Student {
    private String name;
    private int age;

    private School school;

    public Student() {
    }

    public Student(String name, int age, School school) {
        this.name = name;
        this.age = age;
        this.school = school;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setSchool(School school) {
        this.school = school;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

在applicationContext.xml中:

<bean id="myStudent" class="com.jiawen.package4.Student" autowire="byName">
    <property name="name" value="lisi"/>
    <property name="age" value="22"/>
</bean>

<bean id="school" class="com.jiawen.package4.School" autowire="byName">
    <property name="name" value="bj"/>
    <property name="address" value="bjhd"/>
</bean>

测试:

@Test
public void test04(){
    String config = "package4/applicationContext.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
    Student myStudent = (Student) applicationContext.getBean("myStudent");
    System.out.println(myStudent);
}

运行结果:

Student{name='lisi', age=22, school=School{name='bj', address='bjhd'}}

byType(按类型注入)

byType(按类型注入):Java类中引用类型的数据类型和spring容器中(配置文件)<bean>的class属性是同源关系的,这样的
bean能够赋值给引用类型

同源就是一类的意思:

  1. Java类中引用类型的数据类型和<bean>的class的值是一样的
  2. Java类中引用类型的数据类型和<bean>的class的值是父子类的关系
  3. Java类中引用类型的数据类型和<bean>的class的值是接口和实现类关系的

语法:

<bean id="xx" class="xx" autowire="byType">
	简单类型属性赋值
</bean>

以学校和学生为例

在School.java中:

public class School {
    private String name;
    private String address;

    public void setName(String name) {
        this.name = name;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

在Student.java中:

public class Student {
    private String name;
    private int age;

    private School school;

    public Student() {
    }

    public Student(String name, int age, School school) {
        this.name = name;
        this.age = age;
        this.school = school;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setSchool(School school) {
        this.school = school;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

在applicationContext.xml中:

<bean id="myStudent" class="com.jiawen.package5.Student" autowire="byType">
        <property name="name" value="lisi"/>
        <property name="age" value="22"/>
</bean>

<bean id="mySchool" class="com.jiawen.package5.School">
        <property name="name" value="bj"/>
        <property name="address" value="bjhd"/>
</bean>

为了更好的理解“同源”这一概念,我们引入School的子类PrimarySchool,并让Student的school成员指向PrimarySchool的对象

在PrimarySchool中:

public class PrimarySchool extends School{

}

在applicationContext.xml中:

<bean id="myStudent" class="com.jiawen.package5.Student" autowire="byType">
    <property name="name" value="lisi"/>
    <property name="age" value="22"/>
</bean>
<bean id="primarySchool" class="com.jiawen.package5.PrimarySchool">
	<property name="name" value="bjxx"/>
	<property name="address" value="bjdx"/>
</bean>

测试:

@Test
public void test05(){
    String config = "package5/applicationContext.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
    Student myStudent = (Student) applicationContext.getBean("myStudent");
    System.out.println(myStudent);
}

运行结果:

Student{name='lisi', age=22, school=School{name='bjxx', address='bjdx'}}

p名称空间的属性注入

首先需要引入p名称空间:

<beans xmlns="http://www.springframework.org/schema/beans"
    //引入p名称空间
    xmlns:p="http://www.springframework.org/schema/p"
    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"
</beans>

如果是普通属性:

<bean id="car" class="demo.Car" p:name="bmv" p:price="123"></bean>

如果是引用类型:

<bean id="employee" class="demo.Employee"
	p:name="xiaoming" p:car-ref:"car">
</bean>

SpEL(Spring Expression Language)属性注入(Spring 3.x以上版本)

<bean id="car" class="demo.Car">
    <property name="name" value="#{'xiaoming'}">
    <property name="car" ref="#{car}">
</bean>

集合类型属性注入

<bean id="car" class="demo.Car">
    <property name="namelist">
        <list>
            <value>qirui</value>
            <value>baoma</value>
            <value>benchi</value>
        </list>
    </property>
</bean>

多模块开发设置

  1. 在加载配置文件的时候,加载多个配置文件
  2. 在一个配置文件中引入多个配置文件,通过实现

以学校和学生为例

在School.java中:

public class School {
    private String name;
    private String address;

    public void setName(String name) {
        this.name = name;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

在Student.java中:

public class Student {
    private String name;
    private int age;

    private School school;

    public Student() {
    }

    public Student(String name, int age, School school) {
        this.name = name;
        this.age = age;
        this.school = school;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setSchool(School school) {
        this.school = school;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

在spring-school.xml中:

<bean id="mySchool" class="com.jiawen.package6.School">
    <property name="name" value="bj"/>
    <property name="address" value="bjhd"/>
</bean>

在spring-student.xml中:

<bean id="myStudent" class="com.jiawen.package6.Student" autowire="byType">
    <property name="name" value="lisi"/>
    <property name="age" value="22"/>
</bean>

在spring-total.xml中:

<import resource="classpath:package6/spring-school.xml"/>
<import resource="classpath:package6/spring-student.xml"/>

测试:

@Test
public void test06(){
    String config = "package6/spring-total.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
    Student myStudent = (Student) applicationContext.getBean("myStudent");
    System.out.println(myStudent);
}

运行结果:

Student{name='lisi', age=22, school=School{name='bj', address='bjhd'}}

spring-total表示主配置文件:包含其它的配置文件的

主配置文件一般是不定义对象的

语法:<import resource="其它配置文件的路径"/>

关键字:“classpath:”:表示类路径(class文件所在的目录),在spring的配置文件中要指定其它文件的位置需要使用classpath,告诉spring到哪去加载读取文件

在包含关系的配置文件中,可以使用通配符(*:表示任意字符)

IOC注解开发

  1. 引入jar包:除了要引入上述的四个包之外,还需要引入aop包。

  2. 创建 applicationContext.xml ,使用注解开发引入 context 约束(xsd-configuration.html)

    <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"
            <!-- bean definitions here -->
    </beans>
    
  3. 组件扫描:使用IOC注解开发,需要配置组件扫描,也就是哪些包下的类使用IOC的注解。

    声明组件扫描器(component-scan),组件就是Java对象

    base-package:指定注解在你的项目中的包名

    component-scan工作方式:spring会扫描遍历base-package指定的包,把包中和子包中的所有类,找到类中的注解,按照注解的功能创建对象,或给属性赋值

    <context:component-scan base-package="demo1">
    

    指定多个包的三种方式

    ? 第一种方式:使用多次组件扫描器,指定不同的包

    ? 第二种方式:使用分隔符(;或者,)分割多个包名

    ? 第三种方式:指定父包

  4. 在类上添加注解

  5. 使用注解设置属性的值

    //相当于配置了一个<bean> 其id为UserDao,对应的类为该类
    @Component("UserDao")
    public class UserDAOImpl implements UserDAO {
    
    	@Override
    	public void save() {
    		// TODO Auto-generated method stub
    		System.out.println("save");
    	} 
    }
    

注解详解

1.Component

组件注解,用于修饰一个类,将这个类交给 Spring 管理。

有三个衍生的注解,功能类似,也用来修饰类。

  • @Controller:修饰 web 层类
  • @Service:修饰 service 层类
  • @Repository:修饰 dao 层类

2.属性注入

  • 普通属性使用 @Value 来设置属性的值
  • 对象属性使用 @Autowired ,这个注解是按照类型来进行属性注入的。如果希望按照 bean 的名称或id进行属性注入,需要用 @Autowired 和 @Qualifier 一起使用
  • 实际开发中,使用 @Resource(name=" ") 来进行按照对象的名称完成属性注入

3.其它注解

  • @PostConstruct 相当于 init-method,用于初始化函数的注解
  • @PreDestroy 相当于 destroy-method,用于销毁函数的注解
  • @Scope 作用范围的注解,常用的是默认单例,还有多例 @Scope(“prototype”)

IOC的XML和注解开发比较

  • 适用场景:XML 适用于任何场景;注解只适合自己写的类,不是自己提供的类无法添加注解。
  • 可以使用 XML 管理 bean,使用注解来进行属性注入

@Component:创建对象

@Component:是创建对象的,等同于<bean>的标签

属性:

? value 就是对象的名称,也就是bean的id值

? value的值是唯一的,创建的对象在整个spring容器中就一个

? 位置:在类的上面

@Component(value = "myStudent")等同于<bean id="myStudent" class="com.jiawen.package1.Student"/>

spring中和@Component功能一致,创建对象的注解还有:

  1. @Repository(用在持久层类的上面):仿造dao的实现类上面,表示创建dao对象,dao对象是能访问数据库的
  2. @Service(用在业务层类的上面):放在servvice的实现类上面,创建service对象,service对象是做业务处理,可以有事务等功能
  3. @Controller(用在控制器的上面):放在控制器(处理器)的上面,创建控制器对象,控制器对象能够接收用户提交的参数,显示请求的处理结果

以上三个注解的使用语法和@Component一样,都能创建对象,但是这三个注解还有额外的功能

以Student为例

在Student.java中

@Component(value = "myStudent")
public class Student {
    private String name;
    private String age;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(String age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                '}';
    }
}

测试:

@org.junit.Test
public void test01(){
    String config = "applicationContext.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
    Student student = (Student) applicationContext.getBean("myStudent");
    System.out.println(student);
}

运行结果:

Student{name='null', age='null'}

@Value:简单类型的属性赋值

@value:简单类型的属性赋值

属性:value 是String类型的,表示简单类型的属性值

位置:

  1. 在属性定义的上面,无需set方法,推荐使用
  2. 在set方法的上面

以Student为例

在Student.java中

@Component(value = "myStudent")
public class Student {
    @Value(value = "lisi")
    private String name;
    @Value(value = "20")
    private String age;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(String age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                '}';
    }
}

测试:

@org.junit.Test
public void test02(){
    String config = "applicationContext.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
    Student student = (Student) applicationContext.getBean("myStudent");
    System.out.println(student);
}

运行结果:

Student{name='lisi', age='20'}

@Autowire:引用类型的byType自动注入

@Autowired:spring框架提供的注解,实现引用类型的赋值。

@Autowired:默认使用的是byType自动注入

位置:

  1. 在属性定义的上面,无需set方法,推荐使用
  2. 在set方法的上面

属性:

? required,是一个Boolean类型的,默认true

? required=true:表示引用类型赋值失败,程序报错,并终止执行

? required=false:引用类型如果赋值失败,程序正常执行,引用类型是null

以学校和学生为例

在School.java中:

@Component("mySchool")
public class School {
    @Value("bjdx")
    private String name;
    @Value("bjhd")
    private String address;

    public void setName(String name) {
        this.name = name;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

在Student.java中:

@Component(value = "myStudent")
public class Student {
    @Value(value = "lisi")
    private String name;
    @Value(value = "20")
    private String age;

    @Autowired
    private School school;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(String age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                ", school=" + school +
                '}';
    }
}

测试:

@org.junit.Test
public void test03(){
    String config = "applicationContext.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
    Student student = (Student) applicationContext.getBean("myStudent");
    System.out.println(student);
}

运行结果:

Student{name='lisi', age='20', school=School{name='bjdx', address='bjhd'}}

@Autowire+@Qualifier:引用类型的ByNama自动注入

如果要使用byName方式,需要做的是:

  1. 在属性上面加入@Autowired
  2. 在属性上面加入@Qualifier(value = “bean的id”):表示使用指定名称的bean完成赋值

以学校和学生为例

在School.java中:

@Component("mySchool")
public class School {
    @Value("bjdx")
    private String name;
    @Value("bjhd")
    private String address;

    public void setName(String name) {
        this.name = name;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

在Student.java中:

@Component(value = "myStudent")
public class Student {
    @Value(value = "lisi")
    private String name;
    @Value(value = "20")
    private String age;

    @Autowired
    @Qualifier(value = "mySchool")
    private School school;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(String age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                ", school=" + school +
                '}';
    }
}

测试:

@org.junit.Test
public void test04(){
    String config = "applicationContext.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
    Student student = (Student) applicationContext.getBean("myStudent");
    System.out.println(student);
}

@Resource:引用类型赋值(来自JDK)

@Resource:来自jdk中的注解,spring框架提供了对这个注解的功能支持,可以使用它给引用类型赋值

它使用的也是自动注入的原理,支持byName、byType,默认byName

位置:

  1. 在属性定义的上面,无需set方法,推荐使用
  2. 在set方法的上面

默认byName,先使用byName自动注入,如果byName赋值失败,再使用byType

以学校和学生为例

在School.java中:

@Component("mySchool")
public class School {
    @Value("bjdx")
    private String name;
    @Value("bjhd")
    private String address;

    public void setName(String name) {
        this.name = name;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

在Student.java中:

@Component(value = "myStudent")
public class Student {
    @Value(value = "lisi")
    private String name;
    @Value(value = "20")
    private String age;

    @Resource(name = "mySchool")
    private School school;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(String age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                ", school=" + school +
                '}';
    }
}

AOP开发

AOP 是 Aspect Oriented Programming 的缩写,意为面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,是OOP的延续。

AOP 能够对程序进行增强,在不修改源码的情况下,可以进行权限校验,日志记录,性能监控,事务控制等。

也就是说功能分为两大类,一类是核心业务功能,一类是辅助增强功能。两类功能彼此独立进行开发。比如登录功能是核心业务功能,日志功能是辅助增强功能,如果有需要,将日志和登录编制在一起。辅助功能就称为切面,这种能选择性的、低耦合的把切面和核心业务功能结合的编程思想称为切面编程。

底层实现

JDK代理

JDK 动态代理只能对实现了接口的类产生代理。

使用JDK动态代理:

在UserDao.java中:

public interface UserDao {
    public void insert();
    public void delete();
    public void update();
    public void query();
}

创建UserDao的实现类UserDaoImpl

在UserDaoImpl中:

public class UserDaoImpl implements UserDao{
    @Override
    public void insert() {
        System.out.println("insert");
    }

    @Override
    public void delete() {
        System.out.println("delete");
    }

    @Override
    public void update() {
        System.out.println("update");
    }

    @Override
    public void query() {
        System.out.println("query");
    }
}

JDK代理

在JDKProxy中:

public class JDKProxy implements InvocationHandler {
    private UserDao userDao;

    public JDKProxy(UserDao userDao) {
        this.userDao = userDao;
    }

    public UserDao createProxy(){
        UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), this);
        return userDaoProxy;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if("update".equals(method.getName())){
            System.out.println("权限校验");
            return method.invoke(userDao, args);
        }
        return method.invoke(userDao, args);
    }
}

测试程序:

public static void main(String[] args) {
    UserDao userDao = new UserDaoImpl();
    UserDao proxy = new JDKProxy(userDao).createProxy();
    proxy.insert();
    proxy.delete();
    proxy.update();
    proxy.query();
}

运行结果:

insert
delete
权限校验
update
query

通过动态代理增强了 update 函数。

Cglib代理

Cglib 动态代理可以对没有实现接口的类产生代理对象,生成的是子类对象。

CglibCglib 是第三方开源代码生成类库,可以动态添加类的属性和方法。

与上边JDK代理不同,Cglib的使用方式如下:

public class CglibProxy implements MethodInterceptor{
//传入增强的对象
    private UserDao customerDao;
    public CglibProxy(UserDao userDao){
        this.userDao=userDao;
    }
    
    public UserDao createProxy(){
        Enhancer enhancer=new Enhancer();
        enhancer.setSuperclass(userDao.getClass());
        enhancer.setCallback(this);
        UserDao proxy=(UserDao)enhancer.create();
        return proxy;
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args
                            , MethodProxy methodProxy) throws Throwable {
        if("save".equals(method.getName())){
            System.out.println("enhance function");
            return methodProxy.invokeSuper(proxy, args);
        }
        return methodProxy.invokeSuper(proxy, args);
    }
}

如果实现了接口的类,底层采用JDK代理。如果不是实现了接口的类,底层采用 Cglib代理。

Spring 的 AOP 开发(AspectJ 的 XML 方式)

AspectJ 是一个 AOP 的框架,Spring 引入 AspectJ,基于 AspectJ 进行 AOP 的开发。

相关术语

  • Joinpoint: 连接点,可以被拦截到的点。也就是可以被增强的方法都是连接点。
  • Pointcut: 切入点,真正被拦截到的点,也就是真正被增强的方法
  • Advice: 通知,方法层面的增强。对某个方法进行增强的方法,比如对 save 方法进行权限校验,权限校验的方法称为通知。
  • Introduction: 引介,类层面的增强。
  • Target: 目标,被增强的对象(类)。
  • Weaving: 织入,将 advice 应用到 target 的过程。
  • Proxy: 代理对象,被增强的对象。
  • Aspect: 切面,多个通知和多个切入点的组合。

使用方法

  1. 引入相关包

  2. 引入配置文件

    <?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"> <!-- bean definitions here -->
    </beans>
    
  3. 编写目标类并配置:

    public class ProductDaoImpl implements ProductDao {
        @Override
        public void save() {
            System.out.println("save");
        }
       
        @Override
        public void update() {
            System.out.println("update");
        }
      
        @Override
        public void find() {
            System.out.println("find");
        }
      
        @Override
        public void delete() {
            System.out.println("delete");
        }
    }
    
    <bean id="productDao" class="demo1.ProductDaoImpl"></bean>
    
  4. 编写切面类,假设用于权限验证并配置

    public class MyAspectXML {
        public void checkPri(){
            System.out.println("check auth");
        }
    }
    
    <bean id="myAspect" class="demo1.MyAspectXML"></bean>
    
  5. 通过AOP配置完成对目标类的增强

    <aop:config>
    	<aop:pointcut expression="execution(* demo1.ProductDaoImpl.save(..))" id="pointcut1"/>
    	<aop:aspect ref="myAspect">
    		<aop:before method="chechPri" pointcut-ref="pointcut1"/>
    	</aop:aspect> 
    </aop:config>
    

通知类型

前置通知

在目标方法执行前操作,可以获得切入点信息

<aop:before method="chechPri" pointcut-ref="pointcut1"/>		
public void checkPri(JoinPoint joinPoint){
	System.out.println("check auth "+joinPoint);
}

特点:

  1. 在目标方法之前执行
  2. 不会改变目标方法的执行结果
  3. 不会影响目标方法的执行

指定通知方法中的参数:JoinPoint

JoinPoint:业务方法,要加入切面功能的业务方法

作用是:可以在通知方法中获取方法执行时的信息,例如方法名称,方法的实参。如果你的切面功能中需要用到方法的信息,就加入JoinPoint。这个JoinPoint参数的值是由框架赋予的,必须是第一个位置的参数

方法的定义要求:

  1. 公共方法 public
  2. 方法没有返回值
  3. 方法名称自定义
  4. 方法可以有参数,也可以没有参数。如果有参数,参数不是自定义的,有几个参数类型可以使用

后置通知

在目标方法执行后操作,可以获得方法返回值

<aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result"/>
public void writeLog(Object result){
    System.out.println("writeLog "+result);
}

特点:

  1. 在目标方法之后执行
  2. 能够获取到目标方法的返回值,可以根据这个返回值做不同的处理结果
  3. 可以修改这个返回值

方法的定义要求:

  1. 公共方法 public
  2. 方法没有返回值
  3. 方法名称自定义
  4. 方法有参数,推荐使用Object,参数名称自定义

环绕通知

在目标方法执行前和后操作,可以阻止目标方法执行

<aop:around method="around" pointcut-ref="pointcut3"/>
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
    System.out.println("before");
    Object result=joinPoint.proceed();
    System.out.println("after");
    return result;
}
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
    String name = "";
    //获取第一个参数值
    Object[] args = pjp.getArgs();
    if(args!=null && args.length>1){
        Object arg = args[0];
        name = (String) arg;
    }
    //实现环绕通知
    Object result = null;
    System.out.println("环绕通知,在目标方法之前,输出时间:"+new Date());
    //1.目标方法调用
    if("zhangsan".equals(name)){
        //符合条件,调用目标方法
        result = pjp.proceed();  //method.invoke();  Object result = doFirst();
    }
    System.out.println("环绕通知,在目标方法之后,提交事务");
    //2.在目标方法的前或者后加入功能
    if(result != null){
        result = "hello";
    }
    //返回目标方法的执行结果
    return result;
}

特点:

  1. 它是功能最强的通知
  2. 在目标方法的前后都能增加功能
  3. 控制目标方法时候被调用执行
  4. 修改原来的目标方法的执行结果,影响最后的调用结果

环绕通知方法的定义模式:

  1. public
  2. 必须有一个返回值,推荐使用Object
  3. 方法名称自定义
  4. 方法由参数,参数是固定的ProceedingJoinPoint

环绕通知等同于JDK动态代理,InvocationHandler接口

参数:ProceedingJoinPoint等同于jdk动态代理中的Method

作用:执行目标方法的

返回值:是目标方法的执行结果,这个结果可以被修改

环绕通知:经常做事务,在目标方法之前开启事务,执行目标方法,在目标方法之后提交事务

异常抛出通知

程序出现异常时操作

<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="ex"/>
public void afterThrowing(Throwable ex){
	System.out.println("exception "+ex.getMessage());
}

特点:

  1. 在目标方法抛出异常时执行
  2. 可以做异常的监控程序,监控目标方法执行时是不是有异常,如果有异常,可以发送邮件、短信进行通知

异常通知方法的定义格式:

  1. public
  2. 没有返回值
  3. 方法名称自定义
  4. 方法有一个Exception参数,如果还有,就是JoinPoint

执行原理:

 	try{
        SomeServiceImpl.doSecond(..);
    } catch(Exception e){
        myAfterThrowing(e);
    }

最终通知

相当于finally块,无论代码是否有异常,都会执行

<aop:after method="finallyFunc" pointcut-ref="pointcut4"/>
public void finallyFunc(){
	System.out.println("finally");
}

最终通知方法的定义格式:

  1. public
  2. 没有返回值
  3. 方法名称自定义
  4. 方法没有参数,如果有参数,就是JoinPoint

特点:

  1. 总是会执行
  2. 在目标方法之后执行

Spring 切入点表达式

基于 execution 函数完成

语法:[访问修饰符] 方法返回值 包名.类名.方法名(参数)

其中任意字段可以使用*代替表示任意值

Spring 的 AOP 基于 AspectJ 注解开发

开发步骤

  1. 引入jar包

  2. 设置配置文件(Idea自动导入):

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns: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/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
    http://www.springframework.org/schema/tx 
    http://www.springframework.org/schema/tx/spring-tx.xsd">
    </beans>
    
  3. 编写配置目标类

    <bean id="orderDao" class="demo1.OrderDao"></bean>
    
    public class OrderDao {
        public void save(){
            System.out.println("save order");
        }
        
        public void update(){
            System.out.println("update order");
        }
        
        public void delete(){
            System.out.println("delete order");
        }
      
        public void find(){
            System.out.println("find order");
        }
    }
    
  4. 开启aop注解自动代理

    <aop:aspectj-autoproxy/>
    
  5. 编写切面类并配置

    @Aspect
    public class MyAspectAnno {
        @Before(value="execution(* demo1.OrderDao.save(..))")
        public void before(){
            System.out.println("before");
        }
    }
    
    <bean id="myAspect" class="demo1.MyAspectAnno">
    

    @Aspect:事aspectJ框架中的注解

    作用:表示当前类是切面类

    切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码

    位置:在类定义的上面

注解通知类型

@Before:前置通知

@Before(value = "execution(* *..SomeServiceImpl.do*(..))")
public void myBefore(JoinPoint joinPoint){
    //获取方法的完整定义
    System.out.println("方法的签命:"+joinPoint.getSignature());
    System.out.println("方法的名称:"+joinPoint.getSignature().getName());
    //获取方法的实参
    Object[] objects = joinPoint.getArgs();
    for(Object obj:objects){
        System.out.println(obj);
    }
    //就是切面要执行的功能代码
    System.out.println("前置通知,切面功能 "+new Date());
}

@Before:前置通知注解

属性:value,是切入点表达式,表示切面的功能执行的位置

位置:在方法的上面

特点:

  1. 在目标方法之前执行
  2. 不会改变目标方法的执行结果
  3. 不会影响目标方法的执行

指定通知方法中的参数:JoinPoint

JoinPoint:业务方法,要加入切面功能的业务方法

作用是:可以在通知方法中获取方法执行时的信息,例如方法名称,方法的实参。如果你的切面功能中需要用到方法的信息,就加入JoinPoint。这个JoinPoint参数的值是由框架赋予的,必须是第一个位置的参数

方法的定义要求:

  1. 公共方法 public
  2. 方法没有返回值
  3. 方法名称自定义
  4. 方法可以有参数,也可以没有参数。如果有参数,参数不是自定义的,有几个参数类型可以使用

@AfterReturning: 后置通知

@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",returning = "obj")
public void myAfterReturning(Object obj){
    //Object res:是目标方法执行后的返回值,根据返回值做你的切面的功能处理
    System.out.println("后置通知:在目标方法之后执行,获取的返回值:"+obj);
    if(obj.equals("abc")){

    } else{

    }

    //修改目标方法的返回值,看一下是否会影响最后的方法调用结果
    if(obj != null){
        obj = "hello";
    }
}

@AfterReturning:后置通知

属性:

  1. value,切入点表达式
  2. returning 自定义的变量,表示目标方法的返回值。自定义的变量名必须和通知方法的形参名一样

位置:在方法定义的上面

特点:

  1. 在目标方法之后执行
  2. 能够获取到目标方法的返回值,可以根据这个返回值做不同的处理结果
  3. 可以修改这个返回值

方法的定义要求:

  1. 公共方法 public
  2. 方法没有返回值
  3. 方法名称自定义
  4. 方法有参数,推荐使用Object,参数名称自定义

@Around:环绕通知

@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
    String name = "";
    //获取第一个参数值
    Object[] args = pjp.getArgs();
    if(args!=null && args.length>1){
        Object arg = args[0];
        name = (String) arg;
    }
    //实现环绕通知
    Object result = null;
    System.out.println("环绕通知,在目标方法之前,输出时间:"+new Date());
    //1.目标方法调用
    if("zhangsan".equals(name)){
        //符合条件,调用目标方法
        result = pjp.proceed();  //method.invoke();  Object result = doFirst();
    }
    System.out.println("环绕通知,在目标方法之后,提交事务");
    //2.在目标方法的前或者后加入功能
    if(result != null){
        result = "hello";
    }
    //返回目标方法的执行结果
    return result;
}

@Around:环绕通知

属性:

? value:切入点表达式

位置:在方法定义的上面

特点:

  1. 它是功能最强的通知
  2. 在目标方法的前后都能增加功能
  3. 控制目标方法时候被调用执行
  4. 修改原来的目标方法的执行结果,影响最后的调用结果

环绕通知方法的定义模式:

  1. public
  2. 必须有一个返回值,推荐使用Object
  3. 方法名称自定义
  4. 方法由参数,参数是固定的ProceedingJoinPoint

环绕通知等同于JDK动态代理,InvocationHandler接口

参数:ProceedingJoinPoint等同于jdk动态代理中的Method

作用:执行目标方法的

返回值:是目标方法的执行结果,这个结果可以被修改

环绕通知:经常做事务,在目标方法之前开启事务,执行目标方法,在目标方法之后提交事务

@AfterThrowing: 抛出异常

@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "exception")
public void myAfterThrowing(Exception exception){
    System.out.println("异常通知,在方法发生异常时执行:"+exception.getMessage());
    //发送邮件、短信通知开发人员
}

@AfterThrowing:异常通知

属性:

  1. value 切入点表达式
  2. throwing 自定义的变量,表示目标方法抛出的异常对象,变量名必须和方法的参数名一样

特点:

  1. 在目标方法抛出异常时执行
  2. 可以做异常的监控程序,监控目标方法执行时是不是有异常,如果有异常,可以发送邮件、短信进行通知

异常通知方法的定义格式:

  1. public
  2. 没有返回值
  3. 方法名称自定义
  4. 方法有一个Exception参数,如果还有,就是JoinPoint

@After: 最终通知

@After(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void myAfter(){
    System.out.println("执行最终通知,总是会被执行");
    //一般做资源清除工作的
}

@After:最终通知

属性:

? value 切入点表达式

位置:在方法的上面

最终通知方法的定义格式:

  1. public
  2. 没有返回值
  3. 方法名称自定义
  4. 方法没有参数,如果有参数,就是JoinPoint

特点:

  1. 总是会执行
  2. 在目标方法之后执行

@PointCut:切入点注解

@Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void mypt(){

}
@After(value = "mypt()")
public void myAfter(){
    System.out.println("执行最终通知,总是会被执行");
    //一般做资源清除工作的
}

@Before(value = "mypt()")
public void myBefore(){
    System.out.println("执行前置通知");
}

@Pointcut:定义和管理切入点,如果你的项目中有多个切入点表达式是重复的,可以复用的,可以使用@Pointcut

属性:

? value 切入点表达式

位置:在自定义的方法上面

特点:

  • 当使用@Pointcut定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名。
  • 其它的通知中,value属性就可以使用这个方法名称,代替切入点表达式

Spring的JDCB模板

Spring 对持久层也提供了解决方案,也就是 ORM 模块和 JDBC 的模板。

针对 JDBC ,提供了org.springframework.jdbc.core.JdbcTemplate 作为模板类。

使用JDBC模板

  1. 引入jar包,数据库驱动,Spring 的 jdbc 相关包。

  2. 基本使用:

    public void demo1(){
        //创建连接池
        DriverManagerDataSource dataSource=new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql:///spring4");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        //创建JDBC模板 
        JdbcTemplate jdbcTemplate=new JdbcTemplate(dataSource);
        jdbcTemplate.update("insert into account values (null,?,?)" , "xiaoming",1000d);
    }
    
  3. 将连接池和模板交给 Spring 管理

    • 配置文件:

      <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource;">
      <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
      <property name="url" value="jdbc:mysql:///spring4"></property>
      <property name="username" value="root"></property>
      <property name="password" value="123456"></property>
      </bean>
      
      <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate;"> 
      <property name="dataSource" ref="dataSource"></property>
      </bean>
      
    • 测试文件:

      @RunWith(SpringJUnit4ClassRunner.class)
      @ContextConfiguration("classpath:applicationContext.xml")
      public class JdbcDemo2 {
      
          @Resource(name="jdbcTemplate")
          private JdbcTemplate jdbcTemplate;
          
          @Test
          public void demo2(){
              jdbcTemplate.update("insert into account values (null,?,?)" , "xiaolan",1000d);
          }
      }
      

使用开源数据库连接池

  1. 使用 DBCP 的配置:

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://192.168.66.128/spring4"></property>
    <property name="username" value="root"></property>
    <property name="password" value="123456"></property>
    
  2. 使用 C3P0 的配置:

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
    <property name="jdbcUrl" value="jdbc:mysql://192.168.66.128/spring4"></property>
    <property name="user" value="root"></property>
    <property name="password" value="123456"></property>
    </bean>
    
  3. 引入外部属性文件

    • 首先建立外部属性文件

      jdbc.driverClass=com.mysql.jdbc.Driver
      jdbc.url=jdbc:mysql://192.168.66.128/spring4
      jdbc.username=root
      jdbc.password=123456
      
    • 然后对属性文件进行配置

      <context:property-placeholder location="classpath:jdbc.properties"/>
      <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>
      

CRUD操作

insert, update, delete 语句都借助模板的 update 方法进行操作。

public void demo(){
	jdbcTemplate.update("insert into account values (null,?,?)", "xiaoda",1000d);
	jdbcTemplate.update("update account set name=?,money=? where id=?", "xiaoda",1000d,2);
	jdbcTemplate.update("delete from account where id=?", 6);
}

查询操作:

public void demo3(){
	String name=jdbcTemplate.queryForObject("select name from account where id=?",String.class,5);
	long count=jdbcTemplate.queryForObject("select count(*) from account",Long.class);
}

将返回的结果封装成为类:

public void demo4(){
    Account account = jdbcTemplate.queryForObject("select * from account where id=?", new MyRowMapper(),5);
}

其中:

class MyRowMapper implements RowMapper<Account>{
    @Override
    public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
    	Account account=new Account();
    	account.setId(rs.getInt("id"));
    	account.setName(rs.getString("name"));
    	account.setMoney(rs.getDouble("money"));
    	return account;
    }
}

Spring的事务管理

事务是指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部失败。

具有四个特性:

  • 原子性:事务不可分
  • 一致性:事务执行前后数据完整性保持一致
  • 隔离性:一个事务的执行不应该受到其他事务干扰
  • 持久性:一旦事务结束,数据就持久化到数据库

如果不考虑隔离性会引发安全性问题:

  • 读问题:

    • 脏读:一个事务读到另一个事务未提交的数据
    • 不可重复读:一个事务读到另一个事务已经提交的 update 数据,导致一个事务中多次查询结果不一致
    • 幻读:一个事务读到另一个事务已经提交的 insert 数据,导致一个事务中多次查询结果不一致
  • 写问题:

    • 丢失更新

解决读问题:设置事务隔离级别

  • Read uncommitted: 未提交读,无法解决任何读问题
  • Read committed: 已提交读,解决脏读问题
  • Repeatable read: 重复读,解决脏读和不可重复读问题
  • Serializable:序列化,解决所有读问题

事务管理API

  1. PlatformTransactionManager: 平台事务管理器

    这是一个接口,拥有多个不同的实现类,如 DataSourceTransactionManager 底层使用了JDBC 管理事务;HibernateTransactionManager 底层使用了 Hibernate 管理事务。

  2. TransactionDefinition: 事务定义信息用于定义事务的相关信息,如隔离级别、超时信息、传播行为、是否只读等

  3. TransactionStatus: 事务的状态:用于记录在事务管理过程中,事务的状态的对象。

上述API的关系:Spring 在进行事务管理的时候,首先平台事务管理器根据事务定义信息进行事务管理,在事务管理过程当中,产生各种此状态,将这些状态信息记录到事务状态的对象当中。

事务的传播行为

事务的传播行为主要解决业务层(Service)方法相互调用的问题,也就是不同的业务中存在不同的事务时,如何操作。

Spring 中提供了7种事务的传播行为,分为三类:

  • 保证多个操作在同一个事务中

    • PROPAGATION_REQUIRED: B方法调用A方法,如果A中有事务,使用A中的事务并将B中的操作包含到该事务中;否则新建一个事务,将A和B中的操作包含进来。(默认)
    • PROPAGATION_SUPPORTS:如果A中有事务,使用A的事务;否则不使用事务
    • PROPAGATION_MANDATORY:如果A中有事务,使用A的事务;否则抛出异常
  • 保证多个操作不在同一个事务中

    • PROPAGATION_REQUIRES_NEW:如果A中有事务,将其挂起,创建新事务,只包含自身操作。否则,新建一个事务,只包含自身操作。
    • PROPAGATION_NOT_SUPPORTED:如果A中有事务,挂起,不使用事务。
    • PROPAGATION_NEVER:如果A中有事务,抛出异常,也即不能用事务运行。
  • 嵌套事务

    • PROPAGATION_NESTED:如果A有事务,按照A的事务执行,执行完成后,设置一个保存点,然后执行B的操作。如果出现异常,可以回滚到最初状态或保存点状态。

实例

以转账为例,业务层的DAO层类如下:

public interface AccountDao {
	public void outMoney(String from,Double money);
	public void inMoney(String to,Double money);
}
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao{
    @Override
    public void outMoney(String from, Double money) {
        this.getJdbcTemplate().update("update account set money = money - ? where name = ?",money,from);
    }
	
    @Override
    public void inMoney(String to, Double money) {
        this.getJdbcTemplate().update("update account set money = money + ? where name = ?",money,to);
    }
}
public interface AccountService {
    public void transfer(String from,String to,Double money);
}
public class AccountServiceImpl implements AccountService {
    private AccountDao accountDao;
    public void setAccountDao(AccountDao accountDao) {
      this.accountDao = accountDao;
    }

    @Override
    public void transfer(String from, String to, Double money) {
        accountDao.outMoney(from, money);
        accountDao.inMoney(to, money);
    }
}

在xml中进行类的配置:

<bean id="accountService" class="tx.demo.AccountServiceImpl">
	<property name="accountDao" ref="accountDao"/>
</bean>

<bean id="accountDao" class="tx.demo.AccountDaoImpl">
	<property name="dataSource" ref="dataSource"/>
</bean>

事务管理1:编程式事务管理

  1. 配置平台事务管理器

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    	<property name="dataSource" ref="dataSource"></property>
    </bean>
    
  2. 配置事务管理模板类

    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    	<property name="transactionManager" ref="transactionManager"></property>
    </bean>
    
  3. 在业务层注入事务管理模板

    <bean id="accountService" class="tx.demo1.AccountServiceImpl">
    	<property name="accountDao" ref="accountDao"/>
    	<property name="transactionTemplate" ref="transactionTemplate"/>
    </bean>
    
  4. 编码实现事务管理

    //ServiceImpl类中:
    private TransactionTemplate transactionTemplate;
    
    @Override
    public void transfer(String from, String to, Double money) {
      	transactionTemplate.execute(new TransactionCallbackWithoutResult() {
      		@Override
      		protected void doInTransactionWithoutResult(TransactionStatus arg0) {
        			accountDao.outMoney(from, money);
        			accountDao.inMoney(to, money);
      		}
        };
    }
    

声明式事务管理(配置实现,基于AOP思想)

  1. XML 方式的声明式事务管理

    • 配置事务管理器

      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      	<property name="dataSource" ref="dataSource"></property>
      </bean>
      
    • 配置事务通知

      <tx:advice id="txAdvice" transaction-manager="transactionManager">
      	<tx:attributes>
      		<tx:method name="transfer" propagation="REQUIRED"/>
      	</tx:attributes>
      </tx:advice>
      
    • 配置aop事务

      <aop:config>
      	<aop:pointcut expression="execution(* tx.demo2.AccountServiceImpl.*(..))" id="pointcut1"/>
      	<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
      </aop:config>
      
  2. 注解方式

    • 配置事务管理器,和上方一致

    • 开启事务管理的注解:

      <tx:annotation-driven transaction-manager="transactionManager"/>
      
    • 在使用事务的类上添加一个注解@Transactional

本文在这篇博文的基础上进行的扩充与修改
作者:supingemail
原文地址:https://blog.csdn.net/supingemail/article/details/85988220

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-11-29 16:10:49  更:2021-11-29 16:11:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 3:39:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码