当前位置: 首页 > news >正文

《Spring Framework 核心原理与实践指南》

文章目录

    • 1.Spring 相关概念
      • 1.1 Spring Framework 系统架构图
      • 1.2 核心概念
    • 2. IOC内容
      • 2.1 bean的基础配置
        • (1) 非注解写法和注意事项
          • <1> 知识点速览
          • <2> 准备工作
          • <3> 获取IOC容器并调用方法 (id 和 class的使用)
          • <4> 配置别名(name的使用)
          • <5>测试单例/非单例(scope的使用)
        • (2) 注解写法和注意事项
          • <1> 知识点速览:
          • <2> 改进方案:
      • 2.2 bean实例化
        • (1) 非注解写法和注意事项
          • <1> 知识点速览
          • <2> 准备工作
          • <3> 构造方法实例化
          • <4> 静态工厂实例化
          • <5> 实例工厂与FactoryBean
        • (2) 实例化bean的三种方式
      • 2.3 bean的生命周期
        • (1) 非注解写法和注意事项
          • <1> 知识点速览
          • <2> 准备工作
          • <3> 生命周期设置
            • a. 正常实现
            • b. 基于接口实现
        • (2) 注解写法和注意事项
          • <1> 知识点速览
          • <2> 改进方案
      • 2.4 核心容器
        • (1) 知识点速览
        • (2) 容器的创建方式
        • (3) Bean的三种获取方式
        • (4) BeanFactory的使用
    • 3. DI相关内容
        • (1) 非注解写法和注意事项
          • <1> 知识点速览
          • <2> 准备工作
          • <3> setter注入
          • <4> 构造器注入
          • <5> 自动装配
          • <6> 集合注入
        • (2) 注解写法和注意事项
          • <1> 知识点速览
          • <2> 改进方案
    • 4.AOP相关内容
      • (1) AOP的核心概念
      • (2) 基于注解实现步骤
        • <1> 知识点一览
        • <2> 准备工作
        • <3> 实现步骤
      • (3) AOP工作流程
      • (4) AOP配置管理
        • <1> 切入点表达式
        • <2> AOP通知类型
            • a.知识点总结
            • b. 实现
        • <3> AOP通知获取参数
          • (1) 获取参数(⭐)
          • (2) 获取返回值
          • (3) 获取异常(了解)
      • (5) AOP事务管理
        • <1> 知识点总览
        • <2> 概念
        • <3> 快速使用
    • 5. 结语

1.Spring 相关概念

Spring 是一个开源的 Java 企业级应用开发框架,它的核心目标是简化企业级应用开发的复杂性,提供一站式的解决方案。

1.1 Spring Framework 系统架构图

在这里插入图片描述

在Spring这部分内容中主要包含IOC/DIAOP,在后面也会基于这几个部分进行讲解。

1.2 核心概念

概念

IOC (Inversion of Control) 控制反转:对象的创建控制权由程序转移到外部,这种思想称为控制反转。

即控制权从应用程序代码转移到框架容器,由容器负责管理对象生命周期和依赖关系。

Bean:在Spring中提供了IOC容器,而用于充当外部,负责对象的创建、初始化等工作,被创建或被管理的对象在IOC容器中的统称为Bean。

DI(Dependency Injection)依赖注入:在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入

2. IOC内容

IOC主要是控制反转思想,在这里分为bean基础配置,bean实例化,bean的生命周期讲解,分别采用非注解,注解的写法。

同时讲解核心容器,也就是ApplicationContext

2.1 bean的基础配置

(1) 非注解写法和注意事项

在这里插入图片描述

<1> 知识点速览

id:bean的id,使用容器可以通过id值获取对应的bean,在一个容器中id值唯一。

class:bean的类型,即配置的bean的全路径类名

name:bean的别名配置,别名可以有多个,使用逗号、分号、空格分隔

scope:负责控制bean的作用范围,有单例和非单例

<2> 准备工作

前置工作:创建Maven项目,添加Spring对应jar包和所需类
使用思路:添加Spring配置文件,完成配置,进行使用

Spring配置文件示例(在resources下建立)

resources 是 Java 项目(尤其是 Maven/Gradle 等构建工具管理的项目)中专门用于存放非 Java 源代码的资源文件的标准目录

<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!--文档前面内容省略--><bean id="bookDao" class="com.Coolipa.Dao.Impl.BookDaoImpl" scope="singleton" name="bookDao2 bookDao"/><bean id="bookService" class="com.Coolipa.Service.Impl.BookServiceImpl" scope="prototype"/>
</beans>
<3> 获取IOC容器并调用方法 (id 和 class的使用)

启动类通过配置文件中的xml接口完成IOC容器创建,并通过id和class调用内部的方法

