spring_Learn
注意,以下为个人学习总结使用,有他人帖子引用(节省点时间),如有侵权,请联系删改
(注意:有些许代码属于个人工程,使用的是工程名称,但是没给出具体代码,什么?发到git上什么的,有点懒了拉(笑))
Spring
首先,我们先来回顾下之前学过的javaee三层结构

详细见jsp环节
随着Spring,这三层也可以进行一个优化和缩减
什么是Spring
Spring 是分层的 Java SE/EE 应用 full-stack轻量级开源框架,以 IOC(Inverse Of Control:控制反转)和AOP(Aspect Oriented Programming:面向切面编程)为内核提供了展现层 SpringMVC 和持久层Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
然后这三层可以改成

程序中的耦合
什么是耦合
首先我们来回顾下数据库连接(maven工程)
public class testjdbc {
static String username = "root";
static String password = "root";
static String url = "jdbc:mysql://localhost:3306/lesson?serverTimezone=Asia/Shanghai";
//算是回顾数据库操作吧(笑)
public static void main(String[] args) throws Exception{
//想起[注册驱动],这里先用一种错误的方式来连接
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
//想起[获取连接对象]
Connection connection = DriverManager.getConnection(url,username,password);
//想起[使用PreparedStatement对象,预处理sql语句]
String sql = "SELECT * FROM student";
PreparedStatement pst = connection.prepareStatement(sql);
//想起[使用resultSet执行语句]
ResultSet resultSet = pst.executeQuery();//执行语句
while(resultSet.next()){
Integer id = resultSet.getInt("id");
Integer age = resultSet.getInt("age");
String name = resultSet.getString("name");
String sex = resultSet.getString("sex");
student student = new student(id,name,sex,age);
System.out.println(student.toString());
}
//工具用完记得放回工具箱
resultSet.close();
pst.close();
connection.close();
}
}
这是一段标准的数据库连接,让我们聚焦其中第8行
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
这里的启动驱动必须要靠mysql依赖来启动,如果我们去pom.xml中去除这个依赖之后,程序就直接报错了。
设想一下:如果一个数据库查询的功能错误,就会导致其他功能也同时报错,这是不是有点拖人下水的感觉。好了,我们直接说明什么是耦合
耦合
耦合指的是程序间的依赖关系:
类与类的依赖关系(上述代码表述的就是这个)
方法的依赖关系
注意:我们不能消除程序间的依赖关系,只能尽可能的降低程序的依赖关系。
这种降低程序间的依赖关系就叫做解耦。
那么我们要怎么修改上述代码进行解耦呢? (降低耦合度)
使用反射解耦合
Class.forName("com.mysql.cj.jdbc.Driver");
这样就算依赖掉了,也不会出现报错的情况,顶多抛出一个异常
使用工厂解耦
首先需要明确几个概念
1.Bean 组件 在java中指的是dao层 业务层的接口实现
2.javaBean 实体类,avaBean并不完全等同于实体类,因为实体类只是组件中的一部分
3.工厂:创建javabean(创建dao 北务层实现类的对象)
现在我们给出一个处境(详细见com.mrme.maven---dao,service)

