Spring学习笔记


Spring

尚硅谷:https://www.bilibili.com/video/BV1Ya411S7aT?p=63&share_source=copy_web

Spring简介

Spring的概述

  • Spring框架可创建性能好、易于测试、可重用的代码

  • 是一个开源的框架

  • 是一个轻量级的框架

  • 核心特性可用于开发任何Java应用程序

SpringFramework

Spring的基础框架,可以视为Spring的基础设施,基本任何的Spring项目都是以SpringFramework为基础的。

SpringFramework特性

  • 非侵入式:使用SpringFramework开发应用时,Spring对应用本身的结构影响非常小。完全不会破坏原有结构,反而能将组件结构进一步简化。

  • 控制反转:IOC--Inversion of Control,翻转资源获取的方式。把对象的创建权、获取权交给了Spring,因此,对象从主动获取变成被动的由框架来提供

  • 面向切面编程:AOP--Aspect Oriented Programming,在不修改源代码的基础上,增强了代码的功能。

  • 容器:SpringIOC是一个容器,因为它包含且管理组件对象的生命周期。

  • 组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用

  • 声明式:很多需要编写代码才能实现的需求,现只需声明需求即可由框架代为实现

  • 一站式:在IOC和AOP的基础上可以整合各种开源框架和优秀的第三方类库

SpringFramework五大功能模块

功能模块功能介绍
Core Container 核心容器,在Spring环境下使用任何功能必须基于IOC容器
AOP&Aspects 面向切面编程
Testing 提供了对junit或TestNG测试框架的整合
Data Access/Integration 提供了对数据访问/集成的功能
SpringMVC 提供了面向Web应用的集成功能

IOC

IOC容器

IOC思想

IOC:Inversion of Control,反转控制

  • 获取资源的传统方式

    • 组件主动从容器中获取所需资源,开发人员往往需要知道在具体容器中特定资源的获取方式

  • 反转控制获取资源

    • 改由容器主动将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即

  • DI

    • DI:Dependency Injection,依赖注入

      DI是IOC的另一种表述方式:即组件以一些预先定义好的方式(如setter方法)接受来自容器的资源注入。

  • 结论:IOC就是一种反转控制的思想,而DI是对IOC的一种具体实现

IOC容器在Spring中的实现

Spring的IOC容器就是IOC思想的实现。IOC容器管理的组件也叫bean。在创建bean之前,需要先创建IOC容器。Spring提供了两种实现方式:

  • BeanFactory

    • 这是IOC容器的基本实现,是Spring内部使用的接口。面向Spring本身,不提供给开发人员

  • ApplicationContext的主要实现类

    • BeanFactory的子接口,面向开发者

  • ApplicationContext的主要实现类

    类型名简介
    ClassPathXmlApplicationContext 通过读取类路径下的XML格式的配置文件创建IOC容器对象
    FileSystemXmlApplicationContext 提供文件路径读取XML格式的配置文件创建IOC容器对象
    ConfigurableApplicationContext ApplicationContext的子接口,包含一些拓展方法refresh()和close(),让ApplicationContext具有启动、关闭和刷新上下文的能力
    WebApplicationContext 专门为Web应用准备的,基于Web环境创建IOC容器对象,并将对象引入存入ServletContext域中

基于XML管理bean

  1. 创建Maven

  2. 引入依赖

      <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.1</version>
    </dependency>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    </dependency>
    </dependencies>
  1. 创建类

    public class HelloWorld {
    public void sayHello(){
    System.out.println("Hello,Spring");
    }
    }
  2. 创建Spring配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--
    bean:配置一个bean对象,将对象交给IOC容器管理
    属性
    id:bean的唯一标识,不能重复
    class:设置bean对象所对应的类型
    -->
    <bean id="helloworld" class="com.zwb.pojo.HelloWorld"></bean>
    </beans>
  3. 测试

    @Test
    public void test(){
    //获取IOC容器
    ApplicationContext ioc=new ClassPathXmlApplicationContext("applicationContext.xml");
    //获取IOC容器中的bean对象
    HelloWorld helloWorld=(HelloWorld)ioc.getBean("helloworld");
    helloWorld.sayHello();
    }

获取bean

