SpringFramework - Spring - IOC - AOP
文章目录
前言
Spring 使创建 Java 企业应用程序变得容易。它提供了在企业环境中使用 Java 语言所需的一切,并支持 Groovy 和 Kotlin 作为 JVM 上的替代语言,并且可以根据应用程序的需求灵活地创建多种体系结构。从 Spring Framework 5.0 开始,Spring 需要 JDK 8(Java SE 8),并且已经为 JDK 9 提供了现成的支持。
Spring 支持广泛的应用场景。在大型企业中,应用程序通常存在很长时间,并且必须在升级周期不受开发人员控制的 JDK 和应用程序服务器上运行。其他服务器则可以作为单个 jar 运行,并且服务器可以嵌入云环境中。还有一些可能是不需要服务器的独立应用程序(例如批处理或集成工作负载)。
Spring 是开源的。它拥有一个庞大而活跃的社区,可以根据各种实际用例提供持续的反馈。这帮助 Spring 在很长一段时间内成功地 Developing 了。
————SpringFramework中文文档
术语“Spring”:在不同的上下文中表示不同的事物。它可以用来引用 Spring Framework 项目本身,而这一切都是从那里开始的。随着时间的流逝,其他 Spring 项目已经构建在 Spring Framework 之上。通常,当人们说“Spring”时,它们表示整个项目系列。
本文的重点是 Spring 框架本身
Spring 框架分为多个模块。应用程序可以选择所需的模块。核心容器的模块是核心,包括配置模型和依赖项注入机制。除此之外,Spring 框架为不同的应用程序体系结构提供了基础支持,包括消息传递,事务性数据和持久性以及 Web。它还包括基于 Servlet 的 Spring MVC Web 框架,以及并行的 Spring WebFlux 反应式 Web 框架。
下面让我们从图开始吧~~~
核心容器Core Container
(下面这一大块我们采用的都是配置开发)
IoC控制反转
核心概念
- Ioc(Inversion of Control)控制反转 :使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。
- 实现:
-
Spring技术来对IoC思想进行了实现,提供了一个 IoC容器,用来充当IoC思想中的外部
-
IoC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IoC容器中统称为 Bean
- 目标:充分解耦
- 使用IoC容器管理bean( IoC )
- 在IoC容器内将有依赖关系的bean进行关系绑定( DI )
- 最终效果:
使用对象时不仅可以直接从IoC容器中获取,并且获取到的bean已经绑定了所有依赖关系。
- 入门案例分析:
(注意,这里的bookService还是在BookServiceImpl中主动newbookDao对象的。)
- 入门案例具体步骤:
DI依赖注入
核心概念
DI(Dependecncy Injection)在容器中建立bean与been之间的依赖关系的整个过程,称为依赖注入
代码实例
-
dao包
BookDao.java
public interface BookDao { public void save(); }
-
dao.impl
BookDaoImpl.java
public class BookDaoImpl implements BookDao { @Override public void save() { System.out.println("book dao save…………"); } }
-
service包
BookService
public interface BookService { public void save(); }
-
service.impl
BookServiceImpl
public class BookServiceImpl implements BookService { private BookDao bookDao; public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } @Override public void save() { System.out.println("BookService save ... "); bookDao.save(); } }
-
resources.applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="bookDao" class="com.catalpa.dao.impl.BookDaoImpl" /> <bean id="bookService" class="com.catalpa.service.impl.BookServiceImpl"> <property name="bookDao" ref="bookDao" /> </bean> </beans>
-
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>SpringDemo_1</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>12</maven.compiler.source> <maven.compiler.target>12</maven.compiler.target> </properties> <!-- 依赖 --> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> </dependencies> </project>
-
实现类
App.java
import com.catalpa.dao.BookDao; import com.catalpa.service.BookService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClasspathXmlApplicationContext; public class App { 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(); } }
-
实现效果
BookService save …
book dao save…………
book dao save…………
Bean配置
基础配置
别名配置
作用范围配置
bean作用范围说明:
-
为什么bean默认为单例?
IoC容器本身是为了管理那些需要大量重用的对象(也就是我们所说的bean),这些对象在使用过后便不再需要了。如果创建太多对象,对于这个容器来说管理起来也是一个很大的压力。对于这些重复的bean,采取单例便于管理。
-
适合交给容器进行管理的bean
- 表现层对象
- 业务层对象
- 数据层对象
- 工具对象
-
不适合交给容器进行管理的bean
- 封装实体的域对象
Bean实例化
实例化bean的三种方式:
- 构造方法
- 静态工厂
- 实例工厂
- factorybean(实用)第三种的改良—>第四种
构造方法(常用)
默认提供无参,当有其他构造方法生成时,就要你来手动编写无参构造方法了。
静态工厂(了解)
代码实现:
-
BookDaoFactory.java
public class BookDaoFactory { public static BookDao getBookDao(){ return new BookDaoImpl(); } }
-
applicationContext.xml(此处只给出新增的bean部分,其他和上面的基本配置一样)
<!-- 静态工厂--> <bean id="bookDao1" factory-method="getBookDao" class="com.catalpa.factory.BookDaoFactory" />
实例工厂(了解)
代码实现:
-
BookFactory2
public class BookDaoFactory2 { public BookDao getBookDao(){ return new BookDaoImpl(); } }
-
applicationContext.xml
<!-- 实例工厂--> <bean id="bookDaoFactory2" class="com.catalpa.factory.BookDaoFactory2"/> <bean id="bookDao2" factory-method="getBookDao" factory-bean="bookDaoFactory2" />
factorybean(实用)(第三种的改良)
Spring针对第三种存在以下的情况进行了改良。
代码实现:
-
BookDaofactorybean.java
public class BookDaofactorybean implements factorybean<BookDao> { //代替原始实例工厂中创建对象的方法 //创建BookDao对象 //此处的返回类型和返回的实例根据所需进行选择(BookDao、BookDaoImpl()),这时用bean生成的对象就不再是factory对象了,而是直接生成对应的BookDao对象了。 @Override public BookDao getobject() throws Exception { return new BookDaoImpl(); } //返回bean的类型 @Override public Class<?> getobjectType() { return BookDao.class; } // 判断bean的生成是否为单例 @Override public boolean isSingleton() { return factorybean.super.isSingleton(); } }
-
applicationContext.xml
<!-- factorybean来配置bean--> <bean id="bookDao3" class="com.catalpa.factory.BookDaofactorybean" />
对以上四种Bean实例化的方式,调用测试类进行实现,观察是否成功
package com.catalpa;
import com.catalpa.dao.BookDao;
import com.catalpa.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClasspathXmlApplicationContext;
import java.awt.print.Book;
public class App3 {
public static void main(String[] args) {
//加载配置文件得到上下文对象,也就是容器对象
ApplicationContext ctx = new ClasspathXmlApplicationContext("applicationContext.xml");
//获取资源
//构造方法
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
System.out.println(bookDao);
System.out.println("…………………构造方法…………………");
//静态工厂
BookDao bookDao1 = (BookDao) ctx.getBean("bookDao1");
bookDao1.save();
System.out.println(bookDao1);
System.out.println("………………静态工厂……………………");
//实例工厂
BookDao bookDao2 = (BookDao) ctx.getBean("bookDao2");
bookDao2.save();
System.out.println(bookDao2);
System.out.println("………………实例工厂……………………");
//factorybean来获取
BookDao bookDao3 = (BookDao) ctx.getBean("bookDao3");
bookDao3.save();
System.out.println(bookDao3);
System.out.println("………………factorybean来获取……………………");
}
}
实现结果:
book dao save…………
com.catalpa.dao.impl.BookDaoImpl@769e7ee8
…………………构造方法…………………
book dao save…………
com.catalpa.dao.impl.BookDaoImpl@5276e6b0
………………静态工厂……………………
book dao save…………
com.catalpa.dao.impl.BookDaoImpl@71b1176b
………………实例工厂……………………
book dao save…………
com.catalpa.dao.impl.BookDaoImpl@6193932a
………………factorybean来获取……………………Process finished with exit code 0
出现该结果也就是bean实例化成功了。
Bean生命周期
生命周期:从创建到消亡的完整过程
bean生命周期:bean从创建到销毁的整体过程
bean生命周期控制:在bean创建后到销毁前做一些事情
- 使用配置的方式进行生命周期控制
- 使用InitializingBean、disposableBean接口的方式进行生命周期控制
生命周期的过程
bean销毁时机
指的是容器关闭前触发bean的销毁
关闭容器的方式:
-
手工关闭容器
ConfigurableApplicationContext接口的close()操作
-
ConfigurableApplicationContext接口registerShutdownHook()操作
依赖注入
依赖注入的方式按以下划分:
- setter注入
- 简单类型
- 引用类型
- 构造器注入
- 简单类型
- 引用类型
setter注入
简单类型
用property标签 value属性, 注意简单类型属性定义好后要提供set方法。
引用类型
用property标签 ref属性, 注意引用类型属性定义好后要提供set方法。
setter注入: name名称要与setter的标准方法名相同(具体为除去set的前缀,且首字母忽略大小写的名称) ; value标注为简单类型注入, ref标注为引用类型注入; 需要设置响应的setter方法(setter命名格式要标准格式),并且要保证有一个无参构造方法,默认送的那个无参构造方法也行
构造器注入
简单类型
用constructor-arg标签 value属性, 注意普通类型属性定义好后要提供构造方法。
引用类型
用constructor-arg标签 ref属性, 注意引用类型属性定义好后要提供构造方法。
构造器注入:
同样value为简单类型注入,ref为引用类型注入,name写名字,要与构造器的形参名字保持一致,大小写也保持一致;
value标注为简单类型注入, ref标注为引用类型注入;
需要设置对应的构造器,标准的写法,个数和名称保持一致;
(注意!!!构造器的优先级高于setter注入,当两者共存,优先注入构造器,随后将缺少的属性进行setter注入)
构造器优先级高于setter!!!
另外,当采用构造器的方法时,由于标准写法中name参数的名称要与构造器中形参的名称完全一致,导致耦合度增加,故提供了以下的几种方式(这其实是提供兼容性的方法,还有其他各种方法,这些方法也是存在着一定的弊端的)
这两种方式可以混用,但是要保证有相应的构造器存在, 两者的个数之和要和与拥有构造器的形参个数一致,且类型也要保证对应
依赖注入方式选择
-
- 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现
-
- 可选依赖使用setter注入进行,灵活性强
-
- Spring框架倡导使用构造器,第三方框架内部多数采用构造器注入的形式进行数据初始化,相对比较严谨
-
- 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
-
- 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
-
- 自己开发的模块推荐使用setter注入
(总的来说,最主要的是看你所在的项目,需不需要严格把控数据注入,需要的话就用构造器注入,较之严谨安全;不需要的话用setter注入,较之灵活)
依赖自动装配
概念:
IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配
自动装配方式(autowire):
依赖自动装配特征
- 自动装配用于引用类型依赖注入,不能对简单类型进行操作(简单类型它也没啥好装的)
- 使用按类型装配时(byType)必须保证容器中相同类型的bean是唯一的,该方法推荐使用
- 使用按名称装配时(byName)必须保证容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
- 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效
集合注入
示例先上:
集合中也可以放bean(依赖)的,用在数据里面用<bean ref:“bean的名字”>
具体格式标准展示如下:
数组
List
Set
Map
Properties
加载properties文件
加载一个properties文件
加载多个properties文件
- context命名空间可以加参数 system-properties-mode="NEVER"来使其不加载系统属性,避免和系统属性重名。
- context命名空间中共 location参数,可以用逗号“,”来指定多个配置文件,也可以用“ *.properties ”来通配所有点配置文件(常用)。(只能读取本项目的,jar中的配置文件不能读取)
- 更标准的是将location参数设为 “classpath* : *.properties”,这样子不止能调用本项目的全部配置,jar中的配置也可以读取到。
知识点补充
创建容器的多种方式
-
方式一:类路径加载配置文件
ApplicationContext ctx = new ClasspathXmlApplicationContext("applicationContext.xml");
-
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\applicationContext.xml");
-
加载多个配置文件
ApplicationContext ctx = new ClasspathXmlApplicationContext("bean1.xml","bean2.xml");
获取bean的多种方式
-
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
-
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
-
方式三:使用bean类型获取
BookDao bookDao = ctx.getBean(BookDao.class);
容器类层次结构图
下面的初始化了解即可,需要注意的是这个类的bean是延迟加载的,前面用的ApplicationContext是立即加载的。
核心容器知识点总结
- 容器相关
-
bean相关
-
依赖注入相关
注解开发
(以下使用的是注解进行开发 Component)
注解开发的定义
利用注解开发来定义bean,可以省去配置开发中编写大量的配置。
可以使用@Component定义bean
@Component("bookDao")
public class BookDaoImpl implements BookDao{
}
@Component
public class BookServiceImpl implements BookService{
}
同时还需要在核心配置文件applicationContext.xml中,通过组件context扫描加载bean
<context:component-scan base-package="com.catalpa"/>
通过修改base-package的参数值,可以使得扫描相应路径下的注解。加载对应的bean。
此外Spring不止提供了@Component这一个通用型的注解,也提供了三个衍生注解:
- @Controller:用于表现层bean定义
- @Service:用于业务层bean定义
- @Repository:用于数据层bean定义
@Repository("bookDao")
public class BookDaoImpl implements bookDao{
}
@Service
public class BookServiceImpl implements BookService{
}
纯注解开发模式
Spring3.0升级了纯注解开发模式,对于定义中所说的要在核心配置文件中定义扫描,现在3.0后可以用java类替代配置文件,开启了Spring快速开发赛道。
下面来学习这一方面的内容,借助java类来实现纯注解开发,也将引入更多的注解方式。
@Configuration
@ComponentScan("com.catalpa")
public class SpringConfig{
}
添加多个数据扫描路径
@Configuration
@ComponentScan({"com.catalpa.dao","com.catalpa.service"})
public class SpringConfig{
}
在读取Spring核心配置文件初始化容器也有了所变化,要切换成读取Java配置类初始化容器对象
//加载配置文件初始化容器
ApplicationContext ctx = new ClasspathXmlApplicationContext("applicationContext.xml");
//加载配置类初始化容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
bean作用范围与生命周期管理
-
bean的作用范围由@Scope管理
参数值:
- singleton(单例)
- prototype(非单例)
-
bean的声明周期可由以下注解管理
- @postconstruct 该注解声明的是生命周期的初始化。
- @PreDestroy 该注解声明的是生命周期的销毁
@Repository
@Scope("singleton")
public class BookDaoImpl implements BookDao{
public BookDaoImpl(){
System.out.println("book dao constructor ...");
}
@postconstruct
public void init(){
System.out.println("book init ...");
}
@PreDestroy
public void destroy(){
System.out.println("book destroy");
}
}
依赖注入
自动装配
-
引用类型的装配
- 用@Autowired注解来开启自动装配模式(按类型)
(setter可以省略不写)
@Service public class BookServiceImpl implements BookService{ @Autowired private BookDao bookDao; /* public void setBookDao(BookDao bookDao){ this.bookDao = bookDao; }*/ public void save(){ System.out.println("book service save..."); bookDao.save(); } }
注意:
1.自动装配是基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据的,因此无需提供setter方法
2.自动装配建议使用无参构造方法创建对象(默认),如果不提供对应构造方法,请提供唯一的构造方法。
如果存在多个同类型的bean(BookDaoImpl, BookDaoImpl2),自动装配按照类型装配将无法确定目标(NoUniqueBeanDeFinitionException),此时可以给两个bean的注解设置不同的名称(@Repository(“bookDao”) ),从而来对应注入的数据,当注解的名字与变量名相同时,就自动装配上。
但是,我们更推荐采用的是以下的注解@Qualifier
该注解无法单独使用,需要先声明@Autowired注解,与其配合使用。
@Qualifier(“bookDao”),写明要装配的bean的注解名称。
@Service public class BookServiceImpl implements BookService{ @Autowired @Qualifier("bookDao") private BookDao bookDao; }
@Repository("bookDao") public class BookDaoImpl implements BookDao{ public void save(){ System.out.println("book dao save ..."); } }
-
简单类型的装配
使用注解**@Value**实现简单类型注入
@Repository("bookDao") public class BookDaoImpl implements BookDao{ @Value("100") private String connectionNum; }
解释: 为何不直接给变量赋值,因为我们在使用的时候,有时候不一定是直接在这里写数据,有时候是通过properties文件,配置后,可以用${}从中读取数据。
如何配置properties文件呢,如下!
读取properties文件
-
使用@PropertySource注解来加载properties文件。
在Java配置类(比如:SpringConfig.java)中进行如下注解编写。
@Configuration @ComponentScan("com.catalpa") @PropertySource("classpath:jdbc.properties") public class SpringConfig{ }
注意:该形式路径仅支持单一文件配置,若多文件,需要使用数组格式配置,不允许使用通配符*
@PropertySource({"classpath:jdbc.properties","jdbc2.properties","jdbc3.properties"})
相信你们也注意到了,classpath: 是可以省略不写的。
第三方bean
如何管理
-
使用@Bean配置第三方bean。(在核心配置类上)
在配置类"SpringConfig.java"中,为需要的bean进行配置(前提是要先将相应的类导入,相应的包用pom.xml从仓库下载下来)
@Configuration public class SpringConfig{ //1.定义一个方法获得管理的对象 //2.添加@Bean注解,表示当前的返回值是一个bean @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName("com.MysqL.jdbc.Driver"); ds.setUrl("jdbc:MysqL://localhost:3306/spring_db"); ds.setUsername("root"); ds.setPassword("123"); return ds; } }
通过创建AnnotationConfigApplicationContext对象就可以进行调用该配置的bean了。
public class App{ public static void main(String[] args){ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); DataSource dataSource = ctx.getBean(DataSource.class); System.out.println(dataSource); } }
-
使用独立的配置类管理第三方bean
为了避免核心配置类在加多第三方bean后过于冗杂,我们将重新建一个独立的配置类来进行管理第三方bean,事实上我们也是这样子做的,将不同类型的bean进行划分管理,有序进行。
public class JdbcConfig{ @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName("com.MysqL.jdbc.Driver"); ds.setUrl("jdbc:MysqL://localhost:3306/spring_db"); ds.setUsername("root"); ds.setPassword("123"); return ds; } }
重点来了,我们如何将这些独立的配置类加入核心配置呢,我们主要有如下两个方案来执行!
将独立的配置类加入核心配置。
-
方式一:导入式(常用)
使用该方式便于我们观察哪一个类导进去使用。
public class JdbcConfig{ @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName("com.MysqL.jdbc.Driver"); ds.setUrl("jdbc:MysqL://localhost:3306/spring_db"); ds.setUsername("root"); ds.setPassword("123"); return ds; } }
使用@Import注解的方式,手动来加入配置类到核心配置中。
又由于此注解只能添加一次,所有多个数据需要用到数组格式。@Import({JdbcConfig.class})
@Configuration @Import(JdbcConfig.class) public class SpringConfig{ }
-
方式二:扫描式
在使用前需要对独立的配置类加入@Configuration注解,使其能够被扫描到。
package com.catalpa.config; @Configuration public class JdbcConfig{ @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName("com.MysqL.jdbc.Driver"); ds.setUrl("jdbc:MysqL://localhost:3306/spring_db"); ds.setUsername("root"); ds.setPassword("123"); return ds; } }
在核心配置类处使用@ComponentScan注解来扫描配置类所在的包,加载对应的配置类信息,获取到所需的Bean等
@Configuration @ComponentScan({"com.catalpa.config","com.catalpa.service","com.catalpa.dao"}) public class SpringConfig{ }
@ComponentScan也是用数据格式来导入多个数据的。~
-
如何依赖注入
和使用配置一样,和其他非第三方bean一样,第三方bean在了解依赖注入的时候,也是分为引用类型和简单类型两种来看。
-
引用类型依赖注入(方法形参)
引用类型注入只需要为bean定义方法,设置形参即可,容器会根据类型自动装配对象。(由于是根据类型去装配的。所以该类型必须是唯一的,也是存在的)
public class JdbcConfig{ // 此处以DataSource对象下,装配BookService对象 @Bean public DataSource dataSource(BookService bookService){ System.out.println(bookService); DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName("com.MysqL.jdbc.Driver"); ds.setUrl("jdbc:MysqL://localhost:3306/spring_db"); ds.setUsername("root"); ds.setPassword("123"); return ds; } }
-
简单类型依赖注入(成员变量)
简单类型的注入通过创变量的形式来获取,通过@Value注解来获取(包括properties文件的数据)
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("123") 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; } }
XML配置与注解配置比较
如图所示做对比,标红的注解为常用的注解。
★Spring整合Mybatis
首先先对Mybatis重新进行一个回顾,对Mybatis程序核心对象进行一个分析
另外使用AccountDao运用自动代理来执行sql语句获取数据。
大概这些是Mybatis需要注意的点。后面我们通过实例来了解Spring整合Mybatis的情况。
- 首先展示文件的目录结构
-
在开始编写之间,先要对pom.xml进行设置依赖,用Maven下载我们所需要的包,注意,有一些是spring特有的依赖,需要注意版本号的对应。
(此处不将所有依赖都写出来,只写出特殊需要的部分,其他的和使用Mybatis时一致)
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency>
-
依赖导入完成后,我们对AcccountDao进行编写,利用注解来完成sql语句的实现,实现对数据的增删改查操作。
public interface AccountDao{ @Insert("insert into tb1_account(name,money)values(#{name},#{money})") void save(Account account); @Delete("delete from tb1_account where id = #{id}") void delete(Integer id); @Update("Update tb1_account set name = #{name}, money = #{money} where id = #{id}") void update(Account account); @Select("select * from tb1_account") List<Account> findAll(); @Select("select * from tb1_account where id = #{id}") Account findById(Integer id); }
-
服务实现类AccountServiceImpl.java
@Service public class AccountServiceImpl implements AccountService{ @Autowired private AccountDao accountDao; public void save(Account account) { accountDao.save(account);} public void update(Account account) { accountDao.update(account);} public void delete(Integer id) { accountDao.delete(id);} public Account findById(Integer id) { return accountDao.findById(id);} public List<Account> findAll() { return accountDao.findAll();} }
-
完成核心配置类SpringConfig.java
@Configuration @ComponentScan("com.itheima") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) public class SpringConfig{ }
-
完成JDBC配置类
public class JdbcConfig{ @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String userName; @Value("${jdbc.password}") private String password; @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; } }
-
完成Mybatis配置类
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"); } }
(整合Mybatis最主要的就是编写这两个bean -> sqlSessionfactorybean 和 MapperScannerConfigurer)
-
测试类
public class App2{ public static void main(String[] args){ ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); AccountService accountService = ctx.getBean(AccountService.class); Account ac = accountService.findById(1); System.out.println(ac); } }
以上便是对Mybatis的整合,此处也只给出来核心的地方,其余的和mybatis基本类似
整合过程的区别图
<br>
Spring整合JUnit
-
首先导入依赖 pom.xml
<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>
-
在test.com.itheima.service中创建测试类AccountServiceTest.java
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class AccountServiceTest{ @Autowired private AccountService accountService; @Test public void testFindById(){ System.out.println(accountService.findById(2)); } }
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)这两句注解是关键,@RunWith用于指定专用类运行器
@ContextConfiguration 用于指定Spring的运行环境,给出上下文中的配置类,让我们可以找到bean。
以上便是对JUnit的整合
AOP面向切面编程
AOP概念与作用
AOP(Aspect Oriented Programming)面向切面编程,是一种编程范式,指导开发者如何组织程序结构。
AOP核心概念
-
连接点(JoinPoint): 在程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等。(代表所有的方法)
在SpringAOP中,理解为方法的执行
-
切入点(pointcut) : 匹配连接点的式子。(代表要追加功能的方法)
在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法。
- 一个具体方法 : com.catalpa.dao包下的BookDao接口中的无形参无返回值的save方法
- 匹配多个方法:所有save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法
(tips: 连接点包含切入点)
-
切面(Aspect):描述通知与切入点的对应关系
AOP入门案例
主要步骤如下:
- 导入AOP相关坐标
- 定义dao接口和实现
- 定义通知类,制作通知
- 定义切入点
- 绑定切入点和通知关系(切面),并指定通知添加到原始连接点的具体执行位置
- 定义通知类受Sping容器管理,并定义当前类为切面类
- 开启Spring对AOP注解驱动支持
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>springDemo_3</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>12</maven.compiler.source>
<maven.compiler.target>12</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
</project>
BookDao.java
package com.catalpa.dao;
import org.springframework.stereotype.Component;
public interface BookDao {
public void save();
public void update();
}
BookDaoImpl.java
package com.catalpa.dao.impl;
import com.catalpa.dao.BookDao;
import org.springframework.stereotype.Repository;
@Repository
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println(System.currentTimeMillis());
System.out.println("book dao save ....");
}
@Override
public void update() {
System.out.println("book dao update ....");
}
}
MyAdvice.java
package com.catalpa.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.pointcut;
import org.springframework.stereotype.Component;
@Component //声明成一个Bean
@Aspect //告诉Spring,声明是一个AOP
public class MyAdvice {
//@pointcut 定义从哪里开始切入的,execution表示执行的时候切入,在小括号内写(返回类型 切入的方法)中间用空格隔开, 切入点方法 --> 从java目录下开始,具体到方法名,要加上括号
@pointcut("execution(void com.catalpa.dao.impl.BookDaoImpl.update())")
public void pt(){} //定义切入点
//@Before 将通知和切入点关联起来,表示该通知在某方法之前执行, 写方法名要将()也写进去。
@Before("pt()")
public void method(){
//定义通知
System.out.println(System.currentTimeMillis());
}
}
SpringConfig.java
package com.catalpa.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoproxy;
@Configuration
@ComponentScan("com.catalpa")
@EnableAspectJAutoproxy
public class SpringConfig {
}
测试:App1.java
import com.catalpa.config.SpringConfig;
import com.catalpa.dao.BookDao;
import com.catalpa.dao.impl.BookDaoImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class App1 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
bookDao.save();
bookDao.update();
System.out.println(bookDao);
}
}
AOP工作流程
- spring容器启动
- 读取所有切面配置中的切入点
- 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
- 匹配失败,直接创建bean对象
- 匹配成功,创建原始对象(目标对象)的代理对象
- 获取bean执行方法
AOP核心概念
SpringAOP本质: 是代理模式
AOP切入点表达式
-
切入点:要进行增强的方法
-
切入点表达式: 要进行增强的方法的描述方式
举例:
描述方法一、
执行com.catalpa.dao包下的BookDao接口中的无参数update方法
execution(void com.catalpa.dao.BookDao.update())
描述方法二、
执行com.catalpa.dao.impl包下的BookDaoImpl类中的无参数update方法
execution(void com.catalpa.dao.impl.BookDaoImpl.update())
-
切入点表达式的动作关键字
主要由以下这五种类型:
-
切入点表达式标准格式(execution):动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)
- 动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点
- 访问修饰符:public,private等,可以省略
- 返回值
- 包名
- 类/接口名
- 方法名
- 参数
- 异常名:方法定义中抛出指定异常,可以省略
-
-
*:单个独立的任意符号,可以独立出现,也可以作为前缀或后缀的匹配符出现
execution(public * com.catalpa.*.UserService.find*(*))
该句意思是匹配com.catalpa包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
-
… : 多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
execution(public User com..UserService.findById(..))
-
+: 专用于匹配子类类型
execution(* *..*Service+.*(..))
-
-
书写技巧
- 所有代码按照标准规范开发,否则以下技巧全部失效
- 描述切入点通常描述接口,而不描述实现类
- 访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述)
- 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述
- 包名书写尽量不使用…匹配,效率过低,常用*做单个包描述匹配或精准匹配
- 接口名/类名书写名称与模块相关的采用*匹配,例如UserService书写成*Service,绑定业务层接口名。
- 方法名书写以动词进行精准匹配,名词采用*匹配,例如getById书写成getBy*,selectAll已经匹配到最省略了,直接书写成selectAll即可。
- 参数规则较为复杂,根据业务方法灵活调整
- 通常不使用异常作为匹配规则
练习举例子:
@Component //声明成一个Bean
@Aspect //告诉Spring,声明是一个AOP
public class MyAdvice {
//@pointcut 定义从哪里开始切入的,execution表示执行的时候切入,在小括号内写(返回类型 切入的方法)中间用空格隔开, 切入点方法 --> 从java目录下开始,具体到方法名,要加上括号
// @pointcut("execution(void com.catalpa.dao.impl.BookDaoImpl.update())")
// @pointcut("execution(void com.catalpa.dao.BookDao.update())")
// @pointcut("execution(void com..BookDao.update(..))")
// @pointcut("execution(void com..update(..))")
// @pointcut("execution(void com.*.*.BookDao*.*(..))")
// @pointcut("execution(void com.*.*.*Dao*.*(..))")
@pointcut("execution(* com.*.*.*.BookDao*.*(..))")
// @pointcut("execution(* com.*.*.BookDao.*(..))")
// @pointcut("execution(* com.*.*.BookDao+.*(..))")
// @pointcut("within(com.catalpa.dao.impl.BookDaoImpl)")
public void pt(){} //定义切入点
//@Before 将通知和切入点关联起来,表示该通知在某方法之前执行, 写方法名要将()也写进去。
// @Before("pt()")
// public void method(){
// //定义通知
// System.out.println(System.currentTimeMillis());
// }
// @Before("MyAdvice.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("around advice >>>");
// Object ref = pjp.proceed();
// System.out.println("around over >>>");
// return ref;
// }
//
// @AfterReturning("pt()")
// public void afterReturning(){
// System.out.println("afterReturning advice ...");
// }
//
// @AfterThrowing("pt()")
// public void afterThrowing(){
// System.out.println("afterThrowing advice ...");
// }
@Before("MyAdvice.pt()")
public void before(JoinPoint jp){
Object[] args = jp.getArgs();
System.out.println("before advice ..." + Arrays.toString(args)); // Arrays.toString(args)
}
@Before("MyAdvice.pt()")
public void aefore(JoinPoint jp){
Object[] args = jp.getArgs();
System.out.println("aefore advice ..." + Arrays.toString(args)); // Arrays.toString(args)
}
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around advice >>>");
Object ref = pjp.proceed();
Object[] args = pjp.getArgs();
System.out.println("around over >>>" + Arrays.toString(args));
return ref;
}
@AfterReturning(value="pt()",returning = "ret")
public void afterReturning(Object ret){
System.out.println("afterReturning advice ... " + ret);
}
@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(JoinPoint jp,Throwable t){
System.out.println("afterThrowing advice ..." + t);
}
}
AOP通知类型
AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时,要将其加入到合理的位置。
那么位置怎么选呢? 这个时候就要来讲AOP通知的不同类型了
AOP通知共分为5种类型:
具体描述:
-
前置通知
名称:@Before
类型:方法注解
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行。
范例:
//@Before("MyAdvice.pt()") @Before("pt()") public void before(){ System.out.println("before advice ..."); }
相关属性:value(默认):切入点方法名,格式为 类名.方法名()
-
后置通知
名称:@After
类型:方法注解
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行(不管异常,必定运行)
范例:
@After("pt()") public void after(){ System.out.println("after advice ..."); }
相关属性:value(默认):切入点方法名,格式为 类名.方法名()
-
环绕通知(重点,常用)
名称:@Around
类型:方法注解
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
范例:(注意原始操作的调用)
@Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("around before advice ..."); //调用原始操作方法,并执行取回返回值 Object ret = pjp.proceed(); System.out.println("around after advice ..."); return ret; }
相关属性:value(默认):切入点方法名,格式为 类名.方法名()
注意事项:@Around
- ① 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现在原始方法调用的周围添加通知,实现被通知环绕。
- ② 通知中如果未使用ProceedingJoinPoint对原始方法进行调用,讲跳过原始方法的执行
- ③ 对原始方法的调用可以不接受返回值,通知方法设置成void即可,如果接收返回值,必须设定为Object类型(因为不确定返回的是什么值,用Object,包罗一切!)
- ④ 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object(返回类型是void, 返回值为null)
- ⑤ 由于无法预知原始方法运行是否会抛出异常,因此环绕通知方法必须抛出Throwable对象
-
返回后通知(了解)
名称:@AfterReturning
类型:方法注解
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法正常执行完毕后运行
范例:
@AfterReturning("pt()") public void afterReturning(){ System.out.println("afterReturning advice ..."); }
相关属性:value(默认):切入点方法名,格式为 类名.方法名()
-
抛出异常后通知(了解)
名称:@AfterThrowing
类型:方法注解
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行
范例:
@AfterThrowing("pt()") public void afterThrowing(){ System.out.println("afterThrowing advice ..."); }
相关属性:value(默认):切入点方法名,格式为 类名.方法名()
案例
此处不解释了,就直接放图。
AOP通知获取数据
返回后,运用returning参数来获取返回值
JoinPoint jp,再写Throwable t。
案例
Spring事务
事务简介
提供的一个接口
public interface PlatformTransactionManager{
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
事务使用实例
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
事务角色
事务属性
事务注解是可以进行增加配置的,以下是可加在@Transactional注解上的属性。
其中rollbackFor需要注意的是,只有遇到了内存溢出和运行时异常之类的异常才会进行回滚,否则不会回滚,所以我们需要通过这个属性,来设置需要进行回滚的异常。
- 事务传播行为:事务协调员对事务管理员所携带事务的处理态度。
常用的六种事务传播行为:
案例
总结
OVER!~
以上就是学习SpringFramework过程中所记的知识点,over!
就这样,老规矩,不秃头 日志,以上为个人笔记,也作为经验分享,大家可以参考使用,有问题也可以提出。