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

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 复制代码
<?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调用内部的方法

配置文件关键点

xml 复制代码
<bean id="bookDao" class="com.Coolipa.Dao.Impl.BookDaoImpl" />

启动类

java 复制代码
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可能会由于命名习惯而产生分歧,可以利用别名去解决这个问题

配置文件关键点

xml 复制代码
<bean id="bookDao" class="com.Coolipa.Dao.Impl.BookDaoImpl" name="bookDao2"/>

启动类

java 复制代码
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

xml 复制代码
<bean id ="bookService" name="service service2" class="com.Coolipa.Dao.Impl.BookDaoImpl">
      <property name="bookDao" ref="dao"/>
</bean>
<5>测试单例/非单例(scope的使用)

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

配置文件关键点

xml 复制代码
<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文件

配置类

java 复制代码
@Configuration
@Scope("singleton")
@ComponentScan("com.Coolipa")
public class SpringConfig {
}

被替换部分

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

启动类:

java 复制代码
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();
    }
}

被替换部分对比

java 复制代码
        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> 构造方法实例化

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

java 复制代码
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:具体工厂类中创建对象的方法名

xml 复制代码
<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>

编写运行类运行

java 复制代码
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

实例工厂

配置类内容

xml 复制代码
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>

factory-bean:工厂的实例对象

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

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

java 复制代码
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接口,重写方法

java 复制代码
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,观察是否为同一个实例来验证单例行为。

java 复制代码
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. 正常实现

添加初始化和销毁方法

java 复制代码
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...");
    }
}

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

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

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

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

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

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

区别

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

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

java 复制代码
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生命周期

java 复制代码
@Repository
//@Scope设置bean的作用范围
@Scope("singleton")
public class BookDaoImpl implements BookDao {

    public void save() {
        System.out.println("book dao save ...");
    }
    //@PostConstruct设置bean的初始化方法
    @PostConstruct
    public void init() {
        System.out.println("init ...");
    }
    //@PreDestroy设置bean的销毁方法
    @PreDestroy
    public 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配置文件

java 复制代码
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

文件系统下的XML配置文件

java 复制代码
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\workspace\\spring\\spring_10_container\\src\\main\\resources\\applicationContext.xml");

(3) Bean的三种获取方式

方式一:

java 复制代码
BookDao bookDao = (BookDao) ctx.getBean("bookDao");

缺点:需要强转

方式二:

java 复制代码
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);

缺点:需要多加一个参数

方式三:

java 复制代码
BookDao bookDao = ctx.getBean(BookDao.class);

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

(4) BeanFactory的使用

java 复制代码
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注入引用类型对象

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

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

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

简单类型

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

xml 复制代码
    <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属性注入引用类型对象

xml 复制代码
     <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

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

java 复制代码
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属性注入简单类型数据

xml 复制代码
    <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>

在实体类添加构造方法

java 复制代码
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属性进行注入

java 复制代码
    <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> 集合注入

在配置类中直接注入即可

注入数组类型数据

xml 复制代码
<property name="array">
    <array>
        <value>100</value>
        <value>200</value>
        <value>300</value>
    </array>
</property>

注入List类型数据

xml 复制代码
<property name="list">
    <list>
        <value>itcast</value>
        <value>itheima</value>
        <value>boxuegu</value>
        <value>chuanzhihui</value>
    </list>
</property>

注入Set类型数据

xml 复制代码
<property name="set">
    <set>
        <value>itcast</value>
        <value>itheima</value>
        <value>boxuegu</value>
        <value>boxuegu</value>
    </set>
</property>

注入Map类型数据

xml 复制代码
<property name="map">
    <map>
        <entry key="country" value="china"/>
        <entry key="province" value="henan"/>
        <entry key="city" value="kaifeng"/>
    </map>
</property>

注入Properties类型数据

xml 复制代码
<property name="properties">
    <props>
        <prop key="country">china</prop>
        <prop key="province">henan</prop>
        <prop key="city">kaifeng</prop>
    </props>