/**
* 获取bean的三种方法
* 1.根据bean的id获取
* 2.根据bean的类型获取
* 注意:根据类型获取bean时,要求IOC容器中有且只有一个类型匹配的bean
* 3.根据bean的id和类型获取
*/
@Test
public void testIOC(){
//获取IOC容器
ApplicationContext ioc=new ClassPathXmlApplicationContext("applicationContext.xml");
//获取bean
//Student studentOne=(Student)ioc.getBean("studentOne");
//Student studentOne=ioc.getBean(Student.class);
Student studentOne=ioc.getBean("studentOne",Student.class);
System.out.println(studentOne);
}

接口无法获取bean,如果组件类实现了接口,根据接口类型可以获取bean,前提是bean唯一

结论:根据类型获取bean时,满足bean唯一的条件下,只看 对象 instanceof 指定类型 的返回结果,只要是true,就认为匹配,可以获取。即通过bean的类型、bean所继承的类型、bean所实现的接口的类型都可以获取bean对象

依赖注入——setter注入

  1. 创建类

  2. 配置bean时为属性赋值

    <bean id="studentTwo" class="com.zwb.pojo.Student">
    <!--
    property:通过成员变量的set方法进行赋值
    name:设置需要赋值的属性名(和get、set有关,与成员变量无关)
    value:设置为属性所赋的值
    -->
    <property name="sid" value="1001"></property>
    <property name="name" value="张三"></property>
    <property name="age" value="11"></property>
    <property name="gender" value="男"></property>
    </bean>
  3. 测试

    @Test
    public void testDI(){
    //获取IOC容器
    ApplicationContext ioc=new ClassPathXmlApplicationContext("applicationContext.xml");
    //获取bean
    Student studentOne=ioc.getBean("studentTwo",Student.class);
    System.out.println(studentOne);
    }

依赖注入——构造器注入

  1. 添加有参构造

    public Student(Integer sid, String name, Integer age, String gender) {
    this.sid = sid;
    this.name = name;
    this.age = age;
    this.gender = gender;
    }
  2. 在bean中赋值

    <bean id="studentThree" class="com.zwb.pojo.Student">
    <constructor-arg value="1001"></constructor-arg>
    <constructor-arg value="李四"></constructor-arg>
    <constructor-arg value="15" name="age"></constructor-arg>
    <constructor-arg value="女"></constructor-arg>
    </bean>

特殊值处理

  • 字面量赋值

    什么是字面量?

    int a=10;

    声明一个变量a,初始化为10,此时a就不代表字母a了,而是变量名。当引用a时,实际拿到的时10。而如果a是带引号的'a',那么他就不是一个变量,而是a本身,这就是字面量

    <!--使用value给bean赋值时,Spring会把value属性的值看作字面量-->
    <property name="name" value="张三"></property>
  • null值

    <property name="gender">
    <null/>
    </property>
  • xml实体

    <!--
    <:&lt;
    >:&gt;
    -->
    <property name="name" value="&lt;张三&gt;"></property>
  • CDATA节

    <!--CDATA节中的内容会原样解析-->
    <property name="name">
    <!--快捷键:CD+Enter-->
    <value><![CDATA[<王五>]]></value>
    </property>

为类类型赋值

  • 引用外部bean

    <bean id="studentFive" class="com.zwb.pojo.Student">
    <property name="sid" value="1004"></property>
    <property name="name" value="MADAO"></property>
    <property name="age" value="23"></property>
    <property name="gender" value="男"></property>
    <!--ref:引用IOC容器中某个bean的id-->
    <property name="clazz" ref="clazzOne"></property>
    </bean>

    <bean id="clazzOne" class="com.zwb.pojo.Clazz">
    <property name="cid" value="111"></property>
    <property name="cname" value="终极1班"></property>
    </bean>
  • 级联

    <bean id="studentFive" class="com.zwb.pojo.Student">
    <property name="sid" value="1004"></property>
    <property name="name" value="MADAO"></property>
    <property name="age" value="23"></property>
    <property name="gender" value="男"></property>
    <!--ref:引用IOC容器中某个bean的id-->
    <property name="clazz" ref="clazzOne"></property>
    <!--级联的方式,要保证提前为clazz属性赋值或实例化-->
    <property name="clazz.cid" value="22"></property>
    <property name="clazz.cname" value="终极2班"></property>
    </bean>
    <bean id="clazzOne" class="com.zwb.pojo.Clazz">
    <property name="cid" value="111"></property>
    <property name="cname" value="终极1班"></property>
    </bean>
  • 内部bean

    <bean id="studentFive" class="com.zwb.pojo.Student">
    <property name="sid" value="1004"></property>
    <property name="name" value="MADAO"></property>
    <property name="age" value="23"></property>
    <property name="gender" value="男"></property>
    <property name="clazz">
    <bean id="clazzInner" class="com.zwb.pojo.Clazz">
    <property name="cid" value="22"></property>
    <property name="cname" value="终极2班"></property>
    </bean>
    </property>
    </bean>

    内部bean无法通过IOC容器直接获取

