spring笔记

Spring引言

spring 2002年 罗德.约翰逊 创作

定义:

Spring框架是一个集众多设计模式(工厂模式,代理模式,策略模式,单例模式...)于一身的开源的,轻量级的,项目管理框架,致力于JAVAEE轻量级解决方案

轻量级解决方案:提供一个以简单的,统一的,高效的方式构造整个应用,并且可以将单层框架以最佳的组合糅合在一起建立一个连贯的体系

特点:

Spring框架的本质不再是替换项目中某个技术,而是将项目中使用单层框架进行 | 整合 | 管理 |

Spring框架的核心作用

​ Spring框架是用来 管理 (创建|使用|销毁) 项目中的组件,由于spring框架可以帮我们生产项目中组件对象,因此也习惯成spring是一个工厂|容器

组件: 项目中的service,dao,action都是项目中的组件

注意:spring框架通常不管理对实体类对象创建

组件管理

# 组件放入容器
  <bean class="init.UserDaoImpl" id="userDao"></bean>
 
 - bean : 用来管理组件对象的创建
 - class : 用来指定管理组件对象的全限定名 包,类
 - id : 用来指定spring框架创建的当前组件对象在spring(容器|工厂)中唯一标识(全局唯一)
 		推荐: 组件对象首字母小写
 
 # 使用容器中的组件
 	//启动工厂
   ApplicationContext context = new ClassPathXmlApplicationContext("init/spring.xml");
   	获取工厂中创建好的对象  参数: 获取工厂中指定对应的唯一标识
   UserDao userDao = (UserDao) context.getBean("userDao");
   System.out.println(userDao);

bean概述

Spring就是面向bean的编程(BOP,Bean Oriented Programming),Bean 在 Spring 中处于核心地位.Bean对于Spring的意义就像Object对于OOP的意义一样,Spring中没有bean也就没有spring存在的意义.spring IoC容器通过配置文件或者注解的方式来管理bean对象之间的依赖关系.

<!--管理DAO组件-->
<bean class="di.DeptDaoImpl" id="aa"></bean>

<!--管理Service组件-->
<bean class="di.DeptServiceImpl" id="deptService">
	<!--依赖的注入
		property : 用来给组件中的属性进行赋值操作
		name : 用来指定给组件中哪个属性名进行赋值
		ref : 用来指定赋值对象在工厂中唯一标识  bean的Id
		-->
	<property name="deptDao" ref="aa"/>
</bean>

为什么bean如此重要呢?**

  • spring 将bean对象由一个叫IoC容器进行管理
  • bean对象之间的依赖关系在配置文件中体现,并由spring完成

Spring Bean的生命周期?

简单来说,Spring Bean的生命周期只有四个阶段:

  1. 实例化 Instantiation
  2. 属性赋值 Populate
  3. 初始化 Initialization
  4. 销毁 Destruction

但具体来说,Spring Bean的生命周期包含下图的流程:

  1. 实例化Bean:对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。

  2. 设置对象属性(依赖注入):实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成属性设置与依赖注入。

  3. 处理Aware接口:Spring会检测该对象是否实现了xxxAware接口,通过Aware类型的接口,可以让我们拿到Spring容器的一些资源:

    • ①如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,传入Bean的名字;
    • ②如果这个Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
    • ②如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。
    • ③如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;
  4. BeanPostProcessor前置处理:如果想对Bean进行一些自定义的前置处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。

  5. InitializingBean:如果Bean实现了InitializingBean接口,执行afeterPropertiesSet()方法。

  6. init-method:如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。

  7. BeanPostProcessor后置处理:如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;

以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。

  1. DisposableBean:当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;

  2. destroy-method:最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。

Spring中bean的作用域:

  1. singleton:默认作用域,单例bean,每个容器中只有一个bean的实例。
  2. prototype:为每一个bean请求创建一个实例。
  3. request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
  4. session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例。
  5. global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中。

Spring框架中的Bean线程安全问题

Spring容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体情况还是要结合Bean的作用域来讨论。

