Java入门篇-Spring框架 - ☕ 经验茶谈极核论坛 - 知识星球 - 极核GetShell

Java入门篇-Spring框架

笔记配套视频:黑马的SSM https://www.bilibili.com/video/BV1Fi4y1S7ix/

附录、Spring 架构概述

上层依赖下层

Pasted image 20241104201445

一、核心容器

1.0 核心概念(目标:充分解耦)

IoC控制反转:对象的创建控制权由程序转移至外部,这种思想称为控制反转

Spring提供了一个容器,称为IoC容器,用来充当IoC思想中的外部
IoC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IoC容器中统称为Bean

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

service、dao对象都交给IoC容器创建以及依赖注入

Pasted image 20241104202933

目标:

  • 使用IoC容器管理Bean
  • 在IoC容器内将有依赖关系的Bean进行关系绑定(DI)
  • 最终效果:使用对象时不仅可以直接从IoC容器中获取,并且获取到Bean已经绑定了所有的依赖关系

1.1 IoC 容器入门

1.1.1 Java Web 回顾

架构:DAO层用于处理数据,Service层用于处理业务;Service依赖DAO

Pasted image 20241104205041

DAO层

package com.lyc.dao;  
  
public interface BookDao {  
	void save();  
}

实现类

package com.lyc.dao.impl;  

import com.lyc.dao.BookDao;  
  
public class BookDaoImpl implements BookDao {  
	@Override  
	public void save() {  
		System.out.println("book dao save ...");  
	}  
}

Service层

package com.lyc.service;  
  
public interface BookService {  
	void save();  
}

实现类

package com.lyc.service.impl;  
  
import com.lyc.dao.BookDao;  
import com.lyc.dao.impl.BookDaoImpl;  
import com.lyc.service.BookService;  
  
public class BookServiceImpl implements BookService {  
	private BookDao bookDao = new BookDaoImpl();  
	public void save(){  
		System.out.println("book service save ...");  		
		bookDao.save();  
	}  
}

App类

package com.lyc;  
  
import com.lyc.service.BookService;  
import com.lyc.service.impl.BookServiceImpl;  

public class App {  
	public static void main(String[] args) {  
		BookService bookService = new BookServiceImpl();  
		bookService.save();  
	}  
}

运行结果

book service save ...
book dao save ...

1.1.2 IoC 容器

1.在pom.xml导入Spring-context依赖

<dependencies>  
	<dependency>  
		<groupId>org.springframework</groupId>  
		<artifactId>spring-context</artifactId>  
		<version>5.2.10.RELEASE</version>  
	</dependency>  
</dependencies>

2.在resources中创建Spring配置文件applicationContext.xml,在文件中配置Bean
class属性表示bean定义类型,id属性给bean起名字

<bean id="bookDao" class="com.lyc.dao.impl.BookDaoImpl"/>  
<bean id="bookService" class="com.lyc.service.impl.BookServiceImpl"/>

3.创建App2类

package com.lyc;  
  
import com.lyc.dao.BookDao;  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class App2 {  
	public static void main(String[] args) {  
		//4.获取IoC容器  
		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");  
		//5.获取Bean  
		BookService bookService = (BookService)ctx.getBean("bookService");  
		bookService.save();
	}  
}

1.2 DI 入门

上方程序任然不完全解耦,Service实现类中仍然保留着new关键字
使用DI思想彻底解耦对象

重写Service层实现类:删除new关键字,提供对应容器使用赋值的set方法

package com.lyc.service.impl;  
  
import com.lyc.dao.BookDao;  
import com.lyc.service.BookService;  
  
public class BookServiceImpl implements BookService {  
	//5.删除业务层中使用new的方式创建的dao对象  
	private BookDao bookDao;  
	public void save(){  
		System.out.println("book service save ...");  
		bookDao.save();  
	}  
	//6.提供对应的set方法  
	public void setBookDao(BookDao bookDao) {  
		this.bookDao = bookDao;  
	}  
}

继续修改Spring配置文件 ,配置bean属性中的参数,bookService依赖于bookDao,bookService中的对象属性bookDao参照 id为bookDao的bean

<bean id="bookDao" class="com.lyc.dao.impl.BookDaoImpl"/>  
<bean id="bookService" class="com.lyc.service.impl.BookServiceImpl">  
	<!--7.配置Service与dao的关系-->  
	<!-- property标签表示配置当前bean的属性-->  
	<!-- name属性表示配置哪一个具体的属性-->  
	<!-- ref属性表示参照哪一个bean-->  
	<property name="bookDao" ref="bookDao"/>  
</bean>

图解属性配置

Pasted image 20241104212033

1.3 Bean 配置

Pasted image 20241105202844

Bean标签的name属性可以给bean起别名,使用空格、逗号或分号隔开可以写多个别名,此别名可以当做id使用

<bean id="bookDao" class="com.lyc.dao.impl.BookDaoImpl" />  
	<bean id="bookService" name="service service2 bookEbi" class="com.lyc.service.impl.BookServiceImpl">  
	<property name="bookDao" ref="bookDao" />  
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");  
BookService bookService = (BookService) ctx.getBean("service");  
bookService.save();

注意
获取bean无论是通过id还是name获取,如果无法获取到,将抛出异常NoSuchBeanDefinitionException
NoSuchBeanDefinitionException: No bean named ‘service’ available

scopes属性为作用范围,控制bean类是否是单例类,值为singleton时为单例,值为prototype为非单例,默认不写属性为单例

<bean id="bookDao" class="com.lyc.dao.impl.BookDaoImpl" scope="prototype" />  
     <bean id="bookService" name="service service2 bookEbi" class="com.lyc.service.impl.BookServiceImpl">  
     <property name="bookDao" ref="bookDao" />  
</bean>