为数组类型赋值

private String[] hobby;
<property name="hobby">
<array>
<value>唱</value>
<value>跳</value>
<value>rap</value>
<value>篮球</value>
</array>
</property>

为List集合类型赋值

private List<Student> students;
  • 内部list

    <bean id="clazzOne" class="com.zwb.pojo.Clazz">
    <property name="cid" value="111"></property>
    <property name="cname" value="终极1班"></property>
    <property name="students">
    <list>
    <ref bean="studentOne"></ref>
    <ref bean="studentTwo"></ref>
    <ref bean="studentFour"></ref>
    </list>
    </property>
    </bean>
  • 引用list集合类型的bean

    <bean id="clazzOne" class="com.zwb.pojo.Clazz">
    <property name="cid" value="111"></property>
    <property name="cname" value="终极1班"></property>
    <property name="students" ref="studentList"></property>
    </bean>
    <!--配置集合类型的bean,需要使用util的约束-->
    <util:list id="studentList">
    <ref bean="studentOne"></ref>
    <ref bean="studentTwo"></ref>
    <ref bean="studentFour"></ref>
    </util:list>

为Map集合赋值

private Map<String,Teacher> teacherMap;
  • 内部map

    <bean id="studentFive" class="com.zwb.pojo.Student">
    <property name="teacherMap">
    <map>
    <!--一个entry表一个键值对-->
    <entry key="12321" value-ref="teacherOne"></entry>
    <entry key="12345" value-ref="teacherTwo"></entry>
    </map>
    </property>
    </bean>
  • 引用map集合类型的bean

    <util:map id="teacherMap">
    <entry key="12321" value-ref="teacherOne"></entry>
    <entry key="12345" value-ref="teacherTwo"></entry>
    </util:map>

    <bean id="studentFive" class="com.zwb.pojo.Student">
    <property name="teacherMap" ref="teacherMap"></property>
    </bean>

为p命名空间赋值

<bean id="studentSix" class="com.zwb.pojo.Student"
p:sid="1005" p:name="张三" p:age="25" p:teacherMap-ref="teacherMap"></bean>

必须引入p的命名空间

引入外部属性文件

  • 添加依赖:添加数据库和druid的依赖

    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.13</version>
    </dependency>
    <!--数据源-->
    <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.10</version>
    </dependency>
  • 创建属性文件

    jdbc.driver=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis?serverTimezone=UTC
    jdbc.username=root
    jdbc.password=123456
  • 配置数据源DataSource的bean

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
    </bean>
  • 引入外部配置文件

    <!--引入jdbc.properties,之后就可以用${key}来获取value-->
    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>

    context的约束

    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd"

bean的作用域

  • 概念

    在Spring中可以通过配置bean标签的scope来指定bean的作用范围

    取值含义创建对象的时机
    singleton(默认) 在IOC容器中,这个bean的对象始终为单实例 IOC容器初始化时
    prototype 这个bean在IOC容器中有多个实例 获取bean时

    单例就是这个类有且只会有有一个实例对象,大家用的都是同一个对象

<!--
scope:设置bean的作用域
singleton | prototype
singleton(单例):表示获取该bean所对应的对象都是同一个
prototype(多例):表示获取该bean所对应的对象都不是同一个
-->
<bean id="student" class="com.zwb.pojo.Student" scope="prototype">
<property name="sid" value="1001 "></property>
<property name="name" value="张三 "></property>
</bean>

