3-4 Spring

在这里插入图片描述

文章目录

一、引言


1.1 原生web开发中存在哪些问题?
  • 传统Web开发存在硬编码所造成的过度程序耦合(例如:Service中作为属性Dao对象)。

    ​ (耦合 : 程序之间的调用关系的强弱) (过度程序耦合 : 一个程序修改,另外一个必须要修改)

  • 部分Java EE API较为复杂,使用效率低(例如:JDBC开发步骤)。

  • 侵入性强,移植性差(例如:DAO实现的更换,从Connection到SqlSession)。

    ​ (重复性利用率低,代码修改麻烦)

二、Spring框架


2.1 概念
  • Spring是一个项目管理框架,同时也是一套Java EE解决方案。

    (项目管理 : 对象 , 类 ,调用的管理)

  • Spring是众多优秀设计模式的组合(工厂、单例、代理、适配器、包装器、观察者、模板、策略)。

    (单例 : 直接new出来的对象–>Hungary ,

    到最后时刻再创建对象–>Lazy (存在线程安全问题 , 双重判断解决)

    都只创建一个对象 . )

    (包装者设计模式 : )

  • Spring并未替代现有框架产品,而是将众多框架进行有机整合,简化企业级开发,俗称"胶水框架"。

2.2 访问与下载

官方网站:https://spring.io/

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

三、Spring架构组成


Spring架构由诸多模块组成,可分类为

  • 核心技术:依赖注入,事件,资源,i18n,验证,数据绑定,类型转换,SpEL,AOP
  • 测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient。
  • 数据访问:事务,DAO支持,JDBC,ORM,封送XML。
  • Spring MVC和 Spring WebFlux Web框架。
  • 集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
  • 语言:Kotlin,Groovy,动态语言。
Spring架构组成
在这里插入图片描述

Beans , Core , Context , SpEL 四个核心模块 , 组成了核心容器(Spring的工厂) , Spring的一切功能都要依赖核心容器也就是工厂的支持 , 才能正常运行.

工厂设计模式 ,工厂中用来生产项目中一个个对象 , 通过工厂来管理它所生产的对象, 把项目中的对象的协作关系 , 运行模式调整,优化 , 以达到项目的优化管理

GroupIdArtifactId说明
org.springframeworkspring-beansBeans 支持,包含 Groovy
org.springframeworkspring-aop基于代理的AOP支持
org.springframeworkspring-aspects基于AspectJ 的切面
org.springframeworkspring-context应用上下文运行时,包括调度和远程抽象
org.springframeworkspring-context-support支持将常见的第三方类库集成到 Spring 应用上下文
org.springframeworkspring-core其他模块所依赖的核心模块
org.springframeworkspring-expressionSpring 表达式语言,SpEL
org.springframeworkspring-instrumentJVM 引导的仪表(监测器)代理
org.springframeworkspring-instrument-tomcatTomcat 的仪表(监测器)代理
org.springframeworkspring-jdbc支持包括数据源设置和 JDBC 访问支持
org.springframeworkspring-jms支持包括发送/接收JMS消息的助手类
org.springframeworkspring-messaging对消息架构和协议的支持
org.springframeworkspring-orm对象/关系映射,包括对 JPA 和 Hibernate 的支持
org.springframeworkspring-oxm对象/XML 映射(Object/XML Mapping,OXM)
org.springframeworkspring-test单元测试和集成测试支持组件
org.springframeworkspring-tx事务基础组件,包括对 DAO 的支持及 JCA 的集成
org.springframeworkspring-webweb支持包,包括客户端及web远程调用
org.springframeworkspring-webmvcREST web 服务及 web 应用的 MVC 实现
org.springframeworkspring-webmvc-portlet用于 Portlet 环境的MVC实现
org.springframeworkspring-websocketWebSocket 和 SockJS 实现,包括对 STOMP 的支持
org.springframeworkspring-jclJakarta Commons Logging 日志系统

四、自定义工厂


工厂模式 : 程序代码的设计模式和工厂很像 , 工厂使用流水线生产产品 . 没创建一个对象就像工厂生产一个产品

简单工厂 :

创建一个类–>表示工厂

创建一个方法–>标识流水线

方法的返回值–>要生产的产品

抽象工厂 :

创建一个工厂来生产其他工厂

public class Factory {

​ public 返回值 各种工厂 {

​ return工厂

​ }

}

4.1 配置文件

创建两个类UserDao和UserDaoService

  1. UserDao
package com.qf.dao;

public interface UserDao {
     void deleteUserById (Integer id);
}

UserDaoImpl

package com.qf.dao;

public class UserDaoImpl implements UserDao{
    @Override
    public void deleteUserById(Integer id) {
        System.out.println("dao删除了.................");
    }
}

2.UserService

package com.qf.service;

public interface UserService {
    void deleteUserById (Integer id);
}

​ UserServiceImpl

package com.qf.service;

public class UserServiceImpl implements UserService {
    @Override
    public void deleteUserById(Integer id) {
        System.out.println("service删除了.............");
    }
}

配置配置文件bean.properties.

#需要生产的类
userDao=com.qf.dao.UserDaoImpl
userService=com.qf.service.UserServiceImpl
4.2 工厂类
package com.qf.factory;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * 工厂类
 * 1.加载配置文件 才知道要生产什么对象
 *
 */
public class MyFactory {
    private Properties properties = new Properties();
    public MyFactory() throws IOException {
        InputStream resourceAsStream = MyFactory.class.getResourceAsStream("/bean.properties");
        //properties对象读取了bean.properties配置文件的内容
        properties.load(resourceAsStream);
    }

    /**
     *通用工厂类 ,可以生产各种各样的类,只需要在配置文件中配置
     * 通过name标识获得properties配置文件中的类路径,通过反射生产对应的对象
     */
    public Object getBean(String name) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        //通过name标识获取类路径
        String classpath = properties.getProperty(name);
        //通过反射构建加载类对象
        Class clazz = Class.forName(classpath);
        //反射:获得对象
        return clazz.newInstance();
    }
}

测试

package com.qf;

import com.qf.dao.UserDao;
import com.qf.factory.MyFactory;
import com.qf.service.UserService;

import java.io.IOException;