测试代码

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");  
BookDao bookDao = (BookDao) ctx.getBean("bookDao");  
System.out.println(bookDao);  
BookDao bookDao2 = (BookDao) ctx.getBean("bookDao");  
System.out.println(bookDao2);

 

输出结果,地址不同则不为单例

com.lyc.dao.impl.BookDaoImpl@6293abcc
com.lyc.dao.impl.BookDaoImpl@7995092a

适合单例的对象:表象层对象(servlet)、业务层对象(service)、数据层对象(dao)、工具对象(util)
不适合单例的对象:封装实体的域对象(pojo、domain、entity)

1.4 Bean 实例化

1.4.1 构造方法

Spring创建对象时,用的是无参构造方法,私有的构造方法依旧能够调用

public class BookDaoImpl implements BookDao {  
	private BookDaoImpl() {  
		System.out.println("book dao constructor running ...");  
	}  
	  
	@Override  
	public void save() {  
		System.out.println("book save ...");  
	}  
}
public class AppForScope {  
	public static void main(String[] args) {  
		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");  
		BookDao bookDao = (BookDao) ctx.getBean("bookDao");  
	}  
}

运行结果

book dao constructor running ...

若此时将无参构造方法变为有参,则会报错,一般Spring报错从最后看,以下为提取的有效信息

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.lyc.dao.impl.BookDaoImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.lyc.dao.impl.BookDaoImpl.<init>()

Caused by: java.lang.NoSuchMethodException: com.lyc.dao.impl.BookDaoImpl.<init>()

1.4.2 静态工厂

java web 项目架构如下

Pasted image 20241105210117

OrderDao

public interface OrderDao {  
	public void save();  
}

OrderDaoImpl

public class OrderDaoImpl implements OrderDao {  
	@Override  
	public void save() {  
		System.out.println("Order Dao Save ...");  
	}  
}

OrderDaoFactory

public class OrderDaoFactory {  
	public static OrderDao getOrderDao(){  
		return new OrderDaoImpl();  
	}  
}

AppJavaWeb

OrderDao orderDao = OrderDaoFactory.getOrderDao();  
orderDao.save();

Spring 配置bean ,class指定工厂类,factory-method属性指定工厂创建对象的方法

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

1.4.3 实例工厂与FactoryBean

UserDaoFactory (与上节中的工厂区别在于 get方法无静态)

Spring 配置bean实例工厂,需要首先将工厂bean造出来,在调用工厂方法

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

由于userFactory bean只是为了创建userDao bean存在 ,并且每次书写factory-bean和factory-method过于麻烦,Spring使用FactoryBean<?>接口进行优化配置操作

首先创建UserDaoFactoryBean类,实现FactoryBean<?>接口,实现getObjectgetObjectType两个方法

UserDaoFactoryBean

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

配置bean

<bean id="userDao" class="com.lyc.factory.UserDaoFactoryBean" />

UserDaoFactoryBean默认为单例类,需要实现FactoryBean<?>接口中的isSingleton方法,返回false为非单例类

1.5 Bean 生命周期

BookDaoImpl中写入初始化init()和销毁方法deStory()

public class BookDaoImpl implements BookDao {  
	private BookDaoImpl() {  
		System.out.println("book dao constructor running ...");  
	}  
	  
	@Override  
	public void save() {  
		System.out.println("book save ...");  
	}  
	  
	public void init(){  
		System.out.println("BookDao 初始化中");  
	}  
	public void deStory(){  
		System.out.println("BookDao 销毁中");  
	}  
}

在配置文件中配置bean

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

AppForLifeCycle

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

运行结果

book dao constructor running ...
BookDao 初始化中
book save ...

发现没有调用销毁方法,原因是应为bean声明在Spring容器中,当程序退出时直接关闭jvm而不是关闭容器,解决方法在退出jvm之前关闭Spring容器

方法一:使用close()方法

ApplicationContext接口中无close()方法,而ClassPathXmlApplicationContext提供了

public class AppForLifeCycle {  
	public static void main(String[] args) {  
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");  
		BookDao bookDao = (BookDao) ctx.getBean("bookDao");  
		bookDao.save();  
		ctx.close();  
	}  
}

运行结果

book dao constructor running ...
BookDao 初始化中
book save ...
BookDao 销毁中

 

方法二:设置关闭钩子registerShutdownHook()

close()为暴力关闭,而设置关闭钩子在代码运行结束之后才会触发销毁方法

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");  
//设置关闭钩子
ctx.registerShutdownHook();  
BookDao bookDao = (BookDao) ctx.getBean("bookDao");  
bookDao.save();

运行结果

book dao constructor running ...
BookDao 初始化中
book save ...
BookDao 销毁中

若在设置钩子相同的代码位置使用close()则会在初始化bean之后直接退出容器,并报错java.lang.IllegalStateException

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");  
ctx.close();  
  
BookDao bookDao = (BookDao) ctx.getBean("bookDao");  
bookDao.save();

运行结果

book dao constructor running ...
BookDao 初始化中
BookDao 销毁中
Exception in thread "main" java.lang.IllegalStateException: BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext

方法三:实现InitializingBeanDisposableBean接口

由于每次书写init-method和destroy-method过于麻烦,Spring使用InitializingBeanDisposableBean接口进行优化配置操作

public class BookServiceImpl implements BookService , InitializingBean, DisposableBean {  
	private BookDao bookDao;  
	public void setBookDao(BookDao bookDao) {  
		System.out.println("set..");  
		this.bookDao = bookDao;  
	}  
	  
	@Override  
	public void destroy() throws Exception {  
		System.out.println("service destroy");  
	}  
	  
	@Override  
	public void afterPropertiesSet() throws Exception {  
		System.out.println("service init");  
	}  
	  