假设上述使用jdbc实现,但是现在需要我们使用orical实现,那么service部分我们需要修改:
public void addAccount() {
//使用jdbc实现,但是如果后面需要使用orical实现呢?
// AddCountDao addCountDao = new AddCountDaoImpl();
//使用orical实现,这样未免会出现修改源代码的情况
AddCountDao addCountDao = new AddCountDaoOracalImpl();
addCountDao.addCount();
}
这样未免会出现需要修改源代码的地方,嗯哼..这是非常忌讳的,需要我们使用工厂来解决问题
解决方案:
1:通过一个配置文件来管理我们的dao接口实现类和业务类实现类properties配置文件来管理k=v(唯一标识=全限定名(包名+类名))
2.通过读取配置文件中的内容,获取v。基于反射的技术创建对象
下面是工厂
package com.mrme.factory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* 工厂类,专门帮助我们创建对象
*/
public class BeanFactory {
//读取propertiesz中的信息
static Properties properties;
static{
//类加载器加载bean.properties这个路径下的配置文件
try {
InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
properties = new Properties();
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
}
}
public static Object getBean(String name){
//k得到v
try {
String v = properties.getProperty(name);
Object o = Class.forName(v).newInstance();
return o;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
}
}
请注意 bean.properties中的内容要写对
# k = v(javabean名称 = 类全限定名)
AddCountDaoImpl = com.mrme.dao.impl.AddCountDaoImpl
AddCountDaoOracalImpl = com.mrme.dao.impl.AddCountDaoOracalImpl
AccountService = com.mrme.sevice.Impl.AccountServiceImpl
这样service中就可以改成
package com.mrme.sevice.Impl;
import com.mrme.dao.AddCountDao;
import com.mrme.dao.impl.AddCountDaoImpl;
import com.mrme.dao.impl.AddCountDaoOracalImpl;
import com.mrme.factory.BeanFactory;
public class AccountServiceImpl implements com.mrme.sevice.AccountService {
@Override
public void addAccount() {
//使用jdbc实现,但是如果后面需要使用orical实现呢?
// AddCountDao addCountDao = new AddCountDaoImpl();
//使用orical实现,这样未免会出现修改源代码的情况
// AddCountDao addCountDao = new AddCountDaoOracalImpl();
AddCountDao addCountDao = (AddCountDao)BeanFactory.getBean("AddCountDaoImpl");
addCountDao.addCount();
}
}
后续只需要修改一个名称就可以了(15行中字符串)
优化工厂代码,生成单例bean
对于上述代码来说,其实还有一个问题,工厂每次生成的javabean的地址都是不固定的

说明每次生成的javabean都重写创造了新的对象,这样耗时耗资源。因此我们需要尽可能优化成单例
我们预处理所有的bean.propertiesd的语句,然后再从存储的地方取出就行,下面代码是修改工厂部分
package com.mrme.factory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* 工厂类,专门帮助我们创建对象
*/
public class BeanFactory {
//读取properties中的信息
static Properties properties;
static Map<String,Object> beans;
static{
//类加载器加载bean.properties这个路径下的配置文件
try {
//获取输入的流
InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
//存到Properties中(操纵Properties文件程度的能力)
properties = new Properties();
properties.load(is);
beans = new HashMap<>();
//获取所有的k对
Enumeration<Object> keys = properties.keys();
while(keys.hasMoreElements()){
String key = keys.nextElement().toString();
String value = properties.getProperty(key);
Object o = Class.forName(value).newInstance();
beans.put(key,o);
}
} catch (Exception e) {
e.printStackTrace();
}
}
// public static Object getBean(String name){
// //k得到v
// try {
// String v = properties.getProperty(name);
// Object o = Class.forName(v).newInstance();
// return o;
// } catch (Exception e) {
// e.printStackTrace();
// throw new RuntimeException();
// }
// }
public static Object getBean(String name){
return beans.get(name);
}
}
就是用Map存kv对进行预处理

这种方法也是Spring中使用的单例思想(实际实现还是复杂)
IOC(控制反转)
从这开始就是Spring的正课了
首先我们先来阐明IOC是什么:
根据上面我们的例子:我们创建对象有两种方式(1)new(2)通过工厂被动创建
而(2)就是IOC的体现
那么Spring中的IOC是如何做的呢?
使用xml的ioc环境搭建
在resources中创建一个spring的xml文件

使用bean标签对各个bean进行管理
就是帮助我们管理bean的标签,将bean放在spring的ioc容器里面去
id : bean的名称 值任意 但是必须要唯一。
class : bean所属类的全限定名
<bean id="accountDao" class="com.mrme.dao.impl.AccountDaoImpl"></bean>
<bean id="AccountServiceImpl" class="com.mrme.service.impl.AccountServiceImpl"></bean>
访问:
package com.mrme;
import com.mrme.dao.AccountDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAccount {
public static void main(String[] args) {
//读取spring的配置文件 初始化一个ioc容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取bean
AccountDao accountDao = (AccountDao) context.getBean("accountDao");
accountDao.addAccount();
}
}
bean工厂中的细节
上述代码中我们获取ioc容器使用ApplicationContext,当我们查看结构的时候,具有多重继承

其中最上层的继承就有BeanFactory
那么我们就可以抛出两个问题:
这两bean工厂有什么区别
直接给出结论:ApplicationContext在初始化容器的时候就直接初始化完成了,而BeanFactory需要在使用(getbean)才会初始化
ApplicationContext接口的实现类是?
ClassPathXmlApplicationContext
它可以加载类路径下的配置文件,要求配置文件必须在类路径下,否则加载不了(这种比较常用)。之前演示的就是ClassPathXmlApplicationContext,这里不再重复演示了。
FileSystemXmlApplicationContext
它可以加载磁盘任意路径下的配置文件(必须有访问权限)。(少用,毕竟都有地方给你存了还乱存)
AnnotationConfigApplicationContext
它是用于读取注解创建容器的,后面再介绍。
Spring对bean的管理方式
Spring实例化bean的方式
无参数构造函数的实例化方式
使用工厂中的普通方法实例化对象
我们设想一个场景:我们要使用别人写好的代码,别人写好的代码通常是封装在一个jar包中的,我们要怎么创建jar包中字节码文件的对象呢?(我们无法通过修改源码的方式来提供默认构造方法)
模拟一个工厂类(它可能是存在于jar包中的),通常jar包都会暴露一个工厂类,给调用者创建对象。
使用工厂中的静态方法实例化对象
public class TestUSerDao {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applcationContext.xml");
//法1
UserDao userDao = (UserDao) context.getBean("UserDao");
userDao.addUser();
//法2
PersonDao personDao = (PersonDao) context.getBean("PersonDao");
personDao.addPerson();
//法3
OrderDao orderDao =(OrderDao) context.getBean("OrderFactory");
orderDao.addOrder();
}
}
<?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="UserDao" class="com.mrme.dao.impl.addUserImpl"></bean>
<!--
使用工厂中的普通方法实例化对象
factory-bean:工厂bean的id
factory-method:工厂bean中获取bean的方法
-->
<bean id="PersonFactory" class="com.mrme.dao.factory.PersonFactory"></bean>
<bean id="PersonDao" class="com.mrme.dao.impl.PersonDaoImpl" factory-bean="PersonFactory" factory-method="getPerson"></bean>
<!--
使用工厂中的静态方法实例化对象
-->
<bean id="OrderFactory" class="com.mrme.dao.factory.OrderFactory" factory-method="getOrder"></bean>
</beans>
Bean的细节
Bean的作用域
在默认情况下,bean是单例的
如何修改为多例
<bean id="UserDao" class="com.mrme.dao.impl.addUserImpl" scope="prototype"></bean>
<bean id="UserDao" class="com.mrme.dao.impl.addUserImpl" scope="singleton"></bean>
使用,prototype多例,singleton单例
Bean的生命周期
其实很简单,容器初始化后就生成了,摧毁后就销毁了