bean的生命周期

  • 具体生命周期过程

    • bean对象的创建(调用无参构造器)

    • 给bean对象设置属性

    • bean对象初始化之前的操作(由bean的后置处理器负责)

    • bean对象的初始化(需在配置bean时指定初始化方法)

    • bean对象初始化之后操作(由bean的后置处理器负责)

    • bean对象就绪可以使用

    • bean对象销毁(需在配置bean时指定销毁方法)

    • IOC容器关闭

  • User类

    package com.zwb.pojo;

    public class User {
    private Integer id;
    private String username;
    private String password;
    private Integer age;

    public User() {
    System.out.println("生命周期1:实例化");
    }

    public User(Integer id, String username, String password, Integer age) {
    this.id = id;
    this.username = username;
    this.password = password;
    this.age = age;
    }

    public void setId(Integer id) {
    System.out.println("生命周期2:依赖注入");
    this.id = id;
    }

    public void initMethod(){
    System.out.println("生命周期3:初始化");
    }

    public void destroyMethod(){
    System.out.println("生命周期4:销毁");
    }
    }
  • 配置bean

     <bean id="user" class="com.zwb.pojo.User" init-method="initMethod" destroy-method="destroyMethod">
    <property name="id" value="1"></property>
    <property name="username" value="admin"></property>
    <property name="password" value="123456"></property>
    <property name="age" value="23"></property>
    </bean>
    <!--bean的后置处理器-->
    <bean id="MyBeanPostProcessor" class="com.zwb.process.MyBeanPostProcessor"></bean>
    </beans>
  • bean的后置处理器

    • 会在生命周期初始化前后添加额外的操作,需要实现BeanPostProcessor接口,且配置到IOC容器中,bean后置处理器不是单独针对某一个bean,而是IOC容器中的所有bean

    public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    //此方法在bean的生命周期初始化之前执行
    System.out.println("后置处理器的postProcessBeforeInitialization");
    return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    //在bean生命周期初始化之后
    System.out.println("后置处理器的postProcessAfterInitialization");
    return bean;
    }
    }
  • 测试

    /**
    * 1.实例化
    * 2.依赖注入
    * 3.后置处理器的postProcessBeforeInitialization方法
    * 4.初始化,需要通过bean的init-method来指定初始化的方法
    * 5.后置处理器的postProcessAfterInitialization方法
    * 6.IOC容器关闭时销毁,需要通过bean的destroy-method来指定销毁的方法
    * 注意
    * 若bean的作用域为单例时,生命周期前3个步骤会在获取IOC容器时执行
    * 若bean的作用域为多例时,生命周期前3个步骤会在获取bean时执行
    */
    @Test
    public void test(){
    //ConfigurableApplicationContext时ApplicationContext的子接口,扩展了刷新和关闭容器的方法
    ConfigurableApplicationContext ioc =new ClassPathXmlApplicationContext("spring-lifecycle.xml");
    User user=ioc.getBean(User.class);
    System.out.println(user);
    ioc.close();
    }

FactoryBean

Factory是Spring提供的整合第三方框架的常用机制。和普通bean不同,配置一个FactoryBean类型的bean,在获取bean时得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节屏蔽起来,只展示最简洁的使用界面。

整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。

FactoryBean可以帮助我们直接获取工厂所提供的对象 ,不再需要先获取工厂,在获取对象

  • FactoryBean是一个接口,有3个方法

    • getObject()提供一个对象交给IOC容器管理

    • getObjectType()设置所提供对象的类型

    • isSingleton提供的对象是否单例

    • 当FactoryBean的实现类配置bean时,会将getObject()返回的对象交给IOC容器管理

public class UserFactoryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
return new User();
}

@Override
public Class<?> getObjectType() {
return User.class;
}

@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}

xml

<!--将UserFactoryBean的getObject()的返回值交给IOC容器管理-->
<bean class="com.zwb.factory.UserFactoryBean"></bean>

测试

@Test
public void testFactoryBean(){
ApplicationContext ioc=new ClassPathXmlApplicationContext("spring-factory.xml");
User user=ioc.getBean(User.class);
System.out.println(user);
User user2=ioc.getBean(User.class);
System.out.println(user==user2);//true
}