	@Override  
	public void save() {  
		System.out.println("book service ...");  
		bookDao.save();  
	}  
}

注意
只要是放在xml文件中的bean都会被声明
afterPropertiesSet方法指的是属性set方法运行之后运行

启动代码不变,运行结果

book dao constructor running ...
BookDao 初始化中
set..
service init
book save ...
service destroy
BookDao 销毁中

运行周期总结

  • 初始化容器

1. 创建对象(内存分配)

2. 执行构造方法

3. 执行属性注入(set操作)

4. 执行bean初始化方法

  • – 使用bean

1. 执行业务操作

  • – 关闭/销毁容器

1. 执行bean销毁方法

1.6 依赖注入

引入:
向一个类中传递数据分为两个方式 普通方法和构造方法
创建bean与bean之间的依赖过程中 除了依赖bean还会依赖 简单类型和引用类型

注入的方式:

  • setter 注入
    •  简单类型
    •  引用类型
  •  构造器注入
    • 简单类型
    • 引用类型

1.6.1 setter 注入

引用类型注入

使用property标签的ref属性注入就是setter注入引用类型,例

public class BookServiceImpl implements BookService {  
	private BookDao bookDao;  
	private UserDao userDao;  
	public void setBookDao(BookDao bookDao) {  
		this.bookDao = bookDao;  
	}  
	  
	public void setUserDao(UserDao userDao) {  
		this.userDao = userDao;  
	}  
	
	@Override  
	public void save() {  
		System.out.println("book service save ...");  
		userDao.save();  
		bookDao.save();  
	}  
}
<bean id="bookDao" class="com.lyc.dao.impl.BookDaoImpl" />  
	<bean id="bookService" name="service service2 bookEbi" class="com.lyc.service.impl.BookServiceImpl">  

	<property name="bookDao" ref="bookDao" />  
</bean>

基本类型注入

使用property标签的value属性注入就是setter基本引用类型,例

public class BookDaoImpl implements BookDao {  
	private int connectionNum;  
	private String databaseName;  
	  
	public void setConnectionNum(int connectionNum) {  
		this.connectionNum = connectionNum;  
	}  
	  
	public void setDatabaseName(String databaseName) {  
		this.databaseName = databaseName;  
	}  
	  
	@Override  
	public void save() {  
		System.out.println("book dao save ..." + 
			databaseName + "," +
			connectionNum );  
	}  
}
<bean id="bookDao" class="com.lyc.dao.impl.BookDaoImpl" >  
	<property name="databaseName" value="mysql" />  
	<property name="connectionNum" value="100" />  
</bean>

1.6.2 构造器注入

构造器注入引用类型

使用constructor-arg标签的ref属性注入就是构造器参数注入,例

注意
constructor-arg标签的name属性为构造器参数名

public class BookServiceImpl implements BookService {  
	private BookDao bookDao;  
	private UserDao userDao;  
	public BookServiceImpl(BookDao bookDao,UserDao userDao) {  
		this.bookDao = bookDao;  
		this.userDao = userDao;  
	}  
	  
	@Override  
	public void save() {  
		System.out.println("book service save ...");  
		bookDao.save();  
		userDao.save();  
	}  
}
<bean id="bookDao" class="com.lyc.dao.impl.BookDaoImpl" />  
<bean id="userDao" class="com.lyc.dao.impl.UserDaoImpl" />  
  
<bean id="bookService" class="com.lyc.service.impl.BookServiceImpl">  
	<constructor-arg name="bookDao" ref="bookDao"/>  
	<constructor-arg name="userDao" ref="userDao"/>  
</bean>

构造器注入基本类型

使用constructor-arg标签的name属性注入就是构造器参数注入,例

public class BookDaoImpl implements BookDao {  
  
	private int connectionNum;  
	private String databaseName;  
	  
	public BookDaoImpl(int connectionNum, String databaseName) {  
		this.connectionNum = connectionNum;  
		this.databaseName = databaseName;  
	}  
	  
	@Override  
	public void save() {  
		System.out.println("book dao save ..." +  
		databaseName + "," +  
		connectionNum );  
	}  
}
<bean id="bookDao" class="com.lyc.dao.impl.BookDaoImpl">  
	<constructor-arg name="connectionNum" value="10" />  
	<constructor-arg name="databaseName" value="mysql" />  
</bean>  

<bean id="userDao" class="com.lyc.dao.impl.UserDaoImpl" />  
  
<bean id="bookService" class="com.lyc.service.impl.BookServiceImpl">  
	<constructor-arg name="bookDao" ref="bookDao"/>  
	<constructor-arg name="userDao" ref="userDao"/>  
</bean>

以上为标准书写方式

为了降低构造参数名和配置文件的耦合度问题,Spring 使用类型判断传递参数,例

<bean id="bookDao" class="com.lyc.dao.impl.BookDaoImpl">  
	<constructor-arg type="int" value="10" />  
	<constructor-arg type="java.lang.String" value="mysql" />  
</bean>

类型判断传递参数不能解决多个参数类型重复的问题,Spring 使用参数位置传递参数,例

<bean id="bookDao" class="com.lyc.dao.impl.BookDaoImpl">  
	<constructor-arg index="0" value="10" />  
	<constructor-arg index="1" value="mysql" />  
</bean>

分别对应构造器中int connectionNum, String databaseName 两个参数

依赖注入方式选择

1. 强制依赖使用构造器进行
2. 可选择依赖使用setter注入进行
3. Spring框架推荐使用构造器注入
4. 如果有必要可以两者同时使用
5. 实际开发过程中还要根据实际情况分析
6. 自己开发的模块推荐使用setter注入

1.7 依赖自动装配

IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配

  • 自动装配方式(需要搭配set方法)
    1. 按类型(常用)
    2. 按名称
    3. 按构造方法
    4. 不器用自动装配

