Spring Framework系统架构
Spring Framework是Spring生态圈中最基础的项目,是其他项目的根基
Data Access:数据访问
Data Integration:数据集成
Web:Web开发
AOP:面向切面编程
Aspects:AOP思想实现
Core Container:核心容器
Test:单元测试与集成测试
spring核心概念
●IoC(Inversion of Control)控制反转
使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转
●Spring技术对Ioc思想进行了实现
Spring提供了一个容器,称为Ioc容器,用来充当IoC思想中的外部
Ioc容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在Ioc容器中统称为Bean
●DI(Dependency Injection)依赖注入
在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入
●目标:充分解耦
使用Ioc容器管理bean(Ioc)
在Ioc容器内将有依赖关系的bean进行关系绑定(DI)
●最终效果
使用对象时不仅可以直接从IoC容器中获取,并且获取到的bean已经绑定了所有的依赖关系
Ioc与DI入门案列
结构
代码
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring_01_quickstart</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
</project>
applicationContext
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<bean id="bookDao" class="com.ithema.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.ithema.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"></property>
<!--ref指上面的id="bookDao"对象 name指该对象里的变量-->
</bean>
</beans>
BookDaoImpl
package com.ithema.dao.impl;
import com.ithema.dao.BookDao;
public class BookDaoImpl implements BookDao {
public void save(){
System.out.println("book dao save");
}
}
BookDao
package com.ithema.dao;
public interface BookDao {
public void save();
}
BookServiceImpl
package com.ithema.service.impl;
import com.ithema.dao.BookDao;
import com.ithema.dao.impl.BookDaoImpl;
import com.ithema.service.BookService;
public class BookServiceImpl implements BookService{
private BookDao bookDao;
public void save(){
System.out.println("book service save");
bookDao.save();
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
BookService
package com.ithema.service;
public interface BookService {
public void save();
}
App
package com.ithema;
import com.ithema.service.BookService;
import com.ithema.service.impl.BookServiceImpl;
public class App {
public static void main(String[] args) {
BookServiceImpl bookService=new BookServiceImpl();
bookService.save();
}
}
App2
package com.ithema;
import com.ithema.dao.BookDao;
import com.ithema.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App2 {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// BookDao bookDao =(BookDao) ctx.getBean("bookDao");
// bookDao.save();
BookService bookService =(BookService) ctx.getBean("bookService");
bookService.save();
}
}
bean基础配置
bean别名配置
名称
name
类型
属性
所属
bean标签
功能
定义bean的别名,可定义多个,使用逗号(,)分号(;)空格( )分隔
范例
<bean id="bookDao"name="dao bookDaoImpl"class=“com.itheima.dao.impl.BookDaoImpl”/>
<bean name="service,bookServiceImpl"class=“com.itheima.service.impl.BookServiceImpl”/>
bean作用范围配置
名称
scope
类型
属性
所属
bean标签
定义bean的作用范围,可选范围如下
功能
●singleton:单例(默认)
●prototype:非单例
范例
<bean id="bookDao"class="com.itheima.dao.impl.BookDaoImpl"scope=“prototype”/>
bean作用范围说明
为什么bean默认为单例?
适合交给容器进行管理的bean
表现层对象
业务层对象
数据层对象
工具对象
不适合交给容器进行管理的bean
封装实体的域对象
bean实例化
构造方法
bean本质上就是对象,创建bean使用构造方法完成
public class BookDaoImpl implements BookDao{
public BookDaoImpl(){
System.out.println("book constructor is running ...")
}
public void save(){
System.out.println("book dao save ...")
}
}
配置
<bean
id="bookDao"
class="com.itheima.dao.impl.BookDaoImpl"
/>
无参构造方法如果不存在,将抛出异常BeanCreationException
静态工厂(了解)
·静态工厂
public class OrderDaoFactory{
public static OrderDao getorderDao(){
return new OrderDaoImpl();
}
}
配置
<bean
id="orderDao"
factory-method="getorderDao"
class="com.itheima.factory.OrderDaoFactory"
/>
实例工厂
public class UserDaoFactory{
public UserDao getuserDao(){
return new UserDaoImpl();
}
}
配置
<bean
id="userDaoFactory"
class="com.itheima.factory.UserDaoFactory"/>
<bean
id="userDao"
factory-method="getUserDao"
factory-bean="userDaoFactory"
/>
实例化bean的第四种方式一FactoryBean
public class UserDaoFactoryBean implements FactoryBean<UserDao>{
public UserDao getobject()throws Exception{
return new UserDaoImpl();
}
public Class<?>getobjectType(){
return UserDao.class;
}
}
配置
<bean
id="userDao"
class="com.itheima.factory.UserDaoFactoryBean"
/>
bean生命周期控制
●配置
■init-method
■destroy-method
■接口(了解)
■InitializingBean
■DisposableBean
2.关闭容器
■ConfigurableApplicationContext
■close()
■registerShutdownHook()
依赖注入方式
思考:向一个类中传递数据的方式有几种?
■普通方法(set方法)
■构造方法
思考:依赖注入描述了在容器中建立bean与bean之间依赖关系的过程,如果bean运行需要的是数字或字符串呢?
■引用类型
■简单类型(基本数据类型与String)
依赖注入方式
■setter注入
■简单类型
■引用类型
■构造器注入
■简单类型
■引用类型
setter注入一简单类型
在bean中定义引用类型属性并提供可访问的set方法
public class BookDaoImpl implements BookDao {
private int connectionNumber;
public void setConnectionNumber(int connectionNumber){
this.connectionNumber connectionNumber;
}
配置中使用property标签value属性注入简单类型数据
<bean id="bookDao"class="com.itheima.dao.impl.BookDaoImpl">
<property name="connectionNumber"value="10"/>
</bean>
构造器注入一简单类型
<bean id="bookDao"class="com.itheima.dao.impl.BookDaoImpl">
<constructor-arg name="connectionNum"value="10"/>
<constructor-arg name="databaseName"value="mysql"/>
</bean>
<bean id="userDao"class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="bookservice"class="com.itheima.service.impl.BookserviceImpl">
<constructor-arg name="userDao"ref="userDao"/>
<constructor-arg name="bookDao"ref="bookDao"/>
</bean>
依赖注入方式选择
1.强制依赖使用构造器进行,使用setteri注入有概率不进行注入导致null对象出现
2.可选依赖使用setter注入进行,灵活性强
3.Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
4.如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入)
5.实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter,方法就必须使用构造器注入
6.自己开发的模块推荐使用setter注入
依赖自动装配
配置中使用bean标签autowire属性设置自动装配的类型
<bean id="bookDao"class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService"class="com.itheima.service.impl.BookServiceImpl"autowire="byType"/>
依赖自动装配特征
1.自动装配用于引用类型依赖注入,不能对简单类型进行操作
2.使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
3.使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
4.自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效
集合注入
数组
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
List
<property name="list">
<list>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
</list>
</property>
Set
<property name="set">
<set>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
</set>
</property>
Map
<property name="map">
<map>
<entry key="country"value="china"/>
<entry key="province"value="henan"/>
<entry key="city"value="kaifeng"/>
</map>
</property>
Properties
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">henan</prop>
<prop key="city">kaifeng</prop>
</props>
</property>
加载properties:文件
·开启context?命名空间
<?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">
</beans>
●使用context命名空间,加载指定properties文件
<context:property-placeholder location="jdbc.properties"/>
●使用${}读取加载的属性值
<property name="username"value="${jdbc.username}"/>
不加载系统属性
<context:property-placeholder location="jdbc.properties"system-properties-mode="NEVER"/>
加载多个properties文件
<context:property-placeholder location="jdbc.properties,msg.properties"/>
加载所有properties.文件
<context:property-placeholder location="*.properties"/>
加载properties.文件标准格式
<context:property-placeholder location="classpath:*properties"/>
从类路径或jar包中搜索并加载properties.文件
<context:property-placeholder location="classpath*:*.properties"/>
容器
使用绝对路径加载 bean的配置文件
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\JavaProject\\spring\\src\\main\\resources\\applicationContext.xml");
一次加载多个配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml","bean1.xml","bean2.xml");
获取bean的其他方式
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(“applicationContext.xml”);
// UserDao userDao = (UserDao) ctx.getBean(“userDao”);
UserDao userDao1 = ctx.getBean(“userDao”,UserDao.class);
UserDao userDao2 = ctx.getBean(UserDao.class);
System.out.println(userDao2);
System.out.println(userDao1);
}
}
延迟加载bean
BeanFactory 是所有容器类的顶层接口
beanFactory 延迟加载bean
applicationContext 立即加载bean,但是也可以在配置bean的时候添加 lazy-init属性进行配置
容器类层次结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j1Dmviys-1668305956019)(D:\Program Files (x86)]\Typora\images\image-20220824181828577.png)
Ioc,DI,Bean 小结
Ioc容器
BeanFactory是IoC容器的顶层接口,初始化BeanFactory对象时,加载的bean延迟加载
ApplicationContext接口是Spring容器的核心接口,初始化时bean立即加载
ApplicationContext接口提供基础的bean操作相关方法,通过其他接口扩展其功能
ApplicationContext接口常用初始化类
ClassPathXmlApplicationContext
FileSystemXmlApplicationContext
Bean相关
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bxUvxs1K-1668305956021)(D:\Program Files (x86)]\Typora\images\image-20220824182842833.png)
依赖注入相关
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5KOQjQQk-1668305956021)(D:\Program Files (x86)]\Typora\images\image-20220824183351246.png)
注解开发
注解开发入门
想要作为bean被IoC容器管理,需要在类的上面添加 @Component(“BeanName”) 注解,表示被IoC容器被管理
实例:
@Component(“orderDao”) //这样可以通过类名,获取bean
public class OrderDaoImpl implements OrderDao {
@Override
public void save() {
System.out.println(“OrderDao ordered”);
}
}
@Component //没有写名称时,也可以通过BookService.class来获取
public class BookServiceImpl implements BookService {
BookDao bookDao;
@Override
public void save() {
System.out.println(“Service save !”);
bookDao.save();
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
为了使各种bean的分类更清晰,spring中提供使用不用的注解,给不同的MVC不同层级的类
业务层使用 @Service(" ")
数据层使用 @Respository(" ")
@Respository(“orderDao”) //这样可以通过类名,获取bean
public class OrderDaoImpl implements OrderDao {
@Override
public void save() {
System.out.println(“OrderDao ordered”);
}
}
@Service //没有写名称时,也可以通过BookService.class来获取
public class BookServiceImpl implements BookService {
BookDao bookDao;
@Override
public void save() {
System.out.println(“Service save !”);
bookDao.save();
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
纯注解开发
纯注解开发就是在上面的基础上,将applicationContext.xml文件也去掉,换成SpringConfig类
在包 config 中 新建类 SpringConfig中编写配置类,替代bean的xml配置
@Configuration //表示设置当前类为配置类
@ComponentScan(“{com.weifeng.service,com.weifeng.dao}”) //设定扫描路径,该组件自能添加一次,多个数据使用数组格式
public class SpringConfig {
}
如何读取该配置类?
使用 ApplicationContext
public class AppForAnnotation {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
OrderDao orderDao = (OrderDao) ctx.getBean(“orderDao”);
BookService bookService = (BookService) ctx.getBean(BookService.class);
System.out.println(bookService);
orderDao.save();
}
}
bean的作用范围和生命周期
使用 @Scope(“singleton”)
@Repository(“orderDao”)
@Scope(“singleton”) //prototype 作用范围
public class OrderDaoImpl implements OrderDao {
@Override
public void save() {
System.out.println("OrderDao ordered");
}
@PostConstruct //在初始化之后
public void init(){
System.out.println("init orderDao ....");
}
@PreDestroy //在销毁之前
public void destroy(){
System.out.println("destroy orderDao ....");
}
}
注解实现自动装配
使用 @AutoWrited ,其使用的是按照类型自动装配,同时不同编写setter也可以,底层使用的是暴力反射
@Service(“bookService”)
public class BookServiceImpl implements BookService {
@Autowired
BookDao bookDao;
@Override
public void save() {
System.out.println("Service save ~!~");
bookDao.save();
}
// public void setBookDao(BookDao bookDao) {
// this.bookDao = bookDao;
// }
}
如果存在多个实现类实现同一个bean的,可以使用 @Qualifier(“beanName”) 指定该bean的名称
@Service(“bookService”)
public class BookServiceImpl implements BookService {
@Autowired
@Qualifier(“bookDao2”) //可以按照名称指定自动装配的bean
BookDao bookDao;
@Override
public void save() {
System.out.println("Service save ~!~");
bookDao.save();
}
// public void setBookDao(BookDao bookDao) {
// this.bookDao = bookDao;
// }
}
注意∶自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供setter方法
注意∶自动装配建议使用无参构造方法创建对象(默认),如果不提供对应构造方法,请提供唯一的构造方法
使用 @Value() 实现简单类型的注入
@Repository(“bookDao”)
public class BookDaoImpl implements BookDao {
@Value(“weifeng”)
String name;
@Override
public void save() {
System.out.println("dao save ~!~" + name);
}
}
通过properities文件对bean进行注入
@Configuration
@ComponentScan(“com.weifeng”)
@PropertySource(“jdbc2.properties”) //加载多个时,使用数组,不支持使用通配符
public class SpringConfig {
}
properties文件
username=1234
@Repository(“bookDao”)
public class BookDaoImpl implements BookDao {
@Value(“${username}”)
String name;
@Value(“100”)
int age;
@Override
public void save() {
System.out.println("dao save ~!~" + name + "age" + age);
System.out.println(getType(age));
}
}
第三方bean管理,bean依赖注入
第三方bean管理:手动在SpringConfig中编写方法
@Configuration
@ComponentScan(“com.weifeng”)
@PropertySource(“jdbc2.properties”)
public class SpringConfig {
@Bean //这里可以配置名称,但是一般通过类型匹配
DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/tb_brand");
ds.setUsername("root");
ds.setPassword("1234");
return ds;
}
}
但是第三方bean编写在SpringConfig文件下不便于管理,可以将第三方bean分类编写在其他的配置文件中
在 JdbcConfig中
public class JdbcConfig {
@Bean
DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(“com.mysql.jdbc.Driver”);
ds.setUrl(“jdbc:mysql://localhost:3306/tb_brand”);
ds.setUsername(“root”);
ds.setPassword(“1234”);
return ds;
}
}
在SpringConfig中 使用 @Import{类型} 导入配置类即可
@Configuration
@ComponentScan(“com.weifeng”)
@PropertySource(“jdbc2.properties”)
@Import({JdbcConfig.class})
public class SpringConfig {
}
为第三方 bean注入依赖注入(包括简单类型,和引用类型)
@PropertySource(“jdbc2.properties”)
public class JdbcConfig {
@Value("com.mysql.jdbc.Driver")
String driver;
@Value("jdbc:mysql://localhost:3306/tb_brand")
String url;
@Value("${name}")
String name;
@Value("1234")
String password;
@Bean
DataSource dataSource(BookDao bookDao){//注入引用类型,这里面的形参,spring会按类型自动装配
System.out.println(name);
System.out.println(bookDao);
DruidDataSource ds = new DruidDataSource();
//注入简单类型
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(name);
ds.setPassword(password);
return ds;
}
}
小结:注解开发与xml比较
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vrsbAcgW-1668305956022)(D:\Program Files (x86)]\Typora\images\image-20220825113611029.png)
Spring 整合myBatis
分析
分析未整合前的mybatis:
有下图可见:Spring要管理的mybatis中的对象为SqlSessionFactory对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CaSGFDLe-1668307871010)(D:\Program Files (x86)]\Typora\images\image-20220825115620723.png)
使用mybatis查询数据库
public class App {
public static void main(String[] args) throws IOException {
// 1. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2. 加载SqlMapConfig.xml配置文件
InputStream inputStream = Resources.getResourceAsStream(“SqlMapConfig.xml”);
// 3. 创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
// 4. 获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 5. 执行SqlSession对象执行查询,获取结果User
AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
Account ac = accountDao.findById(2);
System.out.println(ac);
// 6. 释放资源
sqlSession.close();
}
}
实现
导包
mysql mysql-connector-java 5.1.46 org.springframework spring-jdbc 5.2.10.RELEASE org.mybatis mybatis-spring 2.0.5 使用bean管理mybatis 中的 SqlSessionFactoryBean对象和MapperScanerConfigurer对象MybatisConfig
public class MybatisConfig {
//定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象
//主要用于配置数据库连接信息
//配置起别名的包
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setDataSource(dataSource);
ssfb.setTypeAliasesPackage(“com.itheima.domain”);
return ssfb;
}
//定义bean,返回MapperScannerConfigurer对象
//用于配置数据层
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage(“com.itheima.dao”);
return msc;
}
}
JdbcConfig
public class JdbcConfig {
@Value(“
j
d
b
c
.
d
r
i
v
e
r
"
)
p
r
i
v
a
t
e
S
t
r
i
n
g
d
r
i
v
e
r
;
@
V
a
l
u
e
(
"
{jdbc.driver}") private String driver; @Value("
jdbc.driver")privateStringdriver;@Value("{jdbc.url}”)
private String url;
@Value(“
j
d
b
c
.
u
s
e
r
n
a
m
e
"
)
p
r
i
v
a
t
e
S
t
r
i
n
g
u
s
e
r
N
a
m
e
;
@
V
a
l
u
e
(
"
{jdbc.username}") private String userName; @Value("
jdbc.username")privateStringuserName;@Value("{jdbc.password}”)
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
SpringConfig
@Configuration
@ComponentScan(“com.itheima”) //主要扫描service层次,dao层的bean已经被mybatis的MapperScannerConfigurer扫描了
//@PropertySource:加载类路径jdbc.properties文件
@PropertySource(“classpath:jdbc.properties”)
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
Spring整合junit
导包
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
测试类
@RunWith(SpringJUnit4ClassRunner.class) //设定专用的类运行器
@ContextConfiguration(classes = SpringConfig.class) //指定Spring上下文的配置类
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testFindById(){
System.out.println(accountService.findById(1));
}
}
Spring AOP
AOP的作用与概念
AOP:(aspect oriented programming) 面向切面编程,一种编程范式,指导自导开发者如何组织程序结构
OOP: (object oriented programming ) 面向对象编程
AOP:在不惊动原始设计的基础上为其进行功能增强,这个其实非常有用,可以方便实现后期添加的一些功能
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HyoZsNek-1668307871011)(D:\Program Files (x86)]\Typora\images\image-20220825151754389.png)
连接点( JoinPoint ):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
在SpringAOP中,理解为方法的执行
切入点 ( Pointcut ) :匹配连接点的式子
在SpringAoP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
一个具体方法: com.itheima.dao包下的BookDao接口中的无形参无返回值的save方法
匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数 的方法
通知(Advice ):在切入点处执行的操作,也就是共性功能
在SpringAOP中,功能最终以方法的形式呈现
通知类:定义通知的类
切面( Aspect ):描述通知与切入点的对应关系
AOP入门案例
设计要求:在接口执行前输出当前系统时间
开发模式:使用注解
思路分析:
导入坐标
制作连接点(原始操作,Dao接口与实现类)
制作共性功能(通知类与通知)
定义切入点
绑定切入点与通知关系(切面)
导入坐标
导入的aspectjweaver默认依赖aop坐标
org.springframework spring-context 5.2.10.RELEASE org.aspectj aspectjweaver 1.9.4 制作连接点(daoimpl)@Repository
public class BookDaoImpl implements BookDao {
public void save(){
System.out.println(System.currentTimeMillis());
System.out.println("book dao save ...");
}
public void update(){
System.out.println(System.currentTimeMillis());
System.out.println("book dao save ...");
}
}
共性功能(通知),定义切入点,绑定切入点和通知的关系
//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {
//设置切入点,要求配置在方法上方
@Pointcut(“execution(void com.itheima.dao.BookDao.update())”)
private void pt(){}
//设置在切入点pt()的前面运行当前操作(前置通知)
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
在SpringConfig中运行切面
@Configuration
@ComponentScan(“com.itheima”)
//开启注解开发AOP功能
@EnableAspectJAutoProxy
public class SpringConfig {
}
调用 切入点 方法
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
// bookDao.update();
System.out.println(bookDao);
System.out.println(bookDao.getClass());
bookDao.update();
bookDao.save();
}
}
AOP工作流程
Spring容器启动
读取所有切面配置中的切入点
初始化bean,判定bean对应的类中的方法是否匹配到任意切入点 匹配失败,创建对象 匹配成功,创建原始对象(目标对象)的代理对象
获取bean执行方法 获取bean,调用方法并执行,完成操作 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
如果定义的切入点可以和要初始化的bean中的方法匹配上,则产生代理对象,可以使用getClass()查看
目标对象(Target ):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
代理(Proxy ) ︰目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现
所以SpringAOP的核心:代理模式
切入点表达式
切入点:要进行增强的方法
切入点表达式:要进行增强的方法的描述
描述方法1:描述接口中的方法
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
描述方法2:描述接口实现类中的方法
@Pointcut("execution(void com.itheima.dao.impl.BookDaoImpl.save())")
表达式规范
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PD0xqOgt-1668307871012)(D:\Program Files (x86)]\Typora\images\image-20220825163752849.png)
使用通配符 * … +
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4oYv4Z6e-1668307871013)(D:\Program Files (x86)]\Typora\images\image-20220825164050008.png)
@Pointcut("execution(* *..*(..))")
表示匹配任意返回值 项目中任意包中的任意方法
书写技巧
所有代码按照标准规范开发,否则以下技巧全部失效
描述切入点通常描述接口,而不描述实现类
访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述)
返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述
包名书写尽量不使用 … 匹配,效率过低,常用 * 单个包描述匹配,或精准匹配
接口名/类名书写名称与模块相关的采用匹配,例如UserService书写成Service,绑定业务层接口名
方法名书写以动词进行精准匹配,名词采用匹配,例如getByld书写成getBy,selectAll书写成selectAll.
参数规则较为复杂,根据业务方法灵活调整
通常不使用异常作为匹配规则
AOP的通知类型
一共有五种通知绑定切入点的关系
通知类型
前置通知后置通知
环绕通知(重点)
环绕通知依赖形参 ProceedingJoinPoint 才能实现对原始万法的调用
环绕通知可以隔离原始方法的调用执行
环绕通知返回值设置为0bject类型
环绕通知中可以对原始方法调用过程中出现的异常进行处理
返回后通知
抛出异常后通知
//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {
//设置切入点,要求配置在方法上方
@Pointcut(“execution(void com.itheima.dao.BookDao.update())”)
private void pt(){}
@Pointcut("execution(int com.itheima.dao.BookDao.select())")
private void pt2(){}
//设置在切入点pt()的前面运行当前操作(前置通知)
@Before("pt()")
public void before(){
System.out.println("before advice ..");
}
@After("pt()") //不论切入点是否正常运行都会执行
public void after(){
System.out.println("after advice ..");
}
//环绕(重要)
@Around("pt2()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ..");
//执行原始切入点的方法
Integer ret = (Integer)pjp.proceed();
System.out.println("around after advice ..");
//最后如果调用这个方法,获得到真正的返回值就是下面的返回值
return ret;
}
@AfterReturning("pt2()") //这个只有在切入点正常运行完才会运行
public void afterReturning(){
System.out.println("afterReturning advice..");
}
@AfterThrowing("pt2()") //只有在抛异常的时候才运行,切入点正常时不会执行
public void afterThrowing(){
System.out.println("afterThrowing advice ..");
}
}
重点讲一下@Around()
注意事项:
环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,必须设定为Object类型(通常必须设定)
原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object
由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象
使用切面,可以进行权限判断,对原始方法进行隔离
AOP(@around)案例
测试业务层接口执行万次耗时多久?,已连接本地数据库
项目为:D:\JavaProject\spring_AOP
切面类
@Component
@Aspect
public class AccountAdvice {
@Pointcut(“execution(* com.itheima.service.Service.(…))”)
public void servicePt(){}
@Around("AccountAdvice.servicePt()")
public void testSpeed(ProceedingJoinPoint pjp) throws Throwable {
//通过对原始方法执行过程的封装,获取签名
Signature signature = pjp.getSignature();
//输出接入点的类型或接口
// System.out.println(signature.getDeclaringType());
// System.out.println(signature.getDeclaringTypeName());
String className = signature.getDeclaringTypeName();
//输出接入点中调用的方法名(切点)
// System.out.println(signature.getName());
String methodName = signature.getName();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
Object ret = pjp.proceed();
}
long end = System.currentTimeMillis();
System.out.println("万次执行业务层接口" + className + "." + methodName + "耗时 :" + (end - start) + "ms");
}
}
启动测试test
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testFindById(){
Account ac = accountService.findById(1);
}
@Test
public void testfindAll(){
List<Account> acs = accountService.findAll();
}
}
//测试结果*****
万次执行业务层接口com.itheima.service.AccountService.findById耗时 :1899ms
万次执行业务层接口com.itheima.service.AccountService.findAll耗时 :1076ms
AOP通知获取数据
通知类中的共同方法:可以获取到,原始方法的,运行传入参数,返回值,抛出的异常
JoinPoint 对象描述了连接点方法的运行状态,可以获取到原始方法的调用参数
使用JoinPoint获取的连接点方法的参数,修改后,只在共同功能方法中起作用,在连接点方法中不起作用
而使用ProceedingJoinPoint获取的连接点方法的参数,修改后,在连接点方法中起作用
@Component
@Aspect
public class MyAdvice {
@Pointcut(“execution(* com.itheima.dao.BookDao.findName(…))”)
private void pt(){}
//JoinPoint:用于描述切入点的对象,必须配置成通知方法中的第一个参数,可用于获取原始方法调用的参数
@Before("pt()")
public void before(JoinPoint jp) {
Object[] args = jp.getArgs();
args[0] = 666;
System.out.println(Array s.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 …”);
}
//ProceedingJoinPoint:专用于环绕通知,是JoinPoint子类,可以实现对原始方法的调用
// @Around(“pt()”)
public Object around(ProceedingJoinPoint pjp) {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
args[0] = 666;
Object ret = null;
try {
ret = pjp.proceed(args); //使用
//模拟返回后通知
} catch (Throwable t) {
t.printStackTrace();
//模拟抛出异常后退出
}
return ret;
}
//设置返回后通知获取原始方法的返回值,要求returning属性值必须与方法形参名相同
// @AfterReturning(value = “pt()”,returning = “ret”)
//如果传入JoinPoint参数,就一定要将jp放在形参列表的第一位
public void afterReturning(JoinPoint jp,String ret) {
System.out.println(“afterReturning advice …”+ret);
}
//设置抛出异常后通知获取原始方法运行时抛出的异常对象,要求throwing属性值必须与方法形参名相同
@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {
System.out.println("afterThrowing advice ..."+t);
}
}
AOP小结
概念:AOP(Aspect Oriented Programming)面向切面编程,一种编程范式
作用:在不惊动原始设计的基础上为方法进行功能增强
核心概念
代理(Proxy ) : SpringAOP的核心本质是采用代理模式实现的
连接点(JoinPoint ) :在SpringAOP中,理解为任意方法的执行
切入点 ( Pointcut ):匹配连接点的式子,也是具有共性功能的方法描述
通知(Advice ):若干个方法的共性功能,在切入点处执行,最终体现为一个方法
切面(Aspect ) :描述通知与切入点的对应关系
目标对象(Target ):被代理的原始对象成为目标对象
Spring事务
Spring事务简介
事务就是指很多的操作,组成在一起执行,如果成功,则修改数据,失败就回滚
事务作用:是数据层保障一系列的数据库操作同成功同失败
Spring事务的作用:在数据层或业务层保障一系列的数据库操作同成功,同失败
Spring的事务最后用的还是jdbc的事务
@Transactional 写在业务层的接口(可以使该接口中的方法都开启事务)上,目的是降低耦合
需求:实现任意两个账户间的转账操作
第一步:在springConfig类上加上 @EnableTransactionManagement
@Configuration
@ComponentScan(“com.itheima”)
@PropertySource(“classpath:jdbc.properties”)
@Import({JdbcConfig.class,MybatisConfig.class})
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}
数据层接口:AccountDao
public interface AccountDao {
@Update("update tbl_account set money = money + #{money} where name = #{name}")
void inMoney(@Param("name") String name, @Param("money") Double money);
@Update("update tbl_account set money = money - #{money} where name = #{name}")
void outMoney(@Param("name") String name, @Param("money") Double money);
}
业务层AccountService接口
public interface AccountService {
/**
* 转账操作
* @param out 传出方
* @param in 转入方
* @param money 金额
*/
//配置当前接口方法具有事务
@Transactional
public void transfer(String out,String in ,Double money) ;
}
业务层接口AccountServiceImpl实现
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void transfer(String out,String in ,Double money) {
accountDao.outMoney(out,money);
int i = 1/0;//故意的异常
accountDao.inMoney(in,money);
}
}
测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer() throws IOException {
accountService.transfer("Tom","Jerry",100D);
}
}
jdbc.properties资源
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db1?useSSL=false
jdbc.username=root
jdbc.password=1234
Spring 整合 Mybatis 配置类
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.itheima.domain");
ssfb.setDataSource(dataSource);
return ssfb;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.itheima.dao");
return msc;
}
}
Spring整合jdbc配置类
public class JdbcConfig {
@Value(“
j
d
b
c
.
d
r
i
v
e
r
"
)
p
r
i
v
a
t
e
S
t
r
i
n
g
d
r
i
v
e
r
;
@
V
a
l
u
e
(
"
{jdbc.driver}") private String driver; @Value("
jdbc.driver")privateStringdriver;@Value("{jdbc.url}”)
private String url;
@Value(“
j
d
b
c
.
u
s
e
r
n
a
m
e
"
)
p
r
i
v
a
t
e
S
t
r
i
n
g
u
s
e
r
N
a
m
e
;
@
V
a
l
u
e
(
"
{jdbc.username}") private String userName; @Value("
jdbc.username")privateStringuserName;@Value("{jdbc.password}”)
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
//配置事务管理器,mybatis使用的是jdbc事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
建数据表的sql
drop table if exists tbl_account
create table tbl_account(
id int PRIMARY KEY ,
name varchar(20) not null unique,
money int default 0
);
insert into tbl_account values
(1,“Tom”,1000),
(2,“Jerry”,500);
select * from tbl_account;
Spring事务角色
事务管理员:发起事务方,在Spring中通常指 业务层开启事务的方法
事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
注意:Spring的事务管理器 PlatformTransactionManager 使用的DataSource是和 mybatis的SqlSessionFactoryBean中使用的是一样的Bean
Spring事务相关属性配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mYH4LaKg-1668307871014)(D:\Program Files (x86)]\Typora\images\image-20220826004227880.png)
一个特别的异常:IOException 。。Spring的事务遇到该异常后不会进行回滚,因为起不属于运行时异常
public void transfer(String out,String in ,Double money) throws IOException {
accountDao.outMoney(out,money);
// int i = 1/0;
if(true){
throw new IOException();
}
accountDao.inMoney(in,money);
}
解决办法:对spring的事务添加属性告诉该事务遇到这个异常时:启动回滚
public interface AccountService {
/**
* 转账操作
* @param out 传出方
* @param in 转入方
* @param money 金额
*/
//配置当前接口方法具有事务
@Transactional(rollbackFor = {IOException.class})
public void transfer(String out,String in ,Double money) throws IOException;
}
新的需求:不论转账成功与否,都要进行log日志记录
分析:
可以使用catch final
由于记录日志,也是在转账的业务层接口的一个操作,所以如果将该操作也添加到事务中,日志操作也会与转账操作同成功同失败,所以要使用spring的一个属性配置,将日志操作从spring事务中拿出来
这里要使用事务的传播行为 propagation
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Autowired
private LogService logService;
public void transfer(String out,String in ,Double money) {
try{
accountDao.outMoney(out,money);
int i = 1/0;
accountDao.inMoney(in,money);
}finally {
logService.log(out,in,money);
}
}
}
@Service
public class LogServiceImpl implements LogService {
@Autowired
private LogDao logDao;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String out,String in,Double money ) {
logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
}
}
事务的传播行为:指的是,事务协调员对于事务管理员的不同情况,对自身的事务的操作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yzoc2LEB-1668307871015)(D:\Program Files (x86)]\Typora\images\image-20220826013444769.png)