基于xml的自动装配

根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中的类类型的属性或接口类型属性赋值

  • no、default:不装配,即bean中的属性不会自动匹配某个bean为属性赋值,此时属性使用默认值

  • byType:根据要赋值的属性的类型,在IOC容器中匹配某个bean,为属性赋值

    • 若通过类型没有找到任何一个类型匹配的bean,此时不装配,使用默认值

    • 若通过类型找到多个bean,会抛异常

    • 通过byType实现,需要IOC容器中有且只有一个类型匹配的bean

  • byName:将要赋值的属性的属性名作为bean的id在IOC容器中匹配某个bean,为属性赋值

<bean id="userController" class="com.zwb.controller.UserController" autowire="byType">
<!--<property name="userService" ref="UserServiceImpl"></property>-->
</bean>
<bean id="userService" class="com.zwb.service.impl.UserServiceImpl" autowire="byType">
<!--<property name="userDao" ref="UserDaoImpl"></property>-->
</bean>
<bean id="userDao" class="com.zwb.dao.impl.UserDaoImpl"></bean>
//Dao层
public class UserDaoImpl implements UserDao {
@Override
public void saveUser() {
System.out.println("保存成功");
}
}

//service层
public class UserServiceImpl implements UserService {
private UserDao userDao;

public UserDao getUserDao() {
return userDao;
}

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

@Override
public void saveUser() {
userDao.saveUser();
}
}

//Controller层
public class UserController {
private UserService userService;

public UserService getUserService() {
return userService;
}

public void setUserService(UserService userService) {
this.userService = userService;
}

public void saveUser(){
userService.saveUser();
}
}
  • 测试

 @Test
public void test(){
ApplicationContext ioc=new ClassPathXmlApplicationContext("spring-autowire-xml.xml");
UserController userController=ioc.getBean(UserController.class);
userController.saveUser();
}

基于注解管理bean

标记与扫描

  • 注解

    和xml配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体功能是框架检测到标记的位置,针对这个位置的注解来执行具体操作。

    本质上一切操作都是Java来完成的,XML和注解只是告诉框架该如何执行

  • 扫描

    Spring为了知道哪些地方标记了什么注解,就需要通过注解扫描的方式,来进行检测。然后根据注解进行后续操作

  • 注解的类型

    • @Component:将类标识为普通组件

    • @Controller 将类标识为控制层组件

    • @Service 将类标识为业务层组件

    • @Repository:将类标识为持久层组件

    • 通过注解+扫描所配置的bean的id默认为类的小驼峰

    • @Component(value),可以通过value来设置自定义id

  • 测试

    配置文件

    <!--
    context:exclude-filter:排除扫描
    type:设置方式
    annotation:根据注解类型进行排除
    assignable:根据类的类型进行排除

    context:include-filter:包含扫描
    需要和context:component-scan的use-default-filters="false"搭配使用

    -->
    <!--扫描组件-->
    <context:component-scan base-package="com.zwb" use-default-filters="false">
    <!--<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>-->
    <!--<context:exclude-filter type="assignable" expression="com.zwb.controller.UserController"/>-->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    @Repository
    public class UserDaoImpl implements UserDao {
    }

    @Service
    public class UserServiceImpl implements UserService {
    }

    @Controller
    public class UserController {
    }

自动装配

@Autowired:实现自动装配的注解

注解位置

  • 成员变量上,此时不需要给成员变量设置set方法

  • 标识在set方法上

  • 有参构造上

@Autowired的原理

  • 默认通过byType的方式,在IOC容器中通过类型匹配某个bean为属性进行赋值

  • 若有多个类型匹配的bean,会自动转化为byName

  • 若都不行,可以在要赋值的属性上+@Qualifier(bean的id)

@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;

@Override
public void saveUser() {
userDao.saveUser();
}
}

@Controller
public class UserController {
@Autowired
private UserService userService;

public void saveUser(){
userService.saveUser();
}
}

AOP

代理模式

通过一个代理类,让我们调用目标方法时,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时,先调用代理对象的方法,减少对目标方法的调用和打扰,同时也让附加功能集中在一起,有利于统一维护

静态代理