配置文件关键点

<bean id="bookDao" class="com.Coolipa.Dao.Impl.BookDaoImpl" />

启动类

public class App {public static void main(String[] args) {ApplicationContext app = new ClassPathXmlApplicationContext("ApplicationContext.xml");BookDao bookDao= (BookDao) app.getBean("bookDao");bookDao.save();}
}
<4> 配置别名(name的使用)

id可能会由于命名习惯而产生分歧,可以利用别名去解决这个问题

配置文件关键点

<bean id="bookDao" class="com.Coolipa.Dao.Impl.BookDaoImpl" name="bookDao2"/>

启动类

public class App {public static void main(String[] args) {ApplicationContext app = new ClassPathXmlApplicationContext("ApplicationContext.xml");BookDao bookDao= (BookDao) app.getBean("bookDao2");bookDao.save();}
}

拓充
可以利用bean依赖注入的ref属性指定bean,必须在容器中存在,不存在将会报错NoSuchBeanDefinitionException

<bean id ="bookService" name="service service2" class="com.Coolipa.Dao.Impl.BookDaoImpl"><property name="bookDao" ref="dao"/>
</bean>

在这里插入图片描述

<5>测试单例/非单例(scope的使用)

在默认情况下,Spring创建的Bean对象都是单例了,所以需要手动更改

配置文件关键点

<bean id="bookDao" class="com.Coolipa.Dao.Impl.BookDaoImpl" 
scope="singleton"/>

其中单例为singleton,非单例为prototype

拓充:

为什么bean默认为单例?

单例指在IOC容器中只会有该类的一个对象,bean对象只有一个就避免了对象的频繁创建与销毁,达到了bean对象的复用,性能高

bean在容器中是单例的,会不会产生线程安全问题?

如果对象有成员变量可以用来存储数据,由于线程共用一个bean对象,会存在线程安全问题。
如果对象没有成员变量进行数据存储,则方法中的局部变量在方法调用完毕后会被销毁,不会存在线程安全问题

(2) 注解写法和注意事项
<1> 知识点速览:

知识点1:@Component等

名称@Component/@Controller/@Service/@Repository
类型类注解
位置类定义上方
作用设置该类为spring管理的bean
属性value(默认):定义bean的id

知识点2:@Configuration

名称@Configuration
类型类注解
位置类定义上方
作用设置该类为spring配置类
属性value(默认):定义bean的id

知识点3:@ComponentScan

名称@ComponentScan
类型类注解
位置类定义上方
作用设置spring配置类扫描路径,用于加载使用注解格式定义的bean
属性value(默认):扫描路径,此路径可以逐层向下扫描

知识点4:@Scope

名称@Scope
类型类注解
位置类定义上方
作用设置该类创建对象的作用范围
可用于设置创建出的bean是否为单例对象
属性value(默认):定义bean作用范围,
默认值singleton(单例),可选值prototype(非单例)
<2> 改进方案:

创建java的配置类,用@configuration注解替换配置类,用@ComponentScan替换指定class文件

配置类

@Configuration
@Scope("singleton")
@ComponentScan("com.Coolipa")
public class SpringConfig {
}

被替换部分

    <bean id="bookDao" class="com.Coolipa.Dao.Impl.BookDaoImpl" scope="singleton" name="bookDao2"/><bean id="bookService" class="com.Coolipa.Service.Impl.BookServiceImpl" scope="prototype"/>

启动类:

public class App {public static void main(String[] args) {ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig.class);        //名称和字节码都可以BookDao bookDao= (BookDao) app.getBean("bookDao");bookDao.save();BookService bookService =app.getBean(BookService.class);bookService.save();}
}

被替换部分对比

        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

ClassPathXmlApplicationContext是加载XML配置文件AnnotationConfigApplicationContext是加载配置类

2.2 bean实例化

这里主要为实例化bean的方式

(1) 非注解写法和注意事项
<1> 知识点速览

bean是由构造方法创建的

Spring的IOC实例化对象的三种方式分别为构造方法,静态方法,实例方法,主要掌握构造方法和实例方法中的FactoryBean

需要注意的一点是,构造方法在类中默认会提供,但是如果重写了构造方法,默认的就会消失,在使用的过程中需要注意,如果需要重写构造方法,最好把默认的构造方法也重写下。

<2> 准备工作

前置工作:准备创建的类,配置类,启动类

<3> 构造方法实例化

在类中添加无参构造函数即可

public class BookDaoImpl implements BookDao {public BookDaoImpl() {System.out.println("book dao constructor is running ....");}public void save() {System.out.println("book dao save ...");}}

