spring基础总结

2023-12-30 21:29:19

一、Spring(配置文件xml模式)

spring-1-ioc技术(DI【依赖注入】)

ioc也可以称为DI,即依赖注入

  1. ioc(控制反转):对象由主动new产生对象转变为由外部提供对象。(控制反转思想:对象创建控制权由程序转移到外部。【这里的外部是指:Spring的IOC容器】)

  2. DI(依赖注入):在ioc容器中建立bean与bean之间的依赖关系。(比如service和dao的关系:dao作为service的一个成员变量。那么这依赖关系就是service和dao之间的关系);(还没学spring之前,我们是通过service层调用dao层的方法进行实现,把dao层作为service层的一个成员变量进行引用)
    在这里插入图片描述

  3. ioc容器的作用:IOC容器负责对象的创建、初始化等一系列工作,其中包含了数据层和业务层的类对象,IOC容器中放的就是一个个的Bean对象(比如service层,dao层等的类对象,都被ioc容器管理,所以都是bean)。其实就是解耦。

  4. bean:被创建或被管理的对象在IOC容器中统称为Bean

spring-2-入门

  1. Spring到底是如何来实现IOC和DI的
    • 1.1、Spring是使用ioc容器来管理bean对象的,管理的bean对象指的就是:项目中所使用到的类对象,比如(Service和Dao)
    • 1.2、spring是通过配置文件(.xml)或者注解,将被管理的对象 告知IOC容器,然后在配置bean对象的内部中添加依赖注入的配置
    • 1.3、Spring框架提供相应的接口获取到ioc容器,然后调用对应接口中的方法获取到bean对象

xml配置文件:

<?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标签就是告知ioc容器,该对象需要被管理(即配置bean对象)
		id属性标示给bean起名字
		class属性表示给bean定义类型
	-->
	<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
	<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
		<!--配置server与dao的关系-->
		<!--property标签表示配置当前bean的属性
		name属性表示配置哪一个具体的属性
		ref属性表示参照哪一个bean
		-->
		<property name="bookDao" ref="bookDao"/>
    </bean>
</beans>

配置文件下的各属性的意义:
在这里插入图片描述

在这里插入图片描述

spring-3-获取ioc容器的两种方式(BeanFactory和ApplicationContext)

BeanFactory工厂(类似反射)也可以说是:延迟加载bean


public class BeanFactoryTest {
    public static void main(String[] args) {

//        1. 创建工厂对象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
//        2. 创建一个读取器(xml文件)
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
//        3. 读取器绑定工厂
         reader.loadBeanDefinitions("beans.xml");
//         4. 根据id获取bean实例对象
        UserService userService = (UserService) beanFactory.getBean("userService");
    }
}

ApplicationContext:ApplicationContext接口常用初始化类:ClassPathXmlApplicationContext(常用),这个类主要是用来加载配置文件的(.xml)

public class ApplicationContextTest {
    public static void main(String[] args) {
        //创建ApplicationContext,加载配置文件,实例化容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
     //根据beanName获得容器中的Bean实例
       UserService userService = (UserService) applicationContext.getBean("userService");
    }
}
  1. BeanFactory与ApplicationContext的关系
    • 1)BeanFactory是Spring的早期接口,称为Spring的Bean工厂,ApplicationContext是后期更高级接口,称之为Spring 容器;
    • 2)ApplicationContext在BeanFactory基础上对功能进行了扩展,例如:监听功能、国际化功能等。BeanFactory的API更偏向底层,ApplicationContext的API大多数是对这些底层API的封装;
    • 3)Bean创建的主要逻辑和功能都被封装在BeanFactory中,ApplicationContext不仅继承了BeanFactory,而且ApplicationContext内部还维护着BeanFactory的引用,所以,ApplicationContext与BeanFactory既有继承关系,又有融合关系。
    • 4)Bean的初始化时机不同,原始BeanFactory是在首次调用getBean时才进行Bean的创建,而ApplicationContext则是配置文件加载,容器一创建就将Bean都实例化并初始化好。

spring-3.1- Bean的实例化方式

  1. 构造方法
  2. 工厂方式
      1. 静态工厂
      1. 实例工厂
      1. FactoryBean规范延迟实例化bean
spring-3.1.1- 使用构造方法实例化(反射)
  1. 使用构造方法实例化bean其底层实际上使用的是反射
    • (把构造方法换成私有的,依旧可以输出该构造方法的语句。)
    • 只要是bean在创建时或者构造时 需要参数,都可以使用 《constructor-arg》标签进行嵌入(该标签不只是对于构造方法)
//service层下的接口
public interface BookDao {
  public void save();
}


//impl层的实现类
public class BookDaoImpl implements BookDao {
	public void save() {
		System.out.println("book dao save ...");
	}
}


//程序执行的主方法类
public class AppForInstanceBook {
	public static void main(String[] args) {
		ApplicationContext ctx = new
		ClassPathXmlApplicationContext("applicationContext.xml");
		BookDao bookDao = (BookDao) ctx.getBean("bookDao");
		bookDao.save();
	}
}



//在impl层下的接口实现类下添加一个无参构造方法(其实默认存在一个无参构造方法的,这里只是加入一条输出语句,方便看到这个无参构造方法被调用)
public class BookDaoImpl implements BookDao {
	public BookDaoImpl() {
		System.out.println("book dao constructor is running ....");
	}
	public void save() {
		System.out.println("book dao save ...");
	}
}
<?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-->
<!--这里使用的是无参构造-->
	<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>



<!-- 一下均为解释-->
	<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
	    <!--基本数据类型-->
	    <constructor-arg name="参数名" value="参数值"/>
	    <!--引用数据类型-->
	    <constructor-arg name="参数名" ref="引用类型的对象"/>
	</bean>
</beans>
spring-3.1.2- 使用静态工厂实例化(不需要创建对象直接调用方法)
  1. 创建一个工厂类,并提供一个静态方法,返回值是需要bean实例化的对象
//接口
public interface OrderDao {
	public void save();
}

//接口对应的实现类
public class OrderDaoImpl implements OrderDao {
	public void save() {
		System.out.println("order dao save ...");
	}
}


//静态工厂创建对象
public class OrderDaoFactory {
//静态方法,返回一个对象
	public static OrderDao getOrderDao(){
		return new OrderDaoImpl();
	}
}



//执行的主方法
public class AppForInstanceBook {
	public static void main(String[] args) {
		ApplicationContext ctx = new
		ClassPathXmlApplicationContext("applicationContext.xml");
		OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
		 orderDao.save();
	}
}