一个代理类对应一个目标类

  • 目标接口

    public interface Calculator {
    int add(int i,int j);
    int sub(int i,int j);
    int mul(int i,int j);
    int div(int i,int j);
    }
  • 目标类

    public class CalculatorImpl implements Calculator{
    @Override
    public int add(int i, int j) {
    int result=i+j;
    System.out.println("方法内部:"+result);
    return result;
    }

    @Override
    public int sub(int i, int j) {
    int result=i-j;
    System.out.println("方法内部:"+result);
    return result;
    }

    @Override
    public int mul(int i, int j) {
    int result=i*j;
    System.out.println("方法内部:"+result);
    return result;
    }

    @Override
    public int div(int i, int j) {
    int result=i/j;
    System.out.println("方法内部:"+result);
    return result;
    }
    }
  • 代理类

    //实现目标对象的接口,以确保目标对象和代理对象实现的方法是一致的
    public class CalculatorStaticProxy implements Calculator{
    CalculatorImpl calculator;

    public CalculatorStaticProxy(CalculatorImpl calculator) {
    this.calculator = calculator;
    }

    @Override
    public int add(int i, int j) {
    System.out.println("日志,方法add,参数:"+i+","+j);
    int result=calculator.add(i,j);
    System.out.println("日志,方法add,结果:"+result);
    return result;
    }

    @Override
    public int sub(int i, int j) {
    System.out.println("日志,方法sub,参数:"+i+","+j);
    int result=calculator.sub(i,j);
    System.out.println("日志,方法sub,结果:"+result);
    return result;
    }

    @Override
    public int mul(int i, int j) {
    System.out.println("日志,方法mul,参数:"+i+","+j);
    int result=calculator.mul(i,j);
    System.out.println("日志,方法mul,结果:"+result);
    return result;
    }

    @Override
    public int div(int i, int j) {
    System.out.println("日志,方法div,参数:"+i+","+j);
    int result=calculator.div(i,j);
    System.out.println("日志,方法div,结果:"+result);
    return result;
    }
    }
  • 测试

    @Test
    public void proxyTest(){
    CalculatorStaticProxy calculator=new CalculatorStaticProxy(new CalculatorImpl());
    calculator.add(1,2);
    }

静态代理代码是写死的,不具备灵活性。如日志功能,如果其他地方也需要,那么就需要更多的静态代理类,会产生大量重复的代码。

进一步的需求:将日志功能集中在一个静态代理类中。有任何日志需求,都可通过这个代理类实现,这就需要动态代理了

动态代理

动态代理有两种

  • jdk动态代理,要求必须有接口,最终生成的代理类在com.sun.proxy包下,类名为$proxy2

  • cglib动态代理,最终生成的代理类会继承目标类,且和目标类在一个包下

动态代理类

/**
* 为任何的目标类动态生成代理类
*/
public class ProxyFactory {
private Object target;

public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxy(){
/**
* ClassLoader loader:指定加载动态生成的代理类的类加载器
* Class[] interfaces:获取目标对象实现的所有接口的class对象的数组
* InvocationHandler h:设置代理类中的抽象方法如何重写
*/
ClassLoader loader=this.getClass().getClassLoader();
Class<?>[] interfaces=target.getClass().getInterfaces();
InvocationHandler h=new InvocationHandler() {
//proxy表示代理对象,method表示要重写的抽象方法,args表示要执行的方法的参数列表
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("日志,方法:"+method.getName()+",参数:"+ Arrays.toString(args));
Object result=method.invoke(target,args);
System.out.println("日志,方法:"+method.getName()+",结果:"+result);
return result;
}
};
return Proxy.newProxyInstance(loader,interfaces,h);
}
}

测试

 @Test
public void proxyTest(){
ProxyFactory proxyFactory=new ProxyFactory(new CalculatorImpl());
Calculator proxy= (Calculator) proxyFactory.getProxy();
proxy.add(1,2);
}

AOP的概念及相关术语

概述

AOP是一种设计思想,面向切面编程,是面向对象编程的一种补充和完善,它以预编译方式和运行期间动态代理方式实现在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。