按类型自动装配,Spring会在容器中自动寻找合适类型的bean装入

使用bean标签的autowire设置为byType,例

public class BookServiceImpl implements BookService {  
	private BookDao bookDao;  
	  
	public void setBookDao(BookDao bookDao) {  
		this.bookDao = bookDao;  
	}  
	  
	@Override  
	public void save() {  
		System.out.println("book service save ...");  
		bookDao.save();  
	}  
}
<bean id="bookDao" class="com.lyc.dao.impl.BookDaoImpl" />
<bean id="bookService" class="com.lyc.service.impl.BookServiceImpl" autowire="byType" />

 

按名称自动装配,Spring会从set方法的方法名中提取需要装配的类型,如setBookDao->bookDao

注意
需要被调用的bean id值必须与被自动装配的参数值名称一致

Pasted image 20241106205538

 

<bean id="bookDao" class="com.lyc.dao.impl.BookDaoImpl" />  
<bean id="bookDao2" class="com.lyc.dao.impl.BookDaoImpl" />

<bean id="bookService" class="com.lyc.service.impl.BookServiceImpl" autowire="byName" />

按类型转入可以省略 id 参数(了解)

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

自动装配特征

1. 自动装配用于引用类型类型依赖注入,不能对简单类型进行操作
2. 使用类型装配时必须保障容器中相同类型的bean唯一(推荐使用)
3. 使用名称装配时必须保障容器中具有指定名称的bean
4. 自动装配优先级低于setter注入和构造器注入,同时出现时自动装配配置失效

1.8 集合注入(set注入)

数组、Set、Map、Properties 注入示例如下

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 setArray(int[] array) {
        this.array = array;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public void setSet(Set<String> set) {
        this.set = set;
    }

    public void setMap(Map<String, String> map) {
        this.map = map;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    @Override
    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);
    }
}
<bean id="bookDao" class="com.lyc.dao.impl.BookDaoImpl" >  
	<property name="array">  
		<array>  
			<value>100</value>  
			<value>200</value>  
			<value>300</value>  
		</array>  
	</property>  
	<property name="list">  
		<list>  
			<value>lyc</value>  
			<value>hhh</value>  
			<value>xxx</value>  
		</list>  
	</property>  
	<property name="set">  
		<set>  
			<value>lyc</value>  
			<value>hhh</value>  
			<value>xxx</value>  
			<value>xxx</value>  
		</set>  
	</property>  
	<property name="map">  
		<map>  
			<entry key="name" value="LY_C" />  
			<entry key="age" value="22" />  
		</map>  
	</property>  
	<property name="properties">  
		<props>  
			<prop key="name">LY_C</prop>  
			<prop key="age">22</prop>  
		</props>  
	</property>  
</bean>

1.9 加载 Properties 文件

第一步:开启context命名空间

在Spring中开辟一块新的命名空间context
1. beans 标签中添加xmlns:context属性
2. beans 标签中的xsi:schemaLocatio属性中添加新的命名空间

<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/spring-context.xsd  
	">

</beans>

第二步:使用context命名空间加载Properties文件

jdbc.properties

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

使用占位符:使用context:property-placeholder标签中的location属性加载文件

<context:property-placeholder location="jdbc.properties"/>

在使用${}方式加载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/spring-context.xsd  
	">  
	<context:property-placeholder location="jdbc.properties"/>  
  
	<bean 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>

注意
properties文件中的属性名不得与系统环境变量冲突,否则将被系统环境变量覆盖
解决方法:system-properties-mode属性设为NEVER

<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>

使用逗号分割可以加载多个properties文件

使用classpath:*加载所有properties文件(classpath:为标准格式字符串)

<context:property-placeholder location="classpath:*.properties" />

以上加载加载方式只能加载本地文件,使用classpath*:*还可以加载jar中的文件

<context:property-placeholder location="classpath*:*.properties" />

1.10 容器

1.10.1 创建容器

方式1:加载类路径下的配置文件

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

方式2:在文件系统下使用绝对路径加载配置文件

ApplicationContext ctx= new FileSystemXmlApplicationContext("E:\\study\\JAVA\\SSM-0\\SSM-12\\src\\main\\resources\\applicationContext.xml");

1.10.2 获取 Bean

方式1:按bean名称获取再强转

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

方式2:按bean名称获取并指定类型

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

方式3:按bean类型获取

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

1.10.3 ApplicationContext 层次结构

层次图

Pasted image 20241107192431

BeanFactory接口为最高接口Spring最早期容器创建方法,使用该接口也可以创建容器

Resource resources = new ClassPathResource("applicationContext.xml");  
BeanFactory beanFactory = new XmlBeanFactory(resources);

ApplicationContext接口为BeanFactory扩展

BeanFactory接口为延迟加载bean(获取bean时加载),而ApplicationContext接口默认为立即加载bean

使用bean标签中的lazy-init属性开启ApplicationContext延迟加载bean

<bean id="bookDao" class="com.lyc.impl.BookDaoImpl" lazy-init="true"/>

二、注解开发

2.1 注解定义 Bean

使用注解@Component代替bean标签

public @interface Component {  
	/**  
	* The value may indicate a suggestion for a logical component name,  
	* to be turned into a Spring bean in case of an autodetected component.  
	* @return the suggested component name, if any (or empty String otherwise)  
	*/  
	String value() default "";  
  
}

使用示例

1. 在类中使用注解

@Component("bookDao")  
public class BookDaoImpl implements BookDao {  
	@Override  
	public void save() {  
		System.out.println("book dao save...");  
	}  
}