Spring容器在创建对象时用的就是构造函数(无参构造),同时Spring底层用的是反射(可以访问私有构造方法)

<4> 静态工厂实例化

配置类添加内容

class:工厂类全名

factory-method:具体工厂类中创建对象的方法名

<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>

编写运行类运行

public class AppForInstanceOrder {public static void main(String[] args) {ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");orderDao.save();}
}

在工厂静态方法,可以作其他的业务操作

<5> 实例工厂与FactoryBean

实例工厂

配置类内容

<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>

factory-bean:工厂的实例对象

factory-method:工厂对象中的具体创建对象的方法名

编写运行类,在类中通过工厂获取对象

public class AppForInstanceUser {public static void main(String[] args) {ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");UserDao userDao = (UserDao) ctx.getBean("userDao");userDao.save();}
}

FactoryBean

创建类,实现FactoryBean接口,重写方法

public class UserDaoFactoryBean implements FactoryBean<UserDao> {//代替原始实例工厂中创建对象的方法public UserDao getObject() throws Exception {return new UserDaoImpl();}//返回所创建类的Class对象public Class<?> getObjectType() {return UserDao.class;}
}

FactoryBean接口有三个方法:

  • getObject():返回由FactoryBean创建的对象实例
  • getObjectType():返回创建对象的类型
  • isSingleton():默认返回true,表示创建的bean是单例的。可以通过重写此方法返回false来创建原型(prototype)bean

验证方法:可以通过在Spring容器中多次获取同一个FactoryBean创建的bean,观察是否为同一个实例来验证单例行为。

T getObject() throws Exception;Class<?> getObjectType();default boolean isSingleton() {return true;
}
(2) 实例化bean的三种方式

2.3 bean的生命周期

这里主要知识点为bean生命周期

(1) 非注解写法和注意事项
<1> 知识点速览

关于Spring中对bean生命周期提供两种方式

  • 在配置文件中的bean标签,可以添加init-methoddestroy-method属性

  • 类实现InitializingBeanDisposableBean接口,这种方式了解下即可。

关闭容器分为close()registerShutdownHook()两个方法

<2> 准备工作

前置工作:准备创建的类,配置类,启动类

<3> 生命周期设置
a. 正常实现

添加初始化和销毁方法

public class BookDaoImpl implements BookDao {public void save() {System.out.println("book dao save ...");}//表示bean初始化对应的操作public void init(){System.out.println("init...");}//表示bean销毁前对应的操作public void destory(){System.out.println("destory...");}
}

在配置文件添加配置,如下:

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>

由于在普通Java应用中,JVM退出时不会自动调用bean的销毁方法,可以通过以下两种方式触发:

  1. 显式关闭:使用ClassPathXmlApplicationContextclose()方法

    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 使用bean...
    ctx.close(); // 显式关闭容器,触发销毁方法
    
  2. 注册关闭钩子:使用registerShutdownHook()方法,在JVM关闭时自动调用销毁方法

    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    ctx.registerShutdownHook(); // 注册JVM关闭钩子
    

区别

  • close():需要手动调用,立即触发销毁
  • registerShutdownHook():自动在JVM关闭时触发,更适合生产环境
b. 基于接口实现

在实现类添加两个接口InitializingBeanDisposableBean并实现接口中的两个方法afterPropertiesSetdestroy

public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {private BookDao bookDao;public void setBookDao(BookDao bookDao) {this.bookDao = bookDao;}public void save() {System.out.println("book service save ...");bookDao.save(); }public void destroy() throws Exception {System.out.println("service destroy");}public void afterPropertiesSet() throws Exception {System.out.println("service init");}
}
(2) 注解写法和注意事项
<1> 知识点速览

知识点1:@PostConstruct

名称@PostConstruct
类型方法注解
位置方法上
作用设置该方法为初始化方法
属性

知识点2:@PreDestroy

名称@PreDestroy
类型方法注解
位置方法上
作用设置该方法为销毁方法
<2> 改进方案

使用@PostConstruct@PreDestroy定义bean生命周期

@Repository
//@Scope设置bean的作用范围
@Scope("singleton")
public class BookDaoImpl implements BookDao {public void save() {System.out.println("book dao save ...");}//@PostConstruct设置bean的初始化方法@PostConstructpublic void init() {System.out.println("init ...");}//@PreDestroy设置bean的销毁方法@PreDestroypublic void destroy() {System.out.println("destroy ...");}
}

2.4 核心容器

(1) 知识点速览

核心容器可以简单理解为ApplicationContext

  • 容器创建的两种方式

    • ClassPathXmlApplicationContext[掌握]
    • FileSystemXmlApplicationContext[知道即可]
  • 获取Bean的三种方式