相关术语

  • 横切关注点

    • 从目标对象抽取出来的非核心业务

  • 通知:将横切关注点封装到切面中,切面中的各个横切关注点就是通知

    • 前置通知:目标方法执行前 执行

    • 返回通知:目标方法执行完后,执行

    • 异常通知:目标方法出现异常时,执行

    • 后置通知:目标方法最终结束后,执行

    • 环绕通知:使用try...catch..finally围绕目标方法,包括上面四种通知

  • 切面

    • 封装通知方法的类

  • 代理

    • 向目标对象应用通知后创建的对象

  • 连接点

    • 抽取横切关注点的位置

  • 切入点

    • 定位连接点的方式

作用

  • 简化代码:把方法里固定位置的重复代码抽取出来,保留核心代码

  • 代码增强:把特定功能封装到切面类中,套入目标代码,目标代码的功能得以增强

基于注解的AOP

技术说明

  • JDK原生方式实现动态代理,被代理目标的类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口

  • cglib:通过继承被代理的目标类实现代理,所以不需要目标类实现接口

  • AspectJ:本质上是静态代理,将代理逻辑“织入”被代理目标类编译得到的字节码文件。所以最终效果是动态的。waver就是织入器。Spring只是借用了AspectJ中的注解

准备工作

  1. 添加依赖

    在IOC所需依赖的基础上,再加入下面的依赖

    <!--aop依赖-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.1</version>
    </dependency>
  2. 添加注解

    <!--
    AOP的注意事项:
    切面类和目标类都需要交给IOC容器管理
    切面类必须通过@Aspect注解标识为一个切面
    -->
    @Component
    @Aspect//将当前主键表示为切面
  3. 切入点表达式

     	@Before("execution(public int com.zwb.spring.aop.annotation.CalculatorImpl.add(int,int))")
    public void beforAdviceMethod(JoinPoint joinPoint){
    //获取连接点所对应方法的签名信息
    Signature signature=joinPoint.getSignature();
    //获取连接点所对应方法的参数
    Object[] args=joinPoint.getArgs();
    System.out.println("LoggerAspect,方法:"+signature.getName()+"参数:"+ Arrays.toString(args));
    }
  4. 表达式其他写法

    @Before("execution(* com.zwb.spring.aop.annotation.CalculatorImpl.*(..))")//CalculatorImpl里任意的方法、任意的参数

    @Pointcut("execution(* com.zwb.spring.aop.annotation.CalculatorImpl.*(..))")
    public void poinCut(){}
    @Before("poinCut()")

     

声明式事务

Jdbc Template

简介

Spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据库操作

准备工作

  1. 加入依赖

    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.1</version>
    </dependency>
    <!--对象关系映射-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>5.3.1</version>
    </dependency>
    <!--spring测试功能-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.1</version>
    </dependency>
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.13</version>
    </dependency>
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.10</version>
    </dependency>
    </dependencies>
  2. 配置xml

    <!--引入properties-->
    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>

    <!--数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
    </bean>

    <bean class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
    </bean>
  3. Spring整合Junit

    @RunWith(SpringJUnit4ClassRunner.class)//指定当前测试类在Spring测试环境中执行,此时可以通过注入的方式直接获取IOC容器的bean
    @ContextConfiguration("classpath:spring-jdbc.xml")//设置Spring测试环境的配置文件
    public class JdbcTemplateTest {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    public void testInsert(){
    String sql="insert into t_user values(null,?,?,?,?,?)";
    jdbcTemplate.update(sql,"zzz","123",23,"男","118@qq.com");
    }
    }

    @Test
    public void testGetUserById(){
    String sql="select * from t_user where id=?";
    User user=jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<>(User.class),1);
    System.out.println(user);
    }

    @Test
    public void testGetAllUser(){
    String sql="select * from t_user";
    List<User> userList=jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(User.class));
    for (User user : userList) {
    System.out.println(user);
    }
    }

声明式事务概念

编程式事务

事务功能的相关操作全部通过自己编写代码实现

缺陷

  • 细节没有被屏蔽:具体操作过程中,所有细节需要程序员自己完成,比较繁琐

  • 代码复用性不高:如果没有有效抽取,每次实现都需要自己编写代码

声明式事务

将固定模式的代码抽取出来,进行相关的封装。封装后,只需要在配置文件中进行简单的配置即可完成操作。

  • 提高了开发的效率

  • 消除了了冗余的代码

  • 框架会综合考虑相关领域中在实际开发环境下可能遇到的各种问题