在配置文件中配置其初始化或者销毁
<bean id="UserDao" class="com.mrme.dao.impl.addUserImpl" init-method="" destroy-method=""></bean>
详细见深究Spring中Bean的生命周期 - Java知音号 - 博客园
DI
什么是DI以及作用
依赖注入:Dependency Injection
DI的作用:依赖关系的管理,依赖都交给spring来维护(IOC的作用:降低程序间的耦合(依赖关系))
依赖注入的方法(三种)
第一种:使用set方法提供(默认)
第二种:使用构造函数提供(有构造的时候才需要用)
第三种:使用注解提供(后面再介绍)
详细见D:\java学习\com.mrme.maven\mrme-spring-03
Set方法注入
假设我们需要向一个新建的User中注入值,前往xml文件中
name:类对象的名称
value:name对应的值(必须是基本数据类型和字符串类型)
<bean id="User" class="com.mrme.pojo.User">
<property name="name" value="MRME"></property>
<property name="age" value="20"></property>
<property name="address" value="幻想乡"></property>
</bean>
测试:
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
User user = (User) applicationContext.getBean("User");
System.out.println(user);
}
如果User内还需要创建一个构造方法Car,就不能直接使用
<bean id="Car" class="com.mrme.pojo.Car">
<property name="name" value="宝马"></property>
<property name="type" value="114514"></property>
</bean>
<bean id="User" class="com.mrme.pojo.User">
<property name="name" value="MRME"></property>
<property name="age" value="20"></property>
<property name="address" value="幻想乡"></property>
<property name="car" ref="Car"></property>
</bean>
这样之后就可以了,测试如下,正常显示
User{name='MRME', age=20, address='幻想乡', car=Car{type='114514', name='宝马'}}
使用构造函数提供
<!--构造方法-->
<bean id="car2" class="com.mrme.pojo.Car2">
<constructor-arg name="type" value="宝马"></constructor-arg>
<constructor-arg name="year" value="114514"></constructor-arg>
</bean>
如果是构造内加一个构造就用ref就行,方法同上
复杂数据类型(少用)
这里导入的例如list,map这样的复杂数据类型
<!--复杂构造-->
<bean id="complex" class="com.mrme.pojo.complex">
<property name="strs">
<!--数组-->
<array>
<value>1</value>
<value>2</value>
<value>3</value>
</array>
</property>
<!--list-->
<property name="list">
<list>
<value>a1</value>
<value>a2</value>
<value>a3</value>
</list>
</property>
<!--set-->
<property name="set">
<set>
<value>b1</value>
<value>b2</value>
<value>b3</value>
</set>
</property>
<!--map-->
<property name="map">
<map>
<entry key="m1" value="1"></entry>
<entry key="m2" value="2"></entry>
<entry key="m3" value="3"></entry>
</map>
</property>
<!--properties-->
<property name="pros">
<props>
<prop key="c1">1</prop>
<prop key="c2">2</prop>
<prop key="c3">3</prop>
</props>
</property>
</bean>
因为这种是硬编码,没有多少灵活性,使用比较少
基于IOC的案例
详细见D:\java学习\com.mrme.maven\mrme-spring-04-account
基于注解型的IOC
在spring中存在4种注解型IOC:
用于创建对象的:
作用:他们的作用就和在XML配置文件中编写一个标签实现的功能是一样的。
用于注入数据的:
作用:他们的作用就和在xml配置文件中的bean标签中写一个标签的作用是一样的。
用于改变作用范围的:
作用:他们的作用就和在bean标签中使用scope属性实现的功能是一样的。
和生命周期相关的:
作用:他们的作用就和在bean标签中使用init-method和destroy-methode的作用是一样的
下面开始正式介绍使用:
前置:需要在xml中开启包扫描,不扫描会报错
<!--context:component-scan:定义包扫描的规则,告诉spring,要去指定的包下面扫描所属的类-->
<!--base-package:规定包扫描范围-->
<context:component-scan base-package="com.mrme"></context:component-scan>
用于创建对象的注解
作用:类似xml中bean标签
使用:
@Component 修饰类
作用就是把当前类交给Spring容器
具有一个value属性,用于指定bean的id
如果不写bean就会默认用当前类名小写
//AccountDaoImpl类
@Component(value = "AccountDao")
@Component//默认就是accountDaoImpl
@Component注解的其他衍生注解:
@Controller @Service @Repository
这三种注解功能和@Component的作用是一样的,仅仅只是明确三层开发
三种分别用于控制层,业务层,持久层(dao)
用于注入数据的注释
@Autowired
AccountDao accountDao;
作用:类似xml中的
使用:
@Autowired标签
修饰成员变量
注入原理:先按照类型进行注入,再按照名称进行注入(其中名称注入是与bean中的id进行匹配),按照类型注入只有唯一的类型才能注入成功,如果有多的就需要再进行名称注入

两个都是AccountDao类,就需要名称注入了
@Component(value = "AccountDao")
也就是注释中的value
详细原理:
类型注入如上,容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。
但是如果有同种的类型出现,就会出现需要名称注入
其他类型的注入数据的注释
这类的标签跟xml中的
@Qualifier:
无法单独使用,需要配合@Component @Controller @Service @Repository使用,在按照类型注入的基础之上再按照名称注入。(也就是在类型匹配基础上再名称匹配)
但是在给方法参数注入时可以单独使用(这个我们后面讨论)

@Autowired
@Qualifier("accountDaoImpl2")//注意名称小写
AccountDao accountDao;
属性:value:用于指定注入bean的id。
@Resource
作用:默认的情况下,可以直接按照bean的id注入。它可以独立使用。
属性:name:用于指定bean的id。
type: 按照类型进行注入。
如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。(名称注入)
@Resource装配顺序:
1,如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
2,如果指定了name,则从Spring上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
3,如果指定了type,则从Spring上下文中找到类型匹配的唯一bean进行装配,找不到或找到多个,都抛出异常
4,如果既没指定name,也没指定type,则自动按照byName方式进行装配
当然也可以配合@Qualifier(唔,有点画蛇添足了耶)
@Value
作用:用于注入基本类型和String类型的数据。
属性:value:用于指定数据的值。它也可以使用spring中SpEL(也就是spring的el表达式)。
SpEL的写法:${表达式}。
@Test
public void test01(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
// AccountService accountService = (AccountService) applicationContext.getBean("AccountService");
// accountService.addService();
Animal animal = (Animal) applicationContext.getBean("animal");
System.out.println(animal);
}
@Value("野兽先辈")
private String name;
@Value("24")
private int age;
@Value("是学生")
private String type;
改变作用域和关于生命周期的注解
@Scope:
作用:用于指定bean的作用范围。
属性:value:指定范围的取值。常用取值:singleton(单例)、prototype(多例)。
@Scope(value = "prototype")
@Scope(value = "singleton")
@PreDestroy 作用:用于指定销毁方法。
@PostConstruct 作用:用于指定初始化方法。