    • getBean(“名称”):需要类型转换
    • getBean(“名称”,类型.class):多了一个参数
    • getBean(类型.class):容器中不能有多个该类的bean对象

    上述三种方式,各有各的优缺点,用哪个都可以。

  • 容器类层次结构

    • 只需要知晓容器的最上级的父接口为 BeanFactory即可
  • BeanFactory

    • 使用BeanFactory创建的容器是延迟加载
    • 使用ApplicationContext创建的容器是立即加载(可通过配置实现延迟加载)
    • 具体BeanFactory如何创建只需要了解即可。

bean常用属性

在这里插入图片描述

依赖注入常用属性

在这里插入图片描述

(2) 容器的创建方式

分为类路径下的XML配置文件文件系统下的XML配置文件

类路径下的XML配置文件

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

文件系统下的XML配置文件

ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\workspace\\spring\\spring_10_container\\src\\main\\resources\\applicationContext.xml");
(3) Bean的三种获取方式

方式一:

BookDao bookDao = (BookDao) ctx.getBean("bookDao");

缺点:需要强转

方式二:

BookDao bookDao = ctx.getBean("bookDao"BookDao.class);

缺点:需要多加一个参数

方式三:

BookDao bookDao = ctx.getBean(BookDao.class);

缺点:IOC容器中对应的bean对象只能有一个

(4) BeanFactory的使用
public class AppForBeanFactory {public static void main(String[] args) {Resource resources = new ClassPathResource("applicationContext.xml");BeanFactory bf = new XmlBeanFactory(resources);BookDao bookDao = bf.getBean(BookDao.class);bookDao.save();}
}
  • BeanFactory是延迟加载,只有在获取bean对象的时候才会去创建

  • ApplicationContext是立即加载,容器加载的时候就会创建bean对象

  • ApplicationContext要想成为延迟加载,只需要按照如下方式进行配置

3. DI相关内容

主要围绕引用类型简单类型的注入方式进行讲解

(1) 非注解写法和注意事项
<1> 知识点速览
<2> 准备工作
<3> setter注入

通过set方法进行注入

引用类型

在配置类中使用propetyref注入引用类型对象

    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"><!--property标签:设置注入属性--><!--name属性:设置注入的属性名,实际是set方法对应的名称--><!--ref属性:设置注入引用类型bean的id或name--><property name="bookDao" ref="bookDao"/></bean>

在bean中定义引用数据属性并提供set方法即可

public class BookServiceImpl implements BookService{private BookDao bookDao;//setter注入需要提供要注入对象的set方法public void setBookDao(BookDao bookDao) {this.bookDao = bookDao;}
}

简单类型

在配置类中使用propetyvalue属性注入简单类型数据

    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"><!--property标签:设置注入属性--><!--name属性:设置注入的属性名,实际是set方法对应的名称--><!--value属性:设置注入简单类型数据值--><property name="connectionNum" value="100"/><property name="databaseName" value="mysql"/></bean>
<4> 构造器注入

通过将set方法改为构造方法传参,进行注入

引用注入

配置类使用constructor-arg标签ref属性注入引用类型对象

     <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"> <!-- 根据构造方法参数名称注入--><constructor-arg name="userDao" ref="userDao"/><constructor-arg name="bookDao" ref="bookDao"/></bean>

这里的ref指向的是参数,如果改成BookServiceImpl(BookDao bookDao1, UserDao userDao1),则配置中name指向userDao1和bookDao1

实现类中定义引用类型属性,并提供构造方法

public class BookServiceImpl implements BookService{private BookDao bookDao;private UserDao userDao;public BookServiceImpl(BookDao bookDao, UserDao userDao) {this.bookDao = bookDao;this.userDao = userDao;}public void save() {System.out.println("book service save ...");bookDao.save();userDao.save();}
}

简单类型

在配置类中使用constructor-arg标签和value属性注入简单类型数据

    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"><constructor-arg name="databaseName" value="mysql"/><constructor-arg name="connectionNum" value="666"/></bean><bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/><bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"><constructor-arg name="bookDao" ref="bookDao"/><constructor-arg name="userDao" ref="userDao"/></bean>

在实体类添加构造方法

public class BookDaoImpl implements BookDao {private String databaseName;private int connectionNum;public BookDaoImpl(String databaseName, int connectionNum) {this.databaseName = databaseName;this.connectionNum = connectionNum;}public void save() {System.out.println("book dao save ..."+databaseName+","+connectionNum);}
}

在配置类中,可以添加type属性,按照类型注入,也可以添加index属性,按照索引下标注入

<5> 自动装配

在配置文件中,可以直接添加autowire属性进行注入