public class FactoryTest {
    public static void main(String[] args) throws IOException, IllegalAccessException, InstantiationException, ClassNotFoundException {
//        创建工厂对象
        MyFactory factory = new MyFactory();
        //从工厂中获取对象,返回一个Object类型,强转
        UserDao userDao = (UserDao)factory.getBean("userDao");
        userDao.deleteUserById(1);
        UserService userService = (UserService)factory.getBean("userService");
        userService.deleteUserById(1);

    }
}

运行结果

dao删除了.................
service删除了.............

优化

//工厂类中"/bean.properties"是写死的,我们将其改成动态的
//1.在 public MyFactory() 中添加参数,补充无参构造方法

private Properties properties = new Properties();

public MyFactory(){}//无参构造
public MyFactory(String config) throws IOException {//添加参数
        													     //这里修改为参数
	InputStream resourceAsStream = MyFactory.class.getResourceAsStream(config);
	properties.load(resourceAsStream);
}
//测试类中创建工厂对象的时候,添加配置文件路径,作为参数(config)
MyFactory factory = new MyFactory("/bean.properties");

五、构建Maven项目


5.1 新建项目
使用IDEA打开已创建的文件夹目录
在这里插入图片描述
|
5.2 选择Maven目录
选择Maven项目
在这里插入图片描述
5.3 GAV坐标
GAV坐标
在这里插入图片描述

六、Spring环境搭建


6.1 pom.xml中引入Spring常用依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation=
         "http://maven.apache.org/POM/4.0.0 
          http://maven.apache.org/xsd/maven-4.0.0.xsd">
  
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.qf</groupId>
    <artifactId>hello-spring</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- Spring常用依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>
    </dependencies>
</project>

6.2 创建Spring配置文件

命名无限制,约定俗成命名有:spring-context.xml、applicationContext.xml、beans.xml

PS : Spring配置文件有自己的规则 叫协议规定 Schema

<?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">
   
</beans>

七、Spring工厂编码


定义目标Bean类型

public class MyClass{
    public void show(){
        System.out.println("HelloWorld");
    }
}

spring-context.xml中的< beans >内部配置bean标签

<!-- 配置实例(id:“唯一标识”  class="需要被创建的目标对象全限定名") -->
<bean id="mc" class="com.qf.spring.part1.factory.MyClass" />

调用Spring工厂API(ApplicationContext接口)

public class TestFactory{
    /**
     * 程序中的对象都交由Spring的ApplicationContext工厂进行创建。
     */
    public static void main(String[] args){
        //1. 读取配置文件中所需创建的bean对象,并获得工厂对象
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-context.xml");
        //2. 通过id获取bean对象
		MyClass mc = (MyClass) ctx.getBean("mc");
        //3. 使用对象
		mc.show();
    }
}

八、依赖与配置文件详解


Spring框架包含多个模块,每个模块各司其职,可结合需求引入相关依赖Jar包实现功能。

8.1 Spring依赖关系
Spring常用功能的Jar包依赖关系
在这里插入图片描述
8.2 schema规范

schema:一个一个xsd文件—>xml的规范定义

配置文件中的顶级标签中包含了语义化标签的相关信息

  • xmlns:语义化标签所在的命名空间。
  • xmlns:xsi:XMLSchema-instance 标签遵循Schema标签标准。
  • xsi:schemaLocation:xsd文件位置,用以描述标签语义、属性、取值范围等。

九、IOC(Inversion of Control )控制反转【重点


Inverse Of Controll:控制反转

反转了依赖关系的满足方式,由之前的自己创建依赖对象,变为由工厂推送。(变主动为被动,即反转)

解决了具有依赖关系的组件之间的强耦合,使得项目形态更加稳健

9.1 项目中强耦合问题
public class UserDAOImpl implements UserDAO{....}
public class UserServiceImpl implements UserService {
    // !!!强耦合了UserDAOImpl!!!,使得UserServiceImpl变得不稳健!!
    private UserDAO userDAO= new UserDAOImpl();
    @Override
    public User queryUser() {
        return userDAO.queryUser();
    }
    ....
}

当前状态下的依赖满足 ,存在很大的问题, 比如,service需要依赖dao,那我们就在service中new一个dao , servlet需要依赖service ,我们就在servlet中new一个service .

存在的问题 : 强耦合 !

强耦合 : 在一个类中明确引用另外一个类 叫做强耦合

// 不引用任何一个具体的组件(实现类),在需要其他组件的位置预留存取值入口(set/get)
public class UserServiceImpl implements UserService {
    // !!!不再耦合任何DAO实现!!!,消除不稳健因素!!
    private UserDAO userDAO;
    // 为userDAO定义set/get,允许userDAO属性接收spring赋值
    //Getters And Setters
    @Override
    public User queryUser() {
        return userDAO.queryUser();
    }
    ....
}
<bean id="userDAO" class="com.qf.spring.part1.injection.UserDaoImpl"></bean>
<!-- UserServiceImpl组件 -->
<bean id="userService" class="com.qf.spring.part1.injection.UserServiceImpl">
    <!-- 由spring为userDAO属性赋值,值为id="userDAO"的bean -->
    <property name="userDAO" ref="userDAO"/>
</bean>

此时,如果需要更换其他UserDAO实现类,则UserServiceImpl不用任何改动!

则此时的UserServiceImpl组件变得更加稳健!

Bean : 每一个Bean标签对应一个对象

id : 对象的

class : 对象的

十、DI(Dependency Injection)依赖注入【重点


10.1 概念

在工厂的配置文件中为属性赋值, , 称之为注入

在Spring创建对象的同时,为其属性赋值,称之为依赖注入。

10.2 Set注入

创建对象时,Spring工厂会通过Set方法为对象的属性赋值。

10.2.1 定义目标Bean类型
public class User {
    private Integer id;
    private String password;
    private String sex;
    private Integer age;
    private Date bornDate;
    private String[] hobbys;
    private Set<String> phones;
    private List<String> names;
    private Map<String,String> countries;
    private Properties files;
    //Getters And Setters
}
10.2.2 基本类型 + 字符串类型 + 日期类型
<bean id="u1" class="com.qf.spring.part1.injection.User">
    <!--base field-->
    <property name="id" value="1001" />
    <property name="password" value="123456" />
    <property name="sex" value="male" />
    <property name="age" value="20" />
    <property name="bornDate" value="1990/1/1" /><!--注意格式"/"-->