2. 在配置文件中使用context命名空间中的component-scan进行组件扫描

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
	xmlns:context="http://www.springframework.org/schema/context"  
	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  
		http://www.springframework.org/schema/context  
		http://www.springframework.org/schema/context/spring-context.xsd  
	">  
	<context:component-scan base-package="com.lyc" />  
</beans>

若未指定名称,则使用类型实例化bean

@Component
public class BookServiceImpl implements BookService {  
	private BookService bookService;  
	  
	public void setBookSerice(BookService bookSerice) {  
		this.bookService = bookService;  
	}  
	  
	@Override  
	public void save() {  
		System.out.println("book service save...");  
	}  
}
BookService bookService = ctx.getBean(BookService.class);  
System.out.println(bookService);
  • Spring提供了@Component三个衍生注解(功能完全一样)
    1. @Controller用于表现层bean定义
    2. @Service用于业务层bean定义
    3. @Repository用于数据层bean定义

2.2 纯注解开发

Spring 3.0 开启了纯注解开发模式,使用Java类代替配置文件
@Configuration用于设定当前类为配置类
@ComponentScan用于设定扫描路径,此注解只能添加一次,多个数据使用数组格式

使用@Configuration代替applicationContext.xml配置文件,相当于xml模版文件

com.lyc.config.SpringConfig

@Configuration  
public class SpringConfig {  
  
}

使用@ComponentScan替代component-scan进行组件扫描

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

使用AnnotationConfigApplicationContext()创建容器

ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);  
BookDao bookDao = (BookDao)ctx.getBean("bookDao");  
System.out.println(bookDao);

2.3 Bean管理

高版本JDK需要导入annotation依赖

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>
  • @Scope用于配置作用范围,非单例值为prototype
  • @PostConstruct用于配置初始化方法
  • @PreDestroy用于配置销毁方法
@Repository("bookDao")  
@Scope("singleton")  
public class BookDaoImpl implements BookDao {  
	@Override  
	public void save() {  
		System.out.println("book dao save...");  
	}  
	@PostConstruct  
	public void init() {  
		System.out.println("book dao init...");  
	}  
	@PreDestroy  
	public void destroy() {  
		System.out.println("book dao destroy...");  
	}  
}
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);  
BookDao bookDao = (BookDao)ctx.getBean("bookDao");  
BookDao bookDao2 = (BookDao)ctx.getBean("bookDao");  
System.out.println(bookDao2);  
System.out.println(bookDao);  
ctx.close();

运行结果

book dao init...
com.lyc.dao.impl.BookDaoImpl@2b91004a
com.lyc.dao.impl.BookDaoImpl@2b91004a
book dao destroy...

2.4 注解开发依赖注入

2.4.1 自动装配引用类型

使用@Autowired实现按类型自动装配,可省略set方法

@Service("bookService")  
public class BookServiceImpl implements BookService {  
	@Autowired  
	private BookDao bookDao;  
	  
	@Override  
	public void save() {  
		System.out.println("book service save ...");  
		bookDao.save();  
	}  
}
@Repository("bookDao")  
public class BookDaoImpl implements BookDao {  
	@Override  
	public void save() {  
		System.out.println("book dao save...");  
	}  
}

使用@Qualifier()搭配@Autowired实现按名称注入

@Service("bookService")  
public class BookServiceImpl implements BookService {  
	@Autowired  
	@Qualifier("bookDao")  
	private BookDao bookDao;  
	  
	@Override  
	public void save() {  
		System.out.println("book service save ...");  
		bookDao.save();  
	}  
}

2.4.2 自动装配简单类型

使用@Value()实现简单类型自动装配

@Repository("bookDao")  
public class BookDaoImpl implements BookDao {  
	@Value("LY_C")  
	private String name;  
	@Override  
	public void save() {  
		System.out.println("book dao save..." + name);  
	}  
}

2.5 注解开发管理第三方 Bean

在配置类中直接书写第三方bean的函数,配合@Bean()将函数返回值创建成bean

@Configuration  
public class SpringConfig {  
	@Bean("dataSource")  
	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("123456");  
		return ds;  
	}  
}

将Jdbc配置剥离Spring配置类

方法一

1.在Spring类同级中创建JdbcConfig,将JdbcConfig注册成配置类

@Configuration  
public class JdbcConfig {  
	@Bean("dataSource")  
	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("123456");  
		return ds;  
	}  
}

2.返回Spring配置类中添加扫描

@Configuration  
@ComponentScan("com.lyc.config")  
public class SpringConfig {  
  
}

方法二

直接在Spring配置类使用@Import()将其他配置类导入

@Configuration  
@Import(JdbcConfig.class)  
public class SpringConfig {  
  
}

此时JdbcConfig无需注册成配置类

public class JdbcConfig {  
	@Bean("dataSource")  
	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("123456");  
		return ds;  
	}  
}

2.6 第三方 Bean 注入资源

简单类型注入示例

public class JdbcConfig {  
	@Value("com.mysql.jdbc.Driver")  
	private String driver;  
	@Value("jdbc:mysql://localhost:3306/spring_db")  
	private String url;  
	@Value("root")  
	private String userName;  
	@Value("123456")  
	private String password;  
	  
	@Bean("dataSource")  
	public DataSource dataSource(){  
		DruidDataSource ds = new DruidDataSource();  
		ds.setDriverClassName(driver);  
		ds.setUrl(url);  
		ds.setUsername(userName);  
		ds.setPassword(password);  
		return ds;  
	}  
}

引用类型注入,给个形参就行
Spring 会按类型自动装配@Bean注册方法中的形参

public class JdbcConfig {  
	@Value("com.mysql.jdbc.Driver")  
	private String driver;  
	@Value("jdbc:mysql://localhost:3306/spring_db")  
	private String url;  
	@Value("root")  
	private String userName;  
	@Value("123456")  
	private String password;  
  