    <bean class="com.itheima.dao.impl.BookDaoImpl"/><bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>

autowire属性可以按照类型(byType),可以按照名称(byName)
使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效

<6> 集合注入

在配置类中直接注入即可

注入数组类型数据

<property name="array"><array><value>100</value><value>200</value><value>300</value></array>
</property>

注入List类型数据

<property name="list"><list><value>itcast</value><value>itheima</value><value>boxuegu</value><value>chuanzhihui</value></list>
</property>

注入Set类型数据

<property name="set"><set><value>itcast</value><value>itheima</value><value>boxuegu</value><value>boxuegu</value></set>
</property>

注入Map类型数据

<property name="map"><map><entry key="country" value="china"/><entry key="province" value="henan"/><entry key="city" value="kaifeng"/></map>
</property>

注入Properties类型数据

<property name="properties"><props><prop key="country">china</prop><prop key="province">henan</prop><prop key="city">kaifeng</prop></props>
</property>

实现类

public class BookDaoImpl implements BookDao {private int[] array;private List<String> list;private Set<String> set;private Map<String,String> map;private Properties properties;public void save() {System.out.println("book dao save ...");System.out.println("遍历数组:" + Arrays.toString(array));System.out.println("遍历List" + list);System.out.println("遍历Set" + set);System.out.println("遍历Map" + map);System.out.println("遍历Properties" + properties);}
}
(2) 注解写法和注意事项
<1> 知识点速览

知识点1:@Autowired

名称@Autowired
类型属性注解 或 方法注解(了解) 或 方法形参注解(了解)
位置构造方法、setter方法、字段、配置方法上 或 方法形参前面
作用为引用类型属性设置值
属性required:true/false,定义该属性是否允许为null

知识点2:@Qualifier

名称@Qualifier
类型属性注解 或 方法注解(了解)
位置属性定义上方 或 标准set方法上方 或 类set方法上方
作用为引用类型属性指定注入的beanId
属性value(默认):设置注入的beanId

知识点3:@Value

名称@Value
类型属性注解 或 方法注解(了解)
位置属性定义上方 或 标准set方法上方 或 类set方法上方
作用为 基本数据类型 或 字符串类型 属性设置值
属性value(默认):要注入的属性值

知识点4:@PropertySource

名称@PropertySource
类型类注解
位置类定义上方
作用加载properties文件中的属性值
属性value(默认):设置加载的properties文件对应的文件名或文件名组成的数组
<2> 改进方案

通过使用注解,可以按照类型、名称、数据类型,甚至可以读取properties配置文件

按照类型注入

使用@Autowired注解开启自动装配模式

@Service
public class BookServiceImpl implements BookService{@Autowiredprivate BookDao bookDao;public void save(){System.out.println("save");bookDao.save();}
}

基于反射设计常见对象,无需提供setter方法,建议使用无参构造方法构建对象

按照名称注入

使用@Qualifier注解开启指定名称装配bean

@Service
public class BookServiceImpl implements BookService{@Autowired@Qualifier("bookDao")private BookDao bookDao;
}
  • @Qualifier注解无法单独使用,必须配合@Autowired注解使用

简单数据类型注入

使用@Value实现简单类型注入

@Repository("bookDao")
public class BookDaoImpl implements BookDao{@Value("100")private String connectionNum;
}

注解读取properties配置文件

对于如@Qualifier@Value这种注解,为了防止写死,可以放到properties文件内

使用方法

使用@PropertySource注解加载properties文件

@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
public class SpringConfig{
}

注意:

  • 如果读取的properties配置文件有多个,可以使用@PropertySource的属性来指定多个

    @PropertySource({"jdbc.properties","xxx.properties"})
    
  • @PropertySource注解属性中不支持使用通配符*,运行会报错

    @PropertySource({"*.properties"})
    
  • @PropertySource注解属性中可以把classpath:加上,代表从当前项目的根路径找文件

    @PropertySource({"classpath:jdbc.properties"})
    

4.AOP相关内容

AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构,它可以在不惊动原始设计的基础上为其进行功能增强。

在这里主要学习AOP核心概念AOP作用,如AOP工作流程AOP配置管理AOP事务管理

(1) AOP的核心概念

概念:AOP(Aspect Oriented Programming)面向切面编程,一种编程范式

作用:在不惊动原始设计的基础上为方法进行功能增强

核心概念:

  • 代理(Proxy):SpringAOP的核心本质是采用代理模式实现的
  • 连接点(JoinPoint):在SpringAOP中,理解为任意方法的执行
  • 切入点(Pointcut):匹配连接点的式子,也是具有共性功能的方法描述
  • 通知(Advice):若干个方法的共性功能,在切入点处执行,最终体现为一个方法
  • 切面(Aspect):描述通知与切入点的对应关系
  • 目标对象(Target):被代理的原始对象成为目标对象

目标对象是要增强的类对应的对象,也叫原始对象。

SpringAOP中式不改变原有代码前提进行增强的,采用的是代理模式,因此对原始对象进行增强,需要对原始方法创建代理对象,在代理对象中的方法把通知加进去,实现了增强,这就是代理。

(2) 基于注解实现步骤

<1> 知识点一览

知识点1:@EnableAspectJAutoProxy

名称@EnableAspectJAutoProxy
类型配置类注解
位置配置类定义上方
作用开启注解格式AOP功能

知识点2:@Aspect

名称@Aspect
类型类注解
位置切面类定义上方
作用设置当前类为AOP切面类

知识点3:@Pointcut

名称@Pointcut
类型方法注解
位置切入点方法定义上方
作用设置切入点方法
属性value(默认):切入点表达式

知识点4:@Before

名称@Before
类型方法注解
位置通知方法定义上方
作用设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
<2> 准备工作

准备工作:创建Maven项目,添加依赖,实现类,配置类,运行类

思路分析:添加依赖,定义接口与实现类,定义通知类和通知,定义切入点,制作切面,将通知类配给容器,并标识为切面类,开启注解格式AOP功能

<3> 实现步骤

添加依赖

<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.4</version>
</dependency>

spring-context中自动导入了spring-aop,这里是AspectJ,是AOP思想的具体实现

接口与实现类

public interface BookDao {public void save();public void update();
}@Repository
public class BookDaoImpl implements BookDao {public void save() {System.out.println(System.currentTimeMillis());System.out.println("book dao save ...");}public void update(){System.out.println("book dao update ...");}
}

定义通知类和通知

public class MyAdvice {public void method(){System.out.println(System.currentTimeMillis());}
}

定义切入点(增强update方法)

public class MyAdvice {@Pointcut("execution(void com.Coolipa.dao.BookDao.update())")private void pt(){}public void method(){System.out.println(System.currentTimeMillis());}
}

制作切面

public class MyAdvice {@Pointcut("execution(void com.itheima.dao.BookDao.update())")private void pt(){}@Before("pt()")public void method(){System.out.println(System.currentTimeMillis());}
}

将通知类配给容器并表示为切面类

@Component
@Aspect
public class MyAdvice {@Pointcut("execution(void com.itheima.dao.BookDao.update())")private void pt(){}@Before("pt()")public void method(){System.out.println(System.currentTimeMillis());}
}

开启注解AOP功能

@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}

运行程序即可

(3) AOP工作流程

  1. Spring容器启动,加载bean(需要被增强的类,通知类),此时bean还没创建
  2. 读取所有切面配置中的切入点
  3. 初始化bean,将要被实例化bean对象的类中的方法和切入点进行匹配
    • 匹配失败,创建原始对象,直接调用原始对象的方法
    • 匹配成功,创建原始对象的代理对象,对其中的方法进行加强
  4. 获取bean的执行方法
    • 获取bean是原始对象时,调用方法并执行,完成操作
    • 获取bean时代理对象是,运行原始方法与增强的内容,完成操作

(4) AOP配置管理

这里主要是切入点表达式AOP通知类型书写技巧

<1> 切入点表达式
  • 切入点:要被增强的方法
  • 切入点表达式:要进行增强方法的描述方式

切入点表达式格式:execution([修饰符] 返回类型 [声明类型].方法名(参数) [throws 异常])

  • [] 表示可选部分
  • 修饰符:public、private等,通常省略
  • 返回类型:方法返回值类型,使用*匹配任意类型
  • 包名.类名:完整的类路径,可以使用..匹配多级包
  • 方法名:具体方法名,可以使用*通配符
  • 参数:参数类型列表,()表示无参数,(..)表示任意参数
execution(public User com.example.service.UserService.findById(int))
  • *:通配符,可以单独出现,可以作为前后缀
  • ..:通配符,多个连续的任意符号,用于简化包名与参数书写
  • +:通配符,匹配子类类型

注意事项