</bean>
10.2.3 容器类型
<bean id="u1" class="com.qf.spring.part1.injection.User">	
	<!--Array-->
    <property name="hobbys">
        <array>
            <value>Run</value>
            <value>Swim</value>
            <value>Climb</value>
        </array>
    </property>

    <!--Set-->
    <property name="phones">
        <set>
            <value>13777777777</value>
            <value>13888888888</value>
            <value>13999999999</value>
        </set>
    </property>

    <!--List-->
    <property name="names">
        <list>
            <value>tom</value>
            <value>jack</value>
            <value>marry</value>
        </list>
    </property>

    <!--Map-->
    <property name="countries">
        <map>
            <entry key="CN" value="China" />
            <entry key="US" value="America" />
            <entry key="KR" value="Korea" />
        </map>
    </property>
    
    <!--Properties-->
    <property name="files">
        <props>
            <prop key="first">One</prop>
            <prop key="second">Two</prop>
            <prop key="third">Three</prop>
        </props>
    </property>
</bean>
10.2.4 自建类型
<!--次要bean,被作为属性-->
<bean id="addr" class="com.qf.spring.part1.injection.Address">
    <property name="position" value="北京市海淀区" />
    <property name="zipCode" value="100001" />
</bean>

<!--主要bean,操作的主体-->
<bean id="u2" class="com.qf.spring.part1.injection.User">
    <property name="address" ref="addr" /><!--address属性引用addr对象-->
</bean>
<!--次要bean,被作为属性-->
<bean id="userDao" class="com.qf.spring.part1.injection.UserDaoImpl" />

<!--主要bean,操作的主体-->
<bean id="userService" class="com.qf.spring.part1.injection.UserServiceImpl">
    <property name="ud" ref="userDao" /><!--ud属性引用userDao对象-->
</bean>
10.3 构造注入【了解】

创建对象时,Spring工厂会通过构造方法为对象的属性赋值。

10.3.1 定义目标Bean类型
public class Student {
    private Integer id;
    private String name;
    private String sex;
    private Integer age;
  
    //Constructors
  	public Student(Integer id , String name , String sex , Integer age){
      	this.id = id;
    	this.name = name;
  	    this.sex = sex;
	    this.age = age;
    }
}
10.3.2 注入
 <!--构造注入-->
<bean id="u3" class="com.qf.zcg.spring.day1.t2.ioc.Student">
    <constructor-arg name="id" value="1234" /> <!-- 除标签名称有变化,其他均和Set注入一致 -->
    <constructor-arg name="name" value="tom" />
    <constructor-arg name="age" value="20" />
    <constructor-arg name="sex" value="male" />
</bean>

构造注入 的缺点 : 构造方法有4个参数, 那么就必须注入4个值

如果想注入3个值 , 就必须在另外写一个含有3个参数的构造方法

10.4 自动注入【了解】

不用在配置中 指定为哪个属性赋值,及赋什么值.

由spring自动根据某个 “原则” ,在工厂中查找一个bean,为属性注入属性值

原则 : 1.必须为单例 2.bean的id和属性名必须一致

public class UserServiceImpl implements UserService {
    private UserDAO userDAO;
    //Getters And Setters
    ....
}
<bean id="userDao" class="com.qf.spring.part1.injection.UserDaoImpl" />
<!-- 为UserServiceImpl中的属性基于类型自动注入值 -->
<bean id="userService" class="com.qf.spring.part1.injection.UserServiceImpl" autowire="byType"></bean>
<bean id="userDao" class="com.qf.spring.part1.injection.UserDaoImpl" />
<!-- 为UserServiceImpl中的属性基于类型自动注入值 -->
<bean id="userService" class="com.qf.spring.part1.injection.UserServiceImpl" autowire="byName"></bean>

autowire=“byName”

autowire=“byType”

十一、Bean细节


11.1 控制简单对象的单例、多例模式

简单对象 :直接new

简单对象 : 多用单例

配置< bean scope=“singleton | prototype” />

<!--
	singleton(默认):每次调用工厂,得到的都是同一个对象。
	prototype:每次调用工厂,都会创建新的对象。
-->
<bean id="mc" class="com.qf.zcg.spring.day1.t1.basic.MyClass" scope="singleton" /> 
  • 注意:需要根据场景决定对象的单例、多例模式。

  • 可以共用:Service、DAO、SqlSessionFactory(或者是所有的工厂)。

  • 不可共用:Connection、SqlSession、ShoppingCart。

    单例 : bean在创建时默认是单例 ,工厂在创建每一个bean对象时都只创建一次 , 无论调用了多少次 , 这个对象始终是同一个对象 .

    单例singleton创建时间 : spring环境启动时创建

    多例:每次调用工厂,都会创建新的对象。

    多例prototype创建时间 : 获得对象的时候创建

11.2 FactoryBean创建复杂对象【了解】

复杂对象 : 不是通过构造方法创建的对象

FactoryBean接口 , 留给创建复杂对象的接口

作用:让Spring可以创建复杂对象、或者无法直接通过反射创建的对象。

FactoryBean解决复杂对象创建
在这里插入图片描述
11.2.1 实现FactoryBean接口
接口方法描述
在这里插入图片描述
  • 注意:isSingleton方法的返回值,需根据所创建对象的特点决定返回true/false。

  • 例如:Connection 不应该被多个用户共享,返回false。

  • 例如:SqlSessionFactory 重量级资源,不该过多创建,返回true。

    getObject获得复杂对象

    getObjectType获得类对象

    isSingleton是否单例

    1.创建一个类实现FactoryBean接口

    2.重写3个方法getObject , getObjectType , isSingleton

    3.调用创建

11.2.2 配置spring-context.xml
配置与获取方式
在这里插入图片描述
11.2.3 特例
获取FactoryBean接口的实现类对象,而非getObject()所生产的对象。
在这里插入图片描述

十二、Spring工厂特性


12.1 饿汉式创建优势

工厂创建之后,会将Spring配置文件中的所有对象都创建完成(饿汉式)。

提高程序运行效率。避免多次IO,减少对象创建时间。(概念接近连接池,一次性创建好,使用时直接获取)

