笔记配套视频:黑马的SSM https://www.bilibili.com/video/BV1Fi4y1S7ix/
附录、Spring 架构概述
上层依赖下层
一、核心容器
1.0 核心概念(目标:充分解耦)
IoC控制反转:对象的创建控制权由程序转移至外部,这种思想称为控制反转
Spring提供了一个容器,称为IoC容器
,用来充当IoC思想中的外部
IoC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IoC容器中统称为Bean
DI依赖注入:在容器中建立Bean与Bean之间的依赖关系的整个过程,称为依赖注入
service、dao对象都交给IoC容器创建以及依赖注入
目标:
- 使用IoC容器管理Bean
- 在IoC容器内将有依赖关系的Bean进行关系绑定(DI)
- 最终效果:使用对象时不仅可以直接从IoC容器中获取,并且获取到Bean已经绑定了所有的依赖关系
1.1 IoC 容器入门
1.1.1 Java Web 回顾
架构:DAO层用于处理数据,Service层用于处理业务;Service依赖DAO
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>
图解属性配置
1.3 Bean 配置
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 项目架构如下
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<?>
接口,实现getObject
和getObjectType
两个方法
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
方法三:实现InitializingBean
和DisposableBean
接口
由于每次书写init-method和destroy-method过于麻烦,Spring使用InitializingBean
和DisposableBean
接口进行优化配置操作
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值必须与被自动装配的参数值名称一致
<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
层次结构
层次图
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
表结构如下:
导入Mybatis
、jdbc
依赖
<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
文件夹中创建Mybatis
、jdbc.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面向切面编程,一种编程的范式,指导开发者如歌组织程序结构
在不改动原始程序之上为其增强功能(无侵入式编程)
首先找到程序中共性功能单独抽出来,创建一个通知类创建一个方法(通知)存放共性功能
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+.*(..))
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:适用于环绕通知
获取切入点方法返回值
– 返回后通知
– 环绕通知
获取切入点方法运行异常信息
– 抛出异常后通知
– 环绕通知
获取参数值:JoinPoint
、ProceedJointPoint
关系图如下:
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会将单个事务层中多个数据层的事务统一合并到一个事务层中的事务,如图
注意
SqlSessionFactoryBean与DataSourceTransactionManager的dataSource必须一致才能开启事务
5.3 事务属性
事务配置
在Spring事务中默认只有运行时异常(RuntimeException
)和Error
会触发事务回滚
使用rollbackFor
指定添加指定异常回滚
public interface AccountService {
@Transactional(rollbackFor = {IOException.class})
public void transfer(String out,String in ,Double money) throws IOException;
}
事务传播行为:事务协调员对事务管理员所携带的事务的处理态度(是否加入到业务层事务中)
propagation属性配置
例:实现无论转账是否成功都会触发日记记录
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);
}
}
}
没有回复内容