使用IOC对先前案例的优化
详细见 mrme-spring-04-account-annotation
主要的优化是:xml中的大多bean配置被配置成为IOC注入
IOC注入的方式无法配置外部导入的依赖,例如
<!--配置c3p0数据库的基本配置-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<!--还是写localhost算了,别jb乱配host-->
<property name="jdbcUrl" value="jdbc:mysql://localhost/lesson?serverTimezone=Asia/Shanghai"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
<property name="checkoutTimeout" value="1000"></property>
</bean>
<!--配置dbUtils-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
这些都是外部的依赖
所以这里还需要进行进一步的优化:使用java配置类优化IOC案例
java配置类(重要)
详细见 mrme-spring-04-account-javaconfig
由于我们导入的类不能进行修改,但是bean标签的配置过于麻烦,有没有方法能够不配置xml又可以使用这些类呢?
有的兄弟有的:java配置类
(1) 配置类的作用和bean.xml一样。首先介绍
Configuration注解:
作用:指定当前类是一个配置类
细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写(后面再介绍这个细节)。
@Configuration
public class SpringConfiguration {
}
我们先来回忆一下,当我们要使用xml时,我们需要先指定扫描范围,但是现在我们已经失去xml了
这就是下一个要介绍的注解:
ComponentScan注解
作用跟xml中的
<context:component-scan base-package="mrme"></context:component-scan>
一样
此注解的属性有两个:value和basePackages,并且互为别名。(用那个都可以)
@Configuration
@ComponentScan(basePackages = "com.mrme")
public class SpringConfiguration {
}
Bean注解
作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
属性: name:用于指定bean的id。当不写时,默认值是当前方法的名称(value和name两个属性都可以使用)
细节:当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。
@Configuration
@ComponentScan(basePackages = "com.mrme")
public class SpringConfiguration {
@Bean
public DataSource dataSource(){
ComboPooledDataSource dataSource = new ComboPooledDataSource();
try {
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setJdbcUrl("com.mysql.cj.jdbc.Driver");
dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
return dataSource;
} catch (PropertyVetoException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
@Bean
public QueryRunner queryRunner(@Qualifier("dataSource") DataSource dataSource){
QueryRunner runner = new QueryRunner(dataSource);
return runner;
}
}
在queryRunner中使用了@Qualifier,使得DataSource dataSource等同与@Qualifier("dataSource")指向内容
这里的用法就是:给方法参数注入,可以单独使用
AnnotationConfigApplicationContext
用于注释配置
配置完后我们需要测试
但是先前使用的ClassPathXmlApplicationContext现在已经不能用了(这个是基于存在resourse中的文件来获取),这次我们需要从方法中调用就需要使用AnnotationConfigApplicationContext
用于读取注解创建容器
@Test
public void test01(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
AccountService acc = (AccountService) applicationContext.getBean("accountServiceImpl");
List<Account> account = acc.findAllAccount();
for(Account x : account){
System.out.println(x);
}
}
跟ClassPathXmlApplicationContext差不多的用法
对java配置类的优化
首先我们来看配置类SpringConfiguration.java,这里创建的两个方法都是属于管理db的方法(详细见上)但是如果后面要引入更多模块的方法,管理就会变的混乱,违反了职责单一性的原则
所以我们可以对各个不同模块的类进行分离,再用一个父类导入各个子模块
那么我们就需要使用
import注解
作用:用于导入其他的配置类
值:Value,传入的是多个Class(用{}分割)
细节:当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类。我们此时不需要加Configuration注解,只需要在父配置类上添加Import注解
public @interface Import {
Class<?>[] value();
}
@Import(value = {dataSourceConfiguration.class})
@Configuration
@ComponentScan(basePackages = "com.mrme")
public class SpringConfiguration {
}
这里已经把原来的类分离成另外的 dataSourceConfiguration类(这个类就不需要再进行修饰了,import后已经是了)
其次看下面代码
@Bean
public DataSource dataSource(){
ComboPooledDataSource dataSource = new ComboPooledDataSource();
try {
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setJdbcUrl("jdbc:mysql://localhost/lesson?serverTimezone=Asia/Shanghai");
dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
dataSource.setCheckoutTimeout(3000);
return dataSource;
} catch (PropertyVetoException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
其中的值设计都使用了硬编码
前面Maven中我们使用到了properties文件进行存储数据
User=root
Password=root
jdbcUrl=jdbc:mysql://localhost/lesson?serverTimezone=Asia/Shanghai
DriverClass=com.mysql.cj.jdbc.Driver
CheckoutTimeout=3000
这里也可以使用
@PropertySource
作用:用于指定properties文件的位置
public @interface PropertySource {
String name() default "";
String[] value();
boolean ignoreResourceNotFound() default false;
String encoding() default "";
Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}
需要传入name,默认可以不写name,也就是property文件的路径
@PropertySource("classpath:db.properties")
public class dataSourceConfiguration {
@Value("${jdbc.User}")
private String User;
@Value("${jdbc.Password}")
private String Password;
@Value("${jdbc.JdbcUrl}")
private String JdbcUrl;
@Value("${jdbc.DriverClass}")
private String DriverClass;
@Bean
public DataSource dataSource(){
ComboPooledDataSource dataSource = new ComboPooledDataSource();
try {
dataSource.setUser(User);
dataSource.setPassword(Password);
dataSource.setJdbcUrl(JdbcUrl);
dataSource.setDriverClass(DriverClass);
dataSource.setCheckoutTimeout(3000);
return dataSource;
} catch (PropertyVetoException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
@Bean
public QueryRunner queryRunner(@Qualifier("dataSource") DataSource dataSource){
QueryRunner runner = new QueryRunner(dataSource);
return runner;
}
}
关于:@Configuration注解一定需要吗?
可以不要,最好是必要的
@Configuration
@ComponentScan(basePackages = "com.mrme")
@Import(value = {dataSourceConfiguration.class})
public class SpringConfiguration {
}
代码中如果注释掉@Configuration代码也会编译通过

如果使用@Configuration由于需要使用到ADataSource,这个时候,不再重新的创建ADataSource的对象,而是直接从spring容器(spring的单例池中获取)。所以ADataSource只会创建1次。
去除后
第一次创建是使用@Bean注解修饰aDataSource方法的时候,创建的。第二次创建是@Bean注解修饰bRunner方法的时候,由于BRunner对象的创建需要依赖于ADataSource。此时ADataSource这个bean不是从单例池中获取,而是重新又创建了1次。
Spring中整合junit
导入Spring整合Junit的maven坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
使用junit提供的一个注解(@Runwith)把原有的main方法替换成spring提供的main方法。
@Runwith
public @interface RunWith {
Class<? extends Runner> value();
}
该注解的功能是替换Runner(运行器)。
接着使用
@ContextConfiguration
public @interface ContextConfiguration {
@AliasFor("locations")
String[] value() default {};
@AliasFor("value")
String[] locations() default {};
Class<?>[] classes() default {};
Class<? extends ApplicationContextInitializer<?>>[] initializers() default {};
boolean inheritLocations() default true;
boolean inheritInitializers() default true;
Class<? extends ContextLoader> loader() default ContextLoader.class;
String name() default "";
}
导入需要使用的ioc环境
其中location和value是一样的,用于xml环境导入,例如
@ContextConfiguration("classpath:xxx.xml")
Class<?>[] classes() default {};用于导入类
@ContextConfiguration(classes = {SpringConfiguration.class, dataSourceConfiguration.class})//测试模板中引入ioc环境
具体的测试类完成后是:
@RunWith(SpringJUnit4ClassRunner.class)//测试模板类需要一个测试启动器
@ContextConfiguration(classes = {SpringConfiguration.class, dataSourceConfiguration.class})//测试模板中引入ioc环境
public class TestJunit {
@Autowired
AccountService accountService;
@Test
public void test01(){
List<Account> account = accountService.findAllAccount();
for(Account x : account){
System.out.println(x);
}
}
}
主要就是为了省去
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
AccountService acc = (AccountService) applicationContext.getBean("accountServiceImpl");
这样全部由Spring来进行管理,不用到处获取bean了,我们程序员可真是轻松啊(* _ *)
补一个东西:获取properties中的数据
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
搭建事物转账案例
搭建完代码后我们来看这的代码
@Override
public void transform(String sourceName, String targetName, int money) {
Account sourceAccount = accountDao.findAccountName(sourceName);
Account targetAccount = accountDao.findAccountName(targetName);
if(sourceAccount != null|| targetAccount != null){
if(sourceAccount.getMoney() >= money){
//转钱
sourceAccount.setMoney(sourceAccount.getMoney()-money);
targetAccount.setMoney(targetAccount.getMoney()+money);
//更新
accountDao.updataAccountMoney(sourceAccount);
accountDao.updataAccountMoney(targetAccount);
}
}
}
看似没什么问题,但是假设代码运行到11后停止报错了,那么转账的钱就之扣不加了,这其中就违反了事务的一致性
让我们回到数据库复习下事务是什么
什么是事务:事务指的是一组逻辑操作,这些操作是最小单位不可分割的,这些操作要么全部执行成功,要么全部执行失败。事务的特性:ACID原则
A:原子性事务的逻辑操作必须是最小单位不可分割的。
C:一致性指的是事务提交前和事务提交后的数据必须保持一致。
I:隔离性指的是多个事务之间必须互相独立不能影响。
D:持久性指的是事务提交之后,数据必须要持久化的保存在数据表里面。
事务的隔离级别:
读未提交:一个事务读到了另外一个事务没有提交的数据容易造成脏读的数据
读已提交:oracle数据库默认的事务隔离级别。解决了脏读数据的出现,但是出现了不可重复读的现象。
可重复读:mysql默认的事务隔离级别。解决了不可重复读的问题,但是出现了幻读的问题。
可串行化:解决了所有事务的问题(脏读不可重复读幻读的问题)
再来看这几段代码
我们选取3,4,11,12行代码
Account sourceAccount = accountDao.findAccountName(sourceName);
Account targetAccount = accountDao.findAccountName(targetName);
accountDao.updataAccountMoney(sourceAccount);
accountDao.updataAccountMoney(targetAccount);
这每行代码都是独立执行的都需要调用一次数据库,也就是在每次执行都是在调用不同的线程池,但是转账这个事务是表示一个事务的,这也表明没有满足原子性,因此我们需要对这一个方法进行控制单一线程池(也就是使得线程中只有一个能控制事物的连接对象)
如何统一数据库连接?
(1)ThreadLocal:将当前线程与数据库连接对象绑定(一个类,不是bean)
(2)定义一个事物管理器类:
开启事务的方法
提交事务的方法
回滚事务的方法
释放资源事务的方法
伪代码如下:

补充try-catch快捷键ctrl+alt+T
单线程连接数据库的类
package com.mrme.Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@Component
public class ConnectionUtil {
@Autowired
DataSource dataSource;
//ThreadLocal用于保存线程的作用
private ThreadLocal<Connection> tl = new ThreadLocal<>();
//获取数据库连接
public Connection getConnection(){
try {
Connection connection = tl.get();
if(connection == null){//如果获取的是null,说明tl还没初始化,还需要从线程池中获取第一个线程(并且初始化tl)
connection = dataSource.getConnection();
tl.set(connection);
}
return connection;
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException();
}
}
//还需要关闭,释放资源
public void closeConnection(){java
tl.remove();
}
}
管理单线程连接数据库的类
package com.mrme.Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.SQLException;
/**
* 事务管理相关的工具类,它包含了开启事务、提交事务、回滚事务和释放连接等方法。
*/
@Component
public class TransactionManager {
@Autowired
ConnectionUtil connectionUtil;//获取到我们上一个唯一获取连接的类
/**
* 开启事物,也就是关闭事务的自动提交(手动提交)
*/
public void beginTransaction(){
try {
connectionUtil.getConnection().setAutoCommit(false);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public void conmit(){
try {
connectionUtil.getConnection().commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public void rollBack(){
try {
connectionUtil.getConnection().rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public void close(){
try {
connectionUtil.getConnection().close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
更改后的业务类
package com.mrme.Service.Impl;
import com.mrme.Dao.AccountDao;
import com.mrme.Service.AccountService;
import com.mrme.Util.TransactionManager;
import com.mrme.pojo.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
AccountDao accountDao;
@Autowired
TransactionManager transactionManager;
/***
* 首先检查双方是否存在,然后检查金额是否足够,最后再更新账户数据
* @param sourceName 转账方
* @param targetName 接收方
* @param money 金额
*/
@Override
public void transform(String sourceName, String targetName, int money) {
try {
//开启事务
transactionManager.beginTransaction();
//实现业务
Account sourceAccount = accountDao.findAccountName(sourceName);
Account targetAccount = accountDao.findAccountName(targetName);
if(sourceAccount != null|| targetAccount != null){
if(sourceAccount.getMoney() >= money){
//转钱
sourceAccount.setMoney(sourceAccount.getMoney()-money);
targetAccount.setMoney(targetAccount.getMoney()+money);
//更新
accountDao.updataAccountMoney(sourceAccount);
int a =10/0;
accountDao.updataAccountMoney(targetAccount);
}
}
//提交事务
transactionManager.conmit();
} catch (Exception e) {
e.printStackTrace();
transactionManager.rollBack();
} finally {
transactionManager.close();
}
}
}
除了以上其实还有问题
(3)那就是dao层的的QueueRunner连接数据库还是使用database连接
那么 ApplictaionContext.xml 中的就不能使用ds了,需要手动导入
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
<!-- <constructor-arg name="ds" ref="dataSource"></constructor-arg>-->
</bean>
也就是我们刚刚手动创的类 ConnectionUtil.java 中获取连接
正好也提供了一个可以手动提供connection的方法
public int update(Connection conn, String sql, Object... params) throws SQLException {
return this.update(conn, false, sql, params);
}
代理
什么是代理:一开始买电脑只能自己从厂家哪里买 到 通过代理商来买(代理商帮你联系厂家)
从这个过程中我们看到代理作用就是增加了个人的个人的方便程度
换到代码中:其实就是在不修改源码的基础上对方法增强。(毕竟都是买电脑对吧,笑)
特点:代理对象在程序的运行过程中创建。
java中拥有两种代理方式:
jdk动态代理,基于子类的动态代理
jdk动态代理
基于接口的动态代理:涉及的类:Proxy
如何创建代理对象:使用Proxy类中的newProxyInstance方法
创建代理对象的要求:被代理类最少实现一个接口,如果没有则不能使用
package com.mrme;
public interface ProductDao {
public void buyProduct(String name);
}
package com.mrme.impl;
import com.mrme.ProductDao;
public class ProductDaoImpl implements ProductDao {
@Override
public void buyProduct(String name) {
System.out.println(name + "售卖了电脑");
}
}
package com.mrme.impl;
import com.mrme.ProductDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 消费者
* 分别直接买
* 让代理买
*/
public class consumer {
public static void main(String[] args) {
final ProductDaoImpl productDaoimpl = new ProductDaoImpl();
productDaoimpl.buyProduct("厂家");
//模拟让代理买电脑
ProductDao proxy =(ProductDao)Proxy.newProxyInstance(productDaoimpl.getClass().getClassLoader(), productDaoimpl.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 {
if(method.getName().equals("buyProduct")){
method.invoke(productDaoimpl,args);
}
return null;
}
});
proxy.buyProduct("经销商");
}
}
基于子类的动态代理
基于接口的动态代理:涉及的类:cglib(这个类需要导入maven)
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
</dependencies>
如何创建代理对象:使用Enhancer.create
不需要接口的实现
package com.mrme.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class proxy {
public static void main(String[] args) {
Producer producer = new Producer();
producer.buyProdecer("用户");
// public Object create(Class[] argumentTypes, Object[] arguments) {
/**
* Class[] argumentTypes:对象的class,
* Object[] arguments:真正干活的
*/
Producer proxy = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* @param o 当前代理对象的引用
* @param method 代理对象需要执行的方法
* @param objects 代理对象执行该方法需要用到的参数
* @param methodProxy 当前执行方法的代理对象(不常用)
* @return 返回值也是跟被代理对象执行方法的返回值一样
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if(method.getName().equals("buyProdecer")){
/**
* public Object invoke(Object obj, Object... args)
* Object obj:要增强的对象
* Object... args:代理对象执行该方法需要用到的参数
*/
method.invoke(producer,objects);
}
return null;
}
});
proxy.buyProdecer("厂商");
}
}
使用动态代理实现事务控制
AOP

AOP:面向切面编程,AOP是OOP的扩展和延伸,用来解决OOP开发中遇到的问题。
AOP利用的是一种横切技术,解剖开封装的对象内部,并将那些影响多个类的公共行为封装到一个可重用模块,这就是所谓的Aspect方面/切面。所谓的切面,简单点所说,就是将哪些与业务无关,却为业务模块所共同调用的行为(方法)提取封装,减少系统的重复代码,以达到逻辑处理过程中各部分之间低耦合的隔离效果。
AOP采取横向抽取机制,取代了传统的纵向继承体系重复性代码。
说人话:类似代理的实现
AOP的相关术语

AOP---AspectJ XML
算是入门把
xml配置
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
切点模拟设置,我们假设要加强deleteUser
public class UserServiceImpl implements UserService {
@Override
public void findUser() {
System.out.println("findUser实现了");
}
@Override
public void updataUser() {
System.out.println("updataUser实现了");
}
@Override
public void insertUser() {
System.out.println("insertUser实现了");
}
@Override
public void deleteUser() {
System.out.println("deleteUser实现了");
}
}
代理对象模拟
public void CheckUser(){
System.out.println("CheckUser实现了");
}
xml配置
<bean id="userService" class="com.mrme.impl.UserServiceImpl"></bean>
<bean id="userAspect" class="com.mrme.aspect.UserAspect"></bean>
<!--aop配置-->
<aop:config>
<!--
配置切点
id:就是起个名字罢了,唯一就行
expression:切点表达式
execution:切点表示式 切点方法的形参形式 包名 + 类名 + 方法名(传入参数)
*:任意形参(通常用于方法重写的多形式传入) ..;表示任意方法参数
-->
<aop:pointcut id="p1" expression="execution(* com.mrme.impl.UserServiceImpl.deleteUser(..))"/>
<!--
配置代理对象 把通知应用到切点方法上
aop:aspect:设置代理对象
ref:设置代理对象的id
-->
<aop:aspect ref="userAspect">
<!--
配置通知的类型
aop:before:表示 前置通知
method:切点方法
pointcut-ref:方法的来源切点
-->
<aop:before method="CheckUser" pointcut-ref="p1"></aop:before>
</aop:aspect>
</aop:config>
这里我们使用了前置通知当然还有很多的通知类型
其他通知的类型
共有
后缀通知:aop:after-returning 顾名思义就是切点方法执行完成后再执行配置方法
环绕通知:aop:around 切点方法执行完成前后都执行配置方法
抛出异常通知:aop:after-throwing 切点方法执行抛出异常后执行配置方法
最终通知:aop:after 切点方法执行无论是否抛出异常都执行配置方法
其中最特殊的用法是环绕通知,需要使用ProceedingJoinPoint joinPoint配合使用
public void around(ProceedingJoinPoint joinPoint){
//以joinPoint.proceed();为分割线前面是前置异常,后面是后缀异常
try {
System.out.println("CheckUser环绕通知实现了1");
joinPoint.proceed();
System.out.println("CheckUser环绕通知实现了2");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
其他通知的方法同前置异常
使用注解(AspectJ 注解开发)
前提:开始spring对aop的注释的支持
<!--开启包扫描-->
<context:component-scan base-package="com.mrme"></context:component-scan>
<!--开始Spring对aop注解支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
在需要开启的代理类前面加上@Aspect,如下(当然@Component还是要加的)
@Component
@Aspect
public class UserAspect {
//前置
@Before(value = "execution(* com.mrme.impl.UserServiceImpl.deleteUser(..))")
public void CheckUser(){
System.out.println("CheckUser实现了");
}
//后置
@AfterReturning(value = "execution(* com.mrme.impl.UserServiceImpl.findUser(..))")
public void after(){
System.out.println("CheckUser后置通知实现了");
}
//环绕
@Around(value = "execution(* com.mrme.impl.UserServiceImpl.insertUser(..))")
public void around(ProceedingJoinPoint joinPoint){
//以joinPoint.proceed();为分割线前面是前置异常,后面是后缀异常
try {
System.out.println("CheckUser环绕通知实现了1");
joinPoint.proceed();
System.out.println("CheckUser环绕通知实现了2");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
//抛出异常通知
@AfterThrowing("execution(* com.mrme.impl.UserServiceImpl.deleteUser(..))")
public void throwing(){
System.out.println("CheckUser抛出异常时通知实现了");
}
}
其他跟没用注释的是一样的
Spring中的事务与新特性
JdbcTemplate
它是spring框架中提供的一个对象。是对原生jdbc的简单封装,Spring框架为我们提供了很多操作的模板类。
操作关系型数据库的:JdbcTemplate,HibernateTemplate
操作nosql数据库的:RedisTemplate
操作消息队列的:JmsTemplate
今天我们的主角在spring-jdbc-5.0.2.RELEASEsh除了要导入这个jar包之外,我们还需要导入spring-tx-5.0.2.RELEASE.jar(它是和事务相关的)。
创建这个类很简单:
JdbcTemplate jt = new JdbcTemplate();
实例代码:
public static void main(String[] args) {
//使用spring自带的数据源
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost/lesson?serverTimezone=Asia/Shanghai");
dataSource.setUsername("root");
dataSource.setPassword("root");
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
String sql = "insert into account(name,money) values('mrme4',114514)";
jdbcTemplate.execute(dsql);
}
但是,上述代码存在明显的隐患,set,new这些就是
JdbcTemplate的CRUD和DAO操作
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:ApplicationContext.xml"})
public class TestJdbcTempPlate {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
public void test01(){
String sql = "select * from account";
List<Account> accountList = jdbcTemplate.query(sql,new MyRoMapper());
accountList.forEach(account -> {
System.out.println(account);
});
}
}
public class MyRoMapper implements RowMapper<Account> {
@Override
public Account mapRow(ResultSet resultSet, int i) throws SQLException {
String name = resultSet.getString("name");
int money = resultSet.getInt("money");
Account account = new Account();
account.setMoney(money);
account.setName(name);
return account;
}
}
其中的核心依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
JdbcTemplate中主要提供了两个核心
query方法:专门执行查询操作的方法(select)
updata方法:专门执行的更新方法(insert,updata,delete)
详细请看源码
配置编程式事务
搭建案例环境
配置平台事务管理器和事务管理模板
作用类似AOP
<!--配置平台事务管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理模板-->
<bean id="transactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<!--注入平台事务管理器-->
<property name="transactionManager" ref="transactionManager"></property>
</bean>
具体实现:
@Component
public class AccountServiceImpl implements AccountService {
@Autowired
AccountDao accountDao;
@Autowired
TransactionTemplate transactionTemplate;
/***
* 首先检查双方是否存在,然后检查金额是否足够,最后再更新账户数据
* @param sourceName 转账方
* @param targetName 接收方
* @param money 金额
*/
@Override
public void transform(final String sourceName,final String targetName,final int money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
Account sourceAccount = accountDao.findAccountNAme(sourceName);
Account targetAccount = accountDao.findAccountNAme(targetName);
if (sourceAccount != null || targetAccount != null) {
if (sourceAccount.getMoney() >= money) {
//转钱
sourceAccount.setMoney(sourceAccount.getMoney() - money);
targetAccount.setMoney(targetAccount.getMoney() + money);
//更新
accountDao.updataAccountMoney(sourceAccount);
// int a =10/0;
accountDao.updataAccountMoney(targetAccount);
}
}
}
});
}
}
配置声明式事务(XML方式,重点)
我们先去除xml中的bean配置和service中的TransactionTemplate transactionTemplate;
<bean id="accountDao" class="com.xq.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="accountService" class="com.xq.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<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://192.168.10.137:3306/ssm">
</property>
<property name="user" value="root"></property>
<property name="password" value="Admin123!"></property>
</bean>
<!--配置平台事务管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
@Component
public class AccountServiceImpl implements AccountService {
@Autowired
AccountDao accountDao;
/***
* 首先检查双方是否存在,然后检查金额是否足够,最后再更新账户数据
* @param sourceName 转账方
* @param targetName 接收方
* @param money 金额
*/
@Override
public void transform(final String sourceName,final String targetName,final int money) {
Account sourceAccount = accountDao.findAccountNAme(sourceName);
Account targetAccount = accountDao.findAccountNAme(targetName);
if (sourceAccount != null || targetAccount != null) {
if (sourceAccount.getMoney() >= money) {
//转钱
sourceAccount.setMoney(sourceAccount.getMoney() - money);
targetAccount.setMoney(targetAccount.getMoney() + money);
//更新
accountDao.updataAccountMoney(sourceAccount);
// int a =10/0;
accountDao.updataAccountMoney(targetAccount);
}
}
}
}
首先需要对配置事务增强(xml)
注意这里引入的tx是(xmlns:tx="http://www.springframework.org/schema/tx")而不是core内的
<!--配置事务增强-->
<!--
id:唯一即可
transaction-manager:平台事务管理器的id
-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--配置事务的属性-->
<tx:attributes>
<!--指定给那个业务方法配置事务-->
<!--
name:业务名称的方法
propagation:事务的传播行为
-->
<tx:method name="transform" propagation="REQUIRED"/>
</tx:attributes>
<!--aop-->
</tx:advice>
<aop:config>
<aop:pointcut id="tran" expression="execution(* com.mrme.Service.impl.AccountServiceImpl.transform(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="tran"></aop:advisor>
</aop:config>
这样救aop上了,结束
整体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"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
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/context
http://www.springframework.org/schema/context/spring-context.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">
<!--开启包扫描-->
<context:component-scan base-package="com.mrme"></context:component-scan>
<!--c3p0-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="password" value="root"></property>
<property name="user" value="root"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost/lesson?serverTimezone=Asia/Shanghai"></property>
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
</bean>
<!--jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务增强-->
<!--
id:唯一即可
transaction-manager:平台事务管理器的id
-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--配置事务的属性-->
<tx:attributes>
<!--指定给那个业务方法配置事务-->
<!--
name:业务名称的方法
propagation:事务的传播行为
-->
<tx:method name="transform" propagation="REQUIRED"/>
</tx:attributes>
<!--aop-->
</tx:advice>
<aop:config>
<aop:pointcut id="tran" expression="execution(* com.mrme.Service.impl.AccountServiceImpl.transform(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="tran"></aop:advisor>
</aop:config>
</beans>
配置声明式事务(注解的方式—重点)
基于上面的xml设置我们直接删除配置事务增强与aop环节
毕竟我们需要使用注解的方式(@)
所以我们需要开启对注解的支持
<!--配置开启注解对事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
前往需要使用代理的业务加上@Transactional
我们进入源码
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
这两行表示的就是传播行为和隔离级别,
@Transactional(isolation = Isolation.REPEATABLE_READ , propagation = Propagation.REQUIRED)


Spring5之整合日志框架
spring5框架自带了通用的日志封装。Spring5已经移除 Log4jConfigListener,官方建议使用 Log4j2。
导入依赖:
<!--日志依赖-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
创建log4j2.xml(请务必叫这个名字,否则爆破)
<?xml version="1.0" encoding="UTF-8" ?>
<!--日志级别以及优先级排序: OFF> FATAL> ERROR> WARN> INFO> DEBUG> TRACE> ALL-->
<!--Configuration后面的 status用于设置 log4j2自身内部的信息输出,可以不设置,
当设置成trace时,可以看到log4j2内部各种详细输出-->
<configuration status="DEBUG">
<!--先定义所有的 appender-->
<appenders>
<!--输出日志信息到控制台-->
<console name="Console" target="SYSTEM_OUT">
<!--控制日志输出的格式-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t]%-5level %logger{36} - %msg%n"/>
</console>
</appenders>
<!--然后定义 logger,只有定义 logger并引入的 appender,appender才会生效-->
<!--root:用于指定项目的根日志,如果没有单独指定 Logger,则会使用 root作为默认的日
志输出-->
<loggers>
<root level="info">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
测试
package Test;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Testlog {
Logger logger = LoggerFactory.getLogger(Testlog.class);
@Test
public void Test01(){
logger.info("114514");
}
}

spring5之@Nullable注解和函数式注册对象
@Nullable注解
@Nullable注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空

Spring5 核心容器支持函数式风格GenericApplicationContext
@Test
public void testGenericApplicationContext() {
//1 创建 GenericApplicationContext 对象
GenericApplicationContext context = new GenericApplicationContext();
//2,需要刷新一下
context.refresh();
//3 调用 context 的方法对象注册
context.registerBean("account1", Account.class,() -> new Account(1002,"kobe",500.0));
//4 获取在 spring 注册的对象
// User user = (User)context.getBean("com.xq.pojo.Account");
Account user = (Account) context.getBean("account1");
System.out.println(user);
}
spring5整合Junit5单元测试
引入依赖
<!--Junit5-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.6.2</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-runner</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version> 5.0.9.RELEASE </version>
<scope>provided</scope>
</dependency>
使用
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:applicationContext.xml")
//@SpringJUnitConfig(locations = "applicationContext.xml")// 组合注解@ExtendWith + @ContextConfiguration
public class MyTest2 {
@Autowired
AccountService accountService;
@Test//注意这个@Test是org.junit.jupiter.api.Test;而不是junit包中的
public void Tg() {
accountService.transfer("eric","james",500.0);
}
}
其实就是上面的注解不太一样,更新下而已
Spring Web Flux
Spring Web Flux是Spring5添加新的模块,用于web开发的,功能和SpringMVC类似的,Webflux使用当前一种比较流行响应式编程出现的框架。
使用传统web框架比如SpringMVC,这些基于Servlet容器,Webflux是一种异步非阻塞的框架,异步非阻塞的框架在Servlet3.1以后才支持,核心是基于Reactor的相关API实现的。
什么是异步非阻塞
异步和同步、非阻塞和阻塞 两者都是针对对象不一样。
异步和同步针对调用者,调用者发送请求,如果等着对方回应之后才去做其他事情就是同步,如果发送请求之后不等着对方回应就去做其他事情就是异步。
阻塞和非阻塞针对被调用者,被调用者受到请求之后,做完请求任务之后才给出反馈就是阻塞,受到请求之后马上给出反馈然后再去做事情就是非阻塞。
当然都是后者好(笑)
Webflux特点:
非阻塞式:在有限资源下,提高系统吞吐量和伸缩性,以Reactor为基础实现响应式编程。
函数式编程:Spring5框架基于java8,Webflux使用java7函数式编程方式实现路由请求。

响应式编程--java实现
什么是响应式编程:
响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流。而相关的计算模型会自动将变化的值通过数据流进行传播。(原生意思,就好像蜜蜂吃熊一样抽象( )
例如,在命令式编程环境中,a=b+c表示将表达式的结果赋给a,而之后改变b或c的值不会影响a。但在响应式编程中,a的值会随着b或c的更新而更新。
我们通过 Observer接口和Observable类来实现响应式编程。
我们首先来认识Observer接口和Observable类。在 Java 程序中,类Observable 和接口 Observer 的最大功能就是实现观察者模式。在Java 应用中,需要被观察的类必须继承于 Observable 类。每个观察者类都需要实现 Observer 接口,其定义如下:
public interface Observer{
// 第一个参数表示被观察者实例,第二参数表示修改的内容
void update(Observable o, Object arg)
}
例如现在房地产调控比较严格,很多购房者都在关注着房子的价格变化,每当房子价格变化时,所有的购房者都可以观察得到。实际上以上的购房者都属于观察者,他们都在关注着房子的价格。这个观察变化的过程就可以成为观察者模式。
观察者和被观察者类
//被观察者继承与Observable类
class house extends Observable {
private float price;
public house() {
}
public house(float price) {
this.price = price;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
super.setChanged();//设置变化
super.notifyObservers(price);
this.price = price;
}
@Override
public String toString() {
return "house{" +
"price=" + price +
'}';
}
}
//每个观察者类都需要实现Observer接口
class HousePriceObserver implements Observer {
private String name;
public HousePriceObserver(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
//观察到更新执行
if(arg instanceof Float){
System.out.println(name + "观察到房价发生变化:" + arg);
}
}
}
模拟变化
public static void main(String[] args) {
house house = new house(40000);
HousePriceObserver h1 = new HousePriceObserver("A");
HousePriceObserver h2 = new HousePriceObserver("B");
HousePriceObserver h3 = new HousePriceObserver("C");
//为被观察者添加观察者
house.addObserver(h1);
house.addObserver(h2);
house.addObserver(h3);
System.out.println(house.getPrice());
//模拟变化后是否通知
house.setPrice(60000);
System.out.println(house.getPrice());
}
说明成功观察
基于Observable类来实现响应式编程:
//被观察者继承Observable
public class Observableimpl extends Observable {
public static void main(String[] args) {
//被观察者
Observableimpl observableimpl = new Observableimpl();
//添加观察者(模拟变化)
observableimpl.addObserver((o,arg)->{
System.out.println("数据流发生变化");//setchange
});
observableimpl.addObserver((o,arg)->{
System.out.println("收到被观察者通知,准备改变");//notifyObservers
});
observableimpl.setChanged();
observableimpl.notifyObservers();
}
}
响应式编程--Reactor实现
响应式编程操作中,Reactor是满足Reactive规范框架。
Reactor有两个核心类,Mono和Flux,这两个类实现接口Publisher,提供丰富操作符。Flux对象实现发布者,返回N个元素;Mono实现发布者,返回0或者1个元素。
Flux和Mono都是数据流的发布者,使用Flux和Mono都可以发出三种数据信号:元素值、错误信号、完成信号,错误信号和完成信号都代表终止信号,终止信号用于告诉订阅者数据流结束了,错误信号终止数据流同时把错误信息传递给订阅者。
使用Reactor请先打xml依赖
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.1.5.RELEASE</version>
</dependency>
public class Reactor {
public static void main(String[] args) {
Flux.just(1,2,3,4,5).subscribe(System.out::print);
Mono.just(1).subscribe(System.out::print);
//其他的方式 发送数据流
//数组
//Integer[] array = {1,2,3,4};
//Flux.fromArray(array);
//列表
//List<Integer> list = Arrays.asList(array);
//Flux.fromIterable(list);
//Stream流
//Stream<Integer> stream = list.stream();
//Flux.fromStream(stream);
}
}
三种信号的特点:
错误信号和完成信号都是终止信号,不能共存的。
如果没有发送任何元素值,而是直接发送错误或者完成信号,表示是空数据流。
如果没有错误信号,没有完成信号,表示是无限数据流。
调用just或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅(subscribe)之后才会触发数据流,不订阅什么都不会发生的。
WebFlux执行流程和核心API
请学完springboot后回来学习2.44和2.45

浙公网安备 33010602011771号