12.2 生命周期方法
  • 自定义初始化方法:在配置文件添加“init-method”属性,Spring则会在创建对象之后,调用此方法。

  • 自定义销毁方法:在配置文件添加“destroy-method”属性,Spring则会在销毁对象之前,调用此方法。

  • 销毁:运行环境(Spring容器, 工厂) 的close()方法被调用之后,Spring会毁掉所有已创建的单例对象。

  • 分类:Singleton对象由Spring容器销毁、Prototype对象由JVM销毁。

12.3 生命周期注解

初始化注解、销毁注解

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@PostConstruct //初始化 
public void init(){
    System.out.println("init method executed");
}

@PreDestroy //销毁
public void destroy(){
    System.out.println("destroy method executed");
}
12.4 生命周期阶段

**单例bean:**singleton

随工厂启动创建对象 ==》 调用构造方法 ==》 set方法(注入值) ==》 init(初始化) ==》 构建完成 ==》随工厂关闭销毁

**多例bean:**prototype

被使用时创建(getBean之后) ==》 构造方法 ==》 set方法(注入值) ==》 init(初始化) ==》 构建完成 ==》JVM垃圾回收销毁

以上工程DIYSpringFactory

十三、代理设计模式

13.1 概念

只有在这个业务中使用 , 不会在别的业务中使用 , 叫核心功能

将核心功能与辅助功能(事务、日志、性能监控代码)分离,达到核心业务功能更纯粹、辅助业务功能可复用

功能分离
在这里插入图片描述
13.2 静态代理设计模式

通过代理类的对象,为原始类的对象(目标类的对象)添加辅助功能,更容易更换代理实现类、利于维护。

静态代理
在这里插入图片描述

原始业务类 :FangDongService , FangDongServiceImpl

package com.qf;

public interface FangDongService {
    void zufang();
}
package com.qf;

public class FangDongServiceImpl implements FangDongService {
    @Override
    public void zufang() {
		//辅助功能
        System.out.println("发布信息");
        System.out.println("带人看房");
        //核心功能
        System.out.println("签合同");
        System.out.println("收房租");
    }
}

创建代理类 : FangDongProxy , 分离业务类辅助功能.

package com.qf;
//代理类
//代理类implements实现原始业务类,保持功能一致,不能原始业务类是租房,代理类变成了卖房
public class FangDongProxy implements FangDongService {
    private FangDongService fangDongService = new FangDongServiceImpl();
    @Override
    public void zufang() {
        //分离辅助功能给代理类
        System.out.println("发布信息");
        System.out.println("带人看房");
        //还可以调用主方法
        fangDongService.zufang();
    }
}

分离后的业务类 : FangDongServiceImpl

package com.qf;

public class FangDongServiceImpl implements FangDongService {
    @Override
    public void zufang() {
        //分离出辅助功能,保持了功能单一只能原则,代码纯粹.
        //核心功能
        System.out.println("签合同");
        System.out.println("收房租");
    }
}

测试 :

package com.qf.test;

import com.qf.FangDongProxy;

public class ProxyTest {
    public static void main(String[] args) {
        FangDongProxy fangDongProxy = new FangDongProxy();
        fangDongProxy.zufang();
    }
}

测试结果 :

发布信息
带人看房
签合同
收房租

此时 , 即需要辅助功能又需要核心功能 ,我们使用代理类 , 只需要使用核心功能时 , 我们使用业务类

  • 代理类 = 实现原始类相同接口 + 添加辅助功能 + 调用原始类的业务方法。
  • 代理类和原始业务类功能要一致!
  • 静态代理的问题
    • 代理类数量过多,不利于项目的管理。
    • 多个代理类的辅助功能代码冗余,修改时,维护性差。
13.3 动态代理设计模式

动态创建代理类的对象,为原始类的对象添加辅助功能。

用代理 , 而不用维护代理

13.3.1 JDK动态代理实现(基于接口)
//目标
final OrderService os = new OrderServiceImpl();
//额外功能
InvocationHandler handler = new InvocationHandler(){//1.设置回调函数(额外功能代码)
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
        System.out.println("start...");
        method.invoke(os, args);
         System.out.println("end...");
        return null;
    }
};
//2.创建动态代理类
Object proxyObj = Proxy.newProxyInstance(ClassLoader , Interfaces , InvocationHandler);

实例测试 :

package com.qf.test;

import com.qf.FangDongService;
import com.qf.FangDongServiceImpl;
import org.junit.Test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DongtaiProxyTest {
    @Test
    public void testJDK(){
        //代理目标
        FangDongService fangDongService = new FangDongServiceImpl();
        //通过反射包下的InvocationHandler创建匿名内部类
        InvocationHandler ih = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //辅助功能
                System.out.println("发布信息");
                System.out.println("带人看房");
                //核心功能
                fangDongService.zufang();
                return null;
            }
        };
        //生成动态代理类
        // 通过newProxyInstance,有三个参数,第一个类加载器,第二个获取业务类所有接口,创建出的InvocationHandler对象
        FangDongService proxy = (FangDongService)Proxy.newProxyInstance(DongtaiProxyTest.class.getClassLoader(),
                fangDongService.getClass().getInterfaces(), ih);
        proxy.zufang();
    }
}

测试结果 :

发布信息
带人看房
签合同
收房租
13.3.2 CGlib动态代理实现(基于继承)
final OrderService os = new OrderServiceImpl();
Enhancer cnh = new Enhancer();//1.创建字节码增强对象
enh.setSuperclass(os.getClass());//2.设置父类(等价于实现原始类接口)
enh.setCallback(new InvocationHandler(){//3.设置回调函数(额外功能代码)
    @Override
    public Object invoke(Object proxy , Method method, Object[] args) throws Throwable{
        System.out.println("start...");
        Object ret = method.invoke(os,args);
        System.out.println("end...");
        return ret;
    }
});
OrderService proxy = (OrderService)enh.create();//4.创建动态代理类
proxy,createOrder();
    @Test
    public void testCGlib(){
        //代理目标
        FangDongService fangDongService = new FangDongServiceImpl();
        //创建CGlib核心对象
        Enhancer enhancer = new Enhancer();
        //设置enhancer的父类,通过继承实现功能一致
        enhancer.setSuperclass(FangDongServiceImpl.class);
        //setCallback(new Invoke)创建匿名内部类 添加实现功能
        enhancer.setCallback(new org.springframework.cglib.proxy.InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                //辅助功能
                System.out.println("发布信息");
                System.out.println("带人看房");
                //核心功能
                fangDongService.zufang();
                return null;
            }
        });
        //创建动态代理类
        FangDongServiceImpl proxy = (FangDongServiceImpl)enhancer.create();
        proxy.zufang();
    }
}