spring的配置

<?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="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>
<!--	
class:工厂类的类全名
factory-mehod:具体工厂类中创建对象的方法名

factory-mehod参数是告诉spring不是要把class="com.itheima.factory.OrderDaoFactory"变成对象,而是找到class=="com.itheima.factory.OrderDaoFactory"内部的方法getOrderDao这个方法,
 把这个方法的返回值当做对象,再以你指定的id作为对象的名字存储到容器中
 -->

</beans>

问题

/*
可能有人会问了,你这种方式在工厂类中不也是直接new对象的,
和我自己直接new没什么太大的区别,
而且静态工厂的方式反而更复杂,这种方式的意义是什么?
  
  意义:
     1. 逻辑更加灵活一点
     2. 方便某一些第三方bean的使用
  
*/

public class OrderDaoFactory {
	public static OrderDao getOrderDao(){
	//bean创建之前进行其他的业务操作
		return new OrderDaoImpl();
		//bean创建之后进行一些其他的逻辑操
		//(可以把返回值提到外面,即OrderDao orderDao = new OrderDaoImpl())
	 }
}


spring-3.1.3- 使用实例工厂实例化(需要创建对象然后再调用方法)
  1. 创建一个工厂类,并提供一个方法,返回值是需要bean实例化的对象
//接口
public interface OrderDao {
	public void save();
}

//接口对应的实现类
public class OrderDaoImpl implements OrderDao {
	public void save() {
		System.out.println("order dao save ...");
	}
}


//静态工厂创建对象
public class OrderDaoFactory {
//普通方法,返回一个对象
	public OrderDao getOrderDao(){
		return new OrderDaoImpl();
	}
}



//执行的主方法
public class AppForInstanceBook {
	public static void main(String[] args) {
		ApplicationContext ctx = new
		ClassPathXmlApplicationContext("applicationContext.xml");
		OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
		 orderDao.save();
	}
}

spring的配置

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

<!-- 1. 得先配置工厂对象-->
    <bean id="orderDaoFactory" class="com.itheima.factory.OrderDaoFactory"/>
    <!-- 2. 再去配工厂方法的返回值-->
	<bean id="orderDao" factory-bean ="orderDaoFactory" factory-method="getOrderDao"/>
<!--	
class:工厂类的类全名
factory-mehod:具体工厂类中创建对象的方法名
factory-bean::工厂的实例对象
 -->

</beans>

问题

可能有人会问了,你这种方式在工厂类中不也是直接new对象的,
和我自己直接new没什么太大的区别,
而且静态工厂的方式反而更复杂,这种方式的意义是什么?
  
  意义:
     1. 逻辑更加灵活一点
     2. 方便某一些第三方bean的使用
  


public class OrderDaoFactory {
	public OrderDao getOrderDao(){
	//bean创建之前进行其他的业务操作
		return new OrderDaoImpl();
		//bean创建之后进行一些其他的逻辑操
		//(可以把返回值提到外面,即OrderDao orderDao = new OrderDaoImpl())
	 }
}


spring-3.1.5- 使用FactoryBean规范延迟实例化bean(spring底层用的比较多)
  1. 创建一个工厂类,实现FactoryBean接口,重写接口的方法。
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
	//代替原始实例工厂中创建对象的方法
	public UserDao getObject() throws Exception {
		return new UserDaoImpl();
	}
	
	//返回所创建类的Class对象
	public Class<?> getObjectType() {
		return UserDao.class;
	}
}

spring配置

<?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">
<!-- 配置FactoryBean交由Spring管理-->
  <bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>

</beans>

执行主方法:

public class AppForInstanceUser {
	public static void main(String[] args) {
		ApplicationContext ctx = new
		ClassPathXmlApplicationContext("applicationContext.xml");
		UserDao userDao = (UserDao) ctx.getBean("userDao");
		UserDao userDao1 = (UserDao) ctx.getBean("userDao");
		// userDao和userDao1是同一个对象
		userDao.save();
	}
}
  1. 通过断点观察发现Spring容器创建时(即只执行了ApplicationContext ctx = new
    ClassPathXmlApplicationContext(“applicationContext.xml”);的时候),FactoryBean被实例化了,并存储到了单例池singletonObjects中,但是getObject() 方法尚未被执行,UserDaoImpl也没被实例化,
  2. 当首次用到UserDaoImpl时(UserDao userDao = (UserDao) ctx.getBean(“userDao”);),才调用getObject() ,
    此工厂方式产生的Bean实例不会存储到单例池singletonObjects中,会存储到 factoryBeanObjectCache 缓存池中,并且后期每次使用到userDao都从该缓存池中返回,且返回的是同一个userDao实例

在这里插入图片描述

spring-3.2- Bean的三种获取方式

  1. 方式一:
BookDao bookDao = (BookDao) ctx.getBean("bookDao");

这种方式存在的问题是每次获取的时候都需要进行类型转换,有没有更简单的方式呢?

  1. 方式二:
 BookDao bookDao = ctx.getBean("bookDao"BookDao.class);

这种方式可以解决类型强转问题,但是参数又多加了一个,相对来说没有简化多少。

  1. 方式三:
 BookDao bookDao = ctx.getBean(BookDao.class);

这种方式就类似我们之前所学习依赖注入中的按类型注入。必须要确保IOC容器中该类型对应的bean对象只能有一个。

spring-4-bean的生命周期的设置

分为两个阶段

  • (初始化方法)bean创建之后,想要添加内容(比如用来初始化需要用到资源)
  • (销毁方法)bean销毁之前,想要添加内容(比如用来释放用到的资源)
  • 初始化方法会在类中属性设置之后执行

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

  • 1)在配置文件中的bean标签中添加init-method和destroy-method属性
  • 2)类实现InitializingBean与DisposableBean接口,这种方式了解下即可。
  1. 添加初始化(init)和销毁(destory)方法。【这里的两个方法名随意】
  2. 需要配置生命周期
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init"
destroy-method="destory"/>
  1. Spring的IOC容器是运行在JVM中的,在运行main方法后,JVM启动,Spring加载配置文件生成IOC容器,从容器获取bean对象,然后调方法执行main方法执行完后,JVM退出,这个时候IOC容器中的bean还没有来得及销毁就已经结束了所以没有调用对应的destroy方法

spring-4.1-close关闭容器(比较暴力)

  1. ApplicationContext中没有close方法需要将ApplicationContext更换成ClassPathXmlApplicationContext
ClassPathXmlApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
  1. 调用ctx的close()方法,就能执行destroy方法的内容