总结:

  • 编程式:自己写代码实现功能

  • 声明式:通过配置让框架实现功能

基于注解的声明式事务

准备工作

  1. 加入依赖

    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.1</version>
    </dependency>
    <!--对象关系映射-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>5.3.1</version>
    </dependency>
    <!--spring测试功能-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.1</version>
    </dependency>
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.13</version>
    </dependency>
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.10</version>
    </dependency>
    </dependencies>
  2. 创建propeties文件

    jdbc.driver=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis?serverTimezone=UTC
    jdbc.username=root
    jdbc.password=123456
  3. 配置spring的配置文件

    <!--引入properties-->
    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>

    <!--数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
    </bean>

    <bean class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
    </bean>

配置步骤

  1. 在Spring的配置文件中配置事务管理器

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
    </bean>
  2. 开启事务的注解驱动

    <!--
    开启事务注解驱动
    将使用@Transcational注解所标识的方法或类中所有的的方法使用事务进行管理
    transaction-manager设置事务管理器的id
    若事务管理器的bean的id为默认transactionManager,则可以省略
    -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
  3. 在需要被事务管理的方法上,添加注解@Transcational

    @Transactional
    public void buyBook(Integer userId,Integer bookId) {
    //查询图书价格
    Integer price=bookDao.getPriceByBookId(bookId);
    //更新图书库存
    bookDao.updateStock(bookId);
    //更新用户余额
    bookDao.updateBalance(userId,price);
    }

@Transcational注解标识的位置

  • 标识在方法上

  • 标识在类上

  • 属性

    • readOnly=true:只读,当操作全为查询时,可以设置

    • timeout= :事务超时,强制回滚

    • noRollbackFor=异常类型/noRollbackForClassName="异常类型":设置异常不回滚

事务属性:事务隔离级别

数据库系统必须具有隔离并发运行各个事务的能力。使它们互不影响,避免各种并发问题。

隔离级别分四种:

  • 读未提交

    • 允许读取数据库还未提交的信息

    • 如B添加了新的数据,还未提交,A可以读取

  • 读已提交

    • 只能读取已提交信息

  • 可重复读

    • 可以多次从一个字段读取到相同的值,即在A执行期间,禁止其他事务对该字段进行更新

  • 串行化

    • 可以多次从一张表读取到相同的行,即在A执行期间,禁止其他事务对该表进行更新。可以避免任何并发问题,但性能十分低下

隔离级别脏读不可重复读幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

数据库对事务隔离级别的支持

隔离级别OracleMySQL
READ UNCOMMITTED ×
READ COMMITTED √(默认)
REPEATABLE READ × √(默认)
SERIALIZABLE

设置隔离级别

@Transcational(isolation=)

事务的传播行为

  • propagation = Propagation.REQUIRED

    • 使用的是调用者的事务

    @Transactional
    public void checkOut(Integer userId, Integer[] bookIds) {
    for (Integer bookId : bookIds) {
    bookService.buyBook(userId,bookId);
    }
    }
  • propagation = Propagation.REQUIRES_NEW

    使用被调用者的事务

    @Transactional(propagation = Propagation.REQUIRED)
    public void buyBook(Integer userId,Integer bookId) {
    //查询图书价格
    Integer price=bookDao.getPriceByBookId(bookId);
    //更新图书库存
    bookDao.updateStock(bookId);
    //更新用户余额
    bookDao.updateBalance(userId,price);
    }

基于xml事务

  1. 步骤同上

  2. 引入依赖

    <!--aop依赖-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.1</version>
    </dependency>
  3. 去掉注解和事务注解驱动

  4. 配置事务通知

     <tx:advice id="tx" transaction-manager="transactionManager">
    <tx:attributes>
    <tx:method name="buyBook"/>
    </tx:attributes>
    </tx:advice>
  5. 配置切入点

    <aop:config>
    <aop:advisor advice-ref="tx" pointcut="execution(* com.zwb.service.impl.*.*(..))"></aop:advisor>
    </aop:config>
  6.  
posted @ 2023-03-10 17:31  H-MADAO  阅读(61)  评论(0)    收藏  举报