测试结果 :

发布信息
带人看房
签合同
收房租

以上代码过于复杂,抽象,正在应用在开发中的是AOP

十四、面向切面编程【重点


14.1 概念

AOP(Aspect Oriented Programming),即面向切面编程,是一种编程思想 ,利用一种称为"横切"的技术,剖开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

14.2 AOP开发术语
  • 连接点(Joinpoint):连接点是程序类中客观存在的方法,可被Spring拦截并切入内容。

  • 切入点(Pointcut):被Spring切入连接点。

  • 通知、增强(Advice):可以为切入点添加额外功能,分为:前置通知、后置通知、异常通知、环绕通知等。

  • 目标对象(Target):代理的目标对象

  • 引介(Introduction):一种特殊的增强,可在运行期为类动态添加Field和Method。

  • 织入(Weaving):把通知应用到具体的类,进而创建新的代理类的过程。

  • 代理(Proxy):被AOP织入通知后,产生的结果类。

  • 切面(Aspect):由切点和通知组成,将横切逻辑织入切面所指定的连接点中。

14.3 作用

Spring的AOP编程即是通过动态代理类为原始类的方法添加辅助功能。

14.4 环境搭建

引入AOP相关依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>

spring-context.xml引入AOP命名空间

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       ">
</beans>
14.5 开发流程

定义原始类

package com.qf.service;

import com.qf.entity.User;

import java.util.List;

public interface UserService {
    public List<User> queryUsers();
    public Integer updateUser();
    public Integer addUser();
    public Integer deleteUser();
}
package com.qf.service;

import com.qf.entity.User;

import java.util.ArrayList;
import java.util.List;

public class UserServiceImpl implements UserService {
    @Override
    public List<User> queryUsers() {
//        System.out.println("事物控制");
//        System.out.println("日志管理");
        System.out.println("查询");
        return new ArrayList<>();
    }

    @Override
    public Integer updateUser() {
//        System.out.println("事物控制");
//        System.out.println("日志管理");
        System.out.println("更新");
        return 1;
    }

    @Override
    public Integer addUser() {
//        System.out.println("事物控制");
//        System.out.println("日志管理");
        System.out.println("添加");
        return 1;
    }

    @Override
    public Integer deleteUser() {
//        System.out.println("事物控制");
//        System.out.println("日志管理");
        System.out.println("删除");
        return 1;
    }
}

定义通知类(添加额外功能)

package com.qf.advice;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

/**
 * 前置通知类
 * 定义一个类,实现MethodBeforeAdvice接口
 * 在接口中实现额外功能(非业务核心功能)
 * 前置通知,表示在核心功能之前加载
 */
public class MyAdviceBefore implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("事物控制");
        System.out.println("日志管理");
    }
}

定义bean标签

<!--    定义目标bean : 原始业务-->
    <bean id="userService" class="com.qf.service.UserServiceImpl"></bean>
<!--    定义通知bean : 额外功能-->
    <bean id="myAdvice" class="com.qf.advice.MyAdviceBefore"></bean>

定义切入点(PointCut)

形成切面(Aspect)

整个<aop:config>叫做编织

<aop:config>
    <!--切入点-->
        <aop:pointcut id="laochen" expression="execution(* queryUsers())"/>
</aop:config>
<aop:config>
    <!--组装切面 -->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="laochen"/>
</aop:config>

测试 :

    @Test
    public void testAOP(){
        //开启工厂
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");
        //通过目标Bean的id获取动态代理对象,使用目标接口接收对象
        UserService proxy = (UserService) context.getBean("userService");
        System.out.println(proxy.getClass());
        proxy.queryUsers();
        System.out.println("***********************");
        proxy.updateUser();

    }
}

测试结果 :

class com.sun.proxy.$Proxy7
事物控制
日志管理
查询
***********************
更新

class com.sun.proxy.$Proxy7—>动态生成的代理类

proxy.queryUsers();打印出了额外功能和核心功能

proxy.updateUser();方法没有配置通知 , 所以只打印出了核心功能

ps : 只需要修改通知类中的额外功能 , 所有调用到额外功能的都随之更改 .

14.6 AOP小结
  • 通过AOP提供的编码流程,更便利的定制切面,更方便的定制了动态代理。

  • 彻底解决了辅助功能冗余的问题;

  • 业务类中职责单一性,维护成本降到最低;

  • 辅助功能也有很好的复用性。

14.7 通知类【可选】

定义通知类,达到通知效果

前置通知:MethodBeforeAdvice

(父类)后置通知:AfterAdvice ://在核心之后执行,无论核心中是否有异常

(子类)后置通知:AfterReturningAdvice //有异常不执行,方法会因异常而结束,无返回值

(子类)异常通知:ThrowsAdvice//核心之后执行,只在核心抛出异常执行

环绕通知:MethodInterceptor

后置通知:AfterReturningAdvice :

创建通知类

package com.qf.advice;

import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

/**
 * 后置通知类,核心方法有异常不执行。
 */
public class MyAdviceAfter implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("我在核心之后执行了,核心没异常");
    }
}

package com.qf.advice;

import org.springframework.aop.ThrowsAdvice;

/**
 * 后置通知类
 * 在核心之后执行,核心抛出异常时执行
 */
public class MyThrowsAdvice implements ThrowsAdvice {
    public void afterThrowing (Exception ex){
        System.out.println("我在核心之后执行,核心有异常我执行");
    }

}

定义通知bean

 <bean id="afterReturningAdvice" class="com.qf.advice.MyAdviceAfter"></bean>

配置切入点

<aop:pointcut id="laochen2" expression="execution(* updateUser())"/>

配置切面

 <aop:advisor advice-ref="afterReturningAdvice" pointcut-ref="laochen2"/>

测试类 : 综合

