Spring
1、Spring框架概述
Spring 是轻量级的开源的 JavaEE 框架(jar包数量和体积都比较小)
Spring 可以解决企业应用开发的复杂性
-
Spring的两个核心部分:IOC、Aop
-
IOC:控制反转,把创建对象过程交给 Spring 进行管理
-
Aop:面向切面,不修改源代码进行功能增强
-
-
Spring的特点:
-
方便解耦,简化开发
-
Aop编程支持
-
方便程序测试
-
方便和其他框架进行整合
-
方便进行事务操作
-
-
1.1、Spring的一个例子
-
第一步:下载Spring
-
第二步:创建普通java工程,导入相关jar包
需要导入beans
, core
, context
, expression
, commons-logging
5个jar包
(Spring5模块中Core Container部分)
-
第三步:创建普通类,类中创建普通方法
public class User {
public void add(){
System.out.println("add");
}
}
-
第四步:创建Spring配置文件,在配置文件中配置想要创建的对象
-
第五步:测试
public class TestSpring5 {
项目结构:
2、IOC容器
2.1、IOC原理
IOC:Inversion of Control,控制反转,面向对象的一种设计原则。将对象创建和对象之间调用的过程交给Spring进行管理,使耦合度降低。
-
IOC底层原理:
-
IOC使用的技术:xml解析、工厂模式、反射
-
传统方法(先
new
对象再调用对象.方法
)耦合度太高,使用工厂模式可以降低耦合度但仍不是最低,IOC在工厂模式的基础上使用xml解析和反射使耦合度降到最低
-
2.2、IOC接口(BeanFactory)
IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂
IOC容器实现的两种方式(两个接口):
-
BeanFactory:
-
IOC 容器的基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用
-
加载配置文件时候不会创建对象,在获取对象(使用时,即上文第五步中的2.)才去创建对象
-
-
ApplicationContext:
-
BeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人员进行使用
-
加载xml配置文件时候就会把在配置文件中的对象进行创建
-
理论上使用时再创建对象更好,但实际web项目中,通常将耗时耗资源的操作放在最开始一起执行,所以使用ApplicationContext更合适
-
ApplicationContext的实现类
idea中快捷键ctrl+h
查看
-
最主要的两个实现类:用于加载配置文件
-
FileSystemXmlApplicationContext
:使用xml文件的绝对路径 -
ClassPathXmlApplicationContext
:使用xml文件的类加载路径
-
BeanDefinition
Bean对象存在依赖嵌套等关系,所以设计者设计了BeanDefinition,它用来对Bean对象及关系定义;
我们在理解时只需要抓住如下三个要点:
-
BeanDefinition 定义了各种Bean对象及其相互的关系
-
BeanDefinitionReader 这是BeanDefinition的解析器
-
BeanDefinitionHolder 这是BeanDefination的包装类,用来存储BeanDefinition,name以及aliases等。
2.3、IOC操作Bean管理(基于xml)
Bean管理:包括两个操作,Spring创建对象和Spring注入属性
Bean管理操作的两种方式:基于xml、基于注解
基于xml方式创建对象
在spring配置文件中使用bean标签,并添加对应属性,就可以实现对象创建(默认使用无参构造创建对象)
<!--配置User对象创建-->
<bean id="user" class="com.spring5.User"></bean>
属性名 | 用途 |
---|---|
id | 配置和使用对象的唯一标识 |
class | 对象所在类的全路径 |
基于xml方式注入属性
DI:依赖注入,也就是注入属性
第一种注入方式:使用set方法
-
创建类,定义属性和对应的set方法
public class Book {
// 创建属性
private String bname;
private String bauthor;
// 创建属性对应的set方法
public void setBname(String bname) {
this.bname = bname;
}
public void setBauthor(String bauthor) {
this.bauthor = bauthor;
}
} -
在spring配置文件配置对象创建和属性注入
<!--set方法注入属性-->
<bean id="book" class="com.spring5.Book">
<!--使用property完成属性注入
name:类里面的属性名称
value:向属性注入的值
-->
<property name="bname" value="book1"></property>
<property name="bauthor" value="author1"></property>
</bean>
第二种注入方式:使用有参构造
-
创建类,定义属性,创建属性对应的有参构造
public class Order {
private String oname;
private String address;
// 有参构造器
public Order(String oname, String address) {
this.oname = oname;
this.address = address;
}
} -
在spring配置文件中进行配置
<!--有参构造注入属性-->
<bean id="order" class="com.spring5.Order">
<constructor-arg name="oname" value="abc"></constructor-arg>
<constructor-arg name="address" value="china"></constructor-arg>
<!--也可以通过索引值注入-->
<!--<constructor-arg index="0" value="abc"></constructor-arg>-->
</bean>
简化的xml注入方式:p名称空间注入(实际上还是set方法)
-
在配置文件中添加p名称空间
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> -
在bean标签内进行属性注入
<!--p名称空间注入属性-->
<bean id="book" class="com.spring5.Book" p:bname="book2" p:bauthor="author2"></bean>
xml注入其他类型属性
设置特殊属性值
<!--设置null值-->
<property name="address"><null/></property>
<!--设置包含特殊符号的属性值,例如:<<NanJing>> -->
<property name="address">
<value><![CDATA[<<NanJing>>]]></value>
</property>
外部bean:直接在beans标签内部直接定义的bean对象,外部bean可以被多个bean对象引用
将对象作为属性注入
例如:在service类中调用dao类的方法
-
原始方式:在UserService类中创建UserDao对象,通过对象调用方法
-
外部bean注入方式:
-
在UserService类中创建UserDao类型的属性,生成set方法
-
配置xml文件:创建service和dao对象、向userService中注入userDao对象
-
//================UserService类=====================
public class UserService {
// 创建UserDao类型的属性,生成set方法
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void add(){
System.out.println("service add ...");
userDao.update();
// 原始方式:创建UserDao对象,调用方法
// UserDao userDao = new UserDaoImpl();
// userDao.update();
}
}
//================UserDao接口=========================
public interface UserDao {
public void update();
}
//================UserDao接口的实现类=====================
public class UserDaoImpl implements UserDao{
@Override
public void update() {
System.out.println("dao update ...");
}
}
//======================测试==========================
@Test
public void testService(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
<!--======================bean2.xml=========================-->
<!--外部bean-->
<!--1.创建service和dao对象-->
<bean id="userDaoImpl" class="com.spring5.dao.UserDaoImpl"></bean><!--注意这里class是实现类-->
<bean id="userService" class="com.spring5.service.UserService">
<!--2.向userService中注入userDao对象
name:userService类中的属性名称
ref:注入属性的对象的bean id
-->
<property name="userDao" ref="userDaoImpl"></property>
</bean>
内部bean:将一个 bean 用作另一个 bean 的属性
在某个bean标签的内部定义的bean对象,内部bean只能被某个对象的某个属性引用。
例如:一对多关系,员工和部门
//======================部门类==========================
public class Dept {
private String dname;
public void setDname(String dname) {this.dname = dname;}
@Override
public String toString() {
return "Dept{" +
"dname='" + dname + '\'' +
'}';
}
}
//======================员工类==========================
public class Emp {
private String ename;
private String gender;
// 所属部门
private Dept dept;
public void setEname(String ename) {this.ename = ename;}
public void setGender(String gender) {this.gender = gender;}
public void setDept(Dept dept) {this.dept = dept;}
public void detail(){
System.out.println(ename + gender + dept);
}
}
//======================测试==========================
@Test
public void testEmp(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
Emp emp = context.getBean("emp", Emp.class);
emp.detail();
}
<!--======================bean2.xml=========================-->
<!--内部bean-->
<bean id="emp" class="com.spring5.bean.Emp">
<!--设置基本属性-->
<property name="ename" value="Niko"></property>
<property name="gender" value="male"></property>
<!--设置对象类型的属性-->
<property name="dept">
<!--在属性中创建对象并设置创建出来的对象的属性-->
<bean id="dept" class="com.spring5.bean.Dept">
<property name="dname" value="D1"></property>
</bean>
</property>
</bean>
级联赋值:在一个bean对象中注入另一个bean对象(外部bean)的属性并对该属性进行赋值。
方法一:
<!--======================bean2.xml=========================-->
<!--级联赋值-->
<bean id="emp" class="com.spring5.bean.Emp">
<!--设置基本属性-->
<property name="ename" value="Niko"></property>
<property name="gender" value="male"></property>
<!--级联赋值-->
<property name="dept" ref="dept"></property>
</bean>
<bean id="dept" class="com.spring5.bean.Dept">
<property name="dname" value="D2"></property>
</bean>
方法二:
// 在emp中添加dept对象的get方法
// 所属部门
private Dept dept;
public Dept getDept() {
return dept;
}
<!--级联赋值-->
<bean id="emp" class="com.spring5.bean.Emp">
<!--设置基本属性-->
<property name="ename" value="Niko"></property>
<property name="gender" value="male"></property>
<!--级联赋值-->
<property name="dept" ref="dept"></property>
<property name="dept.dname" value="D3"></property>
</bean>
<bean id="dept" class="com.spring5.bean.Dept">
<!--<property name="dname" value="D2"></property>-->
</bean>
xml注入集合类型属性
// 创建类,定义类型属性,生成对应的set方法
public class Stu {
// 1.数组类型属性
private String[] courses;
// 2.list集合类型属性
private List<String> list;
// 3.map集合类型属性
private Map<String, String> maps;
public void setList(List<String> list) {this.list = list;}
public void setMaps(Map<String, String> maps) {this.maps = maps;}
public void setCourses(String[] courses) {this.courses = courses;}
public void test(){
System.out.println(Arrays.toString(courses));
System.out.println(list);
System.out.println(maps);
}
}
//======================测试==========================
@Test
public void testCol(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
Stu stu = context.getBean("stu", Stu.class);
stu.test();
}
<!--======================bean2.xml=========================-->
<!--集合类型的属性注入-->
<bean id="stu" class="com.spring5.collectiontype.Stu">
<!--注入数组类型-->
<property name="courses">
<array>
<value>course01</value>
<value>course02</value>
</array>
</property>
<!--注入list类型-->
<property name="list">
<list>
<value>student01</value>
<value>student02</value>
</list>
</property>
<!--注入map类型-->
<property name="maps">
<map>
<entry key="key01" value="value01"></entry>
<entry key="key02" value="value02"></entry>
</map>
</property>
</bean>
注入的集合为多个对象的集合
<!--======================bean2.xml=========================-->
<!--首先创建多个course对象-->
<bean id="course1" class="com.spring5.collectiontype.Course">
<property name="cname" value="c01"></property>
</bean>
<bean id="course2" class="com.spring5.collectiontype.Course">
<property name="cname" value="c02"></property>
</bean>
<!--集合类型的属性注入-->
<bean id="stu" class="com.spring5.collectiontype.Stu">
<!--注入值为对象的list集合类型-->
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
</bean>
提取集合作为公共部分(让不同的类都能使用这个集合)
<!--======================bean3.xml=========================-->
<?xml version="1.0" encoding="UTF-8"?>
<!--1.引入名称空间util-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!--2.在公共部分声明属性-->
<util:list id="bookList">
<value>book1</value>
<value>book2</value>
<value>book3</value>
</util:list>
<!--3.将公共属性注入到不同类中-->
<bean id="book" class="com.spring5.collectiontype.Book">
<property name="list" ref="bookList"></property>
</bean>
<bean id="book" class="com.spring5.collectiontype.Stu">
<property name="bookList" ref="bookList"></property>
</bean>
</beans>
2.4、FactoryBean
普通bean:配置文件中定义的bean类型class
就是返回类型(配置文件中的bean
即为创建对象操作)
FactoryBean:配置文件定义的类型class
和返回类型不同
// 例如:配置文件中定义的是MyBean类型,实际返回的对象类型为Course
//======================MyBean==========================
// 实现FactoryBean接口
public class MyBean implements FactoryBean<Course> {
// 定义返回的bean的类型
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setCname("course000");
return course;
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}
//======================测试==========================
@Test
public void testMyBean(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
Course course = context.getBean("mybean", Course.class);
System.out.println(course);
}
<!--======================bean4.xml=========================-->
<bean id="mybean" class="com.spring5.factorybean.MyBean"></bean>
2.5、Bean的作用域
在spring中默认情况下bean是单实例对象(多次创建的对象内存地址相同,说明是同一个对象)
通过改变配置文件中的scope
可以设置单实例或多实例:
-
scope = singleton
时,加载 spring 配置文件时就创建单实例对象 -
scope = prototype
时,在调用 getBean 方法时创建多实例对象
<!--prototype表示多实例对象,singleton表示单实例对象-->
<bean id="book" class="com.spring5.collectiontype.Book" scope="prototype">
<property name="list" ref="bookList"></property>
</bean>
2.6、Bean的生命周期
生命周期:从对象创建到对象销毁的过程
-
Bean的生命周期:
-
通过构造器创建bean实例(无参构造)
-
设置bean属性值,直接设置或引用其他bean(调用set方法)
-
调用bean中初始化方法(需要配置)
-
使用bean(获取到了对象)
-
当容器关闭时,调用bean的销毁方法(需要配置)
-
//======================Orders==========================
public class Orders {
private String oname;
// 第一步:执行无参构造创建bean实例
public Orders() {
System.out.println("step1");
}
// 第二步:调用set方法设置属性值
public void setOname(String oname) {
this.oname = oname;
System.out.println("step2");
}
// 第三步:创建初始化方法
// 需要在配置文件配置init-method后才能执行
public void initMehtod(){
System.out.println("step3");
}
// 第五步:创建销毁方法
public void destoryMethod(){
System.out.println("step5");
}
}
//======================测试/使用==========================
@Test
public void testOrders(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
// 第四步:获取bean实例对象
Orders orders = context.getBean("orders", Orders.class);
System.out.println("step4");
System.out.println(orders);
// 手动执行bean的销毁方法
// 由于close方法在ApplicationContext接口的子接口的实现类中,所以需要强转
((ClassPathXmlApplicationContext)context).close();
}
<!--======================bean4.xml=========================-->
<bean id="orders" class="com.spring5.bean.Orders" init-method="initMehtod" destroy-method="destoryMethod">
<property name="oname" value="order01"></property>
</bean>
bean的后置处理器:创建类,实现接口BeanPostProcessor
,创建后置处理器;重写的两个方法分别在初始化之前和初始化之后执行
2.7、自动装配
自动装配:根据指定装配规则(属性名称或者属性类型),Spring 自动将匹配的属性值进行注入
//======================Dept1==========================
public class Dept1 {
@Override
public String toString() {
return "Dept1{}";
}
}
//======================Emp1==========================
public class Emp1 {
public Dept1 dept1;
public void setDept1(Dept1 dept1) {
this.dept1 = dept1;
}
@Override
public String toString() {
return "Emp1{" +
"dept1=" + dept1 +
'}';
}
public void test(){
System.out.println("dept1 = " + dept1);
}
}
//======================测试==========================
@Test
public void testEmp1(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml");
Emp1 emp1 = context.getBean("emp1", Emp1.class);
emp1.test();
}
<!--======================bean5.xml=========================-->
<!--手动装配-->
<!--<bean id="dept1" class="com.spring5.autowire.Dept1"></bean>-->
<!--<bean id="emp1" class="com.spring5.autowire.Emp1">-->
<!-- <property name="dept1" ref="dept1"></property>-->
<!--</bean>-->
<!--自动装配
使用bean标签属性autowire配置自动装配
byName:根据属性名称注入,注入值的bean id和类属性名称应一致
byType:根据属性类型注入
-->
<bean id="dept1" class="com.spring5.autowire.Dept1"></bean>
<bean id="emp1" class="com.spring5.autowire.Emp1" autowire="byName"></bean>
2.8、外部属性文件
-
引入外部属性文件:
-
引入context名称空间
-
引入外部属性文件
-
例如:配置数据库信息
//======================测试==========================
@Test
public void testDruid() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("bean6.xml");
DataSource dataSource = context.getBean("dataSource", DataSource.class);
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
<!--======================bean6.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"
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">
<!--直接配置druid连接池-->
<!--<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">-->
<!--<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>-->
<!--<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true"></property>-->
<!--<property name="username" value="root"></property>-->
<!--<property name="password" value="12345678"></property>-->
<!--</bean>-->
<!--引入外部属性文件-->
<!--1.引入context名称空间-->
<!--2.引入外部属性文件-->
<context:property-placeholder location="classpath:/resources/jdbc.properties"/>
<!--3.配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!--使用表达式${名称}获取属性值-->
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
</beans>
#====================src/resources/jdbc.properties====================
# 这里注意username是mysql关键词,直接使用会报错errorCode1045 state28000,所以需要改成jdbc.username,其他同理
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true
jdbc.user=root
jdbc.password=12345678
2.9、IOC操作Bean管理(基于注解)
-
什么是注解:
-
注解是代码特殊标记,格式:
@注解名称(属性名称=属性值,属性名称=属性值...)
-
注解可以作用在类、方法、属性上
-
使用注解的目的:简化xml配置,直接用注解就可以替代
-
基于注解创建对象
-
Spring针对Bean管理中用于创建对象的注解(功能相同):
-
@Component
:创建对象并标识为普通组件 -
@Service
:创建对象并标识为业务层组件 -
@Controller
:创建对象并标识为控制层组件 -
@Repository
:创建对象并标识为持久层组件
-
-
基于注解方式实现对象创建的步骤:
-
导入aop依赖
spring-aop-5.2.6.RELEASE.jar
-
开启组件扫描(即扫描注解)(需要先引入context名称空间)
-
创建类,在类上添加创建对象的注解
-
//======================UserService1==========================
// 注解中的value即bean的id,即配置和使用对象的唯一标识
// value可以省略不写,默认为首字母小写的类名称 UserService --> userService
@Component(value = "userService") // 相当于<bean id="userService" class""/>
public class UserService1 {
public void add(){
System.out.println("service add......");
}
}
//======================测试==========================
@Test
public void testUserService01(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean7.xml");
UserService1 userService1 = context.getBean("userService", UserService1.class);
userService1.add();
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--引入context名称空间-->
<!--开启组件扫描
如果要扫描多个包,可以用逗号隔开
或者也可以扫描包的上层包目录
-->
<context:component-scan base-package="com.spring5"></context:component-scan>
</beans>
-
开启组件扫描细节配置(设置扫描过滤器)
<!--示例1
use-default-filters="false" 表示不使用默认的filter而使用自己配置的filter
context:include-filter 设置想要扫描的内容
-->
<context:component-scan base-package="com.spring5" use-default-filters="false">
<!--只扫描使用Controller的注解-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--示例2
context:include-filter 设置不想扫描的内容
-->
<context:component-scan base-package="com.spring5" use-default-filters="false">
<!--不扫描使用Controller的注解-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
基于注解注入属性
-
Spring基于注解进行属性注入的常用注解:
-
@AutoWired
:根据属性类型自动装配 -
@Qualifier
:根据属性名称进行注入 -
@Resource
:可以根据类型,也可以根据名称注入 -
@Value
:注入普通类型属性
-
-
使用
@AutoWired
基于注解进行属性注入:(以UserDao接口, UserDaoImpl实现类, UserService1类为例)
-
创建service和dao对象:在service和dao类中添加创建对象注解
-
在service中注入dao对象:在service类中添加dao类型属性,在属性上使用注解
-
//======================UserService1==========================
// 添加创建对象注释
@Service
public class UserService1 {
// 定义dao类型属性(不需要添加set方法)
// 添加注入注解
@Autowired
private UserDao userDao;
public void add(){
System.out.println("service add......");
userDao.update();
}
}
//======================UserDao==========================
public interface UserDao {
public void update();
}
//======================UserDaoImpl==========================
@Repository
public class UserDaoImpl implements UserDao{
@Override
public void update() {
System.out.println("dao update ...");
}
}
//======================测试==========================
@Test
public void testUserService01(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean7.xml");
UserService1 userService1 = context.getBean("userService1", UserService1.class);
userService1.add();
}
<!--引入context名称空间-->
<!--开启组件扫描
如果要扫描多个包,可以用逗号隔开
或者也可以扫描包的上层包目录
-->
<context:component-scan base-package="com.spring5"></context:component-scan>
-
使用
@Qualifier
基于注解进行属性注入:@Qualifier
需要和@Autowired
一起使用-
创建service和dao对象:在service和dao类中添加创建对象注解
-
在service中注入dao对象:在service类中添加dao类型属性,在属性上使用注解,设置和dao对象相同的value值
-
//======================UserService1==========================
// 创建对象注释
@Service
public class UserService1 {
// 定义dao类型属性(不需要添加set方法)
// 添加注入注解
@Autowired // 根据类型进行注入
@Qualifier(value = "userDaoImpl1") // 根据名称进行注入
private UserDao userDao;
public void add(){
System.out.println("service add......");
userDao.update();
}
}
//======================UserDaoImpl==========================
@Repository(value = "userDaoImpl1") // 设置dao对象的名称
public class UserDaoImpl implements UserDao{
@Override
public void update() {
System.out.println("dao update ...");
}
}
-
使用
@Resource
基于注解进行属性注入:如果
@Resource
无法使用,需要先导入jar包javax.annotation-api-1.3.2.jar
//======================UserService1==========================
// 创建对象注释
@Service
public class UserService1 {
// @Resource // 根据类型注入
@Resource(name = "userDaoImpl1") // 根据名称进行注入
private UserDao userDao;
public void add(){
System.out.println("service add......");
userDao.update();
}
}
//======================UserDaoImpl==========================
@Repository(value = "userDaoImpl1") // 设置dao对象的名称
public class UserDaoImpl implements UserDao{
@Override
public void update() {
System.out.println("dao update ...");
}
}
-
使用
@Value
基于注解进行属性注入:@Value
可以注入普通类型的属性
//======================UserService1==========================
// 创建对象注释
@Service
public class UserService1 {
@Value(value = "abc")
private String name;
public void add(){
System.out.println(name);
}
}
完全注解开发
-
创建配置类,替代xml配置文件
//======================SpringConfig==========================
@Configuration // 声明这个类是配置类,用于替代xml配置文件
@ComponentScan(basePackages = {"com.spring5"}) // 设置扫描位置
// 相当于<context:component-scan base-package="com.spring5"></context:component-scan>
public class SpringConfig {
}
-
改写测试类
@Test
public void testUserService02(){
// 加载配置类
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService1 userService1 = context.getBean("userService1", UserService1.class);
userService1.add();
}
实际开发中一般使用Springboot进行完全注解开发
3、Aop
3.1、AOP原理
-
什么是AOP:
-
面向切面(方面)编程,对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发的效率。
-
通过不修改源代码的方式,在主干功能里面添加新功能
-
AOP的例子:在登陆功能中添加一个权限判断
-
-
AOP底层原理:
-
AOP底层使用动态代理,动态代理有两种情况
-
有接口:使用JDK动态代理
通过创建接口实现类的代理对象,增强类的方法
-
没有接口:使用CGLIB动态代理
通过创建子类的代理对象,增强类的方法
-
-
3.2、JDK动态代理
-
使用Proxy类里面的方法创建代理对象,调用
newProxyInstance
方法newProxyInstance
方法的三个参数:-
类加载器
-
增强方法所在的类实现的接口,支持多个接口
-
实现接口
InvocationHandler
,创建代理对象,写增强的方法
-
-
编写JDK动态代理代码
-
创建接口,定义方法
-
创建接口实现类,实现方法
-
使用Proxy类创建接口代理对象
-
//======================JDKProxy.java==========================
public class JDKProxy {
public static void main(String[] args) {
// 创建一个接口数组,包含被增强的方法所在的类所实现的接口
Class[] interfaces = {UserDao.class};
// 方法一:匿名内部类
// Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
// @Override
// public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// return null;
// }
// })
// 创建接口实现类的代理对象
UserDaoImpl userDao = new UserDaoImpl(); // 创建一个实现类对象用于传入代理
// 接口对象=实现类的代理对象(增强后的实现类对象)
UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
int result = dao.add(1, 2);
System.out.println("result = " + result);
}
}
// 方法二:创建代理对象,实现InvocationHandler接口
class UserDaoProxy implements InvocationHandler{
// 1.传入被代理的对象(通过有参构造器传入)
private Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}
// 2.增强的逻辑(用于增强方法)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// method表示被增强的方法,args表示传入的参数
// 增强方法之前
System.out.println("before... " + method.getName() + " ars... " + Arrays.toString(args));
// method.invoke 执行被增强的方法
Object res = method.invoke(obj, args);
// 增强方法之后
System.out.println("after..." + obj);
return res;
}
}
//======================UserDao接口==========================
public interface UserDao {
public int add(int a, int b);
public String update(String id);
}
//======================UserDaoImpl.java==========================
public class UserDaoImpl implements UserDao{
@Override
public int add(int a, int b) {
return a+b;
}
@Override
public String update(String id) {
return id;
}
}
3.3、AOP术语
-
连接点:类中可以被增强的方法
-
切入点:实际被增强的方法
-
通知(增强):实际增强的逻辑部分
-
通知有多种类型:
-
前置通知:增强部分在方法执行前执行
-
后置通知:增强部分在方法执行后执行
-
环绕通知:增强部分在方法执行前后都有执行
-
异常通知:增强部分在方法出现异常时执行
-
最终通知:类似
finally
,无论如何增强部分最后都会执行
-
-
-
切面:把通知应用到切入点的过程就是切面(切面是一个动作)(即加入增强功能)
3.4、AOP操作的准备工作
-
Spring 框架一般都是基于 AspectJ 实现 AOP 操作
AspectJ 不是 Spring 组成部分,是独立的 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使用进行 AOP 操作
-
基于 AspectJ 实现 AOP 操作
-
基于xml配置文件实现
-
基于注解方式实现
-
-
需要引入的相关依赖
-
切入点表达式
-
切入点表达式的作用:说明要对哪个类的哪个方法进行增强
-
语法结构:
execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
// 例子1:对com.spring5.dao.BookDao 类里面的 add 进行增强
execution(* com.spring5.dao.BookDao.add(..)) // *表示任意权限,返回类型省略
// 例子2:对com.spring5.dao.BookDao 类中的所有方法进行增强
execution(* com.spring5.dao.BookDao.*(..))
// 例子3:对com.spring5.dao 包中的所有类的所有方法进行增强
execution(* com.spring5.dao.*.* (..))
-
3.5、AOP操作(使用AspectJ注解)
-
使用AspectJ注解进行AOP操作的步骤:
-
创建类,在类中定义方法
-
创建增强类(编写增强逻辑)
-
在增强类中创建方法,让不同方法代表不同通知(增强)类型
-
-
进行通知的配置
-
在spring配置文件中开启注解扫描
-
使用注解创建User和UserProxy类的对象
-
在增强类上添加注解
@Aspect
,生成代理对象 -
在spring配置文件中开启生成代理对象(对包含注解
@Aspect
的类生成代理对象)
-
-
配置不同类型的通知
-
在增强类中通知方法上面添加通知类型注解,并使用切入点表达式配置
-
-
//======================User.java==========================
// 被增强的类
@Component // 使用注解创建类的对象
public class User {
public void add(){
System.out.println("add...");
}
}
//======================UserProxy.java==========================
// 增强的类
@Component // 使用注解创建类的对象
@Aspect //生成代理对象
public class UserProxy {
// 前置通知:在方法执行之前执行
// @Before表示前置通知 valur后加切入点表达式
@Before(value = "execution(* com.spring5.aopanno.User.add(..))")
public void before(){
System.out.println("before...");
}
// 后置(返回)通知:在方法返回结果之后执行(有异常不执行)
@AfterReturning(value = "execution(* com.spring5.aopanno.User.add(..))")
public void afterReturning(){
System.out.println("afterReturning...");
}
// 最终通知:在方法执行之后执行
@After(value = "execution(* com.spring5.aopanno.User.add(..))")
public void after(){
System.out.println("after...");
}
// 异常通知:有异常才执行
@AfterThrowing(value = "execution(* com.spring5.aopanno.User.add(..))")
public void afterThrowing(){
System.out.println("afterThrowing...");
}
// 环绕通知
@Around(value = "execution(* com.spring5.aopanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 在方法执行前,前置通知之前执行
System.out.println("aroundBefore...");
// 执行被增强的方法
proceedingJoinPoint.proceed();
// 在方法执行后,最终通知之前执行(有异常不执行)
System.out.println("aroundAfter...");
}
}
//======================测试==========================
@Test
public void testAopAnno(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
User user = context.getBean("user", User.class);
user.add();
}
/* 测试结果:
aroundBefore...
before...
add...
aroundAfter...
after...
afterReturning...
*/
<!--==================bean1.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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--1.引入context和aop名称空间-->
<!--2.开启注解扫描-->
<context:component-scan base-package="com.spring5"></context:component-scan>
<!--3.开启Aspect生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
-
使用
@Pointcut
在增强的类中抽取相同的切入点
// 抽取相同切入点
@Pointcut(value = "execution(* com.spring5.aopanno.User.add(..))")
public void pointdemo(){
}
// 直接使用方法名称代替切入点表达式
@Before(value = "pointdemo()")
public void before(){
System.out.println("before...");
}
-
当有多个增强类对同一个方法进行增强时,可以设置增强类的优先级
在增强类上面添加注解
@Order(数字类型值)
,值越小优先级越高(注意是对类设置)
@Component
@Aspect
@Order(2)
public class PersonProxy {
// 抽取相同切入点
@Pointcut(value = "execution(* com.spring5.aopanno.User.add(..))")
public void pointdemo(){
}
// 直接使用方法名称代替切入点表达式
@Before(value = "pointdemo()")
@Order(3)
public void personBefore(){
System.out.println("personBefore...");
}
}
-
完全使用注解开发
创建配置类,不需要创建xml配置文件
@Configuration // 表示当前类为配置类
@ComponentScan(basePackages = {"com.spring5"}) // 开启注解扫描
@EnableAspectJAutoProxy(proxyTargetClass = true) // 生成代理对象
public class ConfigAop {
}
3.6、AOP操作(使用AspectJ配置文件)
了解即可
-
使用AspectJ配置文件进行AOP操作的步骤:
-
创建两个类,增强类和被增强类,创建方法
-
在spring配置文件中创建两个类对象
-
在spring配置文件中配置切入点
-
//======================Book.java==========================
public class Book {
public void buy(){
System.out.println("buy...");
}
}
//======================BookProxy.java==========================
public class Book {
public void buy(){
System.out.println("buy...");
}
}
//======================测试==========================
@Test
public void testAopXml(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
Book book = context.getBean("book", Book.class);
book.buy();
}
<!--==================bean2.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: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">
<!--1.引入aop名称空间-->
<!--2.创建对象-->
<bean id="book" class="com.spring5.aopxml.Book"></bean>
<bean id="bookProxy" class="com.spring5.aopxml.BookProxy"></bean>
<!--3.配置aop增强-->
<aop:config>
<!--切入点(被增强方法)-->
<aop:pointcut id="p" expression="execution(* com.spring5.aopxml.Book.buy(..))"/>
<!--配置切面(增强类)-->
<aop:aspect ref="bookProxy">
<!--增强类的增强方法before作用在具体方法(切入点)上-->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
</beans>
4、JdbcTemplate
4.1、概念和准备
-
什么是JdbcTemplate
-
JdbcTemplate 是 Spring 框架对 JDBC 的封装,使用 JdbcTemplate 可以方便的实现对数据库操作
-
-
准备工作
-
引入相关依赖
-
在spring配置文件中配置数据库连接池
-
配置JdbcTemplate对象,注入DataSource
-
创建service类、dao类,在service中注入dao,在dao中注入jdbcTemplate对象(service被dao增强,dao被jdbcTemplate增强)
-
<!--==================bean3.xml=========================-->
<!--通过外部属性文件配置数据库连接池-->
<!--1.引入context名称空间-->
<!--2.引入外部属性文件-->
<context:property-placeholder location="classpath:/com/spring5/resources/jdbc.properties"/>
<!--3.配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!--使用表达式${名称}获取属性值-->
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--创建JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启组件扫描-->
<context:component-scan base-package="com.spring5"></context:component-scan>
//======================BookService.java==========================
@Service
public class BookService {
// 注入dao类
@Autowired
private BookDao bookDao;
}
//======================BookDao接口==========================
public interface BookDao {
}
//======================BookDaoImpl.java==========================
@Repository
public class BookDaoImpl {
// 注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
}
4.2、JdbcTemplate操作数据库(增删改)
-
JdbcTemplate添加数据步骤:
-
对应数据库创建实体类,生成
getter
和setter
方法 -
编写service和dao
-
在dao中进行数据库添加操作,调用
JdbcTemplate.update()
方法,方法的第一个参数是sql语句,第二个参数是可变参数,填充占位符的值
-
-
测试类
-
//======================Book.java==========================
public class Book {
private String userId;
private String username;
private String ustatus;
public String getUserId() {return userId;}
public void setUserId(String userId) {this.userId = userId;}
public String getUsername() {return username;}
public void setUsername(String username) {this.username = username;}
public String getUstatus() {return ustatus;}
public void setUstatus(String ustatus) {this.ustatus = ustatus;}
}
//======================BookService.java==========================
@Service
public class BookService {
// 注入dao类
@Autowired
private BookDao bookDao;
// 数据库添加的方法(使用注入的dao)
public void addBook(Book book){
bookDao.add(book);
}
// 数据库修改的方法
public void updateBook(Book book){
bookDao.updateBook(book);
}
// 数据库删除的方法
public void deleteBook(String id){
bookDao.deleteBook(id);
}
}
//======================BookDao接口==========================
public interface BookDao {
void add(Book book);
void updateBook(Book book);
void deleteBook(String id);
}
//======================BookDaoImpl.java==========================
@Repository
public class BookDaoImpl implements BookDao{
// 注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
// 数据库的添加方法(使用注入的JdbcTemplate)
@Override
public void add(Book book) {
// 1.创建sql语句
String sql = "insert into t_book values(?,?,?)";
// 2.调用方法实现
Object[] args = {book.getUserId(),book.getUsername(),book.getUstatus()};
jdbcTemplate.update(sql, args);
}
// 数据库的修改方法
@Override
public void updateBook(Book book) {
String sql = "update t_book set username=?,ustatus=? where user_id=?";
Object[] args = {book.getUsername(), book.getUstatus(), book.getUserId()};
int update = jdbcTemplate.update(sql, args);
System.out.println(update);
}
// 数据库的删除方法
@Override
public void deleteBook(String id) {
String sql = "delete from t_book where user_id=?";
int update = jdbcTemplate.update(sql, id);
System.out.println(update);
}
}
//======================测试==========================
@Test
public void testJdbcTemplate() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
BookService bookService = context.getBean("bookService", BookService.class);
// 添加
// Book book = new Book();
// book.setUserId("1");
// book.setUsername("name1");
// book.setUstatus("status1");
// bookService.addBook(book);
// 修改
// Book book = new Book();
// book.setUserId("1");
// book.setUsername("name11");
// book.setUstatus("status11");
// bookService.updateBook(book);
// 删除
// bookService.deleteBook("1");
}
<!--xml配置和上一节相同-->
4.3、JdbcTemplate操作数据库(查询)
-
查询返回某个值:
jdbcTemplate.queryForObject
//======================BookService.java==========================
@Service
public class BookService {
// 注入dao类
@Autowired
private BookDao bookDao;
// 查询表的总记录数
public int findCount(){
return bookDao.selectCount();
}
}
//======================BookDao接口==========================
public interface BookDao {
int selectCount();
}
//======================BookDaoImpl.java==========================
@Repository
public class BookDaoImpl implements BookDao{
// 注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
// 查询返回某个值的方法
@Override
public int selectCount() {
String sql = "select count(*) from t_book";
// queryForObject有两个参数,第一个参数为sql语句,第二个参数为返回类型Class
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
return count;
}
}
//======================测试==========================
@Test
public void testJdbcTemplate() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
BookService bookService = context.getBean("bookService", BookService.class);
// 调用方法,查询返回某个值
System.out.println(bookService.findCount());
}
-
查询返回对象:
jdbcTemplate.queryForObject
queryForObject的三个参数: 第一个为sql语句 第二个为RowMapper接口,使用接口中的实现类可以封装查询返回的数据 第三个为sql语句中的值
//======================BookService.java==========================
@Service
public class BookService {
// 注入dao类
@Autowired
private BookDao bookDao;
// 查询返回一个对象
public Book findOne(String id){
return bookDao.findBookInfo(id);
}
}
//======================BookDao接口==========================
public interface BookDao {
Book findBookInfo(String id);
}
//======================BookDaoImpl.java==========================
@Repository
public class BookDaoImpl implements BookDao{
// 注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
// 查询返回对象
@Override
public Book findBookInfo(String id) {
String sql = "select user_id, username, ustatus from t_book where user_id=?";
// queryForObject的三个参数:
// 第一个为sql语句
// 第二个为RowMapper接口,用于封装不同类型数据,使用接口中的实现类可以实现对查询返回的数据封装
// 第三个为sql语句中的值
Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
return book;
}
}
//======================测试==========================
@Test
public void testJdbcTemplate() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
BookService bookService = context.getBean("bookService", BookService.class);
// 查询返回对象
Book book = bookService.findOne("1");
System.out.println(book.toString());
}
-
查询返回集合:
jdbcTemplate.query
//======================BookService.java==========================
@Service
public class BookService {
// 注入dao类
@Autowired
private BookDao bookDao;
// 查询返回一个集合
public List<Book> findAll(){
return bookDao.findAllBook();
}
}
//======================BookDao接口==========================
public interface BookDao {
List<Book> findAllBook();
}
//======================BookDaoImpl.java==========================
@Repository
public class BookDaoImpl implements BookDao{
// 注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
// 查询返回集合
@Override
public List<Book> findAllBook() {
String sql = "select * from t_book";
List<Book> books = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
return books;
}
}
//======================测试==========================
@Test
public void testJdbcTemplate() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
BookService bookService = context.getBean("bookService", BookService.class);
// 查询返回集合
List<Book> list = bookService.findAll();
list.forEach(System.out::println);
}
4.4、JdbcTemplate操作数据库(批量操作)
批量操作方法:jdbcTemplate.batchUpdate
-
批量添加:
jdbcTemplate.batchUpdate
batchUpdate的两个参数:第一个为sql语句,第二个为list集合,包含要添加的多条记录
//======================BookService.java==========================
@Service
public class BookService {
// 注入dao类
@Autowired
private BookDao bookDao;
// 批量添加
public void batchAdd(List<Object[]> batchArgs){
bookDao.batchAddBook(batchArgs);
}
}
//======================BookDao接口==========================
public interface BookDao {
void batchAddBook(List<Object[]> batchArgs);
}
//======================BookDaoImpl.java==========================
@Repository
public class BookDaoImpl implements BookDao{
// 注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
// 批量添加
@Override
public void batchAddBook(List<Object[]> batchArgs) {
String sql = "insert into t_book values(?,?,?)";
// 使用batchUpdate进行批量操作
// 直接传入集合即可,语句会遍历集合并逐个添加
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
}
//======================测试==========================
@Test
public void testJdbcTemplate() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
BookService bookService = context.getBean("bookService", BookService.class);
// 批量添加
List<Object[]> books = new ArrayList<>();
Object[] o1 = {"3" , "33", "333"};
Object[] o2 = {"4" , "44", "444"};
Object[] o3 = {"5" , "55", "555"};
books.add(o1);
books.add(o2);
books.add(o3);
bookService.batchAdd(books);
}
-
批量修改:
jdbcTemplate.batchUpdate
//======================BookService.java==========================
@Service
public class BookService {
// 注入dao类
@Autowired
private BookDao bookDao;
// 批量修改
public void batchUpdate(List<Object[]> batchArgs){
bookDao.batchUpdateBook(batchArgs);
}
}
//======================BookDao接口==========================
public interface BookDao {
void batchUpdateBook(List<Object[]> batchArgs);
}
//======================BookDaoImpl.java==========================
@Repository
public class BookDaoImpl implements BookDao{
// 注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
// 批量修改
@Override
public void batchUpdateBook(List<Object[]> batchArgs) {
String sql = "update t_book set username=?,ustatus=? where user_id=?";
// 直接传入集合即可,语句会遍历集合并逐个修改
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
}
//======================测试==========================
@Test
public void testJdbcTemplate() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
BookService bookService = context.getBean("bookService", BookService.class);
// 批量修改
List<Object[]> books = new ArrayList<>();
Object[] o1 = {"333", "33","3"};
Object[] o2 = {"444", "44", "4"};
Object[] o3 = {"555", "55", "5"};
books.add(o1);
books.add(o2);
books.add(o3);
bookService.batchUpdate(books);
}
-
批量删除:
jdbcTemplate.batchUpdate
//======================BookService.java==========================
@Service
public class BookService {
// 注入dao类
@Autowired
private BookDao bookDao;
// 批量删除
public void batchDelete(List<Object[]> batchArgs){
bookDao.batchDeleteBook(batchArgs);
}
}
//======================BookDao接口==========================
public interface BookDao {
void batchDeleteBook(List<Object[]> batchArgs);
}
//======================BookDaoImpl.java==========================
@Repository
public class BookDaoImpl implements BookDao{
// 注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
// 批量删除
@Override
public void batchDeleteBook(List<Object[]> batchArgs) {
String sql = "delete from t_book where user_id=?";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
}
//======================测试==========================
@Test
public void testJdbcTemplate() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
BookService bookService = context.getBean("bookService", BookService.class);
// 批量删除
List<Object[]> books = new ArrayList<>();
Object[] o1 = {"3"};
Object[] o2 = {"4"};
Object[] o3 = {"5"};
books.add(o1);
books.add(o2);
books.add(o3);
bookService.batchDelete(books);
}
5、事务管理
5.1、事务概念
事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败
5.2、搭建事务操作环境
-
数据库中创建表,添加记录
以转账为例
create table t_account
(
id varchar(20) not null
primary key,
username varchar(50) null,
money int null
); -
创建service,搭建dao,完成对象创建和建立注入关系
-
在service中注入dao,在dao注入JdbcTemplate,在JdbcTemplate中注入DataSource
-
-
在dao中创建增加money和减少money的两个方法(也可以写成同一个修改money的方法),在service中创建转账的方法
//======================UserService.java==========================
@Service
public class UserService {
// 在service中注入dao
@Autowired
private UserDao userDao;
public void accountMoney(){
try {
// 第一步:开启事务
// 第二步:进行业务操作
userDao.reduceMoney(100, "mary");
// 模拟异常
// int i = 10/0;
userDao.addMoney(100, "lucy");
// 第三步:没有异常则提交事务
} catch(Exception e){
// 第四步:有异常时回滚事务
}
}
}
//======================UserDao接口==========================
public interface UserDao {
public void addMoney(int m, String username);
public void reduceMoney(int m, String username);
}
//======================UserDaoImpl.java==========================
@Repository
public class UserDaoImpl implements UserDao{
// 在dao中注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void addMoney(int m, String username) {
String sql = "update t_account set money=money-? where username=?";
int update = jdbcTemplate.update(sql, m, username);
}
@Override
public void reduceMoney(int m, String username) {
String sql = "update t_account set money=money+? where username=?";
int update = jdbcTemplate.update(sql, m, username);
}
}
//======================测试==========================
@Test
public void testAccount(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.accountMoney();
}
<!--xml配置和上一节相同-->
事务操作过程:开启事务,进行业务操作,没有发生异常则提交事务,出现异常时事务回滚
5.3、Spring事务管理介绍
建议将事务添加到JavaEE三层结构(Web Service Dao)的Service层(业务逻辑层)
在Spring中进行事务管理操作有两种方式:编程式事务管理、声明式事务管理(常用)
-
声明式事务管理:声明式事务管理底层使用的是AOP
-
基于注解方式(最常用)
-
基于xml配置文件方式
-
Spring事务管理API:Spring提供了一个接口PlatformTransactionManager
,代表事务管理器,针对不同的数据库操作框架提供了不同的实现类,对于JdbcTemplate使用DataSourceTransactionManager
5.4、基于注解的声明式事务管理
-
使用基于注解的声明式事务管理的步骤:
-
在spring配置文件中配置事务管理器
-
在spring配置文件中引入
tx
名称空间,开启事务注解 -
在service类(或类中的方法)上添加事务注解
@Transactional
,如果在类上添加则说明对类中每一个方法都启用事务
-
<!--1.创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2.引入tx名称空间-->
<!--2.开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
//===================UserService.java=======================
@Service
@Transactional // 事务注解,启用事务
public class UserService {
}
5.5、声明式事务管理的参数配置
在service类添加的注解@Transactional
中可以配置事务相关参数
-
propagation()
:事务传播行为-
-
-
默认为required,通过propagation配置:
@Transactional(propagation = Propagation.REQUIRED)
-
-
isolation()
:事务隔离级别-
-
默认为可重复读,通过isolation配置:
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
-
-
timeout
:超时时间-
超时不提交自动回滚
-
默认为-1,时间以秒为单位,通过timeout配置
-
-
readOnly
:是否只读-
默认为false,表示可增删改查
-
通过readOnly配置
-
-
rollbackFor
:回滚-
设置出现哪些异常时进行事务回滚
-
-
noRollbackFor
:不回滚-
设置出现哪些异常时不回滚
-
5.6、基于xml的声明式事务管理
-
基于xml进行声明式事务管理的步骤:
-
在Spring配置文件中进行配置
-
配置事务管理器
-
配置通知
-
配置切入点和切面
-
-
<!--===================bean4.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: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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--开启组件扫描-->
<context:component-scan base-package="com.spring5"></context:component-scan>
<!--引入外部属性文件-->
<!--1.引入context名称空间-->
<!--2.引入外部属性文件-->
<context:property-placeholder location="classpath:/com/spring5/resources/jdbc.properties"/>
<!--3.配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!--使用表达式${名称}获取属性值-->
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--创建JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--1.创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--引入tx名称空间-->
<!--2.配置通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定在符合哪种规则的方法上添加事务,并设置事务管理参数-->
<!--通过方法名称添加-->
<tx:method name="accountMoney" propagation="REQUIRED" isolation="REPEATABLE_READ"/>
<tx:method name="account*"/>
</tx:attributes>
</tx:advice>
<!--3.配置切入点和切面-->
<!--引入aop名称空间-->
<aop:config>
<!--配置切入点-->
<!--切入点为UserService中的所有方法-->
<aop:pointcut id="pt" expression="execution(* com.spring5.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
</beans>
5.7、完全注解声明式事务管理
-
完全注解声明式事务管理步骤
-
创建配置类,使用配置类代替xml配置文件
-
//====================TxConfig.java=======================
@Configuration // 表示这个类是配置类
@ComponentScan(basePackages = "com.spring5") // 开启组件扫描
@EnableTransactionManagement // 开启事务
public class TxConfig {
// 创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true");
dataSource.setUsername("root");
dataSource.setPassword("12345678");
return dataSource;
}
// 创建JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// 注入dataSource
// 在IOC容器中已经存在dataSource对象,直接注入即可
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
// 创建事务管理器对象
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
//======================测试========================
@Test
public void testAccount2(){
ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.accountMoney();
}
6、Spring5新特性
6.1、整合日志框架
整个 Spring5 框架的代码基于Java8,运行时兼容JDK9,许多不建议使用的类和方法在代码库中删除
Spring 5.0 框架自带了通用的日志封装 Log4j2
-
Spring5.0中使用Log4j2的步骤:
-
导入jar包:
log4j-api-2.11.2.jar
,log4j-core-2.11.2.jar
,log4j-slf4j-impl-2.11.2.jar
,slf4j-api-1.7.30.jar
-
创建log4j2.xml配置文件
-
<!--===================log4j2.xml========================-->
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出-->
<configuration status="INFO">
<!--先定义所有的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>
6.2、@Nullable注解
Spring5 框架核心容器支持 @Nullable
注解
@Nullable
注解可以使用在方法上,属性上,方法的参数上,表示方法返回可以为空,属性值可以为空,参数值可以为空
6.3、函数式注册对象
Spring5 核心容器支持函数式风格 GenericApplicationContext
//函数式风格创建对象,交给 spring 进行管理
@Test
public void testGenericApplicationContext() {
// 1.创建 GenericApplicationContext 对象
GenericApplicationContext context = new GenericApplicationContext();
// 2.调用 context 的方法对象注册
context.refresh();
context.registerBean("user1",User.class,() -> new User());
// 3.获取在 spring 注册的对象
// User user = (User)context.getBean("com.atguigu.spring5.test.User");
User user = (User)context.getBean("user1");
System.out.println(user);
}
6.4、整合JUnit5单元测试框架
Spring5 支持整合 JUnit5
-
整合JUnit4
-
引入相关jar包:
spring-test-5.2.6.RELEASE.jar
-
使用注解方式创建测试类
-
//====================JTest4.java=======================
@RunWith(SpringJUnit4ClassRunner.class) // 引入单元测试框架
@ContextConfiguration("classpath:bean4.xml") // 加载配置文件
public class JTest4 {
@Autowired // 创建对象
private UserService userService;
@Test
public void test1(){
userService.accountMoney();
}
}
-
整合JUnit5
-
引入相关依赖:
JUnit5
-
使用注解创建测试类
-
//====================JTest5.java=======================
// @ExtendWith(SpringExtension.class) // 引入单元测试框架
// @ContextConfiguration("classpath:bean4.xml") // 加载配置文件
// 使用一个复合注解替代上面两个注解完成整合
@SpringJUnitConfig(locations = "classpath:bean4.xml")
public class JTest5 {
@Autowired
private UserService userService;
@Test
public void test2(){
userService.accountMoney();
}
}
//====================JTest5.java=======================
6.5、SpringWerflux使用
SpringWebflux介绍
前置知识:SpringMVC、Springboot、Maven、Java8新特性
SpringWebflux 是 Spring5 添加新的模块,用于 web 开发,功能和 SpringMVC 类似,Webflux 使用当前一种比较流程响应式编程出现的框架。
传统 web 框架,比如 SpringMVC,基于 Servlet 容器,Webflux 是一种异步非阻塞的框架,异步非阻塞的框架在 Servlet3.1 以后才支持,核心是基于 Reactor 的相关 API 实现的。
异步非阻塞: 异步和同步针对调用者,调用者发送请求,如果等着对方回应之后才去做其他事情就是同步,如果发送请求之后不等着对方回应就去做其他事情就是异步; 阻塞和非阻塞针对被调用者,被调用者受到请求之后,做完请求任务之后才给出反馈就是阻塞,受到请求之后马上给出反馈然后再去做事情就是非阻塞。
Webflux的特点: 第一 非阻塞式:在有限资源下,提高系统吞吐量和伸缩性,以 Reactor 为基础实现响应式编程 第二 函数式编程:Spring5 框架基于 java8,Webflux 使用 Java8 函数式编程方式实现路由请求
Webflux和SpringMVC: 第一 两个框架都可以使用注解方式,都运行在 Tomet 等容器中 第二 SpringMVC 采用命令式编程,Webflux 采用异步响应式编程
响应式编程
响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似"=B1+C1"的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。
Webflux执行流程和核心API
SpringWebflux(基于注解编程模型)
SpringWebflux(基于函数式编程模型)