ctx.close();
  1. close()方法是在调用的时候关闭,即一执行该代码,容器就关闭了,该代码后续的代码都不再执行。

spring-4.2- 注册钩子关闭容器

  1. 在容器未关闭之前,提前设置好回调函数,让JVM在退出之前回调此函数来关闭容器(即调用registerShutdownHook()方法)
ClassPathXmlApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
ctx.registerShutdownHook();
  1. 关闭钩子(在任何时间都可以:就是说这行代码可以放任何位置)

spring-4.3-类实现InitializingBean与DisposableBean接口控制bean的生命周期

  1. Spring提供了两个接口来完成生命周期的控制,好处是可以不用再进行配置init-method和
    destroy-method
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");
 	}
 }

spring-5-DI(依赖注入相关)

  1. 向一个类中传递数据的方式有几种?
    • 普通方法(set方法)
    • 构造方法
  2. spring提供了两种提供了两种注入方式:
    • 1)setter注入
      • 简单类型
      • 引用类型
    • 2)构造器注入
      • 简单类型
      • 引用类型

spring-5.1-setter注入 简单类型(普通数据类型)

  1. 声明简单类型的属性并提供setter方法
public class BookDaoImpl implements BookDao {
	private String databaseName;
	private int connectionNum;
	public void setConnectionNum(int connectionNum) {
		this.connectionNum = connectionNum;
	}
	public void setDatabaseName(String databaseName) {
		this.databaseName = databaseName;
	}
}
  1. :配置文件中进行注入配置
<?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="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
		<property name="databaseName" value="mysql"/>
		<property name="connectionNum" value="10"/>
	</bean>
	<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
	<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
		<property name="bookDao" ref="bookDao"/>
		<property name="userDao" ref="userDao"/>
	</bean>
</beans>

spring-5.2-setter注入 引用类型

  1. 在bean中定义引用类型属性,并提供可访问的set方法(主要是提供对应的setter方法就行 setter方法的作用其实就是用于接收注入的对象
public class BookServiceImpl implements BookService {
	private BookDao bookDao;
	public void setBookDao(BookDao bookDao) {
		this.bookDao = bookDao;
	}
}
  1. 配置中使用property标签ref属性注入引用类型对象
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
	<property name="bookDao" ref="bookDao"/>
</bean>
<bean id="bookDao" class="com.itheima.dao.imipl.BookDaoImpl"/>

spring-5.3-构造器注入 简单类型(普通数据类型)

  1. 定义简单类型的属性,并提供对应的构造器方法
public class BookServiceImpl implements BookService{
	private BookDao bookDao;
	public BookServiceImpl(BookDao bookDao) {
		this.bookDao = bookDao;
	}
}
  1. 在配置中使用constructor-arg标签,配置构造方式注入
<?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="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
	<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
		<constructor-arg name="bookDao" ref="bookDao"/>
	</bean>
</beans>

spring-5.4-构造器注入 引用类型

  1. 定义引用类型的属性,并提供对应的构造器方法
public class BookServiceImpl implements BookService{
	private BookDao bookDao;
	private UserDao userDao;
	public BookServiceImpl(BookDao bookDao,UserDao userDao) {
	this.bookDao = bookDao;
	this.userDao = userDao;
	}
}
  1. 在配置中使用constructor-arg标签,配置构造方式注入
<?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="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
	<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>
</beans>

如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选
依赖的注入

实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注

spring-5.5-(依赖自动装配)

  1. 自动装配的方式有3种:
    • 1)按类型(常用)
    • 2)按名称
    • 3)按构造方法
  2. 具体操作:
在setter注入的基础上修改:
(1)将<property>标签删除
(2)在<bean>标签中添加autowire属性
<?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 class="com.itheima.dao.impl.BookDaoImpl"/>
	<!--autowire属性:开启自动装配,通常使用按类型装配-->
	<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"
	autowire="byType"/>