  1. 标准规范开发
  2. 查询操作的返回值建议使用*匹配
  3. 减少使用…的形式描述包
  4. 对接口进行描述,使用*表示模块名,例如UserService的匹配描述为*Service
  5. 方法名书写保留动词,例如get,使用*表示名词,例如getById匹配描述为getBy*
  6. 参数根据实际情况灵活调整

示例

execution(void com.itheima.dao.BookDao.update())
匹配接口,能匹配到
execution(void com.itheima.dao.impl.BookDaoImpl.update())
匹配实现类,能匹配到
execution(* com.itheima.dao.impl.BookDaoImpl.update())
返回值任意,能匹配到
execution(* com.itheima.dao.impl.BookDaoImpl.update(*))
返回值任意,但是update方法必须要有一个参数,无法匹配,要想匹配需要在update接口和实现类添加参数
execution(void com.*.*.*.*.update())
返回值为void,com包下的任意包三层包下的任意类的update方法,匹配到的是实现类,能匹配
execution(void com.*.*.*.update())
返回值为void,com包下的任意两层包下的任意类的update方法,匹配到的是接口,能匹配
execution(void *..update())
返回值为void,方法名是update的任意包下的任意类,能匹配
execution(* *..*(..))
匹配项目中任意类的任意方法,能匹配,但是不建议使用这种方式,影响范围广
execution(* *..u*(..))
匹配项目中任意包任意类下只要以u开头的方法,update方法能满足,能匹配
execution(* *..*e(..))
匹配项目中任意包任意类下只要以e结尾的方法,update和save方法能满足,能匹配
execution(void com..*())
返回值为void,com包下的任意包任意类任意方法,能匹配,*代表的是方法
execution(* com.itheima.*.*Service.find*(..))
将项目中所有业务层方法的以find开头的方法匹配
execution(* com.itheima.*.*Service.save*(..))
将项目中所有业务层方法的以save开头的方法匹配
<2> AOP通知类型
a.知识点总结
  • 前置通知
  • 后置通知
  • 环绕通知(重点)
    • 环绕通知依赖形参ProceedingJoinPoint才能实现对原始方法的调用
    • 环绕通知可以隔离原始方法的调用执行
    • 环绕通知返回值设置为Object类型
    • 环绕通知中可以对原始方法调用过程中出现的异常进行处理
  • 返回后通知
  • 抛出异常后通知

知识点1:@After

名称@After
类型方法注解
位置通知方法定义上方
作用设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行

知识点2:@AfterReturning

名称@AfterReturning
类型方法注解
位置通知方法定义上方
作用设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法正常执行完毕后执行

知识点3:@AfterThrowing

名称@AfterThrowing
类型方法注解
位置通知方法定义上方
作用设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行

知识点4:@Around

名称@Around
类型方法注解
位置通知方法定义上方
作用设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
b. 实现

修改MyAdvice,在方法上加上对应注解即可,前置@Before、后置@After、环绕@Around、返回后通知@AfterReturning、异常后通知@AfterThrowing

前置通知示例:除了环绕通知,其他基本如下

@Component
@Aspect
public class MyAdvice {@Pointcut("execution(void com.itheima.dao.BookDao.update())")private void pt(){}@Before("pt()")//此处也可以写成 @Before("MyAdvice.pt()"),不建议public void before() {System.out.println("before advice ...");}
}

环绕通知

