一、Spring引入
1.软件开发原则
1.OCP:软件七大开发原则中最基本的一个原则,开闭原则。对拓展开放,对修改关闭。 OCP开闭原则的核心:拓展系统功能的时候,没有修改以前的代码,就是符合OCP原则的。
2.DIP:依赖倒置原则,上层不依赖下层,就是符合依赖倒置原则的。 目的:降低程序的耦合度,提高拓展力。依赖倒置原则的核心:面向接口编程,面向抽象编程,不要面向具体编程。
3.可以采用控制反转IoC(Inversion of Control)编程思想使程序符合OCP和DIP原则。不在程序中采用硬编码的方式来new对象和维护对象之间的关系了。
2.Spring框架
1.Spring框架是一个容器,Spring框架实现了控制反转IoC思想,Spring对象可以new对象和维护对象之间的关系。
2.依赖注入DI(Dependency Injection):是控制反转思想的具体实现,对象A和对象B的关系通过注入的手段来维护,包括set注入(执行set方法给属性赋值)和构造方法注入(执行构造方法给属性赋值)。
二、Spring概述
.简介
Spring是开源框架,是轻量级的IoC(控制反转)和AOP(面向切面)的容器框架,为简化开发而生,让程序员只关注核心业务的实现。
2.Spring八大模块
1.Spring Core模块:Spring框架的核心,负责控制反转(IoC)。
2.Spring AOP模块:面向切面编程。
3.Spring Web MVC模块:SSM中的Spring MVC。
4.Spring Webflux模块:Spring提供的响应式web框架。
5.Spring Web模块:支持集成其他的MVC框架。
6.Spring DAO模块:对JDBC操作的API。
7.Spring ORM模块:支持常见的ORM框架(Mybatis等)
8.Spring Context模块:Spring的上下文,包括国际化消息、事件传播、企业服务等。
3.Spring特点
1.轻量,非侵入式的。
2.控制反转IoC。
3.面向切面AOP。
4.容器。
5.框架。
4.Spring下载
https://spring.p2hp.com
docs:文档
libs:Spring框架的核心
schema:配置文件的约束
5.Spring的jar包
spring-aop:AOP的类
spring-aspects:AOP的支持
spring-beans:核心jar包
spring-context:为spring核心提供了大量的拓展
spring-core:spring框架自己的工具
spring-jdbc:spring自己的增删改查
spring-orm:集成ORM框架的支持
spring-tx:事务管理支持
spring-web:集成MVC框架的jar包
spring-webflux:响应式
spring-webmvc:SpringMVC框架的类库
如果只是使用SpringIoC功能的话,只需采用Maven引入context即可,在引入spring-context之后,会将需要的其他依赖全部引入,很方便。
三、第一个Spring程序
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.qiye</groupId>
<artifactId>spring6-002-first</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!-- 引入spring-context之后,就表示将spring的基础依赖引入了 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.2.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
spring.xml
bean标签的两个重要属性:id和class
id:bean的唯一标识。
class:必须填写类的全限定类名。
<?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">
<!--spring的配置文件,IDEA为我们提供了这个模板-->
<!-- 文件名随意,文件最好放到resources中-->
<!-- 配置bean -->
<bean id="userBean" class="com.qiye.spring6.bean.User"/>
</beans>
测试
ApplicationContext:应用上下文(Spring容器),是一个接口,它的一个实现类是ClassPathXmlApplicationContext,这个类是专门从类路径中加载Spring配置文件的一个Spring上下文对象。
@Test
public void testFirstSpring(){
//1.获取Spring容器对象,这行代码会启动Spring容器,解析spring.xml文件并且实例化所有的bean对象并放到Spring容器中。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
//2.根据bean的id从Spring容器中获取bean对象
Object userBean = applicationContext.getBean("userBean");
System.out.println(userBean);
}
细节
1.bean的id是唯一标识,不可以重复。
2.Spring框架底层创建对象的方法:使用反射机制调用无参数构造方法来创建对象。(所以bean类要保证有无参数构造方法)
Class clazz = Class.forName("com.qiye.spring6.bean.User");
Object obj = clazz.newInstance();
3.创建好的对象会存放到Map<String,Object>中,key为bean的id,value为bean对象。
4.Spring框架的配置文件的名字随意,可以有多个,在new ClassPathXmlApplicationContext时也可以写多个配置文件路径。
5.Spring配置文件中也可以配置非自定义的类,例如java.util.Date。
6.getBean时,如果id不存在,会报错。
7.getBean时,通过第二个参数可以指定返回的bean类的类型,我们就无需手动转型了。
User userBean = applicationContext.getBean("userBean", User.class);
8.ClassPathXmlApplicationContext要获取的文件如果不在类路径中,我们可以使用FileSystemXmlApplicationContext通过绝对路径来获取文件。
ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext("d:/spring6.xml");
9.ApplicationContext接口的超级父接口为BeanFactory,是IoC容器的顶级接口。(Spring的IoC容器底层实际上使用了:工厂模式+反射机制+XML解析)
启用Log4j2日志框架
Spring底层支持集成的日志框架为Log4j2。
1.引入依赖
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.19.0</version>
</dependency>
2.在类路径下配置log4j2.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<loggers>
<!--
level指定日志级别,从低到高的优先级:
ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
-->
<root level="DEBUG">
<appender-ref ref="spring6log"/>
</root>
</loggers>
<appenders>
<!--输出日志信息到控制台-->
<console name="spring6log" target="SYSTEM_OUT">
<!--控制日志输出的格式-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
</console>
</appenders>
</configuration>
3.使用
//获取FirstSpringTest类的日志记录器对象
Logger logger = LoggerFactory.getLogger(FirstSpringTest.class);
//记录日志,根据不同级别输出日志
logger.info("消息");
logger.debug("调试信息");
logger.error("错误信息");
四、Spring对IoC的实现
1.概述
IoC控制反转思想:将对象的创建和对象之间的关系维护交给第三方容器负责。
依赖注入DI实现了IoC思想。
Spring通过依赖注入的方式来完成Bean管理。
Bean管理:Bean对象的创建以及Bean对象之间关系的维护。
依赖:对象之间的关联关系。
注入:一种数据传递行为,通过注入行为让对象之间产生关系。
依赖注入分为:set注入和构造方法注入。
2.set注入
基于set方法实现的,所以必须提供set方法。
UserDao
public class UserDao {
private static final Logger logger = LoggerFactory.getLogger(UserDao.class);
public void insert(){
logger.info("数据库正在保存用户信息");
}
}
UserService
public class UserService {
private UserDao userDao;
//set注入,必须提供set方法,Spring容器会调用这个set方法给userDao赋值。
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void saveUser(){
//保存用户信息到数据库
userDao.insert();
}
}
spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置dao -->
<bean id="userDaoBean" class="com.qiye.spring6.dao.UserDao"/>
<!-- 配置service -->
<bean id="userServiceBean" class="com.qiye.spring6.service.UserService">
<!-- 想让Spring调用对应的set方法,需要配置property标签-->
<!-- name为set方法的方法名去掉get,将剩下的首字母变小写 -->
<!-- ref翻译为引用references 后面指定的是要注入的bean的id-->
<property name="userDao" ref="userDaoBean"/>
</bean>
</beans>
测试
@Test
public void testSetDI(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userServiceBean = applicationContext.getBean("userServiceBean", UserService.class);
userServiceBean.saveUser();
}
3.构造注入
通过构造方法给属性赋值。
VipDao
public class VipDao {
private static final Logger logger = LoggerFactory.getLogger(VipDao.class);
public void insert(){
logger.info("数据库正在保存Vip信息");
}
}
CustomerService
public class CustomerService {
private UserDao userDao;
private VipDao vipDao;
public CustomerService(UserDao userDao, VipDao vipDao) {
this.userDao = userDao;
this.vipDao = vipDao;
}
public void save(){
userDao.insert();
vipDao.insert();
}
}
beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDaoBean" class="com.qiye.spring6.dao.UserDao"/>
<bean id="vipDaoBean" class="com.qiye.spring6.dao.VipDao"/>
<bean id="customerBean" class="com.qiye.spring6.service.CustomerService">
<!-- 构造注入 index属性指定参数下标,第n个参数是n-1 ref属性指定注入的bean的id -->
<!-- 指定构造方法的第一个参数,下标是0-->
<constructor-arg index="0" ref="userDaoBean"/>
<!-- 指定构造方法的第二个参数,下标是1-->
<constructor-arg index="1" ref="vipDaoBean"/>
</bean>
<!-- 也可以通过名字获取参数 -->
<bean id="customerBean2" class="com.qiye.spring6.service.CustomerService">
<constructor-arg name="userDao" ref="userDaoBean"/>
<constructor-arg name="vipDao" ref="vipDaoBean"/>
</bean>
<!-- 甚至可以不指定下标和名字,让Spring框架自动匹配,这种方式实际上是根据类型进行注入的-->
<bean id="customerBean3" class="com.qiye.spring6.service.CustomerService">
<constructor-arg ref="vipDaoBean"/>
<constructor-arg ref="userDaoBean"/>
</bean>
</beans>
测试
@Test
public void testConstructorDI(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
CustomerService customerBean = applicationContext.getBean("customerBean", CustomerService.class);
customerBean.save();
CustomerService customerBean2 = applicationContext.getBean("customerBean2", CustomerService.class);
customerBean2.save();
CustomerService customerBean3 = applicationContext.getBean("customerBean3", CustomerService.class);
customerBean3.save();
}
4.set注入专题
1.注入外部Bean和内部Bean
外部bean:bean定义到外边,使用ref属性来引入。
内部bean:在property标签中使用嵌套的bean标签。
public class OrderDao {
//订单Dao
private static final Logger logger = LoggerFactory.getLogger(OrderDao.class);
public void insert(){
logger.info("订单正在生成......");
}
}
public class OrderService {
private OrderDao orderDao;
public void setOrderDao(OrderDao orderDao) {
this.orderDao = orderDao;
}
public void generate(){
orderDao.insert();
}
}
set-di.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 外部bean-->
<bean id="orderDaoBean" class="com.qiye.spring6.dao.OrderDao"/>
<bean id="orderServiceBean" class="com.qiye.spring6.service.OrderService">
<!-- 注入外部bean-->
<property name="orderDao" ref="orderDaoBean"/>
</bean>
<!-- 内部bean,在property标签中使用嵌套的bean标签-->
<bean id="orderServiceBean2" class="com.qiye.spring6.service.OrderService">
<property name="orderDao">
<bean class="com.qiye.spring6.dao.OrderDao"/>
</property>
</bean>
</beans>
测试
@Test
public void testSetDI2(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");
OrderService orderServiceBean = applicationContext.getBean("orderServiceBean", OrderService.class);
orderServiceBean.generate();
OrderService orderServiceBean2 = applicationContext.getBean("orderServiceBean2", OrderService.class);
orderServiceBean2.generate();
}
2.注入简单类型
简单类型:8种基本数据类型,8种包装类,枚举,String,Number(数字),Date,Temporal(时间时区),URI,URL,Locale(语言),Class
在实际开发中,我们一般不会把Date当作简单类型,一般用ref注入。
给简单类型赋值时,要使用value。
User
public class User {
private String username;
private String password;
private int age;
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
'}';
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setAge(int age) {
this.age = age;
}
}
<!-- 注入简单类型 -->
<bean id="userBean" class="com.qiye.spring6.bean.User">
<property name="username" value="张三"/>
<property name="age" value="20"/>
<property name="password" value="123456"/>
</bean>
@Test
public void testSimpleSet(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");
User userBean = applicationContext.getBean("userBean", User.class);
System.out.println(userBean);
}
简单类型的经典应用
把数据源交给Spring容器管理。
//数据源:提供Connection对象的
public class MyDateSource implements DataSource {
private String driver;
private String url;
private String username;
@Override
public String toString() {
return "MyDateSource{" +
"driver='" + driver + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
private String password;
public void setDriver(String driver) {
this.driver = driver;
}
public void setUrl(String url) {
this.url = url;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public Connection getConnection() throws SQLException {
return null;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
<!-- Spring管理数据源-->
<bean id="myDataSource" class="com.qiye.spring6.jdbc.MyDateSource">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="password" value="1234"/>
<property name="username" value="root"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
</bean>
@Test
public void testMyDataSource(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");
MyDateSource myDataSource = applicationContext.getBean("myDataSource", MyDateSource.class);
System.out.println(myDataSource);
}
3.级联属性赋值(了解)
级联属性赋值,需要提供get方法且有顺序要求。
public class Clazz {
//班级
private String name;
@Override
public String toString() {
return "Clazz{" +
"name='" + name + '\'' +
'}';
}
public void setName(String name) {
this.name = name;
}
}
public class Student {
//学生
private String name;
private Clazz clazz;//班级
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", clazz=" + clazz +
'}';
}
public Clazz getClazz() {
return clazz;
}
public void setClazz(Clazz clazz) {
this.clazz = clazz;
}
public void setName(String name) {
this.name = name;
}
}
cascade.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="studentBean" class="com.qiye.spring6.bean.Student">
<property name="name" value="张三"/>
<property name="clazz" ref="clazzBean"/>
<!-- 在这里级联属性赋值,需要在Student中提供clazz的get方法
clazz和clazz.name的顺序不能颠倒 -->
<property name="clazz.name" value="高一二班"/>
</bean>
<bean id="clazzBean" class="com.qiye.spring6.bean.Clazz"/>
</beans>
@Test
public void testCascade(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("cascade.xml");
Student studentBean = applicationContext.getBean("studentBean", Student.class);
Clazz clazzBean = applicationContext.getBean("clazzBean", Clazz.class);
System.out.println(studentBean);
System.out.println(clazzBean);
}
4.注入数组
简单类型:通过array标签和value标签。
非简单类型:通过array标签和ref标签。
public class QiYe {
private String[] hobbies;
private Girl[] girls;
@Override
public String toString() {
return "QiYe{" +
"hobbies=" + Arrays.toString(hobbies) +
", girls=" + Arrays.toString(girls) +
'}';
}
public void setGirls(Girl[] girls) {
this.girls = girls;
}
public void setHobbies(String[] hobbies) {
this.hobbies = hobbies;
}
}
public class Girl {
private String name;
@Override
public String toString() {
return "Girl{" +
"name='" + name + '\'' +
'}';
}
public void setName(String name) {
this.name = name;
}
}
<?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="g1" class="com.qiye.spring6.bean.Girl">
<property name="name" value="小红"/>
</bean>
<bean id="g2" class="com.qiye.spring6.bean.Girl">
<property name="name" value="小绿"/>
</bean>
<bean id="g3" class="com.qiye.spring6.bean.Girl">
<property name="name" value="小蓝"/>
</bean>
<bean id="QiYe" class="com.qiye.spring6.bean.QiYe">
<!-- 给数组中的简单类型赋值-->
<property name="hobbies" >
<array>
<value>打球</value>
<value>下棋</value>
<value>玩游戏</value>
</array>
</property>
<!-- 给数组中的非简单类型赋值 -->
<property name="girls">
<array>
<ref bean="g1"/>
<ref bean="g2"/>
<ref bean="g3"/>
</array>
</property>
</bean>
</beans>
@Test
public void testArray(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-array.xml");
QiYe qiYe = applicationContext.getBean("QiYe", QiYe.class);
System.out.println(qiYe);
}
5.注入List集合和Set集合
使用list和set标签,简单类型用value,非简单类型用ref。
public class Person {
private List<String> names;
private Set<String> addrs;
@Override
public String toString() {
return "Person{" +
"names=" + names +
", addrs=" + addrs +
'}';
}
public void setNames(List<String> names) {
this.names = names;
}
public void setAddrs(Set<String> addrs) {
this.addrs = addrs;
}
}
<?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="personBean" class="com.qiye.spring6.bean.Person">
<property name="names">
<list>
<value>张三</value>
<value>李四</value>
<value>王五</value>
<value>王五</value>
<value>张三</value>
</list>
</property>
<property name="addrs">
<set>
<value>天庭</value>
<value>沙漠</value>
<value>平原</value>
<value>平原</value>
<value>平原</value>
</set>
</property>
</bean>
</beans>
@Test
public void testCollection(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-collection.xml");
Person personBean = applicationContext.getBean("personBean", Person.class);
System.out.println(personBean);
}
6.注入Map集合和Properties
使用map标签,如果key和value是简单类型,用key和value,否则用key-ref和value-ref。
private Map<Integer,String> phones;
<property name="phones">
<map>
<entry key="1" value="111"/>
<entry key="2" value="112"/>
<entry key="3" value="113"/>
</map>
</property>
注入Properties
使用props标签和prop标签
private Properties properties;//注入属性类,Properties本质上也是Map集合,因为它的父类是HashTable,HashTable实现了Map接口。
<property name="properties">
<!-- 注入Properties属性类对象,Properties的key和value只能是字符串-->
<props>
<prop key="driver">com.mysql.cj.jdbc.Driver</prop>
<prop key="username">root</prop>
<prop key="password">123</prop>
</props>
</property>
7.注入null和空字符串
不给属性注入值,就是null,也可以通过<null/>标签手动赋null。
value中什么都不写,就是赋空字符串,也可以通过<value/>标签手动赋空字符串。
public class Dog {
private String name;
private int age;
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
<?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-->
<bean id="orderDaoBean" class="com.qiye.spring6.dao.OrderDao"/>
<bean id="orderServiceBean" class="com.qiye.spring6.service.OrderService">
<!-- 注入外部bean-->
<property name="orderDao" ref="orderDaoBean"/>
</bean>
<!-- 内部bean,在property标签中使用嵌套的bean标签-->
<bean id="orderServiceBean2" class="com.qiye.spring6.service.OrderService">
<property name="orderDao">
<bean class="com.qiye.spring6.dao.OrderDao"/>
</property>
</bean>
<!-- 注入简单类型 -->
<bean id="userBean" class="com.qiye.spring6.bean.User">
<property name="username" value="张三"/>
<property name="age" value="20"/>
<property name="password" value="123456"/>
</bean>
<!-- Spring管理数据源-->
<bean id="myDataSource" class="com.qiye.spring6.jdbc.MyDateSource">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="password" value="1234"/>
<property name="username" value="root"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
</bean>
<bean id="dogBean" class="com.qiye.spring6.bean.Dog">
<!-- 不给属性注入值,就是null,也可以手动赋null-->
<!-- <property name="name" value=""/>这样写就是赋空字符串 -->
<!-- <property name="name">
<value/>
</property> 这样写也可以赋空字符串-->
<property name="name">
<null/>
</property>
<property name="age" value="3"/>
</bean>
</beans>
@Test
public void testDog(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");
Dog dogBean = applicationContext.getBean("dogBean", Dog.class);
System.out.println(dogBean);
}
8.注入的值含有特殊符号
XML中五个特殊符号:< > ' " &
方式一:可以用转移字符来表示特殊符号
> >
< <
' '
" "
& &
方式二:使用<![CDATA[]]> 这里面的代码不会被XML解析。
public class MathBean {
@Override
public String toString() {
return "MathBean{" +
"result='" + result + '\'' +
'}';
}
public void setResult(String result) {
this.result = result;
}
private String result;
}
<bean id="mathBean" class="com.qiye.spring6.bean.MathBean">
<!-- 第一种方案:实体符号代替特殊符号 -->
<!-- <property name="result" value="2 < 3"/>-->
<!-- 第二种方案:使用<![CDATA[]]>-->
<property name="result">
<value><![CDATA[2 < 3]]></value>
</property>
</bean>
@Test
public void testSpecial(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");
MathBean mathBean = applicationContext.getBean("mathBean", MathBean.class);
System.out.println(mathBean);
}
5.p命名空间注入
底层是set注入,只不过p命名空间注入可以让配置更加简单。
首先要在spring配置文件的头部加上p命名空间
xmlns:p="http://www.springframework.org/schema/p"
xml为如下格式(Dog类有String name,String age,Date birth三个属性,且都提供了set方法)
<?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 id="dogBean" class="com.qiye.spring6.bean.Dog" p:name="爆米花" p:age="3" p:birth-ref="birthBean"/>
<!-- 获取当前系统时间 -->
<bean id="birthBean" class="java.util.Date"/>
</beans>
@Test
public void testP(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-p.xml");
Dog dogBean = applicationContext.getBean("dogBean", Dog.class);
System.out.println(dogBean);
}
6.c命名空间注入
c命名空间注入是简化构造方法注入的。
首先要在spring配置文件的头部加上c命名空间
xmlns:c="http://www.springframework.org/schema/c"
xml(People类有String name,int age,boolean sex三个属性,People类中包含构造方法)
<?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:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="peopleBean" class="com.qiye.spring6.bean.People" c:_0="张三" c:_1="31" c:_2="true"/>
<!-- 另一种写法 -->
<!-- <bean id="peopleBean" class="com.qiye.spring6.bean.People" c:name="李四" c:age="20" c:sex="false"/>-->
</beans>
@Test
public void testC(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-c.xml");
People peopleBean = applicationContext.getBean("peopleBean", People.class);
System.out.println(peopleBean);
}
7.util命名空间
util命名空间可以让配置复用。
引入util命名空间,头部添加两行。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!-- 配置util-->
<util:properties id="prop">
<prop key="driver">com.mysql.cj.jdbc.Driver</prop>
<prop key="url">jdbc:mysql://localhost:3306/spring6</prop>
<prop key="username">root</prop>
<prop key="password">123456</prop>
</util:properties>
<!-- 数据源1 -->
<bean id="myDataSource1" class="com.qiye.spring6.jdbc.MyDataSource1">
<property name="properties" ref="prop"/>
</bean>
<!-- 数据源2 -->
<bean id="myDataSource2" class="com.qiye.spring6.jdbc.MyDataSource2">
<property name="properties" ref="prop"/>
</bean>
</beans>
@Test
public void testUtil(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-util.xml");
MyDataSource1 md1 = applicationContext.getBean("md1", MyDataSource1.class);
MyDataSource2 md2 = applicationContext.getBean("md2", MyDataSource2.class);
System.out.println(md2);
System.out.println(md1);
}
8.基于XML的自动装配
Spring可以完成自动化的注入,又称为自动装配,可以根据名字或者类型自动装配。
根据名称自动装配,底层为set注入。
<?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">
<!-- 根据名字自动装配时,被注入的id要为get方法去掉get然后首字母小写 -->
<bean id="orderDao" class="com.qiye.spring6.dao.OrderDao"/>
<!-- 根据名字自动装配 -->
<bean id="orderService" class="com.qiye.spring6.service.OrderService" autowire="byName"/>
</beans>
@Test
public void testAutowire(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowire.xml");
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
orderService.generate();
}
根据类型自动装配,基于set方法
<!-- 根据类型自动装配-->
<bean class="com.qiye.spring6.dao.VipDao"/>
<bean class="com.qiye.spring6.dao.UserDao"/>
<bean id="cs" class="com.qiye.spring6.service.CustomerService" autowire="byType"/>
@Test
public void testAutowire2(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowire.xml");
CustomerService cs = applicationContext.getBean("cs", CustomerService.class);
cs.save();
}
9.引入外部属性配置文件
username要加点别的东西,否则会默认取系统username名。
<?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">
<!-- 先更改xml的头部信息,再引入外部的properties文件-->
<context:property-placeholder location="jdbc.properties"/>
<!-- 配置数据源 value通过${}来写 -->
<bean id="ds" class="com.qiye.spring6.jdbc.MyDateSource">
<property name="driver" value="${driverClass}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username2}"/>
<property name="password" value="${password}"/>
</bean>
</beans>
@Test
public void testPro(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-properties.xml");
MyDateSource ds = applicationContext.getBean("ds", MyDateSource.class);
System.out.println(ds);
}
五、Bean的作用域
1.单例和多例
Spring默认情况下Bean是单例(singleton)的,在Spring上下文初始化的时候实例化,每次调用getBean时得到的都是同一个对象。
我们可以在Spring配置文件中通过scope将Bean设置为多例(prototype)的,此时每调用一次getBean方法,就实例化一个对象。
<bean id="sb" class="com.qiye.spring6.bean.SpringBean" scope="prototype"/>
2.scope的其他值
request和session这两个值只有在web项目中才存在。
request:一次请求一个Bean
session:一次会话一个Bean
global session:portlet应用专用的。
application:一个应用对应一个Bean(WEB项目)
websocket:一个websocket生命周期对应一个Bean。(WEB项目)
自定义scope:很少使用。
了解:自定义scope
自定义一个线程一个Bean。
一、自定义Scope(实现Scope接口)。 二、将自定义的Scope注册到Spring容器中。
三、使用自定义的Scope。
<?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 class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="threadScope">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
<!-- 使用-->
<bean id="sb" class="com.qiye.spring6.bean.SpringBean" scope="threadScope"/>
</beans>
@Test
public void testThreadScope(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");
SpringBean sb = applicationContext.getBean("sb", SpringBean.class);
System.out.println(sb);
//启动新线程
new Thread(new Runnable() {
@Override
public void run() {
SpringBean sb1 = applicationContext.getBean("sb", SpringBean.class);
System.out.println(sb1);
}
}).start();
}
六、GoF之工厂模式
Spring底层使用了大量的工厂模式,我们有必要了解一下工厂模式。
1.设计模式概述
设计模式:一种可以被重复利用的解决方案。
GoF:Gang of Four 四人组,四个人合著了一本书《设计模式》,所以GoF指23种设计模式。
GoF三大类:
创建类:解决对象创建问题
单例模式,工厂方法模式,抽象工厂模式,建造者模式,原型模式
结构类:一些类或对象组合在一起的经典结构
代理模式,装饰模式,适配器模式,组合模式,享元模式,外观模式,桥接模式
行为类:解决类或对象之间交互问题
策略模式,模板方法模式,责任链模式,观察者模式,迭代子模式,命令模式,备忘录模式,状态模式,访问者模式,中介者模式,解释器模式
2.工厂模式三种形态
1.简单工厂模式。 不属于23种设计模式,又叫做静态工厂方法模式,是工厂方法模式的一种特例。
2.工厂方法模式。 23种设计模式之一。
3.抽象工厂模式。 23种设计模式之一。
3.简单工厂模式
简单工厂模式(Simple Factory Pattern )
简单工厂模式有三个角色:抽象产品角色,具体产品角色,工厂类角色。
优点:对于客户端来说,无需知道产品的生产细节,直接向工厂索要即可。生产者和消费者分离。
缺点:1.需要拓展新产品时,工厂类要修改,违背了OCP原则。
2.工厂类的责任重大,不能出现任何问题,称为全能/上帝类,工厂类一旦有问题,那么整个系统都会瘫痪。
例子:
抽象产品:
public abstract class Weapon {
// 抽象产品角色
/**
* 所有的武器都能攻击
*/
public abstract void attack();
}
具体产品:
public class Tank extends Weapon{
//具体产品角色 坦克
@Override
public void attack() {
System.out.println("开炮!!!");
}
}
public class Fighter extends Weapon{
//具体产品角色 战斗机
@Override
public void attack() {
System.out.println("抛出核弹!!!");
}
}
public class Dagger extends Weapon{
//具体产品角色 匕首
@Override
public void attack() {
System.out.println("暗杀!!!");
}
}
工厂
public class WeaponFactory {
//工厂类角色
/**
* 静态方法,获取什么产品,就传什么参数。
* @param weaponType Tank/Fighter/Dagger
* @return 对应产品
*/
public static Weapon get(String weaponType){
if("TANK".equals(weaponType)){
return new Tank();
}else if("DAGGER".equals(weaponType)){
return new Dagger();
}else if("FIGHTER".equals(weaponType)){
return new Fighter();
}else{
throw new RuntimeException("不支持该武器");
}
}
}
测试
public class Test {
//客户端程序
public static void main(String[] args) {
//对于客户端来说,无需知道产品的生产细节,直接向工厂索要即可。生产者和消费者分离。
//需要坦克
Weapon tank = WeaponFactory.get("TANK");
tank.attack();
//需要匕首
Weapon dagger = WeaponFactory.get("DAGGER");
dagger.attack();
//需要战斗机
Weapon fighter = WeaponFactory.get("FIGHTER");
fighter.attack();
}
}
4.工厂方法模式
工厂方法模式解决了简单工厂模式的OCP原则问题,一个工厂对应生产一种产品,这样工厂就不是全能/上帝类了,也符合了OCP原则。
工厂方法模式中的角色:抽象产品角色,具体产品角色,抽象工厂角色,具体工厂角色。
优点:拓展产品时,只需添加具体产品类和具体工厂类即可,符合OCP原则。
缺点:每增加一个产品,都需要增加一个具体类和对象实现工厂,导致类爆炸。
例子:
抽象产品角色:
public abstract class Weapon {
//抽象产品角色
public abstract void attack();
}
具体产品角色:
public class Dagger extends Weapon{
//具体产品角色
@Override
public void attack() {
System.out.println("暗杀!!!");
}
}
public class Gun extends Weapon{
//具体产品角色
@Override
public void attack() {
System.out.println("射击!!!");
}
}
抽象工厂角色:
public abstract class WeaponFactory {
//抽象工厂角色
public abstract Weapon get();
}
具体工厂角色:
public class DaggerFactory extends WeaponFactory{
//具体工厂角色
@Override
public Weapon get() {
return new Dagger();
}
}
public class GunFactory extends WeaponFactory{
//具体工厂角色
@Override
public Weapon get() {
return new Gun();
}
}
测试
public class Test {
public static void main(String[] args) {
WeaponFactory daggerFactory = new DaggerFactory();
Weapon dagger = daggerFactory.get();
dagger.attack();
WeaponFactory gunFactory = new GunFactory();
Weapon gun = gunFactory.get();
gun.attack();
}
}
七、Bean的实例化/获取
Spring为Bean提供了多种实例化方式,这些底层用的其实都是构造方法实例化。
1.通过构造方法实例化。
2.通过简单工厂模式实例化。
3.通过factory-bean(工厂方法模式)实例化。
4.通过FactoryBean接口实例化。
方式一、通过构造方法实例化
在spring配置文件中直接配置全路径,Spring会自动调用该类的无参数构造方法来实例化Bean。
public class SpringBean {
public SpringBean() {
System.out.println("SpringBean的无参构造方法执行了!");
}
}
<?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="sb" class="com.qiye.spring6.bean.SpringBean"/>
</beans>
@Test
public void testInstantiation1(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
SpringBean sb = applicationContext.getBean("sb", SpringBean.class);
System.out.println(sb);
}
方式二、通过简单工厂模式实例化
通过简单工厂模式实例化,需要指定工厂类和调用的方法。
public class Star {
public Star() {
System.out.println("Star的无参数构造方法执行");
}
}
public class StarFactory {
//简单工厂模式中的工厂角色
public static Star get(){
return new Star();
}
}
<!-- 通过简单工厂模式实例化,需要指定工厂类和调用的方法 -->
<bean id="starBean" class="com.qiye.spring6.bean.StarFactory" factory-method="get"/>
@Test
public void testInstantiation2(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Star starBean = applicationContext.getBean("starBean", Star.class);
System.out.println(starBean);
}
方式三、通过factory-bean(工厂方法模式)实例化
和简单工厂模式不同的是,工厂方法模式的工厂要实例化。
public class Gun {
//具体产品角色
public Gun() {
System.out.println("Gun的无参构造方法执行~");
}
}
public class GunFactory {
//具体工厂角色
public Gun get(){
return new Gun();
}
}
<!-- 通过工厂方法模式实例化 factory-bean属性和factory-method属性 调用哪个对象的哪个方法 -->
<bean id="gunFactory" class="com.qiye.spring6.bean.GunFactory"/>
<bean id="gun" factory-bean="gunFactory" factory-method="get"/>
@Test
public void testInstantiation3(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Gun gun = applicationContext.getBean("gun", Gun.class);
System.out.println(gun);
}
方式四、通过FactoryBean接口实例化
以上的三种方式中,factory-bean和factory-method都是我们自己定义的。
在Spring中,编写的类直接实现FactoryBean接口时,factory-bean和factory-method就无需我们指定了。
factory-bean会自动指向实现FactoryBean的类,factory-method会自动指向getObject()方法。
public class Person {
//Bean
public Person() {
System.out.println("Person的无参构造方法执行");
}
}
//工厂Bean
public class PersonFactoryBean implements FactoryBean<Person> {
//重写该方法
@Override
public Person getObject() throws Exception {
return new Person();
}
@Override
public Class<?> getObjectType() {
return null;
}
//这个方法在接口中有默认实现,指定是否为单例的,默认为true
@Override
public boolean isSingleton() {
return true;
}
}
<!-- 通过FactoryBean接口实现,实际上是第三种方式的简化版,通过工厂Bean来返回一个普通的Bean -> Person对象 -->
<bean id="person" class="com.qiye.spring6.bean.PersonFactoryBean"/>
@Test
public void testInstantiation4(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Person person = applicationContext.getBean("person", Person.class);
System.out.println(person);
}
BeanFactory和FactoryBean的区别
BeanFactory是Bean工厂,负责创建Bean对象。是Spring IoC容器的顶级对象。
FactoryBean是一个Bean,是能够辅助Spring实例化其他Bean对象的一个Bean。
注入自定义Date
java.util.Date一般是简单类型,但是注入时有格式要求 -> Mon Apr 07 22:39:39 CST 2025
java.util.Date也可以是非简单类型。
public class Student {
private Date birth;
@Override
public String toString() {
return "Student{" +
"birth=" + birth +
'}';
}
public void setBirth(Date birth) {
this.birth = birth;
}
}
public class DateFactoryBean implements FactoryBean<Date> {
//工厂Bean辅助创建普通Bean
private String strDate;
public DateFactoryBean(String strDate) {
this.strDate = strDate;
}
@Override
public Date getObject() throws Exception {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date date = simpleDateFormat.parse(strDate);
return date;
}
@Override
public Class<?> getObjectType() {
return null;
}
}
<bean id="date" class="com.qiye.spring6.bean.DateFactoryBean">
<constructor-arg index="0" value="1999-10-01"/>
</bean>
<bean id="student" class="com.qiye.spring6.bean.Student">
<property name="birth" ref="date"/>
</bean>
@Test
public void testDate(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = applicationContext.getBean("student", Student.class);
System.out.println(student);
}
八、Bean的生命周期
1.五步
Bean生命周期可以粗略的划分为5大步:实例化Bean,Bean属性赋值,初始化Bean,使用Bean,销毁Bean。
public class User {
private String name;
public void setName(String name) {
System.out.println("第二步,给对象的属性赋值");
this.name = name;
}
public User(){
System.out.println("第一步,无参构造方法执行");
}
public void initBean(){
System.out.println("第三步,初始化Bean");
}
public void destroyBean(){
System.out.println("第五步,销毁Bean");
}
}
<!-- 需要手动指定初始化和销毁方法-->
<bean id="user" class="com.qiye.spring6.bean.User" init-method="initBean" destroy-method="destroyBean">
<property name="name" value="张三"/>
</bean>
@Test
public void testBeanLifecycleFive(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User user = applicationContext.getBean("user", User.class);
System.out.println("第四步,使用Bean"+user);
//关闭Spring容器才会销毁Bean
ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
context.close();
}
2.七步
Bean生命周期还可以划分为7大步:实例化Bean,Bean属性赋值,初始化Bean,使用Bean,销毁Bean之外可以在第三步的前后添加代码,加入Bean后处理器。
需要编写类实现BeanPostProcessor类,并且重写before和after方法。
实例化Bean,Bean属性赋值,Bean后处理器的before方法,初始化Bean,Bean后处理器的after方法,使用Bean,销毁Bean
Bean后处理器:
public class LogBeanPostProcessor implements BeanPostProcessor {
//第一个参数为创建的bean对象,第二个参数为bean的名字
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行Bean后处理器的before方法");
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行Bean后处理器的after方法");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
spring.xml中配置 :
<!-- 配置Bean后处理器,将作用于配置文件中所有的Bean-->
<bean class="com.qiye.spring6.bean.LogBeanPostProcessor"/>
3.十步
两步加到Bean后处理器的before方法前后,一步加到销毁Bean之前(使用Bean之后)。
实例化Bean,Bean属性赋值,检查Bean是否实现了Aware的相关接口并设置相关依赖,Bean后处理器的before方法,检查Bean是否实现了InitializingBean接口并调用接口方法,初始化Bean,Bean后处理器的after方法,使用Bean,检查Bean是否实现了DisposableBean接口并调用接口方法,销毁Bean
Aware有三个接口:BeanNameAware, BeanClassLoaderAware, BeanFactoryAware 分别为Bean的名字,Bean的类的加载器,生产Bean的工厂对象。
InitializingBean接口有一个方法:afterPropertiesSet。
DisposableBean接口的方法:destroy。
public class User implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware , InitializingBean,DisposableBean {
private String name;
public void setName(String name) {
System.out.println("第二步,给对象的属性赋值");
this.name = name;
}
public User(){
System.out.println("第一步,无参构造方法执行");
}
public void initBean(){
System.out.println("第三步,初始化Bean");
}
public void destroyBean(){
System.out.println("第五步,销毁Bean");
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
System.out.println("Bean类的加载器:"+classLoader);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("生产Bean的工厂对象:"+beanFactory);
}
@Override
public void setBeanName(String name) {
System.out.println("这个Bean的名字"+name);
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean的afterPropertiesSet执行了");
}
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean的destroy方法执行了");
}
}
4.Spring容器只对单例的Bean进行完整的生命周期管理
如果是Prototype的Bean,Spring只负责将Bean创建完毕(前八步),当客户端获取到Bean后,Spring就不再管理该对象的生命周期了。
5.自己new的对象让Spring容器管理
通过DefaultListableBeanFactory对象来注册自己new的Bean。
@Test
public void testRegisterBean(){
//自己new的对象,此时并没有被Spring容器管理
Student student = new Student();
//将自己new的对象交给Spring容器管理
// 获取DefaultListableBeanFactory对象
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
//注册自己new的Bean
factory.registerSingleton("studentBean",student);
//获取Bean
Student studentBean = factory.getBean("studentBean", Student.class);
System.out.println(studentBean == student);///true
}
九、Bean的循环依赖问题
A对象中有B属性,B对象中有A属性,这就是循环依赖。
1.singleton下的set注入产生的循环依赖
singleton + setter模式的循环依赖没有任何问题,原因是在这种模式下,Spring对Bean的管理分为清晰的两个阶段,1.在Spring容器加载时实例化Bean,只要其中任意一个Bean实例化之后,马上进行"曝光"。2."曝光"之后再进行属性的赋值。
核心解决方案是:实例化对象和对象的属性赋值分阶段执行。
只有在scope是singlton的情况下,Bean才会提前"曝光"(能保证这种类型的对象只有一个)
<?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">
<!-- singleton + setter模式的循环依赖没有任何问题-->
<bean id="husbandBean" class="com.qiye.spring6.bean.Husband" scope="singleton">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="com.qiye.spring6.bean.Wife" scope="singleton">
<property name="name" value="李四"/>
<property name="husband" ref="husbandBean"/>
</bean>
</beans>
2.prototype下的set注入产生的循环依赖
两个Bean都为prototype时会出现异常。
多例在getBean时才会给属性赋值,在赋值时会循环赋值,出现BeanCurrentlyInCreationException异常。
new husband会导致new wife new wife又会导致new husband 死循环。
将一个prototype改为singleton就能解决问题。
<?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">
<!-- prototype + setter模式的循环依赖会出现问题-->
<bean id="husbandBean" class="com.qiye.spring6.bean.Husband" scope="prototype">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="com.qiye.spring6.bean.Wife" scope="prototype">
<property name="name" value="李四"/>
<property name="husband" ref="husbandBean"/>
</bean>
</beans>
@Test
public void testCD(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);
Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);
System.out.println(husbandBean);
System.out.println(wifeBean);
}
3.构造注入模式下的循环依赖
构造注入,此时是在构造函数中赋值,即使是singleton,也无法"曝光",所以构造注入模式下的循环依赖是无法解决的。
Husband和Wife都提供了构造方法,此时会出现异常。
<?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="h" class="com.qiye.spring6.bean2.Husband" scope="singleton">
<constructor-arg index="0" value="张三"/>
<constructor-arg index="1" ref="w"/>
</bean>
<bean id="w" class="com.qiye.spring6.bean2.Wife" scope="singleton">
<constructor-arg index="0" value="李四"/>
<constructor-arg index="1" ref="h"/>
</bean>
</beans>
4.Spring解决循环依赖的原理
只能在set+singleton下解决,此时可以将实例化Bean和给Bean属性赋值分开。
实例化Bean时,调用无参数构造方法,先不给属性赋值,而是提前将Bean对象(的工厂Bean)"曝光"出去。
通过setter方法给Bean属性赋值。
两个步骤完全是分开完成的,且可以在不同的时间点上完成。
Bean是单例的,我们可以先把所有的单例Bean实例化并放到一个Map集合中(称之为缓存),然后再慢慢的调用setter方法给属性赋值,解决了循环依赖问题。
缓存分别为:三个缓存都为Map,key都为Bean的id
singletonObjects 一级缓存,存储完整的单例Bean对象(属性赋完值了)。
earlySingletonObjects 二级缓存,存储早期Bean对象(属性未赋值)。
singletonFactories 三级缓存,单例工厂对象,存储了大量的工厂对象,存储创建单例对象时对应的单例工厂对象。
十、反射机制回顾
1.回顾反射机制
public class SomeService {
public void doSome(){
System.out.println("doSome~");
}
public String doSome(String s){
System.out.println("doSome String s~");
return s;
}
public String doSome(String s,int i){
System.out.println("doSome(String s,int i)~");
return s + i;
}
}
public class Test {
//使用反射机制调用方法
public static void main(String[] args) throws Exception{
// 调用一个方法,应该有四个要素 调用哪个对象 调用哪个方法 调用方法时传什么参数 返回结果
//1.获取类
Class<?> clazz = Class.forName("com.qiye.reflect.service.SomeService");
//2.获取方法
Method doSomeMethod = clazz.getDeclaredMethod("doSome", String.class, int.class);
//3.调用方法
Object o = clazz.getDeclaredConstructor().newInstance();
String s = (String)doSomeMethod.invoke(o, "乔巴", 1);
System.out.println(s);
}
}
2.SpringDI核心实现
知道了类名和属性名,就可以赋值。
public class Test2 {
public static void main(String[] args) throws Exception {
//有com.qiye.reflect.service.User类
//这个类符合javaBean规范(属性私有化,提供公开的setter和getter方法)
//这个类中有一个属性,属性名是age,age的类型是int
//采用反射机制调用set方法给User对象的age属性赋值。
String className = "com.qiye.reflect.service.User";
String propertyName = "age";
//反射机制调用setAge方法
//获取类
Class<?> clazz = Class.forName(className);
//获取方法名
String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
//根据属性名获取属性类型
Field field = clazz.getDeclaredField(propertyName);
//获取方法
Method setMethod = clazz.getDeclaredMethod(setMethodName, field.getType());
//准备对象
Object obj = clazz.getConstructor().newInstance();
//调用方法
setMethod.invoke(obj,8);
System.out.println(obj);
}
}
十一、Spring IoC注解式开发
注解式开发:简化XML配置。
Spring6倡导全注解式开发。
1.回顾注解
//自定义注解
//标注注解的注解,称为元注解
//使用注解时,只有一个属性且为value,value可以省略。如果数组只有一个元素,{}可以省略。
@Target({ElementType.TYPE,ElementType.FIELD})//表示该注解可以出现在TYPE和FIELD上(类和属性)
@Retention(RetentionPolicy.RUNTIME)//元注解@Retention,RUNTIME用来标注该注解最终保留到class文件中,且可以被反射机制读取。
public @interface Component {
//定义注解的属性
String value();
String name();
String[] names();
}
@Component(value = "userBean",name = "77",names={"66","77"})
public class User {
}
public class ReflectAnnotation1 {
public static void main(String[] args) throws Exception{
//反射机制读取注解
//获取类
Class<?> clazz = Class.forName("com.qiye.factory.bean.User");
//判断类上有无该注解
if (clazz.isAnnotationPresent(Component.class)) {
//获取类上的注解
Component annotation = clazz.getAnnotation(Component.class);
//访问注解属性
System.out.println(annotation.value());
System.out.println(annotation.name());
}
}
}
public class ComponentScan {
public static void main(String[] args) throws Exception{
Map<String,Object> beanMap = new HashMap<>();
//只知道包名,要求扫描包下所有的类,当这个类上有Component注解时,实例化该对象并放到Map集合中。
String packageName = "com.qiye.factory.bean";
//扫描 .正则表达式代表任意字符,这里的.必须是普通的字符 \.代表普通的. 而在java中\\代表\ 所以要写\\.
String replace = packageName.replaceAll("\\.", "/");
URL url = ClassLoader.getSystemClassLoader().getResource(replace);
String path = url.getPath();
//获取绝对路径下的所有文件
File file = new File(path);
File[] files = file.listFiles();
Arrays.stream(files).forEach(f->{
try {
String clasName = packageName + "." + f.getName().split("\\.")[0];
Class<?> clazz = null;
clazz = Class.forName(clasName);
if (clazz.isAnnotationPresent(Component.class)) {
Component annotation = clazz.getAnnotation(Component.class);
String id = annotation.value();
Object obj = clazz.getDeclaredConstructor().newInstance();
beanMap.put(id,obj);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
});
System.out.println(beanMap);
}
}
2.声明Bean的注解
声明Baen使用以下四个注解都可以。
@Component 组件
@Controller 控制器 ,表示层
@Service 业务,业务层
@Repository 仓库Dao,持久层
@Controller,@Service ,@Repository都是@Component的别名,目的是增强程序的可读性。
3.Spring注解使用
要保证加入了aop依赖。
1.配置文件添加context命名空间
2.在配置文件中指定要扫描的包
<?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:component-scan base-package="com.qiye.spring6.bean"/>
</beans>
3.使用注解
value的默认值为首字母变小写后的类名。
@Component("userBean")
public class User {
}
上方注解相当于:
<bean id="userBean" class="com.qiye.spring6.bean.User"/>
@Controller("vipBean")
public class Vip {
}
@Service("orderBean")
public class Order {
}
@Repository("studentBean")
public class Student {
}
测试
@Test
public void testBeanComponent(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User userBean = applicationContext.getBean("userBean", User.class);
Vip vipBean = applicationContext.getBean("vipBean", Vip.class);
Order orderBean = applicationContext.getBean("orderBean", Order.class);
Student studentBean = applicationContext.getBean("studentBean", Student.class);
System.out.println(userBean);
System.out.println(vipBean);
System.out.println(orderBean);
System.out.println(studentBean);
}
4.多个包扫描
1.在配置文件中指定多个包,用,隔开。
<context:component-scan base-package="com.qiye.spring6.bean,com.qiye.spring6.dao"/>
2.可以指定这多个包共同的父包(扫描范围变大,会牺牲效率)
<context:component-scan base-package="com.qiye.spring6"/>
5.选择性实例化Bean
我们可以选择性的实例化Bean,例如只实例化@Controller注解的Bean。
方式一:
use-default-filters="false"表示让对应包下,所有声明Bean的注解全部失效。
context:include-filter标签可以指定生效的注解,注解要写全限定类名。
<?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:component-scan base-package="com.qiye.spring6.bean2" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>
方式二:
use-default-filters="true"表示让对应包下,所有声明Bean的注解全部生效。(true为默认值)
context:exclude-filter标签可以指定失效的注解,注解要写全限定类名。
<?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:component-scan base-package="com.qiye.spring6.bean2">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>
6.负责注入的注解
@Value 属性是简单类型时,可以使用@Value注解进行注入。
@Autowired 注入非简单类型,被翻译为自动装配(自动连线)
@Qualifier 配合Autowired注解实现按名字装配
@Resource
@Value
使用@Value注解时可以不提供set方法。
@Value注解也可以使用到set方法和构造方法上。
@Component
public class MyDataSource implements DataSource {
@Value("com.mysql.cj.jdbc.Driver")
private String Driver;
@Value("jdbc:mysql://localhost:3306/spring6")
private String url;
@Value("root")
private String username;
@Value("123456")
private String password;
@Override
public String toString() {
return "MyDataSource{" +
"Driver='" + Driver + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
public void setDriver(String driver) {
Driver = driver;
}
public void setUrl(String url) {
this.url = url;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public Connection getConnection() throws SQLException {
return null;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
<?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:component-scan base-package="com.qiye.spring6.bean3"/>
</beans>
@Test
public void testDIByAnnotation(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-di-annotation.xml");
MyDataSource myDataSource = applicationContext.getBean("myDataSource", MyDataSource.class);
System.out.println(myDataSource);
}
@Autowired
@Autowired只能根据类型装配(byType)。
@Autowired无需提供set方法。
@Autowired可以写到set方法、构造方法、构造方法的参数上。
在只有一个构造方法且构造方法的参数和类的属性一致时,@Autowired还可以省略。
@Autowired无需任何属性,效果是根据类型进行自动装配。
public interface OrderDao {
void insert();
}
@Repository
public class OrderDaoImplForMySQL implements OrderDao {
@Override
public void insert() {
System.out.println("MySQL数据库正在保存订单信息...");
}
}
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
public void generate(){
orderDao.insert();
}
}
<?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:component-scan base-package="org.qiye"/>
</beans>
@Test
public void testAutowired(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowired.xml");
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
orderService.generate();
}
@Autowired与@Qualifier
如果想按名字装配,需要@Autowired与@Qualifier配合使用。
指定名字。
@Service
public class OrderService {
@Autowired
@Qualifier("orderDaoImplForMySQL")
private OrderDao orderDao;
public void generate(){
orderDao.insert();
}
}
@Resource
@Resource注解也可以完成非简单类型的注入,官方建议使用Resource注解,该注解是java标准规范JSR-250中的类,更加具有通用性。
@Resource注解是按名字装配的,未指定name时,将属性名作为name,找不到属性名时根据类型装配。
@Resource注解可以用到属性和setter方法上。
需要引入该依赖
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
public interface StudentDao {
void deleteById();
}
@Repository
public class StudentDaoImpl implements StudentDao {
@Override
public void deleteById() {
System.out.println("mysql数据库删除学生信息~");
}
}
@Service
public class StudentService {
@Resource(name="studentDaoImpl")
private StudentDao studentDao;
public void deleteStudent(){
studentDao.deleteById();
}
}
<?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:component-scan base-package="cn.qiye"/>
</beans>
@Test
public void testResource(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-resource.xml");
StudentService studentService = applicationContext.getBean("studentService", StudentService.class);
System.out.println(studentService);
}
7.全注解式开发
写一个类代替Spring配置文件
@Configuration为声明该类为配置文件的注解,@ComponentScan注解下写具体要扫描的包
@Configuration
@ComponentScan({"cn.qiye.dao","cn.qiye.service"})
public class SpringConfig {
//代替Spring框架的配置文件
}
此时要new AnnotationConfigApplicationContext
@Test
public void testNoXML(){
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
StudentService studentService = annotationConfigApplicationContext.getBean("studentService", StudentService.class);
studentService.deleteStudent();
}
十二、JDBCTemplate
Spring提供的一个JDBC模板类,是对JDBC的封装,简化JDBC代码。
但是一般来说会让Spring集成其他的ORM,例如MyBatis。
要引入mysql驱动,spring-jdbc依赖。
数据准备:建表t_user,创建User类,MyDataSource类。
1.环境准备
public class MyDataSource implements DataSource {
private String driver;
private String url;
private String username;
private String password;
public void setUsername(String username) {
this.username = username;
}
public void setDriver(String driver) {
this.driver = driver;
}
public void setUrl(String url) {
this.url = url;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public Connection getConnection() throws SQLException {
return null;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
try {
//注册驱动
Class.forName(driver);
//获取数据库连接对象
Connection connection = DriverManager.getConnection(url, username, password);
return connection;
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
<?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="ds" class="com.qiye.spring.bean.MyDataSource">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="ds"/>
</bean>
</beans>
2.增删改查
insert
@Test
public void testInsert(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "insert into t_user(id,real_name,age) values(null,?,?)";
//在jdbcTemplate中只要是,insert delete update语句,都是调用update方法
int count = jdbcTemplate.update(sql,"66",66);
System.out.println("插入了"+count+"条记录");
}
delete
@Test
public void testDelete(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "delete from t_user where id = ?";
int count = jdbcTemplate.update(sql, 2);
System.out.println(count);
}
update
@Test
public void testDelete(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "delete from t_user where id = ?";
int count = jdbcTemplate.update(sql, 2);
System.out.println(count);
}
query一个
new BeanPropertyRowMapper<>()的参数为查询结果封装的类型。
jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 4);
@Test
public void testQueryOne(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "select id,real_name,age from t_user where id = ?";
User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 4);
System.out.println(user);
}
query多个
@Test
public void testQueryAll(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "select id,real_name,age from t_user";
List<User> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
System.out.println(query);
}
查一个值
@Test
public void testQueryOneValue(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "select count(*) from t_user";
Integer integer = jdbcTemplate.queryForObject(sql, int.class);
System.out.println(integer);
}
批量添加
batchUpdate
@Test
public void testBatchInsert(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "insert into t_user(id,real_name,age) values(null,?,?)";
//准备数据
Object[] o1 = {"小白",2};
Object[] o2 = {"小新",6};
Object[] o3 = {"美伢",29};
Object[] o4 = {"广志",30};
List<Object[]> list = new ArrayList<>();
list.add(o1);
list.add(o2);
list.add(o3);
list.add(o4);
int[] ints = jdbcTemplate.batchUpdate(sql, list);
System.out.println(Arrays.toString(ints));
}
批量更新
batchUpdate
@Test
public void testBatchUpdate(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "update t_user set real_name = ?,age = ? where id = ?";
//准备数据
Object[] o1 = {"神犬小白",2,5};
Object[] o2 = {"野原新之助",6,6};
List<Object[]> list = new ArrayList<>();
list.add(o1);
list.add(o2);
int[] ints = jdbcTemplate.batchUpdate(sql, list);
System.out.println(Arrays.toString(ints));
}
批量删除
batchUpdate
@Test
public void testBatchDelete(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "delete from t_user where id=?";
Object[] o1 ={8};
Object[] o2 ={9};
Object[] o3 ={10};
Object[] o4 ={11};
List<Object[]> list = new ArrayList<>();
list.add(o1);
list.add(o2);
list.add(o3);
list.add(o4);
int[] ints = jdbcTemplate.batchUpdate(sql, list);
System.out.println(Arrays.toString(ints));
}
3.jdbcTemplate中使用回调函数(了解)
如果想写JDBC代码,就可以写在回调函数中。
@Test
public void testCallback(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "select id,real_name,age from t_user where id = ?";
User user = jdbcTemplate.execute(sql, new PreparedStatementCallback<User>() {
@Override
public User doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
ps.setInt(1,6);
ResultSet resultSet = ps.executeQuery();
User user = null;
if (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("real_name");
int age = resultSet.getInt("age");
user = new User(id,name,age);
}
return user;
}
});
System.out.println(user);
}
4.JdbcTemplate整合德鲁伊连接池
<!-- 引入德鲁伊连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.13</version>
</dependency>
<?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="ds" class="com.alibaba.druid.pool.DruidDataSource">
<!--注意该name的值-->
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="ds"/>
</bean>
</beans>
十三、GOF之代理模式
1.概述
代理模式是23种设计模式之一,属于结构型设计模式。
代理模式是面向切面编程AOP的底层实现原理。
作用:
1.当一个对象需要受到保护时,可以考虑使用代理对象去完成某个行为。
2.需要给某个对象的功能进行功能增强时,可以考虑找一个代理进行增强。
3.A对象无法直接和B对象直接交互时,也可以使用代理模式。
代理模式三大角色:目标对象,代理对象,目标对象和代理对象的公共接口(目标对象和代理对象应该具有相同的行为动作,因为要让客户端在使用代理对象时就像使用目标对象一样)。
2.静态代理
类和类之间的关系中的泛化关系:继承 is a。
关联关系:一个类(的对象)是另一个类(的对象)的一个属性 has a。
泛化关系的耦合度比关联关系高,优先选择使用关联关系。
缺点:代码复用性不强,导致类爆炸,且不好维护。
目标对象和代理对象的公共接口
public interface OrderService {//公共接口
//订单业务接口
//生成订单
void generate();
//修改订单
void modify();
//查看订单详情
void detail();
}
目标对象
public class OrderServiceImpl implements OrderService{//目标对象
@Override
public void generate() {//目标方法
//模拟网络延时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已生成");
}
@Override
public void modify() {//目标方法
//模拟网络延时
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已修改");
}
@Override
public void detail() {//目标方法
//模拟网络延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("查看订单详情");
}
}
代理对象
public class OrderServiceProxy implements OrderService{//代理对象,和目标对象实现同一些接口
//将目标对象作为代理对象的一个属性,为关联关系
private OrderService target;//目标对象一定实现了OrderService这个公共接口。
//创建代理对象时,需要传一个目标对象作为参数。
public OrderServiceProxy(OrderService target) {
this.target = target;
}
@Override
public void generate() {//代理方法
long begin = System.currentTimeMillis();
target.generate();
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
}
@Override
public void modify() {//代理方法
long begin = System.currentTimeMillis();
target.modify();
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
}
@Override
public void detail() {//代理方法
long begin = System.currentTimeMillis();
target.detail();
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
}
}
测试
public class Test {
public static void main(String[] args) {
OrderService target = new OrderServiceImpl();
OrderService proxy = new OrderServiceProxy(target);
proxy.generate();
proxy.modify();
proxy.detail();
}
}
3.动态代理
使用了字节码生成技术,解决了静态代理的缺点。
内存中生成类的技术:
1.JDK动态代理技术:只能代理接口。
2.CGLIB(Code Generation Library)动态代理技术:高性能,高质量的Code生成类库。既可以代理接口又可以代理类。(底层是通过继承的方式实现的,所以性能强于JDK动态代理技术)(底层有一个字节码处理框架ASM)
3.Javassist动态代理技术。
JDK动态代理
public interface OrderService {//公共接口
//订单业务接口
//生成订单
void generate();
//修改订单
void modify();
//查看订单详情
void detail();
String getName();
}
public class OrderServiceImpl implements OrderService{//目标对象
@Override
public void generate() {//目标方法
//模拟网络延时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已生成");
}
@Override
public void modify() {//目标方法
//模拟网络延时
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已修改");
}
@Override
public void detail() {//目标方法
//模拟网络延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("查看订单详情");
}
@Override
public String getName() {
System.out.println("getName执行了");
return "K.O";
}
}
创建代理对象 Proxy.newProxyInstance() 第一个参数为类加载器,第二个参数为代理类要实现的接口,第三个参数为调用处理器。
newProxyInstance()翻译为新建代理对象,通过调用这个方法可以创建代理对象。
类加载器ClassLoader loader 必须和目标类的类加载器一致。
代理类要实现的接口Class<?>[] interfaces 代理类和目标类要实现同一些接口。
调用处理器InvocationHandler h ,是一个接口,在该接口的实现类中编写增强代码。
public class Client {//客户端程序
public static void main(String[] args) {
//创建目标对象
OrderService target = new OrderServiceImpl();
//创建代理对象
OrderService proxyObj = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new TimerInvocationHandler(target));
//调用代理对象的代理方法
proxyObj.generate();
proxyObj.modify();
proxyObj.detail();
String a = proxyObj.getName();
System.out.println(a);
}
}
invoke方法何时被调用:当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器中的invoke()方法会被调用。
invoke方法的三个参数:
Object proxy:代理对象的引用,使用的少。
Method method:目标方法。
Object[] args:目标方法的实参。
public class TimerInvocationHandler implements InvocationHandler {//专门负责计时的调用处理器
private Object target;
public TimerInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long begin = System.currentTimeMillis();
//调用目标对象的目标方法
Object retValue = method.invoke(target,args);
long end = System.currentTimeMillis();
System.out.println("耗时"+(end-begin)+"毫秒");
//继续向上返回
return retValue;
}
}
我们可以封装这一段代码,简化代码。
public class ProxyUtil {//封装Proxy
public static OrderService newProxyInstance(Object target){
return (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new TimerInvocationHandler(target));
}
}
CGLIB动态代理
引入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
jdk8以上要配置
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/sun.net.util=ALL-UNNAMED
public class UserService {//目标类
//目标方法
public boolean login(String username,String password){
System.out.println("系统正在验证身份");
if ("admin".equals(username) && "123".equals(password)) {
return true;
}
return false;
}
//目标方法
public void logout(){
System.out.println("系统正在退出...");
}
}
public class Client {
public static void main(String[] args) {
//创建字节码增强器对象
//这个对象是CGLIB库中的核心对象,依靠它来生成代理类。
Enhancer enhancer = new Enhancer();
//告诉CGLIB目标类是哪个
enhancer.setSuperclass(UserService.class);
//设置回调,等同于JDK动态代理机制的InvocationHandler
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
long begin = System.currentTimeMillis();
//调用目标对象的目标方法
Object retValue = methodProxy.invokeSuper(target, objects);
long end = System.currentTimeMillis();
System.out.println(end-begin);
return retValue;
}
});
//创建代理对象,这一步会在内存中生成UserService类的子类并创建代理对象。
UserService userServiceProxy = (UserService)enhancer.create();
boolean admin = userServiceProxy.login("admin", "123");
System.out.println(admin ? "登陆成功":"登陆失败");
userServiceProxy.logout();
}
}
十四、面向切面编程
1.概述
动态代理就是面向切面编程的一种实现。
AOP(Aspect Oriented Programming)面向切面编程:把业务逻辑代码看成纵向,将和业务逻辑无关的通用代码提取出来,形成一个横向的切面,以交叉业务的方式应用到业务流程的过程。
AOP是OOP(面向对象编程)的一个补充。
切面:程序中和业务逻辑无关的通用代码。
优点:代码可复用,易于维护,使开发者更专注于业务逻辑。
2.AOP的七大术语
1.连接点Joinpoint:在程序的整个执行流程中,可以织入切面的位置。(方法执行前后、异常抛出之后等位置)
2.切点Pointcut:在程序执行流程中,真正织入切面的方法。(一个切点对应多个连接点)
3.通知Advice:又称为增强,就是具体要织入的代码。
通知又分为:
前置通知(只放到目标代码(切点)之前)
后置通知(只放到目标代码(切点)之后)
环绕通知(目标代码(切点)前后都有)
异常通知(放在了catch中的通知)
最终通知(放在finally中的通知)
4.切面Aspect:切点+通知
5.织入Weaving:把通知应用到目标对象上的过程。
6.代理对象Proxy:一个目标对象被织入通知后产生的新对象。
7.目标对象Target:被织入通知的对象。
3.切点表达式
切点表达式:定义通知往哪些方法上切入。
格式:
execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形参列表) [异常])
访问控制权限修饰符:没写时代表4个权限都包括。
返回值类型:必填,*表示随意。
全限定类名:省略表示所有的类,..代表当前包以及子包下的所有类。
方法名:必填,*代表全部,delete*表示所有以delete开始的方法。
形参列表:..代表参数随意,*代表只有一个参数,*,String代表第一个参数随意,第二个参数是String。
异常:省略时表示任意异常。
4.使用Spring的AOP
方式一和二:Spring框架结合AspectJ框架,基于注解或XML。
方式三:Spring框架自己实现的AOP,基于XML(用的少,基本不用)
AspectJ:Eclipse组织的一个支持AOP的框架。
方式一:基于注解
准备工作
需要引入spring-context,spring-aop,spring-aspects依赖。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.2.5</version>
</dependency>
Spring配置文件中要添加context和aop命名空间。
<?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/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/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
在配置文件中进行组件扫描
<context:component-scan base-package="com.qiye.spring6.service"/>
实现步骤
目标类
@Service("userService")
public class UserService {//目标类
public void login(){//目标方法
System.out.println("登录~~");
}
}
切面
@Before注解标注的方法就是一个前置通知,参数要写切点表达式。
@Aspect注解标注的类就是一个切面。
开启aspectj的自动代理,在扫描类的时候,会给带有@Aspect注解的类生成一个代理。proxy-target-class="true"代表强制使用CGLIB动态代理。
<!-- 开启aspectj的自动代理-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
@Component("logAspect")
@Aspect
public class LogAspect {//切面
//切面 = 通知 + 切点
//通知(增强)Advice,@Before注解标注的方法就是一个前置通知,参数要写切点表达式。
@Before("execution(* com.qiye.spring6.service.UserService.*(..))")
public void beforeAdv(){
System.out.println("我是通知,是增强代码~~");
}
}
测试
@Test
public void testBefore(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.login();
}
其他的通知
@AfterReturning 后置通知
//后置通知
@AfterReturning("execution(* com.qiye.spring6.service.UserService.*(..))")
public void afterReturningAdvice(){
System.out.println("后置通知~~");
}
@Around 环绕通知
环绕是范围最大的通知
//环绕通知,需要一个连接点参数
@Around("execution(* com.qiye.spring6.service.UserService.*(..))")
public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
//前面代码
System.out.println("前环绕");
//执行目标
joinPoint.proceed();
//后面代码
System.out.println("后环绕");
}
@AfterThrowing 异常通知
添加一个异常
@Service("userService")
public class UserService {//目标类
public void login(){//目标方法
System.out.println("登录~~");
if(1 == 1){
throw new RuntimeException("运行时异常");
}
}
}
@After 最终通知
//最终通知
@After("execution(* com.qiye.spring6.service.UserService.*(..))")
public void afterAdvice(){
System.out.println("最终通知");
}
顺序:前环绕通知、前置通知、目标、后置通知、最终通知、后环绕通知
如果发生了异常:前环绕通知、前置通知、目标、异常通知、最终通知
切面执行顺序
可以在切面上添加@Order注解,参数为数字,数字小的先执行。
@Order(1) //数字越小,优先级越高
通用切点
我们可以定义一个通用切点的方法,提高代码复用性。
@Component("logAspect")
@Aspect
@Order(1)
public class LogAspect {//切面
@Pointcut("execution(* com.qiye.spring6.service.UserService.*(..))")
public void 通用切点(){
//方法名随意,且方法体中无需写代码,在@Pointcut注解中定义通用的切点表达式即可
}
//通知(增强)Advice,@Before注解标注的方法就是一个前置通知,参数要写切点表达式。
@Before("通用切点()")
public void beforeAdv(){
System.out.println("我是通知,是增强代码~~");
}
//后置通知
@AfterReturning("通用切点()")
public void afterReturningAdvice(){
System.out.println("后置通知~~");
}
//环绕通知,需要一个连接点参数
@Around("通用切点()")
public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
//前面代码
System.out.println("前环绕");
//执行目标
joinPoint.proceed();
//后面代码
System.out.println("后环绕");
}
//异常通知
@AfterThrowing("通用切点()")
public void afterThrowingAdvice(){
System.out.println("异常通知");
}
//最终通知
@After("通用切点()")
public void afterAdvice(){
System.out.println("最终通知");
}
}
关于连接点
除了环绕通知之外,别的通知也可以传入一个连接点参数JoinPoint joinPoint。
我们可以通过这个参数获取方法的签名。通过这个签名可以获取方法的一些具体信息。
Signature signature = joinPoint.getSignature();
System.out.println(signature);
全注解开发
@Configuration //代替spring.xml配置文件
@ComponentScan({"com.qiye.spring6.service"}) //配置扫描的包
@EnableAspectJAutoProxy(proxyTargetClass = true) //启用aspectj自动代理
public class Spring6Config {
}
@Test
public void TestAllAnnotation(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Config.class);
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.login();
}
方式二:基于XML(了解)
public class UserService {//目标对象
public void logout(){//目标方法
System.out.println("系统正在安全退出~~~");
}
}
public class TimerAspect {//切面
//通知
public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
long begin = System.currentTimeMillis();
joinPoint.proceed();
long end = System.currentTimeMillis();
System.out.println("耗时"+(end-begin)+"毫秒");
}
}
<?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">
<!-- 纳入spring ioc-->
<bean id="userService" class="com.qiye.spring.service.UserService"/>
<bean id="timerAspect" class="com.qiye.spring.service.TimerAspect"/>
<!-- aop配置-->
<aop:config>
<!-- 切点表达式-->
<aop:pointcut id="mypointcut" expression="execution(* com.qiye.spring.service..*(..))"/>
<!-- 切面:通知+切点-->
<aop:aspect ref="timerAspect">
<aop:around method="aroundAdvice" pointcut-ref="mypointcut"/>
</aop:aspect>
</aop:config>
</beans>
@Test
public void testXML(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.logout();
}
5.实际案例:事务处理
编程式事务处理
@Service
public class AccountService {//目标对象
public void transfer(){//转账的业务方法
System.out.println("银行账户正在转账操作~~~");
}
public void withdraw(){//取款
System.out.println("银行账户正在取款~~~");
}
}
@Service
public class OrderService {//目标对象
public void generate(){
System.out.println("正在生成订单~~");
}
public void cancel(){
System.out.println("订单已取消~~~");
}
}
@Component
@Aspect
public class TransactionAspect {//切面
@Around("execution(* com.qiye.spring.service..*(..))")
public void aroundAdvice(ProceedingJoinPoint joinPoint){
try {
//前环绕
System.out.println("开启事务");
//执行目标
joinPoint.proceed();
//后环绕
System.out.println("提交事务");
} catch (Throwable e) {
System.out.println("回滚事务");
}
}
}
@Configuration
@ComponentScan({"com.qiye.spring.service"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringConfig {
}
@Test
public void testAccount(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
accountService.transfer();
accountService.withdraw();
orderService.cancel();
orderService.generate();
}
6.实际案例:安全日志
@Service
public class UserService {//目标对象
public void saveUser(){
System.out.println("新增~~");
}
public void deleteUser(){
System.out.println("删除~~");
}
}
@Service
public class VipService {//目标对象
public void modifyVip(){
System.out.println("修改");
}
public void getVip(){
System.out.println("查找");
}
}
@Component
@Aspect
public class SecurityLogAspect {//切面
@Pointcut("execution(* com.qiye.spring.biz..save*(..))")
public void savePointcut(){}
@Pointcut("execution(* com.qiye.spring.biz..delete*(..))")
public void deletePointcut(){}
@Pointcut("execution(* com.qiye.spring.biz..modify*(..))")
public void modifyPointcut(){}
@Before("savePointcut() || deletePointcut() || modifyPointcut()")
public void beforeAdvice(JoinPoint joinPoint){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String format = sdf.format(new Date());
System.out.println(format + "。。。" + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
}
}
@Configuration
@ComponentScan({"com.qiye.spring.biz"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SprCon {
}
十五、Spring对事务的支持
1.回顾事务
事务transaction:多条DML要么同时成功,要么同时失败。
事务四个特性:ACID 原子性(事务是最小的工作单位,不可再分)、一致性(要么同时成功,要么同时失败)、隔离性(事务之间有隔离,保证互不干扰)、持久性(是事务结束的标志)。
2.Spring对事务的支持
Spring对事务的支持是基于AOP实现的。
采用声明式事务处理,基于注解或XML。
PlatformTransactionManager接口:事务管理器核心接口,在Spring6中有两个实现
DataSourceTransactionManager:支持jdbcTemplate,MyBatis,Hibernate等。
JtaTransactionManager:支持分布式事务管理。
注解实现
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"
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">
<context:component-scan base-package="com.qiye.bank"/>
<!-- 配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</bean>
<!-- 配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启事务注解驱动器 -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
在要控制事务的类/方法上加@Transactional注解即可
@Transactional
@Service("accountService")
@Transactional
public class AccountServiceImpl implements AccountService {
@Resource(name = "accountDao")
private AccountDao accountDao;
@Override
public void transfer(String fromActno, String toActno, double money) {
Account fromAct = accountDao.selectByActno(fromActno);
if(fromAct.getBalance() < money){
throw new RuntimeException("余额不足~~~");
}
Account toAct = accountDao.selectByActno(toActno);
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
//数据库更新
int count = accountDao.update(fromAct);
int a = 1/0;
count += accountDao.update(toAct);
if(count != 2){
throw new RuntimeException("转账失败!!");
}
}
}
@transactional的属性
传播行为
当有事务的方法A调用了有事务的方法B的时候,事务有不同的传播行为。
一共有七个传播行为,默认为REQUIRED。
REQUIRED:支持当前事务,不存在就新建一个。
propagation(传播,繁衍)
@Transactional(propagation = Propagation.REQUIRED)
SUPPORTS:支持当前事务,不存在就以非事务方式执行。
@Transactional(propagation = Propagation.SUPPORTS)
MANDATORY(强制性的):支持当前事务,不存在就抛出异常。
REQUIRES_NEW:开启一个新的事务,将存在的事务挂起。
NOT_SUPPORTED:不支持事务,如果存在事务则挂起。
NEVER:不支持事务,如果存在事务则抛出异常。
NESTED:有事务的话,在该事务中嵌套一个独立的事务,没有事务就和REQUIRED一样。
事务隔离级别
READ_UNCOMMITTED 读未提交
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
READ_COMMITTED 读提交(Oracle数据库默认)
REPEATABLE_READ 可重复读(MySQL数据库默认)
SERIALIZABLE 序列化
事务超时
设置超时时间为10秒,表示超过10秒如果该事务中所有的DML语句还有没执行完毕的,最后会回滚。时间记录的是最后一条DML语句执行结束的时间。
@Transactional(timeout = 10)
只读事务
设置为只读事务的事务不能执行增删改操作。
作用:启动spring的优化策略,提高select的执行效率。
@Transactional(realOnly = true)
设置哪些异常回滚事务
设置为异常是RuntimeException或其子类时,才会回滚
@Transactional(rollbackFor = RuntimeException.class)
设置哪些异常不回滚事务
设置异常为空指针异常时,不回滚。
@Transactional(noRollbackFor = NullPointerException.class)
3.事务的全注解开发
@Configuration //代替spring.xml配置文件
@ComponentScan("com.qiye.bank") //扫描
@EnableTransactionManagement //开启事务注解驱动器
public class Spring6Config {
//spring框架看到这个@Bean注解后,会调用这个方法,返回的对象会被纳入IoC容器管理。
@Bean(name = "dataSource")
public DruidDataSource getDruidDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql://localhost:3306/spring6");
druidDataSource.setUsername("root");
druidDataSource.setPassword("1234");
return druidDataSource;
}
@Bean(name = "jdbcTemplate")
public JdbcTemplate getJdbcTemplate(DataSource dataSource){//spring在调用这个方法时,会自动传递过来dataSource对象
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
@Bean(name = "txManager")
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
@Test
public void testNoXML(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Config.class);
AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
accountService.transfer("act-001","act-002",10000);
}
4.Spring对事务支持XML实现方式
需另外添加spring-aspects依赖。
<?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.qiye.bank"/>
<!-- 配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
<property name="username" value="root"/>
<property name="password" value="20040709qsx"/>
</bean>
<!-- 配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置通知-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- 配置通知的相关属性-->
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
</tx:attributes>
</tx:advice>
<!--配置切面-->
<aop:config>
<!-- 切点-->
<aop:pointcut id="txPointcut" expression="execution(* com.qiye.bank.service..*(..))"/>
<!-- 切面 通知+切点-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
</beans>
十六、Spring6整合JUnit5
spring对JUnit4的支持
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.2.5</version>
</dependency>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class TestSpringTX {
@Autowired
private AccountService accountService;
@Test
public void testSpringTX(){
try{
accountService.transfer("act-001","act-002",10000);
System.out.println("转账成功");
}catch (Exception e){
e.printStackTrace();
}
}
}
spring对JUnit5的支持
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.4</version>
<scope>test</scope>
</dependency>
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:spring.xml")
public class TestSpringTX {
@Autowired
private AccountService accountService;
@Test
public void testSpringTX(){
try{
accountService.transfer("act-001","act-002",10000);
System.out.println("转账成功");
}catch (Exception e){
e.printStackTrace();
}
}
}
十七、Spring6集成MyBatis
需要spring-context spring-jdbc mysql驱动 mybatis mybatis-spring 德鲁伊连接池 junit依赖。
AccountMapper
public interface AccountMapper {//实现类是动态生成的
int insert(Account account);//增
int deleteByActno(String actno);//删
int update(Account account);//改
Account selectByActno(String actno);//查一个
List<Account> selectAll();//查所有
}
AccountMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qiye.bank.mapper.AccountMapper">
<insert id="insert">
insert into t_act values(#{actno},#{balance})
</insert>
<delete id="deleteByActno">
delete from t_act where actno = #{actno}
</delete>
<update id="update">
update t_act set balance = #{balance} where actno = #{actno}
</update>
<select id="selectByActno" resultType="Account">
select * from t_act where actno = #{actno}
</select>
<select id="selectAll" resultType="Account">
select * from t_act
</select>
</mapper>
AccountService
public interface AccountService {
/**
* 保存账户
* @param act
* @return
*/
int save(Account act);
/**
* 删除账户
* @param actno
* @return
*/
int deleteByActno(String actno);
/**
* 修改账户
* @param account
* @return
*/
int modify(Account account);
/**
* 查询账户
* @param actno
* @return
*/
Account getByActno(String actno);
/**
* 获取所有账户
* @return
*/
List<Account> getAll();
/**
* 转账
* @param fromActno
* @param toActno
* @param money
*/
void transfer(String fromActno,String toActno,double money);
}
AccountServiceImpl
@Service("accountService")
@Transactional
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Override
public int save(Account act) {
return accountMapper.insert(act);
}
@Override
public int deleteByActno(String actno) {
return accountMapper.deleteByActno(actno);
}
@Override
public int modify(Account account) {
return accountMapper.update(account);
}
@Override
public Account getByActno(String actno) {
return accountMapper.selectByActno(actno);
}
@Override
public List<Account> getAll() {
return accountMapper.selectAll();
}
@Override
public void transfer(String fromActno, String toActno, double money) {
Account fromAct = accountMapper.selectByActno(fromActno);
if(fromAct.getBalance()< money){
throw new RuntimeException("余额不足");
}
Account toAct = accountMapper.selectByActno(toActno);
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance()+money);
int count = accountMapper.update(fromAct);
count += accountMapper.update(toAct);
if(count != 2){
throw new RuntimeException("转账失败");
}
}
}
mybatis.config
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 日志信息 -->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
</configuration>
spring.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 组件扫描-->
<context:component-scan base-package="com.qiye.bank"/>
<!-- 引入外部属性配置文件-->
<context:property-placeholder location="jdbc.properties"/>
<!-- 数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- SQLSessionFactoryBean-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 指定mybatis核心配置文件-->
<property name="configLocation" value="mybatis-config.xml"/>
<!--指定别名-->
<property name="typeAliasesPackage" value="com.qiye.bank.pojo"/>
</bean>
<!-- Mapper扫描配置器,生成代理类-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.qiye.bank.mapper"/>
</bean>
<!-- 事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 启动事务注解-->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
十八、Spring中的八大模式
简单工厂模式
工厂方法模式
单例模式:双重锁解决循环依赖
代理模式
装饰器模式
观察者模式
策略模式
模板方法模式