</property>

实现类

java 复制代码
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注解开启自动装配模式

java 复制代码
@Service
public class BookServiceImpl implements BookService{
    @Autowired
    private BookDao bookDao;
    
    public void save(){
        System.out.println("save");
        bookDao.save();
    }
}

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

按照名称注入

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

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

简单数据类型注入

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

java 复制代码
@Repository("bookDao")
public class BookDaoImpl implements BookDao{
    @Value("100")
    private String connectionNum;
}

注解读取properties配置文件

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

使用方法

使用@PropertySource注解加载properties文件

java 复制代码
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
public class SpringConfig{
}

注意:

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

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

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

    java 复制代码
    @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> 实现步骤

添加依赖

xml 复制代码
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

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

接口与实现类

java 复制代码
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 ...");
    }
}

定义通知类和通知

java 复制代码
public class MyAdvice {
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

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

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

制作切面

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

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

java 复制代码
@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功能

java 复制代码
@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等,通常省略
  • 返回类型:方法返回值类型,使用*匹配任意类型
  • 包名.类名:完整的类路径,可以使用..匹配多级包
  • 方法名:具体方法名,可以使用*通配符
  • 参数:参数类型列表,()表示无参数,(..)表示任意参数
arduino 复制代码
execution(public User com.example.service.UserService.findById(int))
  • *:通配符,可以单独出现,可以作为前后缀
  • ..:通配符,多个连续的任意符号,用于简化包名与参数书写
  • +:通配符,匹配子类类型

注意事项

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

示例

java 复制代码
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

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

java 复制代码
@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 ...");
    }
}

环绕通知

java 复制代码
    @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

示例:

java 复制代码
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    // 前置逻辑
    Object result = pjp.proceed(); // 调用原始方法
    // 后置逻辑
    return result;
}

<3> AOP通知获取参数

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

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

(1) 获取参数(⭐)

非环绕通知获取方式

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

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

环绕通知获取方式

通过getArgs()方法获取

  • ProceedJointPoint是JoinPoint的子类
java 复制代码
@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是方法的返回值

java 复制代码
    @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;
    }

返回后通知获取返回值

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

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

环绕通知获取异常

将异常捕获即可

java 复制代码
    @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;
    }

获取异常

java 复制代码
    @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注解

    java 复制代码
    	@Transactional
        public 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类中配置事务管理器
java 复制代码
    //配置事务管理器,mybatis使用的是jdbc事务
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
  1. 在配置类中开启即可
java 复制代码
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}

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

  • 写在接口类上,该接口的所有实现类的所有方法都会有事务
  • 写在接口方法上,该接口的所有实现类的该方法都会有事务
  • 写在实现类上,该类中的所有方法都会有事务
  • 写在实现类方法上,该方法上有事务
  • 建议写在实现类或实现类的方法上
相关推荐
Flittly3 小时前
【雕虫大技】Agent 动态 Skill 供应链安全加固(二):执行隔离沙箱实战
java·spring boot·spring
BUG指挥官1 天前
Sa-Token和Spring Security对比
java·后端·spring
geminigoth1 天前
Spring AI Alibaba 入门开发一(备份)
java·人工智能·spring
xbgRS1 天前
spring整合mybaits
java·spring·mybatis
ruleslol1 天前
静态依赖注入 VS 动态依赖查询
spring
墨雨晨曦881 天前
Spring AI总结
java·人工智能·spring
用户3126874877201 天前
Spring Boot 异常处理到底怎么玩的?从 DispatcherServlet 到全局兜底的全链路拆解
spring
不才不才不不才1 天前
Spring 源码系列(17): HandlerMapping 与 HandlerAdapter 两大体系
java·后端·spring
(轻舟已过万重山)1 天前
第40章 Spring AI 实战:企业级 AI 应用架构
人工智能·spring·架构
堕落年代2 天前
Ollama CPU 推理大提示词优化实测报告(细致化数据版)
java·后端·spring