《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可以写在接口类上、接口方法上、实现类上和实现类方法上

  • 写在接口类上,该接口的所有实现类的所有方法都会有事务
  • 写在接口方法上,该接口的所有实现类的该方法都会有事务
  • 写在实现类上,该类中的所有方法都会有事务
  • 写在实现类方法上,该方法上有事务
  • 建议写在实现类或实现类的方法上
相关推荐
8***84825 小时前
spring security 超详细使用教程(接入springboot、前后端分离)
java·spring boot·spring
boonya6 小时前
Java中Plugin设计模式的规范应用
java·spring·设计模式·插件模式
o***59276 小时前
Spring 过滤器:OncePerRequestFilter 应用详解
java·后端·spring
有一个好名字8 小时前
Spring AI ——Java开发者的AI集成神器
java·人工智能·spring
e***95649 小时前
springboot-自定义注解
java·spring boot·spring
白露与泡影9 小时前
spring Security 认证流程闭环与调用链路详解
java·后端·spring
i***58679 小时前
Java开发的AI应用框架简述——LangChain4j、Spring AI、Agent-Flex
java·人工智能·spring
i***27959 小时前
SpringBoot实现异步调用的方法
java·spring boot·spring
阿在在9 小时前
Dubbo 与 Spring 整合全流程解析(含生产者与消费者
java·spring·dubbo
一辉ComeOn10 小时前
[源码系列:手写Spring] AOP第二节:JDK动态代理 - 当AOP遇见动态代理的浪漫邂逅
java·后端·spring