package com.qf.test;

import com.qf.fangdong.FangDongService;
import com.qf.fangdong.FangDongServiceImpl;
import com.qf.service.UserService;
import org.junit.Test;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DongtaiProxyTest {
    @Test
    public void testJDK(){
        //代理目标
        FangDongService fangDongService = new FangDongServiceImpl();
        //通过反射包下的InvocationHandler创建匿名内部类
        InvocationHandler ih = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //辅助功能
                System.out.println("发布信息");
                System.out.println("带人看房");
                //核心功能
                fangDongService.zufang();
                return null;
            }
        };
        //生成动态代理类
        // 通过newProxyInstance,有三个参数,第一个类加载器,第二个获取业务类所有接口,创建出的InvocationHandler对象
        FangDongService proxy = (FangDongService)Proxy.newProxyInstance(DongtaiProxyTest.class.getClassLoader(),
                fangDongService.getClass().getInterfaces(), ih);
        proxy.zufang();
    }
    @Test
    public void testCGlib(){
        //代理目标
        FangDongService fangDongService = new FangDongServiceImpl();
        //创建CGlib核心对象
        Enhancer enhancer = new Enhancer();
        //设置enhancer的父类,通过继承实现功能一致
        enhancer.setSuperclass(FangDongServiceImpl.class);
        //setCallback(new Invoke)创建匿名内部类 添加实现功能
        enhancer.setCallback(new org.springframework.cglib.proxy.InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                //辅助功能
                System.out.println("发布信息");
                System.out.println("带人看房");
                //核心功能
                fangDongService.zufang();
                return null;
            }
        });
        //创建动态代理类
        FangDongServiceImpl proxy = (FangDongServiceImpl)enhancer.create();
        proxy.zufang();
    }
    @Test
    public void testAOP(){
        //开启工厂
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");
        //通过目标Bean的id获取动态代理对象,使用目标接口接收对象
        UserService proxy = (UserService) context.getBean("userService");
        System.out.println(proxy.getClass());
        proxy.queryUsers();
        System.out.println("***********************");
        proxy.updateUser();
        System.out.println("***********************");
        proxy.addUser();
        System.out.println("************************");
        proxy.deleteUser();


    }
}

测试结果

class com.sun.proxy.$Proxy7
事物控制
日志管理
查询
***********************
更新
我在核心之后执行了,核心没异常
***********************
添加
我在核心之后执行,核心有异常我执行

java.lang.NullPointerException: test 空指针

***********************
开始!!!!!!!!!!!
删除
结束!!!!!!!!!!!!
14.8 通配切入点

根据表达式通配切入点

<!--匹配参数-->
<aop:pointcut id="myPointCut" expression="execution(* *(com.qf.aaron.aop.basic.User))" />
<!--匹配方法名(无参)-->
<aop:pointcut id="myPointCut" expression="execution(* save())" />
<!--匹配方法名(任意参数)-->
<aop:pointcut id="myPointCut" expression="execution(* save(..))" />
<!--匹配返回值类型-->
<aop:pointcut id="myPointCut" expression="execution(com.qf.aaron.aop.basic.User *(..))" />
<!--匹配类名-->
<aop:pointcut id="myPointCut" expression="execution(* com.qf.aaron.aop.basic.UserServiceImpl.*(..))" />
<!--匹配包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.qf.aaron.aop.basic.*.*(..))" />
<!--匹配包名、以及子包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.qf.aaron.aop..*.*(..))" />
14.9 JDK和CGLIB选择
  • spring底层,包含了jdk代理和cglib代理两种动态代理生成机制
  • 基本规则是:目标业务类如果有接口则用JDK代理,没有接口则用CGLib代理

Spring的底层设置了如下的判断

是否在在<aop:config >后面配置proxy-target-class=“ture” 表示强制使用CGlib

业务类是否有接口 , 如果有接口 ,使用JDK

两个条件满足一个即可

@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class:Either an interface or a target is required for proxy creation.");
			}
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}
14.10 后处理器
  • spring中定义了很多后处理器;

  • 每个bean在创建完成之前 ,都会有一个后处理过程,即再加工,对bean做出相关改变和调整;

  • spring-AOP中,就有一个专门的后处理器,负责通过原始业务组件(Service),再加工得到一个代理组件。

常用后处理器
在这里插入图片描述
14.10.1 后处理器定义
/**
 * 定义bean后处理器
 * 作用:在bean的创建之后,进行再加工
 */
public class MyBeanPostProcessor implements BeanPostProcessor{