	@Bean("dataSource")  
	public DataSource dataSource(BookDao bookDao){  
		System.out.println(bookDao);//此时Spring会将 bookDao 自动装入
		DruidDataSource ds = new DruidDataSource();  
		ds.setDriverClassName(driver);  
		ds.setUrl(url);  
		ds.setUsername(userName);  
		ds.setPassword(password);  
		return ds;  
	}  
}

三、Spring 整合 组件

3.1 整合 MyBatis

3.1.1 Java web回顾

spring_db.tbl_accout表结构如下:

Pasted image 20241111203253

导入Mybatisjdbc依赖

<dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.2.1</version>
</dependency>
<dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>9.1.0</version>
</dependency>

resource文件夹中创建Mybatisjdbc.properties配置文件

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/sprint_db
jdbc.username=root
jdbc.password=123456
<?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 resource="jdbc.properties">
    </properties>
    <typeAliases>
        <package name="com.lyc.domain"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <package name="com.lyc.dao"/>
    </mappers>
</configuration>

创建表对应的domain

package com.lyc.domain;

public class Account {
    private Integer id;
    private String name;
    private int money;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

创建对应的Dao层,并使用Mybatis注解开发

package com.lyc.dao;

import com.lyc.domain.Account;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface AccountDao {
    @Insert("insert into tbl_accout(name,money)values(#{name},#{money});")
    public void save(Account account);
    @Select("select * from tbl_accout where id = #{id};")
    public Account selectById(int id);
    @Select("select * from tbl_accout;")
    public List<Account> selectAll();
}

创建Mybatis测试类

public class App {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession();

        AccountDao mapper = session.getMapper(AccountDao.class);
        Account accounts = mapper.selectById(1);
        System.out.println(accounts);
        Account account = new Account();
        account.setName("LY_C");
        account.setMoney(2000);
        mapper.save(account);
        session.close();
    }
}

3.1.2 Spring整合

Mybatis测试类编写流程梳理
1. 创建SqlSessionFactoryBuilder对象
2. 加载mybatis-config.xml配置文件
3. 创建sqlSessionFactory对象
4. 获取sqlSession
5. 执行sqlSession对象查询,获取结果
6. 释放资源

使用druid管理SQL链接,Mybatis创建sqlSessionFactory对象并完成Mapper文件映射

导入依赖

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.10</version>  <!-- 最新的稳定版本 -->
        </dependency>

        <!-- 更新 MyBatis-Spring 版本 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>3.0.4</version>
        </dependency>

        <!-- Spring Framework 6.x -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.0</version>  <!-- Spring 6.x -->
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>6.0.0</version>  <!-- Spring 6.x -->
        </dependency>

        <!-- Druid 更新版本 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.9</version>  <!-- 最新版本 -->
        </dependency>

        <!-- MySQL 连接器 -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>9.1.0</version>
        </dependency>
        <!-- javax.annotation-api, 你可以使用 javax.annotation-api 的更新版本 -->
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
        </dependency>

整合Druid,使用Druid管理数据库连接池

创建jdbc.properties

创建JdbcConfig配置类,用于整合Druid

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 getDataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }
}

创建SpringConfig类,并加载jdbc.properties导入JdbcConfig配置类

@Configuration
@ComponentScan("com.lyc")
@PropertySource("jdbc.properties")
@Import(JdbcConfig.class)
public class SpringConfig {

}

整合Mybatis,使用Mybatis创建sqlSessionFactory对象,Mapper文件映射

由于sqlSessionFactory对象需要将mybatis-config.xml配置文件中的参数都加入过于麻烦,Spring框架提出了SqlSessionFactoryBean类用于快速初始化,该类只需要传入dataSource对象即可

使用Spring自动装入将Druid管理的dataSource传入整合方法中,再使用SqlSessionFactoryBean对象的setDataSource方法设置MyBatis使用的数据库连接池 DataSource,然后使用setTypeAliasesPackage方法扫描指定包下的类完成起别名操作

public class MybatisCofig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setDataSource(dataSource);
        ssfb.setTypeAliasesPackage("com.lyc.dao");
        return ssfb;
    }
}

使用MapperScannerConfigurer完成Mapper文件映射到Mapper接口的操作

public class MybatisCofig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setDataSource(dataSource);
        ssfb.setTypeAliasesPackage("com.lyc.dao");
        return ssfb;
    }
    
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.lyc.dao");
        return msc;
    }
}

编写service层之后,编写Spring启动Mybatis测试类

public class App2 {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        AccountService accountService = ctx.getBean(AccountService.class);
        System.out.println(accountService.selectById(1));
    }
}

3.2 整合 JUnit

导入JUnit和Spring整合依赖包

<dependency>  
	<groupId>junit</groupId>  
	<artifactId>junit</artifactId>  
	<version>4.13.2</version>  
	<scope>test</scope>  
</dependency>  
<dependency>  
	<groupId>org.springframework</groupId>  
	<artifactId>spring-test</artifactId>  
	<version>6.0.0</version> <!-- Spring 6.x -->  
</dependency>

使用@RunWith告诉 JUnit 使用特定的测试运行器来执行测试
使用@ContextConfiguration指定Spring 配置文件或配置类

@RunWith(SpringJUnit4ClassRunner.class)  
@ContextConfiguration(classes = SpringConfig.class)  
public class AccountServiceTest {  
	@Autowired  
	private AccountService accountService;  
	@Test  
	public void testFindByID(){  
		System.out.println(accountService.selectById(1));  
	}  
}

四、AOP编程

4.0 动态代理

代理用于转移对象部分职责,对象和代理方法相同,代理和对象之间用接口连接

代理对象的创建使用的是Proxy.newProxyInstance()方法,该方法需要传递三个参数,加载器,类数组,InvocationHandler接口

