Spring
Spring framework体系结构
Core Container ≈ IOC
AOP整行 ≈ AOP
Spring优点
1.方便解耦,简化开发
通过 Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可 以更专注于上层的应用。
2.AOP编程的支持
通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过 AOP 轻松应付。
3.声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
4.方便程序的测试Junit
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
5.方便集成各种优秀框架
Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。
6.通过封装简化原生JavaEE API的调用过程
Spring对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的 使用难度大为降低。
7.Java源码的经典学习范例
Spring的源代码有着对Java设计模式灵活运用以及对 Java技术的高深造诣
类与类耦合解耦
* 程序的耦合
* 耦合:程序间的依赖关系
* 包括:
* 类之间的依赖
* 方法间的依赖
* 解耦:
* 降低程序间的依赖关系
* 实际开发中:
* 应该做到:编译期不依赖,运行时才依赖。
* 解耦的思路:
* 第一步:使用反射(映射)class.forName().newInstance来创建对象,而避免使用new关键字。
* 第二步:通过读取配置文件来获取要创建的对象全限定类名
* DriverManager.registerDriver(new com.mysql.jdbc.Driver()); //new 类依赖【编译不会通过】
* Class.forName("com.mysql.jdbc.Driver"); //通过字符串获取类【编译可通过】
常见场景里:dao层处理4+(增删改查)功能函数,service层处理具体业务逻辑,生成dao完全实现类组合调用dao层4+函数,也就是
dao1 dao2 dao3 dao4
| | | |
\ \ / /
service1
那么这里就出现了第一层耦合,通过new dao层的完全实现类,调用该类方法。使service层实现类与dao层实现类 强耦合。
再往实际应用中思考,那么展示层view层/实体层,也会调用多个service层实现类,以满足实际场景中的客户需求。那么这层层调用
就会产生大量耦合,对于验证 代码的编译错误 是非常浪费精力的及困难的。
解耦合目标是可通过编译,而非通过运行。
####通过工厂模式Factory创建Bean对象(第一版本)
Static静态代码块一次性全部装载至内存(基本依赖类全路径配置文件,无依赖类属性注入)
getBean函数通过Property.getProperty(name)映射函数获取依赖类全路径,再通过Class.forName()实现依赖注册
public class BeanFactory {
private static Properties props;
static {
try {
props = new Properties();
InputStream in = BeanFactory.class.getDeclaringClass().getResourceAsStream("bean.properties");
props.load(in);
}catch (Exception e){
throw new ExceptionInInitializerError("初始化失败");
}
}
/**
* 根据Bean复用组件的名称获取bean对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
Object bean = null;
try {
String beanPath = props.getProperty(beanName);
System.out.println(beanPath);
bean = Class.forName(beanPath).newInstance();//每次都会调用默认构造函数创建对象
}catch (Exception e){
e.printStackTrace();
}
return bean;
}
}
通过上面的BeanFactory类,就可以替换调用Dao层的new方法
private IAccountDao accountDao = new AccountDaoImpl();
|||
private IAccountDao accountDao = (IAccountDao)BeanFactory.getBean("accountDao");
通过工厂模式Factory(第二版本)
Static静态代码块一次全部装载,但每一个装载的依赖类使用Class.forName装入Map容器【Spring core container核心容器的雏形】
getBean函数直接调用Map.get(Name)即可查找Map中存储的Bean对象。
并且可对Bean对象进行分类,对于一个对象就可解决多次服务的,使用OneInstance只生成一次,并将对象地址存入Map
之后,对于该类Bean对象直接将地址传出,不需要再new新的对象
第二版本主要是,将Service层对象,以及Dao层对象从newInstance多个对象变成OneInstance同一个对象。
这样减少了new新对象的内存损耗,以及速度,直接根据class Name从Map<String,Object>读取对象即可。
但此时也会出现一个小问题,即是类数据成员会不断被操作因为共享内存的原因,所以根据需求,选择变量定义位置。
Service层、Dao层类很少定义类数据成员。
public class BeanFactory {
private static Properties props;
//定义一个Map,用于存放我们要创建的对象。我们把它称之为容器
private static Map<String,Object> beans;
//使用静态代码块为Properties对象赋值
static {
try {
//实例化对象
props = new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
//实例化容器
beans = new HashMap<String,Object>();
//取出配置文件中所有的Key
Enumeration keys = props.keys();
//遍历枚举
while (keys.hasMoreElements()){
//取出每个Key,依赖类别名/缩写
String key = keys.nextElement().toString();
//根据key获取value,全类名路径
String beanPath = props.getProperty(key);
//反射创建对象
Object value = Class.forName(beanPath).newInstance();
//把key和value存入容器中
beans.put(key,value);
}
}catch(Exception e){
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
/**
* 根据bean的名称获取对象,直接通过类名即可调用
* @param beanName
* @return
*/
public static Object getBean(String beanName){
return beans.get(beanName);
}
}
Spring工厂功能实现:IOC(控制反转)
中文来源:创建其他类的权利交付于框架,而不自己控制创建其他类实例。
组成:
1.Dependency Injection依赖注入(依赖关系维护(维护beans.xml配置文件))
2.DL依赖查找(通过名称查找类配置文件内对应的完整类路径名称)
作用:削减计算机程序的耦合(解除代码中的编译关系)
Spring IOC实践
1.配置beans.xml(spring的约束)
标签:<bean id="" class=""></bean>
样例:<bean id="accoutDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean>
2.获取spring的Ioc核心容器,并根据id获取bean对象(两种接口)
第一种 ApplicationContext接口
* ApplicationContext的三个常用实现类:
* 1.ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。(更常用)
* 2.FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
*
* 3.AnnotationConfigApplicationContext:它是用于读取注解创建容器的,是明天的内容。
实例
ClassPathXml:
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
FileSystemXml:
ApplicationContext ac = new FileSystemXmlApplicationContext("C:\\Users\\zhy\\Desktop\\bean.xml");
第二种 BeanFactory接口
二种接口区别
* BeanFactory: 多例对象使用
* 它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。
* ApplicationContext: 单例对象适用
* 它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。
*
实例
Resource resource = new ClassPathResource("bean.xml");
BeanFactory factory = new XmlBeanFactory(resource);
IAccountService as = (IAccountService)factory.getBean("accountService");
但ApplicationContext接口更加智能,识别标签内的scope属性来采用不同策略
【singleton单例,ApplicationContext容器创建,则beann对象创建】
【prototype多例,只有当bean对象被调用的时候,才创建】
故采用ApplicationContext接口
IOC对bean对象的管理细节
1.Spring框架创建bean的三种方式
2.bean对象的作用范围
3.bean对象的生命周期
1、创建bean
创建Bean的三种方式
第一种方式:使用默认构造函数【空构造函数,对于非空构造函数创建bean,请参考下面的依赖注入】创建。
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。
采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
案例:<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
第二种方式: 使用普通工厂类中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
错误示范:<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
解析:我们要获取的不是InstanceFactory工厂类,而是其方法(getAccountService)生成的AccountService类。
正确示范:<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
案例: <bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>
2.bean作用范围
bean标签的scope属性
作用:用于指定bean的作用范围
取值: 常用的就是单例和多例
singleton:单例的(默认值)
prototype:多例的(工厂生成不同对象,不再是只使用一个公共对象。)
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
案例:<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype"></bean>
关于global-Session的解释
3.bean对象的生命周期
单例对象
出生:当容器创建时对象出生
活着:只要容器还在,对象一直活着
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同
多例对象
出生:当我们使用对象时spring框架为我们创建
活着:对象只要是在使用过程中就一直活着。
死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
指定bean对象的初始化销毁函数:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
scope="prototype" init-method="init" destroy-method="destroy"></bean>
IOC的Dependency Injection
依赖注入:
能注入的数据:有三类
基本类型和String
其他bean类型(在配置文件中或者注解配置过的bean)
复杂类型/集合类型【采用不同标签/No<Bean/>,但注入方式还是一样的】
注入的方式:有三种
第一种:使用构造函数提供
第二种:使用set方法提供
第三种:使用注解提供(明天的内容)
第一种:使用Bean类的有参构造函数为Bean注入属性
使用的标签:<constructor-arg></constructor-arg>
bean标签的子标签
constructor-arg标签中的属性:
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始
name:用于指定给构造函数中指定名称的参数赋值 常用的
=============以上三个用于指定给构造函数中哪个参数赋值===============================
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的是在spring的Ioc核心容器中使用<bean>标签注册过的bean对象
优势:在获取bean对象时,注入的数据必须完整,否则对象无法创建成功。
弊端:改变了bean对象的实例化方式,【定死了构造所需参数数目,牺牲了实例化的灵活性】
实例:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="泰斯特"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<!-- 配置一个日期对象 -->
<bean id="now" class="java.util.Date"></bean>
<!--
date日期bean对象没有依赖注入,所以使用的是默认构造器,默认构造器采用的是
public Date() {
this(System.currentTimeMillis());
}
-->
第二种:使用Bean类的set方法为Bean注入属性【更常用】
使用的标签:<property></property>
bean标签的子标签
property标签中的属性:
name:用于指定注入时所调用的set方法名称【去掉set并且将第一个字母小写例如 setBirthday=>name="birthday"】
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
优势:创建对象时没有明确的限制,可以直接使用默认构造函数
弊端:如果有某个成员必须有值,则获取对象是有可能set方法没有执行。
实例
<bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2">
<property name="name" value="TEST" ></property>
<property name="age" value="21"></property>
<property name="birthday" ref="now"></property>
</bean>
为Bean对象复杂类型属性赋值(需要set方法)
使用标签:<set || map...>
,constructor-arg与property的子标签
内含标签:<value></value>
复杂类型的注入/集合类型的注入
用于给List结构集合注入的标签:
list array set
用于个Map结构集合注入的标签:
map props
结构相同,标签可以互换
实例
<bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3">
<!-- 数组、集合类型 -->
<property name="myStrs">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="myList">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="mySet">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<!-- Map结构 -->
<property name="myMap">
<props>
<prop key="testC">ccc</prop>
<prop key="testD">ddd</prop>
</props>
</property>
<property name="myProps">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB">
<value>BBB</value>
</entry>
</map>
</property>
</bean>
第三种使用p名称空间注入数据(本质还是调用 set 方法)
…有時間再補。咳咳
使用注解完成Spring Bean对象管理,非XML配置
一般xml配置如下:
<bean id="唯一标识符" class="全路径类名"
scope="作用范围" init-method="生命周期行为" destroy-method="生命周期行为">
<constructor-arg name="构造函数依赖注入" value="" | ref=""></constructor-arg>
<property name="set方法依赖注入" value="" | ref=""></property>
</bean>
当使用注解注入的时候,完全实现类中依赖类对象的Set方法就不再是必要的了。
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
以及,如果使用注解需要在beans.xml中告知Spring扫描包
<context:component-scan base-package="com.itheima"></context:component-scan>
最后,使用@Component及其派生注解,注册Bean对象放入Spring Core Container
Bean对象的依赖类数据成员由@Autowired告诉Spring这个数据成员要用其他bean对象(一般来说是一个接口)注入
如果想指定到底用这个接口的哪个完全实现类的默认构造函数实现注入,@Autowired+@Qualifier(value = “实现类BeanID”)
如果想通过非默认(即是通过有参)构造函数实现注入,对这个依赖类在Bean.xml通过<bean/>的<constructor-arg>
参数标签来实现
有参构造注入【也就是这里xml与注解混合使用】,为什么要混用,可能是因为:你修改不了依赖类(其他官方jar包)
如果想要完全实现注解,无xml,使用四个重点注解+Spring新注解
四个重点@注解:
[bean]创建bean对象 ==> @Component
[property/constructor-arg]依赖注入 ==> @Autowired
[scope]作用范围 ==> @Scope
[init/destory]生命周期 ==> @PostConstruct @PreDestroy
1.使用@Component及其衍生注解创建对象
* 创建对象
* Component(基本父类):
* 作用:用于把当前类对象存入spring容器中
* 属性:
* value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。
* 实例:@Component(value="accountS")//@Component("accountS")
* 以下三个注解他们的作用和属性与Component是一模一样,只是人为定义为三层注解,使用清晰方便。
* Controller:一般用在表现层
* Service:一般用在业务层
* Repository:一般用在持久层
2.使用@Autowired等对依赖类数据成员注入
* 一.只能注入其他bean类型
* Autowired:
* 作用:自动按照类型注入类数据成员。
* 如果ioc容器中有且只有一个接口类型的完全实现类,那么注入的变量类型为该接口类型,则自动匹配该完全实现类
* 如果Ioc容器中有多个同一个接口的完全实现类时:
1.一个接口有多个实现类,那么会根据注入的变量名称来查找是否有匹配的完全实现类名
2.如果没有与变量名称完全匹配的完全实现类名,就会报错"匹配到多个Bean容器"。
* 注解出现位置:
* 可以是数据成员变量上,也可以是方法上??
* 细节:
* 在使用注解注入时,set方法就不是必须的。
* Qualifier:
* 作用:在Autowired类中注入的基础之上再按照实现类名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以
* 属性:
* value:用于指定注入实现某接口多个bean类的id。
* Autowired与Qualifier配合实例:
@Autowired
@Qualifier(value = "IAccountDaoImp1")
private IAccountDao accountDao;
//指定IAccountDao接口的完全实现类Imp1作为下面IAccountDao(数据成员)的注入
Resource:
* 作用:直接按照bean的id注入。它可以独立使用
* 属性:
* name:用于指定bean的id。
* 以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。
* 另外,集合类型的注入只能通过XML来实现。
* 二.注入基本类型及String类型
Value
* 作用:用于注入基本类型和String类型的数据
* 属性:
* value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)
* SpEL的写法:${表达式}
3.使用@Scope改变Bean对象的作用范围
* Scope:
* 作用:用于指定bean的作用范围
* 属性:
* value:指定范围的取值。常用取值:singleton prototype
* 实例:
@Scope("prototype")
//每次请求,都会产生一个新的bean实例
* 注意:不写@Scope即是单例singleton一次只创建一个对象,也就是只有一个实例
4.使用@PostConstruct及@PreDestroy指定初始化及销毁方法
* @PreDestroy
* 作用:用于指定销毁方法
* @PostConstruct
* 作用:用于指定初始化方法
位置:写在依赖类的对应方法上
注意:如果是多例prototype对象,Spring不负责其销毁,无法在Core Bean容器回收前执行其存有的Bean类销毁方法。
Spring新注解
1.@ComponentScan("com.itheima")
包扫描
作用:<context:component-scan base-package=""></context:component-scan>
实例:@ComponentScan({"com.itheima","config"})
可扫多个使用花括号{}。
2.@Configuration
标明此类是Spring的注解配置实体类
作用:替代XML文件
注意:当配置类作为AnnotationConfigApplicationContext(xxx.class)创建的参数时,该注解可以不写。
3.@Bean(name="")
依赖类注册
作用:替代bean标签。【注意:默认单例,多例需要@Scope(“prototype”)】
name:用于指定bean的id。当不写时,默认值是当前方法的名称
案例:
@Bean(name="runner")
public QueryRunner createQueryRunner(dataSource dataSource){
return new QueryRunner(dataSource);
}
@Bean(name="dataSource")
public DataSource createDataSource(){
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setxxx();
return ds;
}
当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有同类型的bean对象。查找的方式和@Autowired注解是一样的。
以上三个注解,已经能实现XML所有功能。怎么让测试类读取注解配置,而非XML配置
在测试类中选择ApplicationContext容器生成方法
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
XML配置读取为ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
4.@Import(xxx.class)
引入其他类【例如JdbcConfig.class(注册runner类,注册dataSource数据源对象)】
5.@PropertySource("xxx.properties")
引入property配置文件内容【jdbcConfig.properties(jdbc.driver/url/un/pw)】
* Import
* 属性:
* value:用于指定其他配置类的字节码。
* 当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类
* PropertySource
* 属性:
* value:指定文件的名称和路径。
* 关键字:classpath,表示类路径下
以下三种方法均可引用多个注解配置文件
主注解配置文件 @Import其他 + @PropertySource其他 ==
@Configuration其他 + @ComponentScan其他所在包 ==
new AnnotationConfigApplicationContext(主注解配置文件.class,其他注解配置文件.class);
一般Spring IOC操作步骤
-
配置Maven pom.xml文件
插入spring-context坐标,引入springframework包
【也可按需求引入其他:数据库连接,连接池,测试单元】 -
书写2层接口(Service层,Dao层)
Service主要是根据业务逻辑,将Dao层基本函数进行排列组合,以及数据集处理等
Dao主要是实现增删改查针对一个特定表。并且可引入Mybatis进行解耦合 -
创建数据库表类即POJO对象
Service和dao都需要一个数据库表类对象用于承载数据库表数据,以及通过这个POJO传递数据【实现序列化接口Serializable】 -
创建Service、Dao层完全实现类
Service完全实现类要使用Dao层对象,故,需要加入私有成员变量Idao dao。
Dao完全实现类需要使用数据库连接对象例如QueryRunner runner,故也要设为私有成员变量。 -
配置Spring的beans.xml
需要注意——使用注解和非注解的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">
</beans>
注解头部
<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">
非注解配置 bean.xml+AccountService.java无注解/…:
<!-- Service层完全实现类(内含dao),dao层完全实现类(内含runner)
QueryRunner(构造函数内含DataSource对象)DataSource(数据库配置) -->
<!-- 配置Service -->
<bean id="accountService" class="com.itheima.service.imp.AccountService">
<!-- 注入dao -->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置Dao对象-->
<bean id="accountDao" class="com.itheima.dao.imp.AccountDao">
<!-- 注入QueryRunner -->
<property name="runner" ref="runner"></property>
</bean>
<!--配置QueryRunner 通过有参构造函数实现注入-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!-- 配置数据源 通过set函数实现注入-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy?serverTimezone=GMT%2B8"></property>
<property name="user" value="root"></property>
<property name="password" value="Root123456"></property>
</bean>
注解+非注解扫描配置 bean.xml+AccountService.java注解/…:
<!-- 扫描包内注解,Service及Dao使用注解@Component及@Autowired -->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!-- 配置QueryRunner -->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!-- 注入数据源 -->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy?serverTimezone=GMT%2B8"></property>
<property name="user" value="root"></property>
<property name="password" value="Root123456"></property>
</bean>
完全注解配置 SpringConfiguration.java:
package config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ComponentScan({"com.itheima","config"})
//@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
}
6.测试
//1.获取Spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.从容器中取得Service层对象
IAccountService as = ac.getBean("accountService",IAccountService.class);
//3.执行方法
List<Account> accounts = as.findAllAccount();
Spring与JUnit整合
每次都要写<1.获取Spring容器><2.从容器中取得Service层对象>非常废话那么如何能让Spring自动注入Service对象
问题分析:
1、应用程序的入口?
main方法
2、junit单元测试中,没有main方法也能执行?
junit集成了一个main方法
该方法就会判断当前测试类中哪些方法有 @Test注解
junit就让有Test注解的方法执行
3、junit不会管我们是否采用spring框架?
在执行测试方法时,junit根本不知道我们是不是使用了spring框架
所以也就不会为我们读取配置文件/配置类创建spring核心容器
4、由以上三点可知
当测试方法执行时,没有Ioc容器,就算写了@Autowired注解,也无法实现自动依赖类注入
解决问题:
* Spring整合junit的配置
* 1、导入spring整合junit的jar(坐标)
* 2、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的
* @Runwith
* 3、告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置
* @ContextConfiguration
* locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
* classes:指定注解类所在地位置
* 实例:
* @RunWith(SpringJUnit4ClassRunner.class)
* @ContextConfiguration(classes = SpringConfiguration.class)
* @ContextConfiguration(locations = "classpath:bean.xml")
*
* 注意:当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上
结果:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
//@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as = null;
@Test
public void findAllAccount() {
List<Account> accounts = as.findAllAccount();
for (Account account : accounts){
System.out.println(account);
}
依赖pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
Spring AOP
引例——银行转账的事务控制问题
事务概念:由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
举例:在关系数据库中,一个事务可以是【一条SQL语句】或【一组SQL语句】或【整个程序】。
事务特性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
原子性(atomicity):一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability):持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
事务类型:
1.手动事务:手动事务允许显式处理若干过程,开始事务、控制事务边界内的每个连接和资源登记、确定事务结果(提交或中止)以及结束事务。
2.自动事务:将一个数据类设置一个事务属性值来控制对象的事务行,以此为其自动在事务范围内执行。例如delete xx from xx等SQL都是自动事务。
问题:银行两用户转账
//1.根据名称查询转出转入账户
Account source = accountDao.findAccountByName(sourceName);
Account target = accountDao.findAccountByName(targetName);
//2.转出减钱转入加钱
source.setMoney(source.getMoney()-money);
//***中间运行错误***
//int i = 1/0;
target.setMoney(target.getMoney()+money);
//3.更新转出转入账户
accountDao.updateAccount(source);
accountDao.updateAccount(target);
一般来说,这段代码确实实现了转账业务。但是有一个问题就是——如果source.setMoney()与target.setMoney()中间哪段代码
编译通过,运行期间报错!!!转入帐号更新updateAccount()转出账户更新updateAccount()都是产生一个独立事务(独立connection)
那么就会造成source用户的钱转出去了,但是收款的Target用户没有拿到钱。首先这样编程违反了事务的一致性,其次实际中会造成重大损失
解决:使用上面共有4次Connection,共用一个transaction事务控制(即是共用一个Connection,因为commit是Connection的方法)
使用ThreadLocal对象把Connection和当前线程绑定,从而使一个线程只有一个控制事务的对象。
Service层是事务控制的地方,而只是单一对象的增删改一般都是默认使用事务自动控制在Dao层持久层。
为了达成共用一个transaction事务控制(即一个线程(运行同一个类的函数的程序)共用一个Connection)则需要创建2个类
ConnectionUtils类:连接的工具类,以线程的方法从数据源中获取一个连接,实现线程与Conn的绑定
TransactionManager类:事务管理相关的工具类,在连接工具类的基础上实现事物管理。
管理通过ConnectionUtils类的getConn()方法获取的Conn对象,实现事物手动控制的基本函数封装
二者关系与Dao层与Service层是一样的,只是根据具体场景需求线性组合Dao层对象的基本函数而已
连接池技术——将与数据库建立链接的过程统一放在程序运行开始,建立多个链接,并管理。连接池允许被使用过的闲置连接被其它需要的线程使用。
线程池技术——服务器端,初始化一大堆线程,然后有需要的直接拿,用完close()放回池内,而不是真正关闭。
也就是一个线程获取到新的连接池中的Conn,用完后关闭close(),Conn返回连接池,线程需要与Conn解绑,下次需要时再获取新的Conn
改造Service层所有需要手动事务控制函数
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer....");
try {
//1.开启事务
transactionManager.beginTransaction();
//2.执行操作
//2.1根据名称查询
Account source = accountDao.findAccountByName(sourceName);
Account target = accountDao.findAccountByName(targetName);
//2.2减钱加钱
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
//2.3更新账户
accountDao.updateAccount(source);
accountDao.updateAccount(target);
//3.提交事务
transactionManager.commit();
//4.返回结果
}catch (Exception e){
//5.回滚操作
transactionManager.rollback();
throw new RuntimeException(e);
}finally {
//6.释放连接
transactionManager.release();
}
}
改造bean.xml,不为QueryRunner注入dataSource,否则QueryRunner采用此有参构造函数会自动生成Connection,
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
改造Dao层,加入ConnectionUtils类connectionUtils对象,在每个QueryRunner的runner对象.query()加入手动事务控制的连接参量
public Account findAccountByName(String accountName) {
try{
List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ? ",new BeanListHandler<Account>(Account.class),accountName);
if(accounts == null || accounts.size() == 0){
return null;
}
if(accounts.size() > 1){
throw new RuntimeException("结果集不唯一,数据有问题");
}
return accounts.get(0);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
public 其他函数(){
runner.query(connectionUtils.getThreadConnection(),"SQL",new BeanListHandler<Account>(Account.class));
}
综合起来就是,在Dao层操作QueryRunner对象,QueryRunner对象操作DataSource对象的时候加了一层ConnectionUtils对象
ConnectionUtils以线程身份获取Conn,然后在基于ConnectionUtils对象操作封装TransactionManager类实现手动事务控制
事务管理是Service层的操作,线程与Conn绑定是Dao的需求(参数)。
重点重点!!!
ConnectionUtils(通过ThreadLocal获取Conn)一共出现在2个类中,一个是封装Conn行为的TransactionManager,一个是Dao层完全实现类的增删改查函数,也就是——这两个类都要共用一个ConnectionUtils获取相同的Conn对象,也就是——————我们需要单例的ConnectionUtils!!!不要写Scope=Prototype!!!否则会造成,TransactionManager与Dao层的Conn对象不同,导致事务无法回滚。
又一个问题,相互依赖变得多了起来,乱七八糟,并且出现了重复性代码,事务管理TransactionManager在Service层,十分麻烦。这也会导致,TransactionManager类不能进行频繁更改函数名。否则会导致很多的Service层代码修改。【我们希望Service层只是业务逻辑(Dao层的线性组合)那么事务管理如何进行?参看下面的动态代理】
动态代理
动态代理基础知识:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于接口的动态代理:
* 涉及的类:Proxy
* 提供者:JDK官方
1.基于接口实现的动态代理
* 创建基于接口的代理对象:
* 使用Proxy类中的newProxyInstance方法
* 创建代理对象的要求:
* 被代理类最少实现一个接口,如果没有则不能使用
* newProxyInstance方法的参数:
* ClassLoader:类加载器
* 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
* Class[]:字节码数组
* 它是用于让代理对象和被代理对象有相同方法。固定写法。
* InvocationHandler:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
Java实现过程:
引用Proxy动态代理类,使用其newProxyInstance方法完成,
通过IProducer接口的完全实现类producer代理IProducer接口方法的一系列操作
接口1 代理对象变量 = (接口1) Proxy.newProxyInstance(参数1,2,3);
共有3个参数:
xxx.getClass().getClassLoader()获取被代理类
xxx.getClass().getInterfaces()获取被代理类实例
new InvocationHandler(){动态代理增强函数}(注:对xxx类所有的函数都执行)
final Producer producer = new Producer();
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(
producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法
* 方法参数的含义
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
proxyProducer.saleProduct(10000f);
2.基于子类的动态代理
基于子类的动态代理:
* 涉及的类:Enhancer
* 提供者:第三方cglib库【dependency<cglib cglib 2.1_3>】
* 如何创建代理对象:
* 使用Enhancer类中的create方法
* 创建代理对象的要求:
* 被代理类不能是最终类,否则无法产生代理子类
* create方法的参数:
* Class:字节码
* 它是用于指定被代理对象的字节码。
* Callback:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
* 我们一般写的都是该接口的子接口实现类:MethodInterceptor
java实现:
final Producer producer = new Producer();
Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
*
* @param o
* @param method
* @param objects
* 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
* @param methodProxy 当前执行方法的代理对象
* @return
* @throws Throwable
*/
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)objects[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(12000f);
动态代理的用途:
1.连接池技术中的close函数,并不是销毁Conn对象,而是将对象放回池中。那么就可以使用动态代理来增强处理conn.close()函数
2.全站中文乱码的解决———针对request对象的动态代理,增强getparameter()、getparametervalue()、getparametermap();
3.动态代理实现事务控制与Service层分离
3.1代理Service对象工厂(产生套有事物管理的代理Service)
import com.itheima.service.IAccountService;
import com.itheima.utils.TransactionManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 用于创建Service的代理对象的工厂
*/
public class BeanFactory {
private IAccountService accountService;
private TransactionManager txManager;
//用于Spring注入
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
//被代理的对象要用final修饰以免发生变量引用改变
public final void setAccountService(IAccountService accountService) {
this.accountService = accountService;
}
/**
* 获取Service代理对象
* @return
*/
public IAccountService getAccountService() {
return (IAccountService)Proxy.newProxyInstance(
accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 添加事务的支持
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果是test方法,直接放行
if("test".equals(method.getName())){
return method.invoke(accountService,args);
}
//其他方法做事务处理
Object rtValue = null;
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
rtValue = method.invoke(accountService, args);
//3.提交事务
txManager.commit();
//4.返回结果
return rtValue;
} catch (Exception e) {
//5.回滚操作
txManager.rollback();
throw new RuntimeException(e);
} finally {
//6.释放连接
txManager.release();
}
}
}
);
}
}
3.2依赖注入Spring Bean对象注入
<!--配置动态代理所需的,通过BeanFactory工厂获取的,特殊service对象-->
<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
<!--配置beanFactory-->
<bean id="beanFactory" class="com.itheima.factory.BeanFactory">
<!-- 注入普通Service对象 -->
<property name="accountService" ref="accountService"></property>
<!-- 注入事务管理器对象 -->
<property name="txManager" ref="txManager"></property>
</bean>
<!-- 配置普通Service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<!-- 注入dao对象 -->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置Dao-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!-- 注入QueryRunner -->
<property name="runner" ref="runner"></property>
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
<!-- 配置Connection的工具类 ConnectionUtils -->
<bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
<!-- 注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</bean>
<!-- 配置事务管理器-->
<bean id="txManager" class="com.itheima.utils.TransactionManager">
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
在test时候,使用动态代理产生的Service类@Qualifier
/**
* 使用Junit单元测试:测试我们的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
@Qualifier("proxyAccountService")
private IAccountService as;
@Autowired
@Qualifier("proxyAccountService")
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}
AOP概念
AOP:全称是 Aspect Oriented Programming 即:面向切面编程。通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
作用:AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
举例来说:业务层对象多个方法中重复的代码【事务管理代码】抽取出来单独在一个类【代理执行类】中,使用动态代理的技术,对业务层对象进行一层【事务管理】封装,生成代理Service对象,这样就可以在不修改业务层代码的基础上,对我们的已有对象的方法进行增强【加套】。
实现了——业务层与事务管理代码的分离——解耦。维护方便(即便事务管理类发生方法名修改,也只需要修改代理执行类的一行代码)
Spring AOP 术语
Joinpoint(连接点): 所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的 连接点。 Pointcut(切入点): 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
Advice(通知/增强): 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。通知类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
Target(目标对象): 代理的目标对象。
Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Proxy(代理): 一个类被 AOP 织入增强后,就产生一个结果代理类。
Aspect(切面): 是切入点和通知(引介)的结合。
切面示意图
通知類型
Spring AOP如何精简繁复的编写过程
a、开发阶段
编写核心业务逻辑方法joinpoint
抽取公用业务逻辑,制成advice
在beans.xml配置文件中,声明切入点与通知间的关系,即切面
b、运行阶段(Spring框架完成的)
Spring 框架监控pointcut方法的执行
一旦监控到切入点方法被运行,使用代理机制,动态创建target的代理对象,根据通知类别,在Proxy的对应位置,将不同类型的通知运行,完成一个完整的业务逻辑。
原理
将动态代理结果/拦截类的编写中重复的代码制成一个模版,通过读取XML的AOP标签,将对应的标签内容插入到对应的动态代理模版中,
生成动态代理结果/拦截类。说实话,都是一个原理,只不过是不需要再写动态代理结果/拦截类重复代码的部分。
基于XML的AOP
spring中基于XML的AOP配置步骤:
1、把通知Bean也交给spring来管理
2、使用aop:config标签表明开始AOP的配置
3、使用aop:aspect标签表明配置切面
id属性:是给切面提供一个唯一标识
ref属性:是指定通知类bean的Id。
4、在aop:aspect标签的内部使用对应标签来配置通知的类型
现在示例是让printLog方法在切入点方法执行之前运行:所以是前置通知
aop:before:表示配置前置通知
method属性:用于指定Logger类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
实例
<!-- 第一步:配置SrpingIoc容器 -->
<!-- 配置service类-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!-- 配置Logger类(AOP的advice类——切面的实体类) -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<!--第二步:配置AOP-->
<aop:config>
<!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
此标签写在aop:aspect标签内部只能当前切面使用。
它还可以写在aop:aspect外面,此时就变成了所有切面可用-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知:在切入点方法执行之前执行 -->
<aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>
<!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个 -->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
<!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个 -->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
<!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行 -->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
<!-- 配置环绕通知 详细的注释请看Logger类中-->
<aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
切入点标签
第一种直接指定pointcut切入点方法
<aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
第二种配置全局pointcut切入点方法,然后通过pointcut-ref属性引用唯一id在<aop:advice/>
中
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>
<aop:pointcut id="" expression=""></aop:pointcut>
id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
当此标签写在aop:aspect标签内部只能这个切面内使用,且只能在<aop:advice5种类型></aop:advice5种类型>
之前。有序
写在aop:aspect外面,此时就变成了所有切面可用
切入点表达式
切入点表达式需要引包【org.aspectj aspectjweaver 1.8.7】否则无法识别* *..*.*()
通配符
切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表)
标准的表达式写法:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略
void com.itheima.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值
* com.itheima.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
* *.*.*.*.AccountServiceImpl.saveAccount())
包名可以使用..表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
类名和方法名都可以使用*来实现通配
* *..*.*()
参数列表:
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但是必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型
全通配写法:
* *..*.*(..)
实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法
* com.itheima.service.impl.*.*(..)
四种常用的aop:advice
类型
前置通知:在切入点方法执行 前 执行
后置通知:在切入点方法执行 后 执行
异常通知:在切入点方法产生异常 后 执行
最终通知:无论切入点方法发生什么 都要执行
环绕通知:就是一个完整的动态代理结果类,程序猿可以手动书写前置通知后置通知异常通知最终通知,切入点方法,通过。
这些函数写在动态代理结果类/处理类中
【如果使用Spring框架,就不需要写动态代理结果类/处理类,写在xml,当然如果要使用注解,还得写在动态代理结果类/处理类中】
环绕通知实例:
/**
* 环绕通知
* Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
*
* spring中的环绕通知:
* 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
*/
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
//1.获取参数
Object[] args = pjp.getArgs();//得到方法执行所需的参数
//2.前置advice
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
//3.执行方法
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
//4.后置advice
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
//返回代理对象处理完毕
return rtValue;
}catch (Throwable t){
//5.异常advice
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
//6.最终advice
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
}
}
基于注解的AOP
1.修改bean.xml,在AOP约束基础上增加Context约束,开启注解扫描以及AOP注解支持
实例
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--1.开启注解扫描-->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!--2.开启aspect注解支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
2.使用注解对切面类及其advice方法标注【切面类也需要@Component进行Spring Bean对象管理】
@Aspect(“唯一标识”)
@Pointcut(“切入点表达式”)
@Before(“Pointcut注解的函数()”):前置
@AfterReturning(“pt1()”):后置
@AfterThrowing(“pt1()”):异常
@After(“pt1()”):最终
@Around(“pt1()”):环绕
注意:advice执行顺序不同。这个没办法改变,Spring AOP内部的问题。
前置通知
执行了保存账户
最终通知
后置通知
但环绕就不会有这个执行顺序问题
实例
//1.标注切面
@Component("logger")
@Aspect("logAdvice")
public class Logger {
//1.创建 切入点方法()
@Pointcut("execution(* com.itheima.service.imp.*.*(..))")
private void pt1(){}
/**
* 2.1前置通知@Before("切入点()")
*/
@Before("pt1()")
public void beforePrintLog(){
System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
}
/**
* 2.2后置通知@AfterReturning("切入点()")
*/
@AfterReturning("pt1()")
public void afterReturningPrintLog(){
System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
}
/**
* 2.3异常通知@AfterThrowing("切入点()")
*/
@AfterThrowing("pt1()")
public void afterThrowingPrintLog(){
System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}
/**
* 2.4最终通知@After("切入点()")
*/
@After("pt1()")
public void afterPrintLog(){
System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
}
//
3.如果连xml都不想写的话
在Spring @Configuration 注解配置类中写:@EnableAspectJAutoProxy即可启用Aspect注解
@Configuration
@ComponentScan(basePackages="com.itheima") @EnableAspectJAutoProxy
public class SpringConfiguration { }
关于Spring的advice顺序问题
最先执行——前置,再执行最终,再执行后置,如果有异常再执行异常
也就是最终与后置的顺序有不协调的地方,那么这样的意义是——如果你需要完整的前置–后置–(异常)–最终那么请选择环绕advice
这里的前置、后置、最终、异常只是片段功能,如果想要完整功能选择环绕around,而不是写四个before after…
Spring自带的事务管理器API
上面自己写的事务管理TransactionManager中有2个函数是无用的。
一个是事务开启【获取线程是就设置自动提交关闭】
一个是释放【直接在回滚和提交函数中写释放即可】
当省略后,只剩下提交事务和回滚事务——而这个两个事务的关系是固顶的,提交事务=>没有回滚,不提交事务=>回滚
也就是——Spring Transaction API 也只有commit(),rollback(),getTransaction()三个函数。
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
rtValue = method.invoke(accountService, args);
//3.提交事务
txManager.commit();
//4.返回结果
return rtValue;
} catch (Exception e) {
//5.回滚操作
txManager.rollback();
throw new RuntimeException(e);
} finally {
//6.释放连接
txManager.release();
}
具体的API接口
事务管理类接口
PlatformTransactionManager
接口
commit(),rollback(),getTransaction()
完全实现类:1.DataSourceTransactionManager 2.HibernateTransactionManager
事务属性定义接口
TransactionDefinition
getName(),getIsolationLevel(),getPropagationBehavior(),getTimeout,isReadOnly()
获取事务名称,事务隔离级别,事务传播行为,事务超时时间
事务状态接口
TransactionStatus
flush(),hasSavepoint,isCompleted(),isNewTransaction(),isRollbackOnly(),setRollbackOnly()
刷新事务,获取是否存在存储点,事务是否完成,是否为新事物,是否回滚,设置事务回滚
存储点=>用来不完全回滚,操作了99次数据库,最后一次出问题抛出异常,但不想完全回到第一次操作数据库之前,那就设置存储点
回滚就回滚到上一个存储点而不是起始原点。
事务处于Service层,Spring为我们提供了一组事务控制接口API,在Spring-tx.jar中。
这个实现即可基于xml,也可以编程事务管理类实现。Spring与Mybatis都可以通过xml来配置Interface接口。
基于Spring事务管理接口的 XML配置
beans.xml bean标签头部
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
配置Service层及Dao层及DataSource[注意:此处的accountDao继承了JdbcDaoSupport也就是下面的JdbcTemplate,有dataSource属性]
<!-- 配置业务层-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置账户的持久层-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/eesy?serverTimezone=GMT%2B8"></property>
<property name="username" value="root"></property>
<property name="password" value="Root123456"></property>
</bean>
配置事务管理及advice标签
<!-- spring中基于XML的声明式事务控制配置步骤
1、配置事务管理器
2、配置事务的通知
此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的
使用tx:advice标签配置事务通知
属性:
id:给事务通知起一个唯一标识
transaction-manager:给事务通知提供一个事务管理器引用
3、配置AOP中的通用切入点表达式
4、建立事务通知和切入点表达式的对应关系
5、配置事务的属性
是在事务的通知tx:advice标签的内部
-->
<!-- 配置transactionManager事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置transactionManager的advice -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置事务的属性
isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
配置advice与AOP标签联系
<!-- 配置aop与advice关联-->
<aop:config>
<!-- 配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<!--建立切入点表达式和事务通知的对应关系 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
注意:别引错org.aspectj的aspectjweaver包
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
基于Spring事物管理API 注解配置
bean.xml 增加context名称空间及context约束文件
头部
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"
中下部
<!-- 1.包扫描 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!-- 2.开启aspect注解支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- 3.开启transaction注解支持 -->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
<!-- jdbcTemplate是jar包所以只能在xml -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- DataSource作为jdbcTemplate的属性也要在这 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
</bean>
<!-- 配置事务管理器 也jar包的 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
使用注解,省略了 Dao层,Service层,以及 advice,aop的配置。
Service层注解:
@Service(“accountService”)
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true) //只读查型事务属性配置
数据成员IAccountDao的@Autowired
@Transactional(propagation = Propagation.REQUIRED,readOnly = false) //增删改型事务属性配置
函数成员头顶上也可以配置【优先级为具体函数头顶的局部优先,类头顶的是全局覆盖】
假设Service层有10个函数,5个是增删改,5个查询,那最好的情况就是 配置1个全局只读型,再+5个增删改型。也就是6个注解
而xml直接通配符就可以了。这也是注解和xml配置的选择。——————最好是,自己写的用注解(如果多也用xml),别人的jar包用xml
Dao层注解:
@Repository(“accountDao”)
数据成员JdbcTemplate的@Autowired
基于Spring事务管理API 純注解
1.創建Spring注解配置类
//可写可不写,因为这个类要被指定读取
@Configuration
//包扫描
@ComponentScan(“com.itheima”)
//jdbc配置类包含jdbcTemplate以及DataSource Bean对象注入
@Import({JdbcConfig.class,TransactionConfig.class})
//引入数据源4个属性,供jdbc配置类的DataSource对象以@Value注解读取
@PropertySource(“jdbcConfig.properties”)
//启用事务注解
@EnableTransactionManagement
public class SpringConfiguration {
}
2.创建JdbcConfig.class类
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建JdbcTemplate
* @param dataSource
* @return
*/
@Bean(name="jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name="dataSource")
public DataSource createDataSource(){
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
3.创建TransactionConfig.class类
@Bean(name="transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
隐含
Dao层完全实现类以上面的注解+xml混合配置(非继承父类JdbcDaoSupport)
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//函数中直接使用jdbcTemplate.query
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
Service层完全实现类
@Service("accountService")
@Transactional(propagation= Propagation.SUPPORTS,readOnly=true)//只读型事务的配置
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDao accountDao;
//读写型事务配置
@Transactional(propagation= Propagation.REQUIRED,readOnly=false)
public void transfer(String sourceName, String targetName, Float money) {}
}
4.test测试类注解更换
@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(locations = "classpath:bean.xml")
@ContextConfiguration(classes = SpringConfiguration.class)
基于Spring事务模版的 编程式实现事务控制
说白了就是回归第一个原始代码进行事务控制。也就是会造成大量重复代码,失去了AOP存在的意义
只不过是Spring帮你写了个事务控制模版类TrancationTemplate(设置手动提交+前置通知+具体事务运行块+后置通知+异常回滚/无异常提交)
你直接把需要管理的事务代码写进[具体事务运行块]就行
需要在TrancationTemplate注入TranscationManager对象属性
在Service层完全实现类注入TrancationTemplate对象属性
Service完全实现类与TrancationTemplate
private TransactionTemplate transactionTemplate;
public Account findAccountById(Integer accountId) {
return transactionTemplate.execute(new TransactionCallback<Account>() {
public Account doInTransaction(TransactionStatus status) {
return accountDao.findAccountById(accountId);
}
}
);
}
因为execute函数的参数是一个类,所以直接采用匿名内部类。方便
JDBC Template
本質:是對JDBC驅動的薄薄封装
剧透:Mybatis就是替代JDBCTemplate的存在
最原始使用JT:
1.创建数据源对象:spring的内置数据源
写入,驱动,链接地址,用户名,密码四个属性
2.创建JdbcTemplate对象
写入,数据源属性
3.JT执行CRUD操作
加入Spring IOC使用JT:
1.将创建对象交给Spring IOC,书写bean容器内容
bean.xml注册jdbcTemplate,dataSource对象,并为这两个对象写入属性DataSource是xml注册jdbcTemplate属性
2.获取Spring IOC容器
通过new ClassPathXmlApplicationContext("bean.xml")加载Spring IOC对象
3.通过容器ApplicationContext对象获取jdbcTemplate对象
4.JT执行CRUD操作
CRUD操作
增删改都是 jt.update()
查询是 jt.query(),query函数有很多,我们需要的是下面两个返回值为List<T>
query(String sql,Object[] args,RowMapper<T> rowMapper) List<T>;
query(String sql,RowMapper<T> rowMapper,Object... args) List<T>;
第二个是可变参数Object…要求JDK1.5以上,第一个是所有JDK通用JT查询函数
<T>
就是封装我们查询的数据库表对象,rowMapper是用于帮助我们将一个个T对象装入到List集合中。
关于查询一行一列(一个值)
queryForObject(String sql,Class<T> requireType,Object... args) <T>;
根据需求,定义返回想要的值的类型(Integer.class/…)
//1.增删改
//增添
jt.update("insert into account(name,money)values(?,?)","eee",3333f);
//更新
jt.update("update account set name=?,money=? where id=?","test",4567,7);
//删除
jt.update("delete from account where id=?",8);
//2.查询
//2.1查询所有
//2.1.1自己写rowMapper生数据映射封装类
List<Account> accounts = jt.query("select * from account where money > ?",new AccountRowMapper(),1000f);
//2.1.2Spring提供的rowMapper生数据映射封装类
List<Account> accounts = jt.query("select * from account where money > ?",new BeanPropertyRowMapper<Account>(Account.class),1000f);
for(Account account : accounts){
System.out.println(account);
}
//2.2查询一个【逻辑仍是使用查询所有的函数,只不过是将querySQL改为“=”,get(0)List中第一个对象而已】
List<Account> accounts = jt.query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),1);
System.out.println(accounts.isEmpty()?"没有内容":accounts.get(0));
//查询返回一行一列(一个值,例如:计数…求和…等等)(使用聚合函数,但不加group by子句)
Long count = jt.queryForObject("select count(*) from account where money > ?",Long.class,1000f);
System.out.println(count);
AccountRowMapper自定义生数据映射封装类
/**
* 定义Account的封装策略
*/
class AccountRowMapper implements RowMapper<Account>{
/**
* 把结果集中的数据封装到Account中,然后由spring把每个Account加到集合中
* @param rs
* @param rowNum
* @return
* @throws SQLException
*/
@Override
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getFloat("money"));
return account;
}
JT在Dao层的使用
1.使用Spring IOC注入
Dao及JdbcTemplate及DataSource三个对象【若不用注释–>需要有set方法】
dao完全实现类.java
private JdbcTemplate jt = null;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public Account findAccountById(Integer accountId) {
List<Account> accounts = jdbcTemplate.query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty()?null:accounts.get(0);
}
bean.xml
<!-- 配置账户的持久层-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!--配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置dataSource 略-->
测试.java
//1.获取ApplicationContext容器
//2.从容器中getBean()Dao对象
//3.Dao对象执行相应函数
2.继承JdbcDaoSupport改进版
根源问题:如果有多个不同类Dao对象则会产生重复代码(JdbcTemplate定义及其set方法)
解决思路:建立一个父类,声明私有属性jdbcTemplate对象,并有set、get函数,这个父类再新建一个setDataSource(DataSource dataSource)
函数,通过setDataSource注入数据源,并且当jdbcTemplate为空的时候,创建一个以dataSource为数据源的jdbcTemplate对象。也就是
可以直接通过setDataSource函数来创建jdbcTemplate对象(以dataSource数据源构建的)
public void setDataSource(DataSource dataSource) {
if(jdbcTemplate == null){
jdbcTemplate = createJdbcTemplate(dataSource);
}
}
private JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
这个父类我们不需要写,Spring已经写好了,只需要继承Spring JdbcDaoSupport父类即可
import org.springframework.jdbc.core.support.JdbcDaoSupport;
但是呢,缺点是,Spring提供的父类,我们没办法进行注解,也就是使用注解会很麻烦。
如果使用xml配置则可继承Spring JdbcDaoSupport父类
如果使用注解,则需要自己声明定义一个JdbcDaoSupport变量即可,并且不可使用super了。
问答:
1.Spring IOC解决什么问题
答:IOC将依赖类创建的控制权从当前使用类转移到Spring框架中的AbstractApplicationContext抽象类实例中
(基类接口为BeanFactory,ResourceLoader),实现了通过字符串name查找Spring的Core Container(Beans容器)
装载指定依赖类以及复杂的依赖注册,可在依赖项不存在的时候通过编译,即实现低耦合结构,通过额外的beans.xml文件配置依赖类。
当然这些功能都是由Core Container实现的,也就是创建(包括查找步骤)依赖类的控制权交给了Spring也就是,如果丢失了
AbstractApplicationContext的实例,那么接下来运行的程序都将找不到自己需要的依赖类。
2.如何搭建Spring IOC基于XML的开发环境
答:引入Spring框架,通过Maven的pom.xml引入依赖项org.springframework
.spring-context
.5.0.2.RELEASE
groupId、artifactId、version。
对项目引入的有:spiring-aop(注释),spring-beans,spring-context,spring-core,spring-expression,spring-jcl(apache日志封装)
3.如何实现Spring IOC类之间依赖注入(依赖关系维护)
共有3种方法,其中两种在beans.xml中配置依赖注入,通过bean标签内的属性实现依赖注入,分别为constructor-arg及property标签
通过构造函数实现依赖注入,以及通过set函数实现依赖注入。
Spring 5 JDK 8 JUnit 5 Tomcat 8.5
-
基于JDK 8
Jdk1.8大幅度提升了创建对象以及映射创建对象的速度相较于Jdk1.7,大概有百倍的差距
现在Spring封装过的JUnit仍是4,也就是Spring-test(JUnit 4)仍会伴随一段时日 -
组件索引功能
Spring Framework 5.0 现在支持候选组件索引作为类路径扫描的替代方案。
从索引读取实体而不是扫描类路径对于小于 200 个类的小型项目是没有明显差异。但对大型项目影响较大。 加载组件索引开销更低。因此,随着类数的增加,索引读取的启动时间将保持不变。 -
JetBrains Kotlin 支持
Kolin概述:是一种支持函数式编程编程风格的面向对象语言 -
新特性:响应式堆栈 WEB 框架
这个堆栈完全的响应式且非阻塞,适合于事件循环风格的处理,可以进行少量线程的扩展。
Reactive Streams 是一个为响应式编程实现的实现的公共 API。Hibernate的 JPA 就是这个 API。
Reactive Streams API 是 Java 9 官方版本的一部分。在 Java 8 中, 你需要引入依赖来使用 Reactive Streams API。
Spring 对于流式处理的支持依赖于 Project Reactor 来构建, 其专门实现了 Reactive Streams API
官方文档里的东西
1.bean.xml基础版本——头部引用[spring-framework-reference/Core]
<?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">
</beans>
2.bean.xmlAOP版本——头部引用[spring-framework-reference/Core]
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
3.事务及AOP版本——头部引用[spring-framework-reference/Data Access]
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
idea 涉及到牛皮的东西
- diagram功能的使用场景——>类继承关系图表,右键想看的类,還可以show implement查看实现类
- Maven项目依赖jar图表(右侧Maven功能区Show Dependencies功能)
- 最后一个Ctrl+Q快速查看解释文档Documentation
- eclipse對於xml約束文件,可以采用網上連接,以及本地xml文件,點擊window–preference–XML Catalog–add…
- ctrl+shift+T快速创建当前所在类的测试类
- shift+f6修改所有出现过的函数名