    /**
     * 在bean的init方法之前执行
     * @param bean  原始的bean对象
     * @param beanName
     * @return
     * @throws BeansException
     */
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("后处理器 在init之前执行~~~"+bean.getClass());
        return bean;
    }
	/**
     * 在bean的init方法之后执行
     * @param bean  postProcessBeforeInitialization返回的bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("后处理器 在init之后执行~~~"+bean.getClass());
        return bean;// 此处的返回是 getBean() 最终的返回值
    }
}
14.10.2 配置后处理器
<!-- 配置后处理器,将对工厂中所有的bean声明周期进行干预 -->
<bean class="com.qianfeng.beanpostprocessor.MyBeanPostProcessor"></bean>
14.10.3 bean生命周期

构造 》 注入属性 满足依赖 》 后处理器前置过程 》 初始化 》后处理器后置过程 》 返回 》 销毁

14.10.4 动态代理源码(了解)
// AbstractAutoProxyCreator是 AspectJAwareAdvisorAutoProxyCreator的父类
// 该后处理器类中的 wrapIfNecessary方法即动态代理生成过程
AbstractAutoProxyCreator#postProcessAfterInitialization(Object bean, String beanName){
    if (!this.earlyProxyReferences.contains(cacheKey)) {
        // 开始动态定制代理
        return wrapIfNecessary(bean, beanName, cacheKey);
   	}
}

十五、Spring + MyBatis【重点


15.1 配置数据源

将数据源配置到项目中

15.1.1 引入jdbc.properties配置文件
#jdbc.properties
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=123456
15.1.2 整合Spring配置文件和properties配置文件
<!--spring-context.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       ">

    <!--配置文件参数化(参数占位符)-->
	<context:property-placeholder location="classpath:jdbc.properties" />
    
    <!--与PooledDataSource集成(二选一)-->
    <bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
        <property name="driver" value="${driverClass}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
    </bean>

    <!--与DruidDataSource集成(二选一)-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!--基本配置-->
        <property name="driverClassName" value="${jdbc.driverClass}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</bean>
15.1.3 Druid连接池可选参数
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <!--基本配置-->
    <property name="driverClassName" value="${jdbc.driverClass}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>

    <!-- 配置初始化大小、最小、最大 -->
    <property name="initialSize" value="${jdbc.init}"/>
    <property name="minIdle" value="${jdbc.minIdle}"/>
    <property name="maxActive" value="${jdbc.maxActive}"/>

    <!-- 配置获取连接等待超时的时间 -->
    <property name="maxWait" value="60000"/>

    <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
    <property name="timeBetweenEvictionRunsMillis" value="60000"/>

    <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
    <property name="minEvictableIdleTimeMillis" value="300000"/>
</bean>
15.1.4 Druid监控中心
<!--web.xml-->
<servlet>
    <servlet-name>DruidStatView</servlet-name>
    <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>DruidStatView</servlet-name>
    <url-pattern>/druid/*</url-pattern>
</servlet-mapping>
15.1.5 测试监控中心

配置tomcat,并访问protocol://ip:port/project/druid/index.html

15.2 整合MyBatis

将 SqlSessionFactory、DAO、Service 配置到项目中

15.2.1 导入依赖
<!-- spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>

<!-- spring+mybatis集成依赖 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.1</version>
</dependency>
15.2.2 配置SqlSessionFactory
<!-- 工厂bean:生成SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!-- 注入连接池 -->
    <property name="dataSource" ref="dataSource"></property>
    <!-- 注入dao-mapper文件信息 ,如果映射文件和dao接口 同包且同名,则此配置可省略-->
    <property name="mapperLocations">
        <list>
            <value>classpath:com/qf/spring/dao/*.xml</value>
        </list>
    </property>
    <!-- 为 dao-mapper文件中的实体 定义缺省包路径 
		如:<select id="queryAll" resultType="User"> 中 User类可以不定义包
    -->
    <property name="typeAliasesPackage" value="com.qf.entity"></property>
</bean>
15.2.3 配置MapperScannerConfigurer

管理DAO实现类的创建,并创建DAO对象,存入工厂管理

  • 扫描所有DAO接口,去构建DAO实现

  • 将DAO实现存入工厂管理

  • DAO实现对象在工厂中的id是:“首字母小写的-接口的类名”,

    例如:UserDAO==>userDAO , OrderDAO==>orderDAO

<!-- mapperScannerConfigurer -->
<bean id="mapperScannerConfigurer9" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
   	<!-- dao接口所在的包  如果有多个包,可以用逗号或分号分隔 
  		<property name="basePackage" value="com.a.dao,com.b.dao"></property>
   	-->
    <property name="basePackage" value="com.qf.spring.dao"></property>
    <!-- 如果工厂中只有一个SqlSessionFactory的bean,此配置可省略 -->
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
15.2.4 配置Service
<bean id="userService" class="com.qf.spring.service.UserServiceImpl">
    <!-- 注意ref中的值是对应DAO接口的首字母小写的接口名 -->
	<property name="userDAO" ref="userDAO"></property>
</bean>

十六、事务【重点


16.1 配置DataSourceTransactionManager

事务管理器,其中持有DataSource,可以控制事务功能(commit,rollback等)。

<!-- 1. 引入一个事务管理器,其中依赖DataSource,借以获得连接,进而控制事务逻辑 -->
<bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

注意:DataSourceTransactionManager 和 SqlSessionFactoryBean 要注入同一个DataSource的Bean,否则事务控制失败!!!

16.2 配置事务通知

基于事务管理器,进一步定制,生成一个额外功能:Advice。

此Advice可以切入任何需要事务的方法,通过事务管理器为方法控制事务。

事物要在核心之前开启 , 核心之后结束 , 所以配置环绕通知 .

<tx:advice id="txManager" transaction-manager="tx">
	<!--事物属性-->
    <tx:attributes>
        <!--可以为不同的业务方法,定制不同的事物属性-->
        <!-- 以User结尾的方法,切入此方法时,采用对应事务实行-->
        <tx:method name="*User" rollback-for="Exception"/>
        <!-- 以query开头的方法,切入此方法时,采用对应事务实行-->
        <tx:method name="query*" propagation="SUPPORTS"/>
        <!-- 剩余所有方法 -->
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>
16.3 事务属性
16.3.1 隔离级别
16.3.1.1 概念

isolation 隔离级别

当多事物并发时 , 事物之间是有隔离的 , 隔离级别从小到大相隔越来越远 , 隔离级别越小 , 事物之间共享的数据就多 , 并发性好 , 但是事物之间相互干扰 , 安全性低 .防止同理

隔离级别由低到高为:read-uncommited < read-commited < repeatable-read < serialized-read

名称描述
default(默认值)(采用数据库的默认的设置) (建议)
read-uncommited读未提交
read-commited读提交 (Oracle数据库默认的隔离级别)
repeatable-read可重复读 (MySQL数据库默认的隔离级别)
serialized-read序列化读

隔离级别由低到高为:read-uncommited < read-commited < repeatable-read < serialized-read

16.3.1.2 特性
  • 安全性:级别越高,多事务并发时,越安全。因为共享的数据越来越少,事务间彼此干扰减少。

  • 并发性:级别越高,多事务并发时,并发越差。因为共享的数据越来越少,事务间阻塞情况增多。

16.3.1.3 并发问题

事务并发时的安全问题

问题描述
脏读一个事务读取到另一个事务还未提交的数据 ( 隔离级别过低,共享的数据太多 )。大于等于 read-commited 可防止
不可重复读一个事务内多次读取一行数据的相同内容,其结果不一致。大于等于 repeatable-read 可防止(更新中)
幻影读一个事务内多次读取一张表中的相同内容,其结果不一致。serialized-read 可防止(增删中)
16.3.2 传播行为

propagation传播行为

当涉及到事务嵌套(Service调用Service)时,可以设置:

  • SUPPORTS = 不存在外部事务,则不开启新事务;存在外部事务,则合并到外部事务中。(适合查询)

  • REQUIRED = 不存在外部事务,则开启新事务;存在外部事务,则合并到外部事务中。 (默认值)(适合增删改)

16.3.3 读写性

readonly 读写性

  • true:只读,可提高查询效率。(适合查询)

  • false:可读可写。 (默认值)(适合增删改)

16.3.4 事务超时

timeout事务超时时间

当前事务所需操作的数据被其他事务占用,则等待。

  • 100:自定义等待时间100(秒)。
  • -1:由数据库指定等待时间,默认值。(建议)
16.3.5 事务回滚

rollback-for 回滚属性

  • 如果事务中抛出 RuntimeException,则自动回滚

  • 如果事务中抛出 CheckException(非运行时异常 Exception),不会自动回滚,而是默认提交事务

  • 处理方案 : 将CheckException转换成RuntimException上抛,或 设置 rollback-for=“Exception”

16.4 编织

将事务管理的Advice 切入需要事务的业务方法中

<aop:config>
	<aop:pointcut expression="execution(* com.qf.spring.service.UserServiceImpl.*(..))" id="pc"/>
    <!-- 组织切面 -->
    <aop:advisor advice-ref="txManager" pointcut-ref="pc"/>
</aop:config>

十七、注解开发


注解的核心价值 : 简化开发

17.1 声明bean

用于替换自建类型组件的 <bean…>标签;可以更快速的声明bean

  • @Service 业务类专用
    @Repository dao实现类专用
    @Controller web层专用

  • @Component 通用

  • @Scope 用户控制bean的创建模式

// @Service说明 此类是一个业务类,需要将此类纳入工厂  等价替换掉 <bean class="xxx.UserServiceImpl">
// @Service默认beanId == 首字母小写的类名"userServiceImpl"
// @Service("userService") 自定义beanId为"userService"
@Service //声明bean,且id="userServiceImpl"
@Scope("singleton") //声明创建模式,默认为单例模式 ;@Scope("prototype")即可设置为多例模式
public class UserServiceImpl implements UserService {
 	...   
}
17.2 注入(DI)

用于完成bean中属性值的注入

  • @Autowired 基于类型自动注入
  • @Resource 基于名称自动注入 , 想找属性名 , 在找属性名的数据类型
  • @Qualifier(“userDAO”) 限定要自动注入的bean的id,一般和@Autowired联用
  • @Value 注入简单类型数据 (jdk8种+String)
@Service
public class UserServiceImpl implements UserService {
    
    @Autowired //注入类型为UserDAO的bean
    @Qualifier("userDAO2") //如果有多个类型为UserDAO的bean,可以用此注解从中挑选一个
    private UserDAO userDAO;
}
@Service
public class UserServiceImpl implements UserService {
    
	@Resource("userDAO3") //注入id=“userDAO3”的bean
    private UserDAO userDAO;
    /*
    @Resource //注入id=“userDAO”的bean
    private UserDAO userDAO;
    */
}
public class XX{
    @Value("100") //注入数字
    private Integer id;
    @Value("shine") //注入String
	private String name;
}
17.3 事务控制