程序实例:
首先创建需要被代理的类,以及接口,由于是借助于接口实现代理类需要被代理类实现接口

public class BigStar implements Star {
    private String name;

    public BigStar(String name) {
        this.name = name;
    }

    public String sing(String name){
        System.out.println(this.name + "正在唱" + name);
        return "thanks";
    }

    public void dance(){
        System.out.println(this.name + "正在跳舞");
    }
}
public interface Star {
    public String sing(String name);
    public void dance();
}

编写代理工具类

Proxy.newProxyInstance参数解析:
1. ProxyUtil.class.getClassLoader():指定类加载器,用于加载代理类。
2. new Class[]{Star.class}:指定代理对象要实现的接口,这里是 `Star` 接口。
3. new InvocationHandler() { ... }:实现 `InvocationHandler` 接口,定义方法拦截逻辑

invoke
动态代理对象调用任何方法时,都会被转发到这个方法处理。

– 参数说明:
1. Object proxy
当前生成的代理对象实例。
2. Method method
被调用的方法对象(反射方式提供)。
3. Object[] args
方法调用时传递的参数,按调用顺序排列。如果方法无参数,则为 null 或空数组。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyUtil {
    public static Star createProxy(BigStar bigStar){

        Star starProxy = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
                new Class[]{Star.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if (method.getName().equals("sing")){
                            System.out.println("准备话筒,收钱20万");
                        } else if (method.getName().equals("dance")) {
                            System.out.println("准备场地,收钱10000万");
                        }
                        return method.invoke(bigStar, args);
                    }
                });
        return starProxy;
    }
}

4.1 核心概念

AOP面向切面编程,一种编程的范式,指导开发者如歌组织程序结构
在不改动原始程序之上为其增强功能(无侵入式编程)

首先找到程序中共性功能单独抽出来,创建一个通知类创建一个方法(通知)存放共性功能

Pasted image 20241115161659

Pasted image 20241115161750

4.2 AOP入门案例

案例目标:在接口执行前输出当前系统时间

思路分析
1. 导入依赖
2. 制作连接点(原始操作,Dao接口与实现类)
3. 制作共性功能
4. 定义切入点
5. 绑定切入点和通知关系

导入AOP、aspect包

<dependency>  
	<groupId>org.springframework</groupId>  
	<artifactId>spring-context</artifactId>  
	<version>6.0.0</version>  
</dependency>  
<dependency>  
	<groupId>javax.annotation</groupId>  
	<artifactId>javax.annotation-api</artifactId>  
	<version>1.3.2</version>  
</dependency>  
<dependency>  
	<groupId>org.aspectj</groupId>  
	<artifactId>aspectjweaver</artifactId>  
	<version>1.9.4</version>  
</dependency>

创建Spring配置类,并开包扫描

@Configuration  
@ComponentScan("com.lyc")  
public class SpringConfig {

}

编写dao层和实现类

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

编写AOP类aop.Mydvice.class,首先使用@Component将此类声明成Bean,然后在使用@Aspect将此类声明成通知类

@Component  
@Aspect  
public class MyAdvice {  

}

使用@Pointcut@Before编写通知类
@Pointcut定义切入点,execution(void com.lyc.dao.BookDao.update())锁定接口BookDao 的 `public void update();`函数,
@Before("pt()")用于绑定切入点和通知,在函数pt之前执行

注意
定义切入点依托一个不具有实际意义的方法,即私有的 无返回值 无参数值 无方法体

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

返回Spring配置类使用@EnableAspectJAutoProxy告诉Spring注解开发通知类

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

遍写测试类

public class App {  
	public static void main(String[] args) {  
		ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);  
		BookDao bean = ctx.getBean(BookDao.class);  
		bean.update();  
	}  
}

运行结果

1731741679552
book dao update...

4.3 AOP 工作流程(本质就是代理模式)

1. Spring 容器启动
2. 读取所有切面配置中的切入点
3. 初始化Bean,判定Bean对应类中的方法是否匹配到任意切入点
– 匹配失败,创建对象
– 匹配成功,创建原始(目标)对象的代理对象
4. 获取Bean执行方法
– 获取Bean,调用方法并执行,完成操作
– 获取的Bean是代理对象时,根据代理对象的运行模式原始方法与增强的内容,完成操作

4.4 AOP 切入点表达式

切入点表达式标准式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)

修饰符和异常名可以省略

可以使用通配符描述切入点,快速描述

  • *:单个独立的任意符号,可以独立出现,也可以作为前后缀的匹配符出现
execution(public * com.lyc.*.UserService.find*(*))

匹配com.lyc包下的任意包中的UserService类或接口中所有find开头的带有一个参数方法

  • ..:多个连续的任意符号,可以独立出现,常用于简化包名和参数的书写
execution(public User com..UserService.findById(..))

匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法

  • +:专用于匹配子类类型
execution(* *..*.Service+.*(..))

Pasted image 20241116172424

4.5 AOP 通知类型

AOP通知共分为五种
– 前置通知@Before
– 后置通知@After
– 环绕通知@Around
– 返回后通知@AfterReturning
– 抛出异常后通知@AfterThrowing

代码示例:

注意
在环绕通知时,需要使用ProceedingJoinPoint对象的proceed()方法指定调用原方法的地方,若原方法有返回值则需要返回proceed()方法的返回值

@AfterReturning原始方法无异常成功运行之后调用,若遇到抛异常则不会运行,而@Before在会抛异常之前运行
@AfterThrowing遇到抛异常会在抛异常之前运行,若无异常则不运行

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.lyc.dao.BookDao.update())")
    private void pt(){}
    @Pointcut("execution(int com.lyc.dao.BookDao.select())")
    private void pt2(){}