(1)对于prototype作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题。

(2)对于singleton作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Controller类、Service类和Dao等,这些Bean大多是无状态的,只关注于方法本身。

有状态Bean(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。

无状态Bean(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。

对于有状态的bean(比如Model和View),就需要自行保证线程安全,最浅显的解决办法就是将有状态的bean的作用域由“singleton”改为“prototype”。


IOC : Inversion Of Control(控制反转)⭐⭐⭐

定义:控制权力的反转,即 创建对象的权力由 程序员 --> Spring工厂 ,简单来说就是将对象的创建由原来(new)的方式转移到配置文件(bean)中,交给spring工厂来创建对象

​ 但是IOC是大部分容器都有使用的一种方法,再把IOC作为Spring中独有的一种思想就不太合适了,因此作者在IOC的基础之上又提出了DI的概念.

DI: Dependency Injection (依赖注入)

定义:为组件中成员变量完成赋值过程 这个过程就称之为 依赖注入

语法:

1.组件对象中需要哪个组件,就把那个组件设置成成员变量,并且提供公开的set方法

public class DeptServiceImpl implements DeptService {
    //需要Dao组件对象  依赖Dao组件
    private DeptDao deptDao;
    //公开的Set方法
    public void setDeptDao(DeptDao deptDao) {
        this.deptDao = deptDao;
    }
    
    @Override
    public void save(String name) {
        ......
    }
}

2.在spring的配置文件中对应的组件标签内完成赋值操作

<!--管理DAO组件-->
<bean class="di.DeptDaoImpl" id="aa"></bean>

<!--管理Service组件-->
<bean class="di.DeptServiceImpl" id="deptService">
	<!--依赖的注入
		property : 用来给组件中的属性进行赋值操作
		name : 用来指定给组件中哪个属性名进行赋值
		ref : 用来指定赋值对象在工厂中唯一标识  bean的Id
		-->
	<property name="deptDao" ref="aa"/>
</bean>

IOC总结

​ 所谓IOC: 控制反转 就是将原来手动通过new关键字创建对象的权利交给spring容器,由spring工厂创建对象的过程,当然spring不仅要创建对象,还要在创建对象的同时通过DI的方式维护组件与组件间的调用关系


BeanFactory解析

spring中bean的创建就是典型的工厂模式,这一系列的bean工厂,即IoC容器,为开发者管理对象之间的依赖关系提供了很多便利和基础服务,在spring中有许多IoC容器的实现供用户选择,其相互关系如下如所示

其中,BeanFactory作为最顶层的一个接口,定义了IoC容器的基本功能规范,BeanFactory有三个重要的子接口:

  • ListableBeanFactory
  • HierarchicalBeanFactory
  • AutowireCapableBeanFactory

但是最终的默认实现类是DefaultListableBeanFactory,它实现了所有的接口

那么为何要定义这么多层次的接口呢?

每个接口都有它的使用场景,主要是为了区分在spring内部操作过程中对象的传递和转化,对对象的数据访问做限制

  • ListableBeanFactory接口表示这些Bean可列表化
  • HierarchicalBeanFactory表示这些Bean是有继承关系的,也就是每个Bean可能有父Bean
  • AutowireCapableBeanFactory接口定义Beand额自动装配规则

这三个接口共同定义了Bean的集合,Bean之间的关系及Bean的行为.最基本的IoC容器接口是BeanFactory

在BeanFactory里只对IoC容器的基本行为做了定义,不关心你的Bean是如何定义以及怎样加载的.

BeanFactory有一个很重要的子接口,就是ApplicationContext接口,该接口主要用来规范容器中的bean对象是非延时加载,即在创建容器对象的时候就对对象bean进行初始化,并存储到一个容器中

Spring提供了许多IoC容器实现,比如:

  • ClassPathXmlApplicationContext:根据类路径加载xml配置文件,并创建IoC容器对象
  • FileSystemXmlApplicationContext:根据系统路径加载xml配置文件,并创建IoC容器对象
  • AnnotationConfigApplicationContext:加载注解类配置,并创建IoC容器

FactoryBean解析

factorybean是一个Bean主要是获取到bean

Spring 中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean 即 FactoryBean。
FactoryBean跟普通Bean不同,其返回的对象不是指定类的一个实例,而是该FactoryBean的getObject方法所返回的对象。

举个栗子:

创建一个简单的类

public class person {
    private String name;

    public person(String name) {
        System.out.println("创建了一个人");
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

创建一个FactoryBean测试类

/**
 * @PROJECT_NAME: Longda_spring
 * @DESCRIPTION:
 * @USER: 罗龙达
 * @DATE: 2021/2/7 1:20
 */
public class UserDaoFactoryBean implements FactoryBean {

    public Object getObject() throws Exception {
        return new person("王五");
    }

    public Class<?> getObjectType() {
        return person.class;
    }
}

在配置文件中配置好

<?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 id="UserDaoFactoryBean" class="springTest.dao.UserDaoFactoryBean">
    </bean>
</beans>

测试

public class UserController {
    public static void main(String[] args) throws Exception {
        //创建spring的容器对象
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        person userDaoFactoryBean = context.getBean("UserDaoFactoryBean", person.class);
        String name = userDaoFactoryBean.getName();
        System.out.println(name);
    }
}

/*  结果:
> 创建了一个人
> 王五
*/

如果说想要创建UserDaoFactoryBean对象就会报错

public class UserController {
    public static void main(String[] args) throws Exception {
        //创建spring的容器对象
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDaoFactoryBean userDaoFactoryBean = context.getBean("UserDaoFactoryBean", UserDaoFactoryBean.class);   //如果说想要获取FactoryBean对象需要在ud前加上 '&'
        //例如
        //UserDaoFactoryBean userDaoFactoryBean = context.getBean("&UserDaoFactoryBean", UserDaoFactoryBean.class); 
        System.out.println(userDaoFactoryBean);
    }
}
/*
BeanNotOfRequiredTypeException: 
Bean named 'UserDaoFactoryBean' is expected to be of type 'springTest.dao.UserDaoFactoryBean' but was actually of type 'springTest.pojo.person'
*/

一般情况下,Spring通过反射机制利用的class属性 指定实现类 实例化Bean,
在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要在中 提供大量的配置信息。
配置方式的 灵活性是受限的,这时采用编码的方式 可能会得到一个简单的方案。Spring为此提供了一个FactoryBean的 工厂类接口,用户可以通过实现该接口 定制实例化Bean的逻辑。
FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。
它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。

BeanDefinition解析

spring IoC容器管理我们定义的各种bean对象及其相互关系,而bean对象在spring实现中是以BeanDefinition来描述的

<!--管理DAO组件-->
<bean class="di.DeptDaoImpl" id="aa"></bean>

其继承体系如下:

BeanDefinitionReader解析

Bean的解析过程非常复杂,功能被分的很细,因为这里需要被扩展的地方很多,必须保证足够的灵活性,以应对可能的变化.Bean的解析主要就是对Spring配置文件的解析.这个解析过程主要通过BeanDefinitionReader来完成,类结构图如下:

public interface BeanDefinitionReader {
    
    //获取BeanDefinitionRegistry注册器对象
    BeanDefinitionRegistry getRegistry();
    
	@Nullable
	ResourceLoader getResourceLoader();
    
    @Nullable
	ClassLoader getBeanClassLoader();
    
    BeanNameGenerator getBeanNameGenerator();
    
    /*
    下面的 loadBeanDefinitions 都是加载bean的定义,从指定的资源中
    */
    int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
	int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
	int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
	int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;

}

BeanDefinitionRegistry解析

BeanDefinitionRegistry用来解析bean的定义,并封装成BeanDefinition对象,而我们定义的配置文件中定义了很多bean标签,所以就有一个问题,解析的BeanDefinition对象存储到那了?

答案就是BeanDefinition的注册中心,而该注册中心顶层接口就是BeanDefinitionRegistry

public interface BeanDefinitionRegistry extends AliasRegistry {
    
    //往注册表中注册bean
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
        throws BeanDefinitionStoreException;
    
    //从注册表中删除bean
    void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
    
    //获取注册表中指定名称的bean
	BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
    
    //判断注册表中是否已经注册了指定名称的bean
	boolean containsBeanDefinition(String beanName);
    
    //获取注册表中所有的bean的名称
	String[] getBeanDefinitionNames();
    
	int getBeanDefinitionCount();
	boolean isBeanNameInUse(String beanName);
}

继承结构图如下:

从上面类图可以看到BeanDefinitionRegistry接口的子实现类主要有以下几个:

  • DefaultListableBeanFactory

    在该类中定义了如下代码,就是用来注册bean的

    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
    
  • SimpleBeanDefinitionRegistry

    在该类中定义了如下代码,就是用来注册bean的

    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(64);
    

Spring中注入方式以及SET注入相关语法

SET注入 (重点⭐)

定义:使用类中属性的SET方法为属性完成赋值的过程

语法:1. 注入八种基本类型 + String类型 + Date类型 统一使用 value属性进行注入

<property name="name" value="张三"/>
<property name="age" value="23"/>
<property name="sex" value="true"/>
<property name="counts" value="23.33"/>
	//注意:在spring技术栈中日期格式默认为yyyy/MM/dd HH:mm:ss
<property name="bir" value="2020/09/08 23:12:13"/>

​ 2. 注入对象类型 | 引用类型 统一使用ref属性进行注入

<property name="deptDao" ref="aa"/>

​ 3. 注入数组类型

<property name="qqs">
	<array>
		<value>小陈</value>
		<value>小明</value>
		<value>小黑</value>
		<value>小亮</value>
	</array>
</property>

​ 4. 注入list类型

<property name="habbys">
	<list>
		<value>吃饭</value>
		<value>睡觉</value>
		<value>打泡泡</value>
		<value>开冰箱</value>
	</list>
</property>

​ 5. 注入map entry类型

<property name="maps">
	<map>
		<entry key="aa" value="xiaochen"/>
		<entry key="bb" value="小黑"/>
		<entry key="cc" value="李斯"/>
</property>

​ 6. 注入properties类型

<property name="properties">
	<props>
		<prop key="driver">com.mysql.jdbc.Driver</prop>
		<prop key="username">root</prop>
		<prop key="password">123456</prop>
		</props>
</property>

构造注入

定义:使用类中构造方法为类中成员变量赋值的过程

语法:1. 需要哪个组件属性将谁声明为成员变量,并提供公开构造方法

​ 2. 在配置文件中对应的组件标签内部使用<constructor-arg>标签进行注入

代码冗余,不推荐

使用构造方法形式进行属性的赋值

自动注入

定义:通过在配置文件中完成类中属性自动赋值

注意:1. 底层使用原理也是SET注入

			2.  自动注入需要在对应组件标签上开启才能使用

语法:1. 需要谁将谁声明为成员变量,并提供Set方法

​ 2. 在对应组件标签上加入autowired 属性并指定自动注入方式即可完成注入

	//管理Dao组件
<bean class="adi.StudentDaoImpl" id="studentDao"></bean>
	//管理Service组件
	//autowire : 用来给组件中成员变量完成自动赋值操作
	//byType : 根据类型完成自动注入  根据成员变量类型去工厂找  找到对应类型完成赋值  找不到不赋值
	//byName : 根据名称完成自动注入  根据成员变量名去工厂找  找到对应类型完成赋值  找不到不赋值
<bean class="adi.StudentServiceImpl" id="studentService" autowire="byName"></bean>

​ 3. 只能用于引用类型的注入

看不见有哪些配置类

Spring工厂的相关特性


Spring中工厂创建对象的模式

注意:工厂默认在管理对象都是单例模式,单例方式无论在工厂获取多少次始终获取的是同一个对象

修改创建对象模式:

	//scope : 用来指定工厂创建对象的模式
		默认值 : singleton  单例
				prototype  多例
<bean id="aaa" class="xxxx.." scope=" singleton | prototype (多例)"

原理: 反射 + 构造方法

UserDao userdao = (UserDao) Class.forName("scope.UserDaoImpl").newInstance();

工厂管理组件生命周期:

单例对象:

  • 工厂启动,工厂中所有单例的对象随之创建
  • 工厂正常关闭(调用close()方法),工厂中所有单例对象随之销毁

多例对象:

  • 每次在工厂中使用时创建
  • 工厂不负责多例对象的销毁

Spring工厂好处

  • 解耦合 使用配置文件管理java类,在生产环境中更换类的实现时不需要重新部署,修改文件即可
  • 减少内存占用 spring默认使用单例的模式创建bean
  • 方便维护 通过依赖注入的方式建立了类与类之间的关系(使java之间关系更为清晰,方便了维护与管理)

AOP(面向切面编程)⭐⭐⭐

代理对象(Proxy)

  • 作用 : 在整个业务逻辑中完成传话(附加操作)的作用,同时也可以中断整个业务
  • 好处 : 既可以保证原始业务逻辑 原始业务功能不变的同时还可以完成一些附加的操作

如何开发一个代理对象

静态代理

1.  代理对象和业务逻辑对象(真正业务逻辑对象)  实现相同  接口

2.  代理对象中  依赖  真正业务逻辑对象
//原始业务逻辑对象
//又称之为目标对象(Target Object)
public class UserServiceImpl implements UserService {
    //处理业务
    @Override
    public void save(String name) {
        System.out.println("处理业务逻辑,调用Dao~~~");
    }
    
        @Override
    public void update(String name) {
        System.out.println("处理业务逻辑,调用Dao~~~");
    }
    
        @Override
    public void delete(String name) {
        System.out.println("处理业务逻辑,调用Dao~~~");
    }
}
//注意 : 这里为每一个业务层通过手动开发一个代理对象的过程称之为 静态代理对象
//代理对象
public class UserServiceStaticProxy implements UserService {
    //依赖原始业务逻辑对象
    private UserService userService;
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    
    @Override
    public void save(String name) {
        try {
            System.out.println("开启事务");
            userService.save(name);
            System.out.println("开启事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
    }
    
    @Override
    public void update(String name) {
        try {
            System.out.println("开启事务");
            userService.update(name);
            System.out.println("开启事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
    }
    
        @Override
    public void delete(String name) {
        try {
            System.out.println("开启事务");
            userService.delete(name);
            System.out.println("开启事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
    }
}
	<!--工厂管理-->
<beans .....>

<!--管理Service-->
<bean id="userService" class="UserServiceImpl"></bean>

<!--随着业务扩大,可能会有很多Service需要去管理-->
<bean id="managerService" class="ManagerServiceImpl"></bean>
<bean id="studentService" class="StudentServiceImpl"></bean>
<bean id="teacherService" class="TeacherServiceImpl"></bean>
........

<!--管理 Service 中 proxy -->
<bean id="UserServiceStaticProxy" class="UserServiceStaticProxy">
	<!--依赖于真正业务逻辑对象 -->
	<property name="userService" ref="userService"/>
</bean>

</beans>

**弊端 : **往往在开发过程中我们书写的不仅仅是一个业务层,两个业务层,而我们的业务层会有很多,如果为每一个业务层开发一个静态代理类,不仅没有减轻工作量,甚至让我们的工作量多了一倍不止.

**解决 : **为业务层在运行过程中动态创建代理类,通过动态代理类去解决我们现有业务层中业务代码冗余的问题


动态代理的原理

理解:

(代理)过去比较传统,约女孩子出来看电影吃饭,直接把女孩子叫出来是不合适的,需要通过家人来约的。女孩子的家人很多,有父母,爷爷奶奶,姐妹等等,(动态 proxy 调度器)我去约女孩子的时候不一定谁来开门,如果说是爸爸来开门,我就需要征询爸爸的同意,如果是妈妈来开门我就需要征询妈妈的同意。(InvocationHandler)女孩子跟我出来约会是因为爱情,(事前增强)但是家人出于多方面考虑需要对我调查一下,(事后增强)在约晚会之后,家人会询问女孩子的感受。

真实角色(王美丽):

public interface Girl {

    public void Date();

    public void WatchingMovie();
}
//-------------------------------
public class WangMeiLi implements Girl {
    public void Date() {
        System.out.println("跟你约会很开心");
    }

    public void WatchingMovie() {
        System.out.println("电影一点也不好看");
    }
}

代理角色(家人):

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/*
业务增强实现
 */
public class WangMeiLiProxy implements InvocationHandler {
    private Girl girl;

    public void setGirl(Girl girl) {
        this.girl = girl;
    }

    //得到一个Proxy对象
    //参数1 : classLoader  类加载器  底层通过类加载器创建出一个代理对象所以需要类加载器
    //参数2 : Class[] 目标对象的接口类型的数组
    //参数3 : InvocationHandler 接口类型  invoke方法
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), girl.getClass().getInterfaces(), this);
    }

	//通过动态代理对象调用自己里面代理方法时会优先指定invocationHandler类中的invoke方法
    //参数1 : 当前创建好的代理对象
    //参数2 : 当前代理对象执行的方法对象
    //参数3 : 当前代理对象执行方法的参数
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //前置增强
        Before();
        Object result = method.invoke(girl,args);
        //后置增强
        After();
        return result;
    }

    public void Before() {
        System.out.println("调查一下这个小伙子背景");
    }

    public void After() {
        System.out.println("问问这个小伙子人好不好");
    }
}

测试:

public class Test {

    public static void main(String[] args) {
        Girl girl = new WangMeiLi();
        WangMeiLiProxy family = new WangMeiLiProxy();
        family.setGirl(girl);
        Girl mother  = (Girl) family.getProxy();
        mother.Date();
        System.out.println("------------------------");
        mother.WatchingMovie();


    }
}

AOP编程

AOP : Aspect(切面) Oriented(面向) Programing 面向切面编程

底层原理: java代理设计模式 动态代理

定义:

通过在程序运行的过程中动态的为项目中某些组件生成动态代理对象,通过在生成的动态代理对象中执行相应的附加操作 | 额外功能,减少项目中通用代码的冗余问题

好处 :

  • 在保证原始业务功能不变情况下,通过代理对象完成业务中附加操作(事务)
  • 将业务中核心操作放在目标对象中执行,实现了附加操作和核心操作解耦

通知(advice) : 除了目标方法以外的操作都称之为通知 附加功能(事务通知 日志通知 性能通知 ......)

切入点(Pointcut) : 指定开发好的通知应用于项目中的哪些组件 一般通知多用于业务层(UserService)

切面(Aspect) : = 通知(advice) + 切入点(pointcut)

AOP面向切面编程步骤 :

  • 1 开发通知类(附加功能)
public class MethodInvokeTimeAdvice implements MethodInterceptor {
    
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("---进入环绕通知---");
        try{
            System.out.println("---前置通知---");
            //放行目标方法    ***重要
            Object proceed = invocation.proceed();  //继续处理
            System.out.println("---后置通知---");

        } catch (Exception e) {
            e.printstackTrace();
            System.out.println("出现异常时业务处理");
        }
        return null;
    }
}
  • 2 配置spring.xml

    • 管理业务Dao组件

    • 配置切面

      • 注册通知组件

      • <!--注册通知类-->
        <bean id="methodInvokeTimeAdvice" class="com.longda.advices.MethodInvokeTimeAdvice"></bean>
        
      • 配置切面(一般情况下都是切service层)

      • <!--配置切面-->
        <aop:config>
            <!--配置切入点-->
        	<aop:pointcut id="pc" expression="excution(* com.longda.service.*ServiceImpl.*(..))"/>
        	<!--组装切面-->
        	<aop:advisor advice-ref="methodInvokeTimeAdvice" pointcut-ref="pc"/>
        </aop:config>
        

spring(pointcut)切入点表达式:

作用: 主要是用来决定项目中哪些组件中哪些方法需要加入通知

expression="切入点表达式"

  • excution 切入点表达式 --->方法级别的切入点表达式 控制粒度 : 方法级别

    完整语法 📑 excution(访问权限修饰符 返回值 包名.类名.方法(参数类型))

    ​ 📑excution(返回值 包名.类名.方法名(参数类型))


    例子 1 : [常见]

    .execution(* com.longda.service.*.*(..))

    解释 :

    ​ 包 : com.longda.service

    ​ 类 : 任意类

    ​ 方法 : 任意方法

    ​ 参数 : 参数任意

    ​ 返回值 : 任意返回值类型


    例子 2 :

    .excution(String com.longda.service.*ServiceImpl.*(..))

    解释 :

    ​ 包 : com.longda.service

    ​ 类 : 以ServiceImpl结尾的类

    ​ 方法 : 任意方法

    ​ 参数 : 任意参数

    ​ 返回值 : 返回值必须String类型相关方法


    例子 3 :

    .excution(String com.longda.service.*Service*.*(String))

    解释 :

    ​ 包 : com.longda.service

    ​ 类 : 类名中包含Service关键字的类

    ​ 方法 : 任意方法

    ​ 参数 : 参数只有一个类型必须是String

    ​ 返回值 : 返回值必须是String


    例子 4 : [常见]

    .excution(* com.longda.service..*.*(..))

    解释 :

    ​ 包 : com.longda.service以及这个包中的子包和子包的子包..

    ​ 类 : 任意类

    ​ 方法 : 任意方法

    ​ 参数 : 任意参数

    ​ 返回值 : 任意类型返回值


    例子 5 :

    .excution(* *.*(..))

    解释 :

    ​ 包 : 项目中所有包

    ​ 类 : 项目中所有类

    ​ 方法 : 所有方法

    ​ 参数 : 所有参数

    ​ 返回值 : 任意类型返回值


    注意 : 尽可能精准切入 避免不必要的切入

  • within 切入点表达式 --->类级别的切入点表达式 控制粒度 : 类级别 效率高

    expression="within()"

    完整语法 📑 within(包.类名)

    例子 1 :

    .within(com.longda.service.*ServiceImpl)

工厂创建复杂对象:

  • 1 创建 类 implements FactoryBean
//用来在工厂中创建复杂对象
public class CalenderFactoryBean implements FactoryBean<Calender> {
	//用来书写复杂对象的创建方式
    @Override
    public Calender getObject() throws Exception {
        return Calender.getInstance();
    }
    //指定创建的复杂对象的类型
    @Override
    public Class<?> getObjectType() {
        return Calendar.class;
    }
    //用来指定创建的对象模式 true 单例 false 多例
    @Override
    public boolean isSingleton() {
        return true;
    }
}
  • 2 通过工厂配置创建复杂对象
<bean id="calendar" clas="xxxx.CalendarFactoryBean"/>

事务简介

Spring 本身并不实现事务,Spring事务 的本质 还是 底层数据库 对事务的支持,没有 数据库 事务的支持,Spring事务就不会生效。

Spring 事务 提供一套抽象的事务管理,并且结合 Spring IOC 和 Spring AOP,简化了应用程序使用数据库事务,通过声明式事务,可以做到对应用程序无侵入的实现事务功能。例如 使用JDBC 操作数据库,想要使用事务的步骤为:

  1. 获取连接 Connection con = DriverManager.getConnection()

  2. 开启事务con.setAutoCommit(true/false);

  3. 执行CRUD

  4. 提交事务/回滚事务 con.commit() / con.rollback();

  5. 关闭连接 conn.close();

采用Spring 事务后,只需要 关注第3步的实现,其他的步骤 都是Spring 完成。

Spring事务的本质 其实就是 AOP 和 数据库事务,Spring 将数据库的事务操作提取为 切面,通过AOP的方式 增强 事务方法。

事务传播属性

事务传播:

  • 多个业务层之间相互调用时传递事务的过程称之为事务传播
  • 或者说将事务对象在业务层之间进行传递的过程
OrderService.save {
    orderDao.save(); //保存订单
    //调用其他业务层
    userService.update();  //修改积分

事务传播属性(propagation)

REQUIRD:需要事务 如果外层没有事务 则开启新的事务,如果外层存在事务 则融入当前事务.

SUPPORTS:支持事务 如果外层没有事务 不会开启新的事务,如果外层存在事务,则融入当前事务. -->建议查询方法添加,保证事务传播的正常

REQUIRES_NEW:每次开启新的事务 如果外层存在事务,外层事务挂起,自己开启新的事务执行,执行完成,恢复外层事务继续执行

NOT_SUPPORTED:不支持事务 如果外层存在事务,外层事务挂起,自己以非事务方式执行,执行完成,恢复外部事务

NEVER:不能有事务 存在事务则报错

MANDATORY:强制事务 没有事务则报错

NESTED:嵌套事务 事物之间可以嵌套运行

事务的隔离级别(isolation)

DEFAULT:使用数据库默认的隔离级别 -- 推荐

READ_UNCOMMITTED:读未提交 一个客户端读到另一个客户端没有提交的数据 脏读现象

READ_COMMITTED:读提交 一个客户端只能读到另一个客户端提交的数据 避免脏读现象

REPEATABLE_READ:可重复读 主要是用来避免不可重复读现象出现 -- 行锁

SERIALIZABLE:序列化读 主要是用来避免幻影读现象出现 -- 表锁

注意 : 隔离级别越高,查询效率越低 一般推荐使用数据库默认隔离级别

注解(Annotation)式开发

定义:通过Spring框架提供的一系列相关注解完成项目中快速开发

相关注解:

前置条件:必须在工厂配置文件中完成注解扫描

  • 创建对象相关注解
    • @Component注解
      • @Component("userDao")
      • 作用 : 用来负责对象的创建
      • 修饰范围 : 只能用在类上
      • value属性作用 : 用来指定创建的对象在工厂中唯一标识 推荐 : 存在接口 接口首字母小写 | 不存在使用默认
    • @Repository注解
      • 作用 : 一般用来创建Dao中组件的注解
    • @Service注解
      • 作用 : 一般用来创建Service中组件的注解
    • @Controller注解
      • 作用 : 一般用来创建Action中组件的注解
  • 控制对象在工厂中创建次数
    • @Scope
      • 作用 : 用来指定对象的创建次数 默认单例
      • 修饰范围 : 只能加在类上
      • value属性 : singleton(默认) | prototype
  • 属性注入相关注解
    • @Autowire
      • 修饰范围 : 用在类中的成员变量,或者是类中成员变量的SET方法上
      • 注意 : 默认根据类型进行注入
    • @Resource
      • 修饰范围 : 用在类中的成员变量,或者是类中成员变量的SET方法上
      • 注意 : 默认根据名字注入,名字找不到的时候再根据类型找
    • 注意 : 使用注解进行注入时,日后注入的成员变量可以不再提供SET方法
  • 控制事务注解
    • @Transactional
      • 作用 : 用来给类中方法加入事务控制 简化配置文件中的
        • 1.事务通知以及事务细粒度配置
        • 2.简化事务切面配置
      • 修饰范围 : 类 方法上都可以 遵循局部优先原则
        • 加在类上 代表类中所有方法加入事务控制
        • 加在方法上代表当前方法加入事务控制
        • 类和方法同时存在 方法优先
      • value属性 : @Transactional(propagation = Propagation.SUPPORTS)

posted @ 2020-10-04 21:50  longda666  阅读(224)  评论(0)    收藏  举报