用于控制事务切入

  • @Transactional

  • 工厂配置中的 <tx:advice… 和 <aop:config… 可以省略 !!

//类中的每个方法都切入事务(有自己的事务控制的方法除外)
@Transactional(isolation=Isolation.READ_COMMITTED,propagation=Propagation.REQUIRED,readOnly=false,rollbackFor=Exception.class,timeout = -1)
public class UserServiceImpl implements UserService {
	...
    //该方法自己的事务控制,仅对此方法有效
	@Transactional(propagation=Propagation.SUPPORTS)
	public List<User> queryAll() {
		return userDao.queryAll();
	}
	public void save(User user){
		userDao.save(user);
	}
}
17.4 注解所需配置
<!-- 告知spring,哪些包中 有被注解的类、方法、属性 -->
<!-- <context:component-scan base-package="com.qf.a,com.xx.b"></context:component-scan> -->
<context:component-scan base-package="com.qf"></context:component-scan>
	
<!-- 告知spring,@Transactional在定制事务时,基于txManager=DataSourceTransactionManager -->
<tx:annotation-driven transaction-manager="txManager"/>
17.5 AOP开发
17.5.1 注解使用
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect // 声明此类是一个切面类:会包含切入点(pointcut)和通知(advice)
@Component //声明组件,进入工厂
public class MyAspect {
    // 定义切入点
    @Pointcut("execution(* com.qf.spring.service.UserServiceImpl.*(..))")
    public void pc(){}
    
    @Before("pc()") // 前置通知
    public void mybefore(JoinPoint a) {
        System.out.println("target:"+a.getTarget());
        System.out.println("args:"+a.getArgs());
        System.out.println("method's name:"+a.getSignature().getName());
        System.out.println("before~~~~");
    }

    @AfterReturning(value="pc()",returning="ret") // 后置通知
    public void myAfterReturning(JoinPoint a,Object ret){
        System.out.println("after~~~~:"+ret);
    }
    
    @Around("pc()") // 环绕通知
    public Object myInterceptor(ProceedingJoinPoint p) throws Throwable {
        System.out.println("interceptor1~~~~");
        Object ret = p.proceed();
        System.out.println("interceptor2~~~~");
        return ret;
    }
    
    @AfterThrowing(value="pc()",throwing="ex") // 异常通知
    public void myThrows(JoinPoint jp,Exception ex){
        System.out.println("throws");
        System.out.println("===="+ex.getMessage());
    }
}
17.5.2 配置
<!-- 添加如下配置,启用aop注解 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

十八、集成JUnit


18.1 导入依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>4.3.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
18.2 编码

可以免去工厂的创建过程;

可以直接将要测试的组件注入到测试类。

@RunWith(SpringJUnit4ClassRunner.class) //由SpringJUnit4ClassRunner启动测试
@ContextConfiguration("classpath:applicationContext.xml") //spring的配置文件位置
public class SpringTest{//当前测试类也会被纳入工厂中,所以其中属性可以注入

    @Autowired // 注入要测试的组件
    @Qualifier("userDAO")
    private UserDAO userDAO;

    @Test
    public void test(){
        // 测试使用userDAO
        userDAO.queryUser();
        ....
    }
}

以上工程Spring03

  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值