//    @Before("pt()")
    public void before(){
        System.out.println("before advice");
    }

//    @After("pt()")
    public void after(){
        System.out.println("after advice");
    }

//    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("before advice");
        Object ret = pjp.proceed();
        System.out.println("after advice");
        return ret;
    }

//    @Around("pt2()")
    public Object aroundSelcet(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("before advice");
        Object ret = pjp.proceed();
        System.out.println("after advice");
        return ret;
    }
//  @AfterReturning("pt2()")
    public void afterReturning(){
        System.out.println("afterReturning advice");
    }
//    @AfterThrowing("pt2()")
    public void afterThrowing(){
        System.out.println("afterThrowing advice");
    }
}

4.6 案例 测试业务层接口万次执行效率

@Component
@Aspect
public class ProjectAdvice {
    @Pointcut("execution( * com.lyc.service.*Service.*(..))")
    private void servicePt(){}
    @Around("ProjectAdvice.servicePt()")
    public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
        Signature signature = pjp.getSignature();
        String classname = signature.getDeclaringTypeName();
        String methodName = signature.getName();

        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            pjp.proceed();
        }
        long end = System.currentTimeMillis();
        System.out.println("业务层接口万次"+classname +"."+methodName+"的执行时间: "+(end-start)+"ms");
    }
}

4.7 AOP 通知获取数据

获取三个数据:获取参数,获取返回值,获取异常

获取切入点方法的参数
– JoinPoint:适用于前置、后置、返回后、抛出异常后通知
– ProceedJointPoint:适用于环绕通知

获取切入点方法返回值
– 返回后通知
– 环绕通知

获取切入点方法运行异常信息
– 抛出异常后通知
– 环绕通知

获取参数值:JoinPointProceedJointPoint

关系图如下:

Pasted image 20241117185527

public interface ProceedingJoinPoint extends JoinPoint {
    void set$AroundClosure(AroundClosure var1);

    default void stack$AroundClosure(AroundClosure arc) {
        throw new UnsupportedOperationException();
    }

    Object proceed() throws Throwable;

    Object proceed(Object[] var1) throws Throwable;
}

使用JoinPoint对象的getArgs()方法可以获取到传递的参数值
ProceedingJoinPoint继承JoinPoin所以也有getArgs()方法

可以在通知中 篡改参数

//    @Before("pt()")
    public void before(JoinPoint jp){
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("before advice ...");
    }

//    @After("pt()")
    public void after(JoinPoint jp){
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("after advice ...");
    }
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        args[0]=666;

        Object rel = pjp.proceed(args);
        return  rel;
    }

返回值获取:ProceedJointPoint、或使用@AfterReturning中的returning值结合通知方法

使用ProceedJointPoint对象的proceed()方法可以获取到返回值

首先在通知中定义一个形参,然后再赋予@AfterReturning中的returning值

//    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object rel = pjp.proceed(args);
        return rel;
    }

    @AfterReturning(value = "pt()",returning = "ret")
    public void afterReturning(Object ret){
        System.out.println("afterReturning advice ..."+ret);
    }

注意
JoinPoint在通知方法参数中必须在第一位

异常信息获取:

//    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) {
        Object rel = null;
        try {
            rel = pjp.proceed(args);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
        return rel;
    }

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

4.8 案例:数据兼容处理

目标:去除用户输入密码中的空格

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution( boolean com.lyc.service.ResourcesService.openUrl(..))")
    private void pt(){}

    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp){
        Object rel = null;
        Object[] args = pjp.getArgs();
        for (int i = 0; i < args.length; i++) {
            if (args[i].getClass().equals(String.class)) {
                args[i] = args[i].toString().trim();
            }
        }
        try {
            rel = pjp.proceed(args);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
        return rel;
    }
}

五、Spring 事务

5.1 事务概念

事务作用:在数据层保障一系列的数据库操作同成功同失败
Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败

Spring提供了PlatformTransactionManager接口,用于管理事务

实现流程如下:

使用@Transactional将Service层指定方法开启事务,一般加到Service层接口

public interface AccountService {
    @Transactional
    public void transfer(String out,String in ,Double money);
}

修改JdbcConfig将指定的dataSource注入到由Spring提供的事务管理器PlatformTransactionManager

public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String name;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource getDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(name);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager  = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

在SpringConfig中使用注解@EnableTransactionManagement开启事务

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

5.2 事务角色

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

在业务层开启Spring事务时,Spring会将单个事务层中多个数据层的事务统一合并到一个事务层中的事务,如图

1731902263398

注意
SqlSessionFactoryBean与DataSourceTransactionManager的dataSource必须一致才能开启事务

5.3 事务属性

事务配置

1731902659089

在Spring事务中默认只有运行时异常(RuntimeException)和Error会触发事务回滚
使用rollbackFor指定添加指定异常回滚

public interface AccountService {  
	@Transactional(rollbackFor = {IOException.class})  
	public void transfer(String out,String in ,Double money) throws IOException;  
}

事务传播行为:事务协调员对事务管理员所携带的事务的处理态度(是否加入到业务层事务中)

propagation属性配置

Pasted image 20241118123526

例:实现无论转账是否成功都会触发日记记录

public interface AccountService {
    @Transactional()
    public void transfer(String out,String in ,Double money);
}
public interface LogService {
    @Transactional(propagation = Propagation.REQUIRES_NEW) //将该事物从事务管理员中分离出来
    public void log(String out,String in,Double money);
}
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    @Autowired
    private LogService logService;

    @Override
    public void transfer(String out, String in, Double money) {
        try {
            accountDao.inMoney(out, money);
            accountDao.outMoney(in, money);
        } finally {
            logService.log(out, in, money);
        }
    }
}

 

 

 

 

 

 

请登录后发表评论

    没有回复内容