</beans>
  1. 注意事项:
    • 1)需要注入属性的类中对应属性的setter方法不能省略
    • 2)被注入的对象必须要被Spring的IOC容器管理
    • 3)按照类型在Spring的IOC容器中如果找到多个对象,会报NoUniqueBeanDefinitionException`
  2. 总结:最后对于依赖注入,需要注意一些其他的配置特征:
      1. 自动装配用于引用类型依赖注入,不能对简单类型进行操作
      1. 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
      1. 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
      1. 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效

spring-6-集合注入(用到的时候搜就行了)

spring-7-IOC/DI配置管理第三方bean

需求:使用Spring的IOC容器来管理Druid连接池对象
	1.使用第三方的技术,需要在pom.xml添加依赖
	2.在配置文件中将【第三方的类】制作成一个bean,让IOC容器进行管理
	3.数据库连接需要基础的四要素驱动、连接、用户名和密码,【如何注入】到对应的bean中(使用setter注入)

第三方的类:DruidDataSource
  1. 配置第三方bean
    (德鲁伊【DruidDataSource】)
<?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">
	<!--管理DruidDataSource对象-->
	<bean class="com.alibaba.druid.pool.DruidDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
		<property name="url" value="jdbc:mysql://localhost:3306/spring_db"/>
		<property name="username" value="root"/>
		<property name="password" value="root"/>
	</bean>
</beans>

C3P0数据源【ComboPooledDataSource】

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="driverClass" value="com.mysql.jdbc.Driver"/>
	<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_db"/>
	<property name="user" value="root"/>
	<property name="password" value="root"/>
	<property name="maxPoolSize" value="1000"/>
</bean>

C3P0的四个属性和Druid的四个属性是不一样的

  1. 注意:
    • 数据连接池在配置属性的时候,除了可以注入数据库连接四要素外还可以配置很多其他的属性,具体都有哪些属性用到的时候再去查,一般配置基础的四个,其他都有自己的默认值
    • Druid和C3P0在没有导入mysql驱动包的前提下,一个没报错一个报错,说明Druid在初始化的时候没有去加载驱动,而C3P0刚好相反
    • Druid程序运行虽然没有报错,但是当调用DruidDataSource的getConnection()方法获取连接的时候,也会报找不到驱动类的错误

spring-7.1-第三方bean属性优化

这两个数据源中都使用到了一些固定的常量如数据库连接四要素,把这些值写在Spring的配置文件中不利于后期维护需要将这些值提取到一个外部的properties配置文件中(就是使用properties文件)
properties文件具体内容:(采用键值对的方式)

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=root

注意

  • .在properties中配置键值对的时候,如果key设置为username(即username=xxx),usename的值不会是你自己设置的xxx,而是自己电脑的用户名
    • 出现问题的原因是 《context:property-placeholder》 标签会加载系统的环境变量,而且环境变量的值会被优先加载

解决方案

system-properties-mode:设置为NEVER,表示不加载系统属性,就可以解决上述问题。
当然还有一个解决方案就是避免使用username作为属性的key。

<?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="
 http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans.xsd
 http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/springcontext.xsd">

 <context:property-placeholder location="jdbc.properties" systemproperties-mode="NEVER"/>
 
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/springcontext.xsd">

<!-- 加载properties文件-->
	<context:property-placeholder location="jdbc.properties"/>
	
	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
		<property name="driverClassName" value="${jdbc.driver}"/>
		<property name="url" value="${jdbc.url}"/>
		<property name="username" value="${jdbc.username}"/>
		<property name="password" value="${jdbc.password}"/>
	</bean>
</beans>

spring-7.2-当有多个properties配置文件需要被加载

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="
 http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans.xsd
 http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/springcontext.xsd">
	<!--方式一 -->
	<context:property-placeholder
	location="jdbc.properties,jdbc2.properties" system-propertiesmode="NEVER"/>
	<!--方式二-->
	<context:property-placeholder location="*.properties" systemproperties-mode="NEVER"/>
	<!--方式三 -->
	<context:property-placeholder location="classpath:*.properties"
	system-properties-mode="NEVER"/>
	<!--方式四-->
	<context:property-placeholder location="classpath*:*.properties"
	system-properties-mode="NEVER"/>
</beans>
  1. 说明:
    • 方式一:可以实现,如果配置文件多的话,每个都需要配置
    • 方式二: *.properties代表所有以properties结尾的文件都会被加载,可以解决方式一的问题,但是不标准
    • 方式三:标准的写法,classpath:代表的是从根路径下开始查找,但是只能查询当前项目的根路径
    • 方式四:不仅可以加载当前项目还可以加载当前项目所依赖的所有项目的根路径下的
      properties配置文件

二、Spring(注解开发)

spring注解-1-注解开发定义bean

  1. 将配置文件中的标签(原来配置bean的配置,如下)删除掉
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
  1. 在Dao(即你需要定义的bean)上添加注解@Component(:@Component注解不可以添加在接口上,因为接口是无法创建对象的,应该添加到接口的实现类上)
    @Component注解如果不起名称,会有一个默认值就是当前类名首字母小写,所以也可以按照名称
    获取
@Component("bookDao")
public class BookDaoImpl implements BookDao {
	public void save() {
		System.out.println("book dao save ..." );
	}
}
  1. 在service层上也要添加注解@Component
    在这里插入图片描述

  2. 为了让Spring框架能够扫描到写在类上的注解,需要在配置文件上进行包扫描

<?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">
	<context:component-scan base-package="com.itheima"/>
</beans>

spring注解-1.1-@Component注解

名称@Component/@Controller/@Service/@Repository
类型类注解
位置类定义上方
作用设置该类为spring管理的bean
属性value(默认) :定义bean的id
  1. 对于@Component注解,还衍生出了其他三个注解@Controller、@Service、@Repository这三个注解和@Component注解的作用是一样的
  2. 为什么要衍生出这三个呢?
    • 方便我们后期在编写类的时候能很好的区分出这个类是属于表现层、业务层还是数据层的类

spring纯注解-2-使用Java类替代配置文件

  1. 将配置文件applicationContext.xml删除掉,使用类来替换。
  2. 实现步骤:
    • 1)创建配一个类,标识该类为配置类(在配置类上添加@Configuration注解,将其标识为一个配置类,替换applicationContext.xml
    • 2)用注解替换包扫描配置:(在配置类上添加包扫描注解@ComponentScan替换<context:component-scan base-package=“”/>
    • 3)读取Spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象
@Configuration
@ComponentScan("com.itheima")
public class SpringConfig {

}
//加载配置文件初始化容器
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
//加载配置类初始化容器
ApplicationContext ctx = new
AnnotationConfigApplicationContext(SpringConfig.class);
  • ClassPathXmlApplicationContext是加载XML配置文件
  • AnnotationConfigApplicationContext是加载配置类

spring纯注解-2.1-@Configuration注解

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

spring纯注解-2.2-@ComponentScan注解

名称@ComponentScan
类型类注解
位置类定义上方
作用设置spring配置类扫描路径,用于加载使用注解格式定义的bean
属性value(默认):扫描路径,此路径可以逐层向下扫描
  1. @ComponentScan注解用于设定扫描路径,此注解只能添加一次,多个数据请用数组格式

spring纯注解-2.3-@Scope注解

名称@Scope
类型类注解
位置类定义上方
作用设置该类创建对象的作用范围,可用于设置创建出的bean是否为单例对象
属性value(默认):定义bean作用范围,默认值singleton(单例),可选值prototype(非单例)
@Repository
//@Scope设置bean的作用范围
@Scope("prototype")
public class BookDaoImpl implements BookDao {
	public void save() {
		System.out.println("book dao save ...");
    }
}

spring纯注解-2.4-@PostConstruct和@PreDestroy注解

名称@PostConstruct
类型方法注解
位置方法上方
作用设置该方法为初始化方法
属性
名称@PreDestroy
类型方法注解
位置方法上方
作用设置该方法为销毁方法
属性
@Repository
 public class BookDaoImpl implements BookDao {
	 public void save() {
	 	System.out.println("book dao save ...");
	 }
	 @PostConstruct //在构造方法之后执行,替换 init-method
	 public void init() {
		 System.out.println("init ...");
	 }
	 @PreDestroy //在销毁方法之前执行,替换 destroy-method
	 public void destroy() {
	 	System.out.println("destroy ...");
	 }
 }
  1. 注意的是destroy只有在容器关闭的时候,才会执行((close方法或者是注册构子registerShutdownHook()方法)
  2. 注意:@PostConstruct和@PreDestroy注解如果找不到,需要导入下面的jar包
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>

找不到的原因是,从JDK9以后jdk中的javax.annotation包被移除了,这两个注解刚好就在这个包
中。
在这里插入图片描述

spring纯注解-2.5-注解开发依赖注入

  1. Spring为了使用注解简化开发,并没有提供构造函数注入、setter注入对应的注解,只提供了自动装配的注解实现
spring纯注解-2.5.1-注解实现按照类型注入@Autowired注解
名称@Autowired
类型属性注解 或 方法注解(了解) 或 方法形参注解(了解)
位置属性定义上方 或 标准set方法上方 或 类set方法上方 或 方法形参前面
作用为引用类型属性设置值
属性required:true/false,定义该属性是否允许为null
  1. 定义(要被注入的类型)的属性,然后在属性的上面加入@Autowired注解
@Service
public class BookServiceImpl implements BookService {
	@Autowired
	private BookDao bookDao;
	// public void setBookDao(BookDao bookDao) {
	// this.bookDao = bookDao;
	// }
	public void save() {
		System.out.println("book service save ...");
		bookDao.save();
	}
}

注意

  • 1)@Autowired可以写在属性上,也可也写在setter方法上,最简单的处理方式是写在属性上并将setter方法删除掉
  • 2)为什么setter方法可以删除呢?
    • 自动装配基于反射设计创建对象并通过暴力反射为私有属性进行设值
    • 普通反射只能获取public修饰的内容
    • 暴力反射除了获取public修饰的内容还可以获取private修改的内容所以此处无需提供setter方法
  • 3)@Autowired默认按照类型自动装配,如果IOC容器中同类的Bean找到多个,就按照变量名和Bean的名称(bean的名字默认是该类名的小写)匹配。
spring纯注解-2.5.2- 注解实现按照名称注入@Qualifier
名称@Qualifier
类型属性注解 或 方法注解(了解)
位置属性定义上方 或 标准set方法上方 或 类set方法上方
作用为引用类型属性指定注入的beanId
属性value(默认):设置注入的beanId
  1. 当根据类型在容器中找到多个bean,注入参数的属性名又和容器中bean的名称不一致,这个时候就需要使用到@Qualifier来指定注入哪个名称的bean对象。
@Service
public class BookServiceImpl implements BookService {
@Autowired
@Qualifier("bookDao1")
private BookDao bookDao;
public void save() {
	System.out.println("book service save ...");
	bookDao.save();
  }
}
  1. 注意
    • 1)@Qualifier不能独立使用,必须和@Autowired一起使用,@Qualifier注解后的值就是需要注入的bean的名称。
spring纯注解-2.5.3- 简单数据类型注入@value注解
名称@value
类型属性注解 或 方法注解(了解)
位置属性定义上方 或 标准set方法上方 或 类set方法上方
作用为 基本数据类型 或 字符串类型 属性设置值
属性value(默认):要注入的属性值
  1. 注意数据格式要匹配
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Value("itheima")
private String name;
public void save() {
	System.out.println("book dao save ..." + name);
	}
}
  1. @Value一般会被用在从properties配置文件中读取内容进行使用
spring纯注解-2.5.4-使用注解加载properties配置文件@PropertySource
名称@PropertySource
类型类注解
位置类定义上方
作用加载properties文件中的属性值
属性value(默认):设置加载的properties文件对应的文件名或文件名组成的数组
  1. 在配置类上添加@PropertySource注解
@Configuration
@ComponentScan("com.itheima")
@PropertySource("jdbc.properties")
public class SpringConfig {
}
  1. 注意
    • 1)如果读取的properties配置文件有多个,可以使用@PropertySource的属性来指定多个
    • 2)@PropertySource注解属性中不支持使用通配符*,运行会报错
    • 3)@PropertySource注解属性中可以把classpath:加上,代表从当前项目的根路径找文件
//读取的properties配置文件有多个
@PropertySource({"jdbc.properties","xxx.properties"})

//classpath
@PropertySource({"classpath:jdbc.properties"})
  1. 作用:@PropertySource是Spring框架中的注解,用于加载指定的属性文件。通过该注解,我们可以将属性文件中的键值对加载到Spring的环境变量中,从而在程序中使用@Value注解获取属性值。@PropertySource注解可以用在类上或者方法上,用于指定要加载的属性文件路径。
spring纯注解-2.5.5- 注解开发管理第三方bean
名称@bean
类型方法注解
位置方法定义上方
作用设置该方法的返回值作为spring管理的bean
属性value(默认):定义bean的id

举例:对Druid数据源的管理

  1. 第一种方法:直接在配置类上添加方法
    • 1)在spring的配置类上添加一个方法:该方法的返回值就是要创建的Bean对象类型
    • 2)在方法上添加@Bean注解,@Bean注解的作用是将方法的返回值制作为Spring管理的一个bean对象
@Configuration
public class SpringConfig {
	@Bean
	public DataSource dataSource(){
		DruidDataSource ds = new DruidDataSource();
		ds.setDriverClassName("com.mysql.jdbc.Driver");
		ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
		ds.setUsername("root");
		ds.setPassword("root");
		return ds;
	}
}
  1. 第二种方法:引入外部配置类
名称@Import
类型类注解
位置定义在类上方
作用导入配置类
属性value(默认):定义导入的配置类类名,当配置类有多个时使用数组格式一次性导入多个配置类

注意

  • @Import参数需要的是一个数组,可以引入多个配置类。
  • @Import注解在配置类中只能写一次

原因:如果把所有的第三方bean都配置到Spring的配置类SpringConfig中,虽然可以,但是不利于代码阅
读和分类管理,我们可以按照类别将这些bean配置到不同的配置类中。
步骤

  • 1)新建一个JdbcConfig配置类,并把数据源(就是上面的datasource第三方bean)配置到该类下
  • 2)将该配置类加入到spring的核心配置类上(比如:springConfig类),使用@Import注解
public class JdbcConfig {
@Bean
public DataSource dataSource(){
	DruidDataSource ds = new DruidDataSource();
	ds.setDriverClassName("com.mysql.jdbc.Driver");
	ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
	ds.setUsername("root");
	ds.setPassword("root");
	return ds;
	}
}
@Configuration
//@ComponentScan("com.itheima.config")
@Import({JdbcConfig.class})
public class SpringConfig {
}
spring纯注解-2.5.6-注解开发实现为第三方bean注入资源(注入引用数据类型)

注入引用数据类型步骤

  • 1):在SpringConfig中扫描所要注入的引用类型数据(比如bookDao,扫描的目的是让Spring能管理到BookDao,也就是说要让IOC容器中有一个bookDao对象)
  • 2)在JdbcConfig类的方法上添加参数。引用类型注入只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象。
@Configuration
@ComponentScan("com.itheima.dao")
@Import({JdbcConfig.class})
public class SpringConfig {
}
public class JdbcConfig {
	@Bean
	public DataSource dataSource(BookDao bookDao){
		System.out.println(bookDao);
		DruidDataSource ds = new DruidDataSource();
		ds.setDriverClassName(driver);
		ds.setUrl(url);
		ds.setUsername(userName);
		ds.setPassword(password);
		return ds;
	}
}

三、spring-整合框架

mybatis框架

  1. mybatis的核心配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<!--读取外部properties配置文件-->
	<properties resource="jdbc.properties"></properties>
	<!--别名扫描的包路径-->
		<typeAliases>
			<package name="com.itheima.domain"/>
		</typeAliases>
	<!--数据源-->
		<environments default="mysql">
			<environment id="mysql">
				<transactionManager type="JDBC"></transactionManager>
				<dataSource type="POOLED">
					<property name="driver" value="${jdbc.driver}"></property>
					<property name="url" value="${jdbc.url}"></property>
					<property name="username" value="${jdbc.username}">
					</property>
					<property name="password" value="${jdbc.password}">
					</property>
				</dataSource>
			</environment>
		</environments>
		<!--映射文件扫描包路径-->
		<mappers>
			<package name="com.itheima.dao"></package>
		</mappers>
</configuration>

  1. mybatis的应用类
public class App {
public static void main(String[] args) throws IOException {
	// 1. 创建SqlSessionFactoryBuilder对象
	SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new
	SqlSessionFactoryBuilder();
	// 2. 加载SqlMapConfig.xml配置文件
	InputStream inputStream =
	Resources.getResourceAsStream("SqlMapConfig.xml");
	// 3. 创建SqlSessionFactory对象
	SqlSessionFactory sqlSessionFactory =
	sqlSessionFactoryBuilder.build(inputStream);
	// 4. 获取SqlSession
	SqlSession sqlSession = sqlSessionFactory.openSession();
	// 5. 执行SqlSession对象执行查询,获取结果User
	AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
	//执行user对象的方法
	Account ac = accountDao.findById(1);
	System.out.println(ac);
	// 6. 释放资源
	sqlSession.close();
	}
}

spring整合-1-整合mybatis框架

  1. 思路:

    • 第一件事是:Spring要管理MyBatis中的SqlSessionFactory
    • 第二件事是:Spring要管理Mapper接口的扫描
  2. 步骤:

    • 1):创建Spring的主配置类
    • 2)创建数据源的配置类
    • 3)主配置类中读properties并引入数据源配置类
    • 4)创建Mybatis配置类并配置SqlSessionFactory
    • 5)主配置类中引入Mybatis配置类

1):创建Spring的主配置类

//配置类注解
@Configuration
//包扫描,主要扫描的是项目中的AccountServiceImpl类
@ComponentScan("com.itheima")
public class SpringConfig {
}

2)创建数据源的配置类

public class JdbcConfig {
	@Value("${jdbc.driver}")
	private String driver;
	@Value("${jdbc.url}")
	private String url;
	@Value("${jdbc.username}")
	private String userName;
	@Value("${jdbc.password}")
	private String password;
	@Bean
	public DataSource dataSource(){
		DruidDataSource ds = new DruidDataSource();
		ds.setDriverClassName(driver);
		ds.setUrl(url);
		ds.setUsername(userName);
		ds.setPassword(password);
		return ds;
	}
}

3)主配置类中读properties并引入数据源配置类

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

4)创建Mybatis配置类并配置SqlSessionFactory

public class MybatisConfig {
//定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
	SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
	//设置模型类的别名扫描
	ssfb.setTypeAliasesPackage("com.itheima.domain");
	//设置数据源(通过参数的方式进行传递数据源)
	ssfb.setDataSource(dataSource);
	return ssfb;
}
//定义bean,返回MapperScannerConfigurer对象
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
	MapperScannerConfigurer msc = new MapperScannerConfigurer();
	//设置包扫描的路径
		msc.setBasePackage("com.itheima.dao");
		return msc;
	}
}

5)主配置类中引入Mybatis配置类

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

spring整合-2-整合junit

  1. 步骤:使用 @RunWith 注解指定运行器,使用 @ContextConfiguration 注解来指定配置类或者配置文件

编写一个测试类:

//设置类运行器
@RunWith(SpringJUnit4ClassRunner.class)
//设置Spring环境对应的配置类
@ContextConfiguration(classes = {SpringConfiguration.class}) //加载配置类
//@ContextConfiguration(locations={"classpath:applicationContext.xml"})//加载配置文件
	public class AccountServiceTest {
	//支持自动装配注入bean
	@Autowired
	private AccountService accountService;
	@Test
	public void testFindById(){
		System.out.println(accountService.findById(1));
	}
	@Test
	public void testFindAll(){
		System.out.println(accountService.findAll());
	}
}
  1. 注意:
    • 1)单元测试,如果测试的是注解配置类,则使用@ContextConfiguration(classes = 配置类.class)
    • 2)单元测试,如果测试的是配置文件,则使用@ContextConfiguration(locations={配置文件名,…})
    • 3)Junit运行后是基于Spring环境运行的,所以Spring提供了一个专用的类运行器,这个务必要设置,这个类运行器就在Spring的测试专用包中提供的,导入的坐标就是这个东西SpringJUnit4ClassRunner
    • 4)上面两个配置都是固定格式,当需要测试哪个bean时,使用自动装配加载对应的对象,下面的工作就和以前做Junit单元测试完全一样了
名称@RunWith
类型测试类注解
位置测试类上方
作用设置JUnit运行器
属性value(默认):运行所使用的运行期
名称@ContextConfiguration
类型测试类注解
位置测试类上方
作用设置JUnit加载的Spring核心配置
属性classes:核心配置类,可以使用数组的格式设定加载多个配置类locations:配置文件,可以使用数组的格式设定加载多个配置文件名称

四、spring-aop-切面编程

  1. aop:面向切面编程,一种编程范式,指导开发者如何组织程序结构。(SpringAOP的本质或者可以说底层实现是通过代理模式。
  2. aop的作用:在不惊动原始设计的基础上为其进行功能增强(是在不改原有代码的前提下对其进行增强),Spring的AOP是对一个类的方法在不进行任何修改的前提下实现增强
  3. AOP的核心概念:
    • 1)连接点:所有的方法都可以是连接点,程序执行过程中的任意位置,可以是执行方法、抛出异常、设置变量等(在SpringAOP中,理解为方法的执行)
    • 2)切入点::匹配连接点的式子(切入点定义:依托一个不具有实际意义的方法进行,即无参数、无返回值、方法体无实际逻辑。
      • 切入点表达式:要进行增强的方法的描述方式
      • 在SpringAOP中,一个切入点可以描述一个具体方法,也可也匹配多个方法
        • 一个具体的方法:如com.itheima.dao包下的BookDao接口中的无形参无返回值的save方法
        • 匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意
          方法,所有带有一个参数的方法。
      • 连接点范围要比切入点范围大,是切入点的方法也一定是连接点,但是是连接点的方法就不一
        定要被增强,所以可能不是切入点。
    • 3)通知:在切入点处执行的操作,也就是共性功能
      • 在SpringAOP中,功能最终以方法的形式呈现
    • 4)通知类:定义通知的类(即定义共性功能的方法的类)
    • 5)切面:描述通知与切入点的对应关系。
    • 6)目标对象(Target):(要增强的类的对应的对象)原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终
      工作的(即增强的功能)
    • 7)代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实

spring-aop-入门思路分析

1.导入坐标(pom.xml)
2.制作连接点(原始操作,Dao接口与实现类)
3.制作共性功能(通知类与通知)
4.定义切入点
5.绑定切入点与通知关系(切面)
  1. 制作连接点(即定义接口与实现类)
  2. 定义通知类和通知,定义切入点,制作切面
//MyAdvice类其实就是通知类。
public class MyAdvice {
//定义切入点,@Pointcut,参数则是指定切入点的位置
	@Pointcut("execution(void com.itheima.dao.BookDao.update())")
	private void pt(){}  //pt:其实就是切入点的名字
	
//制作切面(绑定切入点和通知的关系),@Before:在什么之前,即通知(即共性功能)会在切入点方法执行之前执行
	@Before("pt()")
	public void method(){  // method方法其实就是通知
		System.out.println(System.currentTimeMillis());
	}
}
名称@Pointcut
类型方法注解
位置切入点方法定义上方
作用设置切入点方法
属性value(默认):切入点表达式
名称@Before
类型方法注解
位置通知方法定义上方
作用设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
  1. 将通知类配给容器并标识其为切面类(@Component,@Aspect)
@Component  //告知spring管理这个类
@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());
	}
}

名称@Aspect
类型类注解
位置切面类定义上方
作用设置当前类为AOP切面类
  1. 开启注解格式AOP功能@EnableAspectJAutoProxy
@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}
名称@EnableAspectJAutoProxy
类型配置类注解
位置配置类定义上方
作用开启注解格式AOP功能

spring-aop-切入点表达式

  1. 切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名),举例说明:
execution(public User com.itheima.service.UserService.findById(int))
execution:动作关键字,描述切入点的行为动作,例如execution表示执行到指定切入点
public:访问修饰符,还可以是public,private等,可以省略
User:返回值,写返回值类型
com.itheima.service:包名,多级包使用点连接
UserService:类/接口名称
findById:方法名
int:参数,直接写参数的类型,多个类型用逗号隔开
异常名:方法定义中抛出指定异常,可以省略
  1. 切入点表达式就是要找到需要增强的方法,所以它就是对一个具体方法的描述

spring-aop-切入点表达式-通配符

1)* :单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现

execution(public * com.itheima.*.UserService.find*(*)) 
匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法


2)..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

execution(public User com..UserService.findById(..)) 
匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法


3)+:专用于匹配子类类型

execution(* *..*Service+.*(..))
这个使用率较低,描述子类的,咱们做JavaEE开发,继承机会就一次,使用都很慎重,所以很少用它。
*Service+,表示所有以Service结尾的接口的子类。

spring-aop-通知类型

  1. 一共有5种通知类型

    • 1)前置通知:追加功能到方法执行
    • 2)后置通知:追加功能到方法执行,不管方法执行的过程中有没有抛出异常都会执行
    • 3)环绕通知(重点):,环绕通知功能比较强大,它可以追加功能到方法执行的前后,这也是比较常用的方式,它可以实现其他四种通知类型的功能
    • 4)返回后通知(了解):追加功能到方法执行,只有方法正常执行结束后才进行,如果方法执行抛出异常,返回后通知将不会被添加
    • 5)抛出异常后通知(了解):追加功能到方法抛出异常后,只有方法执行出异常才进行
  2. 环绕通知:

@Component
@Aspect
public class MyAdvice {
	@Pointcut("execution(void com.itheima.dao.BookDao.update())")
	private void pt(){}
	@Around("pt()")
	public void around(){
		System.out.println("around before advice ...");
		System.out.println("around after advice ...");
	}
}

运行结果中,通知的内容打印出来,但是原始方法的内容却没有被执行。
因为环绕通知需要在原始方法的前后进行增强,所以环绕通知就必须要能对原始操作进行调用

@Component
@Aspect
public class MyAdvice {
	@Pointcut("execution(void com.itheima.dao.BookDao.update())")
	private void pt(){}
	@Around("pt()")
	public void around(ProceedingJoinPoint pjp) throws Throwable{
		System.out.println("around before advice ...");
		//表示对原始操作的调用
		pjp.proceed();
		System.out.println("around after advice ...");
	}
}

所以如果我们使用环绕通知的话,要根据原始方法的返回值来设置环绕通知的返回值:

@Component
@Aspect
public class MyAdvice {
	@Pointcut("execution(void com.itheima.dao.BookDao.update())")
	private void pt(){}
	@Pointcut("execution(int com.itheima.dao.BookDao.select())")
	private void pt2(){}
	
	@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;
	}
}

为什么返回的是Object而不是int的主要原因是Object类型更通用。
在环绕通知中是可以对原始方法返回值就行修改的。

  1. 注意事项:

    • 1)1. 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法
      调用前后同时添加通知
    • 2)通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
    • 3)对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,最好设定为
      Object类型
    • 4)原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object
    • 5)由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理Throwable异常
  2. 注解:

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

spring-aop-AOP通知获取数据

  1. 获取切入点方法的参数,所有的通知类型都可以获取参数
    • JoinPoint:适用于前置、后置、返回后、抛出异常后通知
    • ProceedingJoinPoint:适用于环绕通知

非环绕通知获取参数:在方法上添加JoinPoint,通过JoinPoint来获取参数

@Component
@Aspect
public class MyAdvice {
	@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
	private void pt(){}
	@Before("pt()")
	public void before(JoinPoint jp)
		Object[] args = jp.getArgs();
		System.out.println(Arrays.toString(args));
		System.out.println("before advice ..." );
	}
//...其他的略
}

环绕通知获取参数:环绕通知使用的是ProceedingJoinPoint,
因为ProceedingJoinPoint是JoinPoint类的子类,所以对于ProceedingJoinPoint类中应该也会有对应的getArgs()方法

@Component
@Aspect
public class MyAdvice {
	@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
	private void pt(){}
	@Around("pt()")
	public Object around(ProceedingJoinPoint pjp)throws Throwable {
		Object[] args = pjp.getArgs();
		System.out.println(Arrays.toString(args));
		//执行原始方法
		Object ret = pjp.proceed();
//       pjp.proceed()方法是有两个构造方法,调用无参数的proceed,
//      当原始方法有参数,会在调用的过程中自动传入参数
//		所以调用这两个方法的任意一个都可以完成功能
//   但是当需要修改原始方法的参数时,就只能采用带有参数的方法
//		args[0] = 666;
//		Object ret = pjp.proceed(args);
//  有了这个特性后,我们就可以在环绕通知中对原始方法的参数进行拦截过滤,
//  避免由于参数的问题导致程序无法正确运行,保证代码的健壮性。
		return ret;
	}
	//其他的略
}
  1. 获取切入点方法返回值,前置和抛出异常后通知是没有返回值,后置通知可有可无,所以不做研究
    • 返回后通知
    • 环绕通知(如获取参数的一样)

返回后通知获取返回值

@Component
@Aspect
public class MyAdvice {
	@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
	private void pt(){}
	@AfterReturning(value = "pt()",returning = "ret")
	public void afterReturning(Object ret) {
		System.out.println("afterReturning advice ..."+ret);
	}
//其他的略
}

注意:

  • 1)参数名问题:参数名必须和返回值的名字(即returning的值)一样
  • 2)afterReturning方法参数类型的问题:参数类型可以写成String,但是为了能匹配更多的参数类型,建议写成Object类型
  • 3)afterReturning方法参数的顺序问题:有JoinPoint参数,必须放在首位
  1. 获取切入点方法运行异常信息,前置和返回后通知是不会有,后置通知可有可无,所以不做研究
    • 抛出异常后通知
    • 环绕通知

环绕通知获取异常:只需要将异常捕获,就可以获取到原始方法的异常信息了

@Component
@Aspect
public class MyAdvice {
	@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
	private void pt(){}
	@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;
	}
//其他的略
}

抛出异常后通知获取异常

@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {
	System.out.println("afterThrowing advice ..."+t);
}
	//其他的略
}

这里的参数名必须和throwing的值一样。

五、spring-aop-事务管理

  1. 事务的作用:使代码同成功同失败。(:在数据层保障一系列的数据库操作同成功同失败)
  2. 步骤:
    • 1)在需要被事务管理的方法上添加 @Transactional注解
      • @Transactional可以写在接口类上、接口方法上、实现类上和实现类方法上
        • 写在接口类上,该接口的所有实现类的所有方法都会有事务
        • 写在接口方法上,该接口的所有实现类的该方法都会有事务
        • 写在实现类上,该类中的所有方法都会有事务
        • 写在实现类方法上,该方法上有事务
        • 建议写在实现类或实现类的方法上、
    • 2)在JdbcConfig类中配置事务管理器
    • 3)开启事务注解,在SpringConfig(spring的核心配置类)的配置类中开启

在JdbcConfig类中配置事务管理器

public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource(){
	DruidDataSource ds = new DruidDataSource();
	ds.setDriverClassName(driver);
	ds.setUrl(url);
	ds.setUsername(userName);
	ds.setPassword(password);
	return ds;
}
//配置事务管理器,mybatis使用的是jdbc事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
	DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
	transactionManager.setDataSource(dataSource);
	return transactionManager;
	}
}

注意:事务管理器要根据使用技术进行选择,Mybatis框架使用的是JDBC事务,可以直接使用DataSourceTransactionManage

开启事务注解,在SpringConfig(spring的核心配置类)的配置类中开启,加入@EnableTransactionManagement

@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class

//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}
名称@EnableTransactionManagement
类型配置类注解
位置配置类定义上方
作用设置当前Spring环境中开启注解式事务支持
名称@Transactional
类型接口注解 类注解 方法注解
位置业务层接口上方 业务层实现类上方 业务方法上方
作用为当前业务层方法添加事务(如果设置在类或接口上方则类或接口中所有方法均添加事务)

spring-aop-事务管理-Spring事务角色

  1. 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
  2. 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
  3. 开启Spring的事务管理后

spring-aop-事务管理-事务属性

spring-aop-事务管理-事务属性-事务配置

在这里插入图片描述
这些属性都可以在@Transactional注解的参数上进行设置

  • readOnly:true只读事务,false读写事务,增删改要设为false,查询设为true。
  • timeout:设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚,-1表示不设置超时时间。
  • rollbackFor:当出现指定异常进行事务回滚
  • noRollbackFor:当出现指定异常不进行事务回滚
    • 思考:出现异常事务会自动回滚,这个是我们之前就已经知道的
    • noRollbackFor是设定对于指定的异常不回滚
    • rollbackFor是设定对于指定的异常回滚,对于异常事务不应该都回滚么,为什么还要指定?这块需要更正一个知识点,并不是所有的异常都会回滚事务
    • Spring的事务只会对Error异常和RuntimeException异常及其子类进行事务回滚,其他的异常类型是不会回滚的
  • rollbackForClassName等同于rollbackFor,只不过属性为异常的类全名字符串
  • noRollbackForClassName等同于noRollbackFor,只不过属性为异常的类全名字符串
  • isolation设置事务的隔离级别
    • DEFAULT :默认隔离级别, 会采用数据库的隔离级别
    • READ_UNCOMMITTED : 读未提交
    • READ_COMMITTED : 读已提交
    • REPEATABLE_READ : 重复读取
    • SERIALIZABLE: 串行化

spring-aop-事务管理-事务属性-事务传播行为

  1. 事务传播行为:事务协调员对事务管理员所携带事务的处理态度。
log方法、inMoney方法和outMoney方法都属于增删改,分别有事务T1,T2,T3
transfer因为加了@Transactional注解,也开启了事务T
前面我们讲过Spring事务会把T1,T2,T3都加入到事务T中
所以当转账失败后,所有的事务都回滚,!!!!!!!!!!!!!导致日志没有记录下来!!!!!!
这和我们的需求不符,这个时候我们就想能不能让log方法单独是一个事务呢?

propagation属性

@Service
public class LogServiceImpl implements LogService {
@Autowired
private LogDao logDao;
//propagation设置事务属性:传播行为设置为当前操作需要新事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String out,String in,Double money ) {
	logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
	}
}
  1. 事务传播行为的可选值

在这里插入图片描述

文章来源:https://blog.csdn.net/weixin_64044840/article/details/133191459
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。