    @Around("pt2()")public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {System.out.println("around before advice ...");//表示对原始操作的调用Object ret = pjp.proceed();System.out.println("around after advice ...");return ret;}

环绕通知注意事项

  1. 必须使用ProceedingJoinPoint:环绕通知必须依赖ProceedingJoinPoint参数才能调用原始方法
  2. 必须调用proceed():如果不调用pjp.proceed(),原始方法将被跳过
  3. 返回值类型:建议设置为Object类型以兼容各种返回值
  4. 异常处理:必须处理Throwable异常,因为无法预知原始方法是否会抛出异常
  5. void方法处理:即使原始方法返回void,环绕通知也可以返回Object或void

示例:

@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {// 前置逻辑Object result = pjp.proceed(); // 调用原始方法// 后置逻辑return result;
}
<3> AOP通知获取参数

准备工作:Maven项目,依赖,实现类,配置类,通知类

思路分析:通过JoinPoint获得参数,通过getArgs()从环绕通知获得参数,通过AfterReturningAround获得返回值,通过AfterThrowingAround获得异常

(1) 获取参数(⭐)

非环绕通知获取方式

此方法适用于前置后置返回后抛出异常后通知。

  • JoinPoint对象描述了连接点方法的运行状态,可以获取到原始方法的调用参数
@Before("pt()")
public void before(JoinPoint jp){Object[] args = jp.getArgs();System.out.println(Arrays.toString(args));
}

环绕通知获取方式

通过getArgs()方法获取

  • ProceedJointPoint是JoinPoint的子类
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {Object[] args = pjp.getArgs();System.out.println(Arrays.toString(args));Object ret = pjp.proceed();return ret;
}
(2) 获取返回值

只有返回后AfterReturing和环绕Around这两个通知类型可以获取

环绕通知获取返回值

ret是方法的返回值

    @Around("pt()")public Object around(ProceedingJoinPoint pjp) throws Throwable{Object[] args = pjp.getArgs();System.out.println(Arrays.toString(args));args[0] = 666;Object ret = pjp.proceed(args);return ret;}

返回后通知获取返回值

    @AfterReturning(value = "pt()",returning = "ret")public void afterReturning(Object ret) {System.out.println("afterReturning advice ..."+ret);}
(3) 获取异常(了解)

只有抛出异常后AfterThrowing和环绕Around这两个通知类型可以获取

环绕通知获取异常

将异常捕获即可

    @Around("pt()")public Object around(ProceedingJoinPoint pjp){Object[] args = pjp.getArgs();System.out.println(Arrays.toString(args));args[0] = 666;Object ret = null;try{ret = pjp.proceed(args);}catch(Throwable throwable){t.printStackTrace();}return ret;}

获取异常

    @AfterThrowing(value = "pt()",throwing = "t")public void afterThrowing(Throwable t) {System.out.println("afterThrowing advice ..."+t);}

(5) AOP事务管理

<1> 知识点总览

事务:在数据层或业务层保障一系列的数据库操作同成功失败
通过@Transactional注解实现,可拓展属性
在这里插入图片描述

<2> 概念
  • 事务作用:在数据层保障一系列的数据库操作同成功同失败
  • Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败
<3> 快速使用

在Spring中,提供了PlatformTransactionManager平台事务管理器

  1. 在方法上添加@Transactional注解

    	@Transactionalpublic void transfer(String out,String in ,Double money) {accountDao.outMoney(out,money);int i = 1/0;accountDao.inMoney(in,money);}
    

    在这里插入图片描述

上面这些属性都可以在@Transactional注解的参数上进行设置。

  • readOnly:true只读事务,false读写事务,增删改要设为false,查询设为true。

  • timeout:设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚,-1表示不设置超时时间。

  • rollbackFor:当出现指定异常进行事务回滚

  • noRollbackFor:当出现指定异常不进行事务回滚

  1. 在Config类中配置事务管理器
    //配置事务管理器,mybatis使用的是jdbc事务@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource){DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);transactionManager.setDataSource(dataSource);return transactionManager;}
  1. 在配置类中开启即可
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}

@Transactional可以写在接口类上、接口方法上、实现类上和实现类方法上

  • 写在接口类上,该接口的所有实现类的所有方法都会有事务
  • 写在接口方法上,该接口的所有实现类的该方法都会有事务
  • 写在实现类上,该类中的所有方法都会有事务
  • 写在实现类方法上,该方法上有事务
  • 建议写在实现类或实现类的方法上

5. 结语

如果这篇文章对你有帮助,希望可以点赞收藏+关注,如果有什么问题,可以评论留言.

http://www.dtcms.com/a/597367.html

相关文章:

  • Java 开发 - 粘包处理器 - 基于分隔符实现
  • 高阅读量CSDN文章分析
  • Linux基本命令与工具(一)
  • 类似12306网站开发行唐县网站建设
  • 基于Django实现的智慧校园考试系统-自动组卷算法实现
  • Java垃圾收集器全解:从Serial到G1的进化之旅
  • 如何免费建造网站苏州有哪些做网站公司
  • 基于IMM交互式多模型卡尔曼滤波的目标位置和速度估计matlab仿真
  • 微信_网站提成方案点做南昌网站建设推广专家
  • Phoenix+Hbase和Doris两个方案如何选择,能不能拿Doris完全替代Phoenix+Hbase?有什么难点?
  • 免费网站大全下载全球速卖通卖家注册
  • 生物信息学 (101计划核心教程)Chapter4
  • 【疑难解答】@Value 注解不生效的原因
  • 【题解】【深基2.例2】英文字母
  • 关于建设殡葬网站的报告范文wordpress免费模板小而美
  • ssm项目,邮箱验证码
  • 多网卡同网段 IP 引发的 ARP Flux
  • 机器学习日报16
  • 11月份运维面试题
  • H100服务器维修“病历卡”:五大常见故障现象与根源分析
  • 9. Linux-riscv内存管理41-46问
  • 用mcu做灯光效果网站大连金州新区规划建设局网站
  • React 实战: Todo 应用学习小结
  • 网站性能优化方案网络设计及网络设计文档
  • 香港科技大学广州|可持续能源与环境学域博士招生宣讲会—兰州大学专场
  • 下午察:当机器人变得太像人
  • 青海城乡与建设厅网站个人简历简短范文
  • 黑马JAVAWeb -Vue工程化-API风格 - 组合式API
  • ubuntu更新nvidia显卡驱动
  • React Native 自建 JS Bundle OTA 更新系统:从零到一的完整实现与踩坑记录