Spring6 学习和复习笔记
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
该笔记是学习B站动力节点杜老师的视频整理,这位老师讲得非常不错,喜欢学习得小伙伴们,砸门一起卷起来~~~~,整理一篇属于自己的笔记方便以后复习。
小编学习视频链接:B站杜老师Spring视频
1、软件开发原则-OCP开闭原则
对扩展开放
对修改关闭
OCP是最基本的,最核心的,其他的6个原则为其服务。
只要你在扩展系统功能时不需要修改代码,就是符合OCP开放原则,否者,在扩展系统功能时修改了源代码,那么这个设计就是失败的,违背了OCP原则。
2、软件开发原则-DIP依赖倒置原则
只要“下”一改动,“上”就会受到牵连。
依赖倒置原则的目的:降低程序耦合度,提高生产力。
3、控制反转IoC思想
当程序既违背OCP、DIP原则,可以采用将创建对象的权利外抛,交给第三方来管理。
发转是什么?
反转的两件事:
第一件是事:不在程序中采用硬编码的方式创建(new)对象,将new对象的权利抛出去了。
第二件事事:不在程序中采用硬编码的方式来维护对象,对象之间的维护权,也将其抛出去。
控制反转:是一种编程思想。或者叫做一种设计模式。由于出现时间较晚,没有被纳入GoF23种设计模式范围。
4、Spring框架
*Spring 框架实现了IoC控制反转思想
Spring框架可以帮你new对象。
Spring框架可以帮你维护对象和对象之间的关系。
*Spring是一个实现IoC思想的容器。
*控制反转的实现方式有多种,其中比较重要的叫做:依赖注入(Dependency Injection,简称 DI)
*控制反转是思想。依赖注入是这种思想的具体实现。
*依赖注入DI,包括常见的两种方式:
第一种:set注入(通过set方法给属性赋值)
第二种:构造方法注入(通过构造方法给属性赋值)
*依赖注入中的“依赖”是什么意思?“注入”是什么?
依赖:是A对象与B对象的一种关系。
注入:是一种手段,通过这一种手段,可以让A对象与B对象产生关系。
依赖注入:A对象与B对象之间的关系,靠注入的手段来维护。而注入包括:set注入和构造注入。
一、Spring概述?
1.1 Spring简介
来自百度百科介绍:
Spring是一个开源框架,它由Rod Johnson创建。它是为了解决企业应用开发的复杂性而创建的。
从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。
*Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架
*Spring最初的出现是为了解决EJB臃肿的设计,以及难以测试等问题
*Spring为简化开发而生,让程序员只需关注核心业务的实现,尽可能的不再关注非业务逻辑代码(事务控制,安全日志等)。
1.2 Spring8大模块
1、Spring Core模块
是Spring框架的基础部分,它提供了依赖注入(Dependency Injection)特征来实现容器Bean的管理。Spring容器的核心组件是BeanFactory,BeanFactory是工厂模式的一个实现,是任何Spring应用的核心。
2、Spring Context模块
这个模块拓展了BeanFactory,增加了对国际化消息、事件传播、验证的支持。还提供了许多企业服务:电子邮件、JNDI访问、EJB集成等。
3、Spring AOP模块
Spring在它的AOP模块中提供了对面向切面编程的丰富支持,Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中,可以自定义拦截器、切点、日志等操作。
1.3 Spring特点
1、轻量
从大小与开销两方面而言Spring都是轻量级的;Spring是非侵入式的:Spring应用中的对象不依赖于Spring的特定类,也不依赖外部的API等。
2、控制反转
Spring采用了控制反转(IoC)的思想促进了松耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。
3、面向切面
Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。
4、框架
Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。
1.4 配置文件xml属性含义
bean的id和class属性:
● id属性:代表对象的唯一标识。可以看做一个人的身份证号。
● class属性:用来指定要创建的java对象的类名,这个类名必须是全限定类名(带包名)。
● bean标签的id属性不可以重复,在声明对象和注入对象是要保证其唯一性。
● 底层是通过反射机制调用无参构造方法进行创建对象。
// Spring在启动初始化上下文,解析beans.xml文件,从中获取class的全限定类名
//初始化Spring容器上下文(解析beans.xml文件,创建所有的bean对象)
// 通过反射机制调用无参数构造方法创建对象
Class clazz = Class.forName(“com.powernode.spring6.bean.User”);
Object obj = clazz.newInstance();
●底层使用了Map数据结构来存储对象
该方式是Set注入:
<!--配置UserDao对象-->
<bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"></bean>
<!--配置UserService对象-->
<bean class="com.powernode.spring6.service.UserService" id="userService">
<!--想让Spring调用对应的set方法,西药配置property标签-->
<!--name属性指定值:set方法名,去掉set,首字母变小写-->
<!--ref翻译为引用 英语单词references ref指定值为是要注入的bean的 id-->
<property name="userDao" ref="userDaoBean"></property>
<!--set注入的name不是属性名 而是去掉set 后的首字母小写-->
<!--<property name="vipDao" ref="vipDaoBean"></property>-->
<property name="abc" ref="vipDaoBean"></property>
</bean>
<bean class="com.powernode.spring6.dao.VipDao" id="vipDaoBean"></bean>
在类中必须提供set方法
public class UserService {
private UserDao userDao;
private VipDao vipDao;
// set注入,必须提供一个set方法
// Spring容器会调用这个set方法,来给该属性赋值。
public void saveInfo(){
userDao.insert();
vipDao.insert();
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void setAbc(VipDao vipDao) {
this.vipDao = vipDao;
}
}
1.5 Spring6启用Log4j2日志框架
●引入依赖
<!--log4j2的依赖-->
<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>
●添加配置文件:名必须为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>
●使用日志框架
Logger logger = LoggerFactory.getLogger(FirstSpringTest.class);
logger.info("打印日志中……");
二、Spring 对IoC容器的实现
- 控制反转是一种思想
- 控制反转是为了降低程序的耦合度,提高程序扩展力,达到OCP原则,达到DIP原则
- 控制反转,在Spring容器中只做两件事
1、将对象的创建权利交给Spring框架负责。
2、将对象域对象之间关系的维护权利交给Spring容器负责。 - 控制反转通常实现方式:依赖注入(Dependency Injection,简称DI)
2.1 依赖注入(重点面试可能提及)
- 依赖注入:依赖注入实现了控制反转IoC的思想。
- Spring通过依赖注入的方式来完成对Bean对象的管理
- Bean对象的管理包括:Bean对象的创建、给Bean对象的属性赋值(或者叫Bean对象之间的关系维护)
- 依赖注入:
依赖是指对象与对象之间的关联关系;
注入是一种数据传递行为,通过注入行为来让对象与对象之间产生关系。 - 依赖注入常见的两种方式
1、set注入
2、构造注入
2.2 set注入
set注入:通过set方法实现,Spring底层调用反射机制调用属性对应的set方法然后给属性赋值。该方法要求必须对外提供set方法!
ref翻译为引用 英语单词references ref指定值为是要注入的bean的 id
想让Spring调用对应的set方法,需要配置property标签。
- 测试类中测试
- Spring注入流程和原理
<?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.powernode.spring6.dao.UserDao"/>
<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
<property name="userDao" ref="userDaoBean"/>
</bean>
</beans>
实现原理(这里的set方法是Idea生成set+属性名首字母大写,如不是上图述有介绍):
通过property标签获取到属性名:userDao
通过属性名推断出set方法名:setUserDao()
通过反射机制调用setUserDao()方法给属性赋值
property标签的name是属性名。(自定义set方法,去掉set首字母变小写)。
property标签的ref是要注入的bean对象的id。(通过ref属性来完成bean的装配,装配指的是:创建系统组件之间关联的动作)。
- property标签的name属性,演变规律
<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
<property name="userDao">
<ref bean="userDaoBean"/>
</property>
</bean>
总结:set注入的核心实现原理:通过反射机制调用set方法来给属性赋值,让两个对象之间产生关系。
2.3 构造注入
核心原理:通过调用构造方法来给属性赋值。
<bean id="orderDaoBean" class="com.powernode.spring6.dao.OrderDao"/>
<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
<!--index="0"表示构造方法的第一个参数,将orderDaoBean对象传递给构造方法的第一个参数。-->
<constructor-arg index="0" ref="orderDaoBean"/>
</bean>
如果构造方法有两个参数:
- 不使用参数下标,使用参数的名字也可以
<bean id="orderDaoBean" class="com.powernode.spring6.dao.OrderDao"/>
<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
<!--这里使用了构造方法上参数的名字-->
<constructor-arg name="orderDao" ref="orderDaoBean"/>
<constructor-arg name="userDao" ref="userDaoBean"/>
</bean>
<bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>
- 不使用参数下标,不使用参数的名字也行(顺序可以与参数列表的顺序不一致)
<bean id="orderDaoBean" class="com.powernode.spring6.dao.OrderDao"/>
<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
<!--没有指定下标,也没有指定参数名字-->
<constructor-arg ref="orderDaoBean"/>
<constructor-arg ref="userDaoBean"/>
</bean>
<bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>
2.4 Set注入部分
2.4.1 注入外部Bean
2.4.2 注入内部Bean
内部Bean的方式:在bean标签中嵌套bean标签,其底层基于set方法。
这种方式作为了解。
2.4.3 注入简单类型
即是:对象的属性是另一个对象!
如果是其他类型呢?int ,String会怎么样?
- 需要特别注意:如果给简单类型赋值,使用value属性或value标签。而不是ref。
- 注释掉其一,自然是没有问题的啦!
2.4.5 Spring中简单类型有哪一些?(面试可能涉及)
- 对于简单类型的判断我们可以使用BeanUtils的isSimpleProperty方法来判断!但这样每一次判断一下是不是太麻烦了呢?接下来我们看该方法的源码看看有哪一些类型!
@Test
public void testSimpleData(){
System.out.println(BeanUtils.isSimpleProperty(Date.class));
}
- 总结
通过源码可知:简单类型包括
-基本数据类型
-基本数据类型对应的比本包装类
-String或其他的CharSequence(字符序列)子类
-Number子类
-Enum子类
-URI
-URL
-Temporal(时域)子类
-Locale(语系)子类
-Class
-另外还包括以上简单值类型对应的数组类型。另外还包括以上简单值类型对应的数组类型。
需要注意的是:时间Date虽然是Spring 10种基本简单数据类型,但是通常把他当作引用类型使用。
● 如果把Date当做简单类型的话,日期字符串格式不能随便写。格式必须符合Date的toString()方法格式。显然这就比较鸡肋了。如果我们提供一个这样的日期字符串:2010-10-11,在这里是无法赋值给Date类型的属性的。
● spring6之后,当注入的是URL,那么这个url字符串是会进行有效性检测的。如果是一个存在的url,那就没问题。如果不存在则报错。
2.4.6 注入数组
- 注意:注入List集合的时候使用list标签,List集合是基本类型使用value,是引用类型是用ref。
2.4.7 注入List集合
- 测试注入List集合xml配置文件写法
<!--注入List集合,类型为User对象,这里使用只演示ref-->
<bean id="userBean1" class="com.powernode.spring6.bean.User">
<property name="username" value="小涂"></property>
<property name="password" value="@xiaotu"></property>
<property name="age" value="34"></property>
</bean>
<bean id="person2" class="com.powernode.spring6.bean.Person">
<property name="favariteFoods">
<list>
<value>张三</value>
<value>李四</value>
<value>王晓</value>
</list>
</property>
<property name="userList">
<list>
<ref bean="userBean"></ref>
<ref bean="userBean1"></ref>
</list>
</property>
</bean>
- 测试程序
/**
* 注入简单数组类型
*/
@Test
public void trstSinmpleList(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-simple-type.xml","set-di.xml");
Person person = applicationContext.getBean("person2", Person.class);
System.out.println(person);
}
2.4.8 注入Set集合
- Set集合:无序不可重复.
<!--注入Set集合简单类型-->
<bean id="person3" class="com.powernode.spring6.bean.Person2">
<property name="phones">
<set>
<!--注入不是简单数据类型使用ref-->
<value>183****1087</value>
<value>184****3087</value>
<value>187****7087</value>
</set>
</property>
</bean>
- 测试程序及结果
2.4.9 注入Map集合
- 注入map简单数据类型xml配置
<!--注入map集合简单数据类型,非简单数据类型使用ref-->
<bean id="person4" class="com.powernode.spring6.bean.Person3">
<property name="map">
<map>
<!--如果key不是简单类型,使用 key-ref 属性-->
<!--如果value不是简单类型,使用 value-ref 属性-->
<entry key="1" value="贵州"></entry>
<entry key="2" value="云南"></entry>
<entry key="2" value="四川"></entry>
</map>
</property>
</bean>
- 键不能重复,否则会覆盖掉相同key的值,例如:该处四川覆盖了云南。
- 注意:
● 使用
2.4.10 注入Properties
- java.util.Properties继承java.util.Hashtable,所以Properties也是一个Map集合。
- xml 写法
<!--注入properties对象-->
<bean id="people" class="com.powernode.spring6.bean.People">
<property name="properties">
<props>
<prop key="driver">com.mysql.cj.jdbc.Driver</prop>
<prop key="url">jdbc:mysql://localhost:3306/spring</prop>
<prop key="username">root</prop>
<prop key="password">yuy</prop>
</props>
</property>
</bean>
- 单元测试结果
使用props标签嵌套prop标签完成。
2.4.11 注入null和空字符串
- 注入空字符串使用: 或者 value=“”,注入null使用: 或者 不为该属性赋值。
- xml配置规范
<!--注入空字符串-->
<bean id="vip" class="com.powernode.spring6.bean.Vip">
<!--第一种方式-->
<!-- <property name="name" value=""></property>-->
<!--第二种方式-->
<property name="name">
<value></value>
</property>
</bean>
- 测试运行结果
- 注入null
- xml写法
<!--注入null-->
<!--第一种方式:不给其注入赋值-->
<!-- <bean id="vip" class="com.powernode.spring6.bean.Vip"></bean>-->
<!--第二种方式:使用null标签-->
<bean id="vip" class="com.powernode.spring6.bean.Vip">
<property name="name">
<null></null>
</property>
</bean>
- 测试运行结果,为空指针说明null
2.4.12 注入的值中含有特殊符号
- xml中右5个特殊字符,分别是: <, >, ', ",&;
- 如果出现在注入字符串中就会出现错误,如图;
- 解决方案:
1、特殊符号使用转义字符代替
2、将含有特殊符号的字符串放到:<![CDATA[]]> 当中。CDATA区中的数据不会被XML文件解析器解析。 - 常见特殊字符的转义字符
特殊字符 | 转义字符 |
---|---|
> | > |
< | < |
’ | ' |
" | " |
& | & |
- 修改后
- CDATA方式解决
2.4.12 P命名空间注入
- 目的:简化配置
- 使用P命名空间的步骤:
第一:在XML头部信息中添加p命名空间的配置息:xmlns:p=“http://www.springframework.org/schema/p”
第二:p命名空间注入是基于setter方法的,所以需要对应的属性提供setter方法。
package com.powernode.spring6.bean;
public class Customer {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Customer{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 测试结果:
2.4.13 C命名空间注入
- c命名空间是简化构造方法注入的
- 第一:需要在xml配置文件头部添加信息:xmlns:c=“http://www.springframework.org/schema/c”
- 第二:需要提供构造方法。
package com.powernode.spring6.bean;
public class MyBirthd {
private int year;
private int methday;
private int day;
@Override
public String toString() {
return "MyBirthd{" +
"year=" + year +
", methday=" + methday +
", day=" + day +
'}';
}
public MyBirthd(int year, int methday, int day) {
this.year = year;
this.methday = methday;
this.day = day;
}
}
- 第二步为普通正常写法
-
c标签写法
![在这里插入图片描述](https://img-blog.csdnimg.cn/f541d7a6d4434f79998bf8d3ad45ef8b.png -
测试结果
2.4.13 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
www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!--一般的注入配置-->
<bean id="myDataSource" class="com.powernode.spring6.bean.MyDataSource">
<property name="properties">
<props>
<prop key="tu">1512568946</prop>
</props>
</property>
</bean>
<bean id="myDataSource1" class="com.powernode.spring6.bean.MyDataSource1">
<property name="properties">
<props>
<prop key="Tu">1512568946</prop>
</props>
</property>
</bean>
</beans>
- 引入util配置复用
2.4.15 基于XML的自动装配
- Spring还可以完成自动化的注入,自动化注入又被称为自动装配。它可以根据姓名进行自动装配,也可以根据类型进行自动装配。
- 根据名称自动装配
package com.powernode.spring6.dao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UserDao {
public final Logger logger = LoggerFactory.getLogger(UserDao.class);
public void insert(){
logger.info("数据库正在插入一条信息……");
}
}
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
public class UserService {
private UserDao userDao;
private VipDao vipDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void setAbc(VipDao vipDao) {
this.vipDao = vipDao;
}
// set注入,必须提供一个set方法
// Spring容器会调用这个set方法,来给该属性赋值。
public void saveInfo(){
userDao.insert();
vipDao.insert();
}
}
xml配置细节:
● UserService Bean中需要添加autowire=“byName”,表示通过名称进行装配。
● UserService类中有一个UserDao属性,而UserDao属性的名字是userDao,对应的set方法是setUserDao(),正好和UserDao Bean的id是一样的。这就是根据名称自动装配。
- 测试程序结果
2.4.16 根据类型自动装配
- dao层
package com.powernode.spring6.dao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AcountDao {
public final static Logger logger = LoggerFactory.getLogger("AcountDao");
public void sava(){
logger.info("正在保存用户信息……");
}
}
- Service层
package com.powernode.spring6.service;
import com.powernode.spring6.dao.AcountDao;
public class AcountService {
private AcountDao acountDao;
public void setAcountDao(AcountDao acountDao) {
this.acountDao = acountDao;
}
public void sava(){
acountDao.sava();
}
}
- 测试程序
@Test
public void testAutoweire(){
/*通过name*/
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowire.xml");
// UserService userServiceBean = applicationContext.getBean("userServiceBean", UserService.class);
// userServiceBean.saveInfo();
/*通过类型*/
AcountService acountService = applicationContext.getBean("acountService", AcountService.class);
acountService.sava();
}
-
测试结果
-
注意
1、使用类型自动装配xml配置文件中,同一个Bean不能出现多次,即是唯一的,若出现多个Bean,则会爆出异常。
测试结果说明了,当byType进行自动装配的时候,配置文件中某种类型的Bean必须是唯一的,不能出现多个。
2.4.16 Spring引入外部属性配置文件
- 我们都知道编写数据源的时候是需要连接数据库的信息的,例如:driver url username password等信息。这些信息通常时写到一个单独的配置文件中,接下来这个案例就是模拟这个过程。
- 数据源类,提供相关属性
package com.powernode.spring6.sql;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
public class MyDataSource implements DataSource {
private String driver;
private String url;
private String username;
private String password;
@Override
public String toString() {
return "MyDataSource{" +
"driver='" + driver + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + 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;
}
}
- 外部配置类的编写
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/spring
username=root
password=root123
- 测试类编写
<?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:property-placeholder标签的location属性找到配置文件,
该标签默认重类的根路径下加载资源-->
<context:property-placeholder location="jdbc.properties"></context:property-placeholder>
<!--配置数据源-->
<bean id="ds" class="com.powernode.spring6.sql.MyDataSource">
<!--通过占位符取值 ${key}-->
<property name="driver" value="${driver}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${username}"></property>
<property name="password" value="${password}"></property>
</bean>
</beans>
- 测试结果
原因是${username},优先读取的是我们系统的变量,这里是我系统用户变量,所以我们经常在配置文件中看见jdbc.sdl.dsad等一直点下去,就是防止这种情况的发生。
三、Bean的作用域
- Spring默认情况下是如何管理这个Bean的:
1、默认情况下Bean是单例的,在进行创建对象(实例化对象)完成,就对其进行“曝光”。
2、在Spring上下文初始化的时候实例化,每一次getBean都会返回一个单例对象。
- 由以上可知:默认情况下,Bean对象的创建是在初始化Spring上下文的时候就完成的。
- prototype
如果我们想要每一次从Spring容器中拿到对象都是不同的,就需要在Bean的申明中使用scope属性:prototype,这样Spring会在每一次执行getBean()方法的时候创建Bean对象,调用几次则创建几次。
- 单例Bean与多例Bean的优缺点
- scope如果没有配置,它的默认值是什么呢?默认值是singleton,单例的。
- 自定义线程级别的scope
spring内置了线程范围的类:org.springframework.context.support.SimpleThreadScope,可以直接用。
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="myThread">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
<bean id="sb" class="com.powernode.spring6.beans.SpringBean" scope="myThread" />
不同线程之间的Bean是不同的,线程安全问题就得以解决。
四、GoF之工厂模式
设计模式:一种可以被重复利用的解决方案。
工厂模式是解决对象创建问题的,所以工厂模式属于创建型设计模式。这里为什么学习工厂模式呢?这是因为Spring框架底层使用了大量的工厂模式。
4.1 工厂模式的三种形态
-
第一种:简单工厂模式 ,或者叫静态工厂方法模式,其不属于23中设计模式之一。
简单工厂模式是 工厂方法模式的一种特殊实现。 -
第二种:工厂方法模式,是23中设计模式之一。
-
第三种:抽象工厂模式,是23中设计模式之一。
4.2 简单工厂模式
- 简单工厂的角色包括3个(简称:一个工厂,两个产品。)
1、抽象产品 角色
2、具体产品 角色
3、工厂类 角色
4.3 简单工厂模式的简单模拟
- 抽象产品角色:
package powernode.spring6.factory;
/**
* 武器(抽象产品角色)
*/
public abstract class Weapon {
/**
* 武器的攻击行为
*/
abstract void attack();
}
- 具体产品1:
package powernode.spring6.factory;
/**
* 坦克具体产品角色
*/
public class Tank extends Weapon{
@Override
void attack() {
System.out.println("开炮!");
}
}
- 具体产品2:
package powernode.spring6.factory;
/**
* 具体产品:战斗机
*/
public class Fighter extends Weapon{
@Override
void attack() {
System.out.println("战斗机抛下原子弹,飞走了!");
}
}
- 具体产品3:
package powernode.spring6.factory;
/**
* 匕首(具体产品角色)
*/
public class Dagger extends Weapon{
@Override
void attack() {
System.out.println("砍死他!");
}
}
- 工厂类角色:
package powernode.spring6.factory;
/**
* 工厂类角色
*/
public class WeaponFactory {
/**
* 根据不同的武器类型生产武器
* @param weaponType 武器类型
* @return 武器对象
*/
public static Weapon get(String weaponType){
if (weaponType == null || weaponType.length()==0){
return null;
}
if (weaponType.equals("Tank")){
return new Tank();
} else if (weaponType.equals("Dagger")) {
return new Dagger();
} else if (weaponType.equals("Fighter")) {
return new Fighter();
}else {
throw new RuntimeException("该工厂不生产该武器!");
}
}
}
- 客户端(客户)
package powernode.spring6.factory;
public class Client {
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();
}
}
- 程序运行结果
- 总结
1、简单工厂模式的优点:
1.1 客户端不需要知道,产品的构造细节,只需要向工厂索取。客户端只负责“消费”,工厂只负责“生产”,初步实现了初步责任的简单分离,即生产与消费分离。
2、简单工厂模式的缺点:
● 缺点1:工厂类集中了所有产品的创造逻辑,形成一个无所不知的全能类,有人把它叫做上帝类。显然工厂类非常关键,不能出问题,一旦出问题,整个系统瘫痪。
● 缺点2:不符合OCP开闭原则,在进行系统扩展时,需要修改工厂类。
- Spring中的BeanFactory就使用了简单工厂模式。
4.4 工厂方法模式
工厂方法保留了简单工厂的优点,初步责任分离。也解决了简单工厂的缺点。
工厂方法模式的角色包括(简称:“两个厂,两产品”):
● 抽象工厂角色
● 具体工厂角色
● 抽象产品角色
● 具体产品角色
我们可以看到在进行功能扩展的时候,不需要修改之前的源代码,显然工厂方法模式符合OCP原则。
工厂方法模式的优点:
● 一个调用者想创建一个对象,只要知道其名称就可以了。
● 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
● 屏蔽产品的具体实现,调用者只关心产品的接口。
工厂方法模式的缺点:
● 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
五、Bean的实例(获取方式)化方式
Spring为Bean提供了多种实例化方式,通常包括4种方式。(也就是说在Spring中为Bean对象的创建准备了多种方案,目的是:更加灵活)
● 第一种:通过构造方法实例化
● 第二种:通过简单工厂模式实例化
● 第三种:通过factory-bean实例化
● 第四种:通过FactoryBean接口实例化
5.1 通过构造方法实例化
5.2 通过简单工厂模式(静态工厂模式)实例化
5.3 通过factory-bean实例化
本质上是:通过工厂方法模式进行实例化。
定义一个Bean
package powernode.spring6.bean;
public class Order {
}
定义具体工厂类,工厂类中定义实例方法
package powernode.spring6.bean;
public class OrderFactory {
public Order get(){
return new Order();
}
}
在Spring配置文件中指定factory-bean以及factory-method
<bean id="orderFactory" class="com.powernode.spring6.bean.OrderFactory"/>
<bean id="orderBean" factory-bean="orderFactory" factory-method="get"/>
编写测试程序
@Test
public void testSelfFactoryBean(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Order orderBean = applicationContext.getBean("orderBean", Order.class);
System.out.println(orderBean);
}
- 通过FactoryBean接口实例化
在Spring中,当你编写的类直接实现FactoryBean接口之后,factory-bean不需要指定了,factory-method也不需要指定了。
factory-bean会自动指向实现FactoryBean接口的类,factory-method会自动指向getObject()方法。 - FactoryBean的写法
package powernode.spring6.bean;
import org.springframework.beans.factory.FactoryBean;
public class PersonBeanFactory implements FactoryBean<Person> {
@Override
public Person getObject() throws Exception {
return new Person();
}
@Override
public Class<?> getObjectType() {
return null;
}
}
FactoryBean在Spring中是一个接口。被称为“工厂Bean”。“工厂Bean”是一种特殊的Bean。所有的“工厂Bean”都是用来协助Spring框架来创建其他Bean对象的。
5.4 BeanFactory和FactoryBean的区别(面试点)
5.4.1 BeanFactory
Spring IoC容器的顶级对象,BeanFactory被翻译为“Bean工厂”,在Spring的IoC容器中,“Bean工厂”负责创建Bean对象。
BeanFactory是工厂。
5.4.2 FactoryBean
FactoryBean:它是一个Bean,是一个能够辅助Spring实例化其它Bean对象的一个Bean。
在Spring中右两种Bean类型
一类:普通Bean
一类:工厂Bean(工厂Bean也是一种Bean,只不过这种Bean比较特殊,它可以辅助Spring实例化其它Bean对象。)
六、 Bean的生命周期
6.1 什么是Bean的生命周期
Speing其实就是管理Bean对象的工厂。它负责对象的创建,对象的销毁等。
所谓生命周期就是从对象开始创建到最终销毁的之间的各个过程。
6.2 Bean的生命周期之5步
Bean生命周期的管理,可以参考Spring的源码:AbstractAutowireCapableBeanFactory类的doCreateBean()方法。
- 定义一个Bean
package com.itbaizhan.Bean;
public class User {
private String name;
public User() {
System.out.println("1.实例化Bean");
}
public User(String name) {
this.name = name;
}
public void initBean(){
System.out.println("3.初始化Bean");
}
public void destroyBean(){
System.out.println("5.销毁Bean");
}
public void setName(String name) {
this.name = name;
System.out.println("2.Bean属性赋值");
}
@Override
public String toString() {
return "第4步使用Bean";
}
}
- 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">
<!--
init-method属性指定初始化方法。
destroy-method属性指定销毁方法。
-->
<bean id="userBean" class="com.powernode.spring6.bean.User" init-method="initBean" destroy-method="destroyBean">
<property name="name" value="zhangsan"/>
</bean>
</beans>
- 测试程序
@Test
public void BeanLife5(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User userBean = applicationContext.getBean("userBean", User.class);
System.out.println(userBean);
ClassPathXmlApplicationContext applicationContext1 = (ClassPathXmlApplicationContext) applicationContext;
applicationContext1.close();
}
- 测试结果
- 注意
只有Spring容器关闭时才会销毁Bean。
ClassPathXmlApplicationContext才有关闭容器的方法。
配置文件中的init-method指定初始化方法。destroy-method指定销毁方法。
6.3 Bean的生命周期之7步
如果想在第三步初始化之前或之后添加操作,可以加一个“Bean后处理器”,编写一个类实现BeanPostProcessor类,并且重写before和after方法:
package com.itbaizhan.Bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class LogBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Bean后处理器的before方法执行,即将开始初始化");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Bean后处理器的after方法执行,已完成初始化");
return bean;
}
}
- 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的生命周期5步-->
<bean id="userBean" class="com.itbaizhan.Bean.User" init-method="initBean" destroy-method="destroyBean">
<property name="name" value="张三"></property>
</bean>
<!--配置Bean后处理器,改后处理器作用与当前噢诶之文件下的所有Bean-->
<bean class="com.itbaizhan.Bean.LogBeanPostProcessor"></bean>
</beans>
一定要注意:在spring.xml文件中配置的Bean后处理器将作用于当前配置文件中所有的Bean。
- 测试结果
6.4 Bean生命周期之10步
- Aware相关的接口包括:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware
● 当Bean实现了BeanNameAware,Spring会将Bean的名字传递给Bean。
● 当Bean实现了BeanClassLoaderAware,Spring会将加载该Bean的类加载器传递给Bean。
● 当Bean实现了BeanFactoryAware,Spring会将Bean工厂对象传递给Bean。
可以让User类实现5个接口,并实现所有方法:
● BeanNameAware
● BeanClassLoaderAware
● BeanFactoryAware
● InitializingBean
● DisposableBean
- 都在Bean中实现这几个接口,就构成了10步
package com.itbaizhan.Bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
public class User implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean,DisposableBean {
private String name;
public User() {
System.out.println("1.实例化Bean");
}
public User(String name) {
this.name = name;
}
public void initBean(){
System.out.println("4.初始化Bean");
}
public void destroyBean(){
System.out.println("7.销毁Bean");
}
public void setName(String name) {
this.name = name;
System.out.println("2.Bean属性赋值");
}
@Override
public String toString() {
return "6.使用Bean";
}
@Override
public void setBeanName(String name) {
System.out.println("setBeanName:"+name);
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
System.out.println("ClassLoader:"+classLoader);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("BeanFactory:"+beanFactory);
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet:");
}
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean");
}
}
- Bean后处器
package com.itbaizhan.Bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class LogBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("4.Bean后处理器的before方法执行,即将开始初始化");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("5.Bean后处理器的after方法执行,已完成初始化");
return bean;
}
}
通过测试可以看出来:
● InitializingBean的方法早于init-method的执行。
● DisposableBean的方法早于destroy-method的执行。
6.5 Bean的作用域不同,管理方式不同
Spring 根据Bean的作用域来选择管理方式。
● 对于singleton作用域的Bean,Spring 能够精确地知道该Bean何时被创建,何时初始化完成,以及何时被销毁;
● 而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。
以上10步中,对于多例Bean Spring容器只负责到使用Bean(第8步)
6.6 自己new的对象如何让Spring管理
通过DefaultListableBeanFactory对象调用registerSingleton进行交给Spring管理
@Test
public void testBeanRegister(){
Student student = new Student();
System.out.println(student);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerSingleton("student",student);
Student student1 = factory.getBean("student", Student.class);
System.out.println(student1);
}
七、Bean的循环依赖问题
7.1 什么是循环依赖
A对象中右B对象作为属性,B对象中右A对象作为属性。我依赖你,你依赖我。
比如丈夫类中有妻子属性,妻子类中有丈夫属性。即是Husband中有Wife的引用。Wife中有Husband的引用。
- Husband类
package com.itbaizhan.Bean;
public class Husband {
private String name;
private Wife wife;
public Husband() {
}
public void setName(String name) {
this.name = name;
}
public void setWife(Wife wife) {
this.wife = wife;
}
@Override
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife +
'}';
}
}
- Wife类
package com.itbaizhan.Bean;
public class Wife {
private String name;
private Husband husband;
public Wife() {
}
public void setName(String name) {
this.name = name;
}
public void setHusband(Husband husband) {
this.husband = husband;
}
@Override
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband +
'}';
}
}
7.2 prototype下的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">
<bean id="husbandBean" class="com.powernode.spring6.bean.Husband" scope="prototype">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="prototype">
<property name="name" value="小花"/>
<property name="husband" ref="husbandBean"/>
</bean>
</beans>
该异常信息:创建名为“husbandBean”的bean时出错:请求的bean当前正在创建中:是否存在无法解析的循环引用?
当循环依赖的所有Bean的scope="prototype"的时候,产生的循环依赖,Spring是无法解决的,会出现BeanCurrentlyInCreationException异常。
大家可以测试一下,以上两个Bean,如果其中一个是singleton,另一个是prototype,是没有问题的。
7.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="husbandBean" class="com.itbaizhan.Bean.Husband" scope="singleton">
<!-- <property name="name" value="小涂"></property>
<property name="wife" ref="wifeBean"></property>-->
<constructor-arg index="0" value="小涂"></constructor-arg>
<constructor-arg index="1" ref="wifeBean"></constructor-arg>
</bean>
<bean id="wifeBean" class="com.itbaizhan.Bean.Wife" scope="singleton">
<!-- <property name="name" value="红红"></property>
<property name="husband" ref="husbandBean"></property>-->
<constructor-arg index="0" value="小涂"></constructor-arg>
<constructor-arg index="1" ref="husbandBean"></constructor-arg>
</bean>
</beans>
启动测试程序后,还是爆出了循环依赖异常
- 主要原因是因为通过构造方法注入导致的:因为构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,必须在一起完成导致的。
7.4 Spring解决循环依赖的机理(面试点)
Spring为什么能可以解决singleton模式 + set注入下的循环依赖问题?
- 根本原因是在于:在这种方式下,“实例化Bean对象”和“属性赋值”这两个阶段(动作)是分开的,
- 实例化Bean的时候:可以调用无参构造方法来完成。此时可以不给Bean的属性赋值,可以提前将Bean“曝光”给外界
- 到了给Bean赋值的时候:调用set方法来完成。
- 两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。
- Bean是单例的,我们可以先把Bean实例化出来,放入一和集合中(二级缓存),等所有单例Bean全部实例化完成之后,再掉set方法给属性赋值,构成一个完整的Bean,再放入一级缓存当中。这样Spring就解决了循环依赖问题。
- Spring底层源码实现(参考DefaultSingletonBeanRegistry类):
- 在以上类中包含三个重要的属性:
Cache of singleton objects: bean name to bean instance. 单例对象的缓存:key存储bean名称,value存储Bean对象【一级缓存】
Cache of early singleton objects: bean name to bean instance. 早期单例对象的缓存:key存储bean名称,value存储早期的Bean对象【二级缓存】
Cache of singleton factories: bean name to ObjectFactory. 单例工厂缓存:key存储bean名称,value存储该Bean对应的ObjectFactory对象【三级缓存】 - 在该类中有这样一个方法addSingletonFactory(),这个方法的作用是:将创建Bean对象的ObjectFactory对象提前曝光
八、Spring IoC 注解式开发
8.1 声明Bean的注解
- 负责声明Bean的注解,常见的包括四个:
@Component
@Controller
@Service
@Repository - 源码
package com.powernode.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = {ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Component {
String value();
}
package org.springframework.stereotype;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
package org.springframework.stereotype;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
package org.springframework.stereotype;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
通过源码可以看到,@Controller、@Service、@Repository这三个注解都是@Component注解的别名,其作用一样的
只是为了增强程序的可读性,建议:
● 控制器类上使用:Controller
● service类上使用:Service
● dao类上使用:Repository
他们都是只有一个value属性。value属性用来指定bean的id,也就是bean的名字。
Spring6 倡导全注解式开发
- Spring 注解的使用
第一步:添加aop的依赖
第二步:在配置文件中引入context命名空间
第三步:在配置文件中配置扫描的包
第四步:在Bean上使用注解标签
第一步咋子引入spring-context时就会引入关联依赖aop
第二步
第三步:
第四步 使用并测试
如果注解的属性名是value,那么value是可以省略的。
如果把value属性彻底去掉,spring会被Bean自动取名吗?会的。并且默认名字的规律是: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"
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.powernode.spring6.bean,com.powernode.spring6.bean2"/>
</beans>
8.2 选择性实例化Bean
假设在某个包下有很多Bean,有的Bean上标注了Component,有的标注了Controller,有的标注了Service,有的标注了Repository,现在由于某种特殊业务的需要,只允许其中所有的Controller参与Bean管理,其他的都不实例化。这应该怎么办呢?
- 另一种方式
8.3 负责注入的注解
@Component @Controller @Service @Repository 这四个注解是用来声明Bean的,声明后这些Bean将被实例化。接下来我们看一下,如何给Bean的属性赋值。给Bean属性赋值需要用到这些注解:
- @Value
- @Autowired
- @Qualifier
- @Resource
@Value:负责简单类型的注入
@Value注解可以出现在属性上、setter方法上、以及构造方法的形参上。可见Spring给我们提供了多样化的注入。太灵活了
8.4 @Autowired与@Qualifier
@Autowired注解可以用来注入非简单类型。被翻译为:自动连线的,或者自动装配。
单独使用@Autowired注解,默认根据类型装配。【默认是byType】
该注解有一个required属性,默认值是true,表示在注入的时候要求被注入的Bean必须是存在的,如果不存在则报错。如果required属性设置为false,表示注入的Bean存在或者不存在都没关系,存在的话就注入,不存在的话,也不报错。
当有多个构造方法时,是不能省略的。
到此为止,我们已经清楚@Autowired注解可以出现在哪些位置了。
@Autowired注解默认是byType进行注入的,也就是说根据类型注入的,如果以上程序中,UserDao接口还有另外一个实现类,会出现问题吗?
显然是不可以的,只要一个接口有多个实现类,按类型注入就会不知道要注入哪一个,就会报错。
意思:需要单例的bean,找到了两个同类型的Bean。
怎么解决这个问题呢?当然要byName,根据名称进行装配了。
@Autowired注解和@Qualifier注解联合起来才可以根据名称进行装配,在@Qualifier注解中指定Bean名称。
也就是说,使用这两者就可以实现通过名称进行自动装配。
8.5 @Resource
xml<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
一定要注意:如果你用Spring6,要知道Spring6不再支持JavaEE,它支持的是JakartaEE9。(Oracle把JavaEE贡献给Apache了,Apache把JavaEE的名字改成JakartaEE了,大家之前所接触的所有的 javax.* 包名统一修改为 jakarta.*包名了。)
Resource源码
通过测试得知,当@Resource注解使用时没有指定name的时候,还是根据name进行查找,这个name是属性名。
@Resource是可以用在Set方法上的
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
@Resource
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
注意这个setter方法的方法名,setUserDao去掉set之后,将首字母变小写userDao,userDao就是name
当然也可以指定name
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
@Resource(name = "userDaoForMySQL")
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
总结@Resource注解:默认byName注入,没有指定name时把属性名当做name,根据name找不到时,才会byType注入。byType注入时,某种类型的Bean只能有一个。
8.5 全注解式开发
完全不依赖配置文件,只需要编写一个配置类就可以了
package com.powernode.spring6.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({"com.powernode.spring6.dao", "com.powernode.spring6.service"})
public class Spring6Configuration {
}
测试
@Test
public void testNoXml(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Configuration.class);
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.save();
}
九、 面向切面编程AOP
IoC是软件松耦合,AOP能捕捉系统中常用功能,将其转化为组件。
AOP:面向切面编程,面向方面编程。
AOP是对OOP的补充延伸。
AOP底层使用动态代理实现:JDK动态代理+CGLIB动态代理,JDK动态代理只能代理接口,如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB。当然,你也可以强制通过一些配置让Spring只使用CGLIB。
9.1 AOP介绍
一个系统中有很多的代码与核心业务代码无关,却是不可缺少的。我们称其为交叉业务,例如:日志模块、事务管理、安全等。
- 如果在每一个业务处理过程当中,都掺杂这些交叉业务代码进去的话,存在两方面问题:
● 第一:交叉业务代码在多个业务流程中反复出现,显然这个交叉业务代码没有得到复用。并且修改这些交叉业务代码的话,需要修改多处。
● 第二:程序员无法专注核心业务代码的编写,在编写核心业务代码的同时还需要处理这些交叉业务。
总结:将与核心业务无关的代码独立抽取出来,形成一个独立得到组件,然后以横向交叉的方式应用到核心业务流程中的过程,就叫AOP面向切面编程。
AOP的优点:
第一:代码复用性增强。
第二:易维护
第三:使开发者更专注于业务逻辑的开发。
9.2 AOP的七大术语
- 连接点(Joinpoint):可以织入切面的位置,方法执行前后,,异常抛出后等。
- 切点(Pointcut):在程序执行流程中,真正织入切面的方法。
- 通知(Advice)
通知又叫增强,就是具体你要织入的代码。
包括前置通知,后置通知,环绕通知,异常通知,最终通知。 - 切面(Aspect):切点+通知
- 织入(Weaving):把通知应用到目标对象的过程。
- 代理对象(Proxy):一个目标对象被织入后产生的对象。
- 目标对象(Target):被织入通知的对象。
9.3 切点表达式
切点表达式就是为了确定往哪个切点切入。
- 格式:
execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
访问控制权限修饰符:
● 可选项。
● 没写,就是4个权限都包括。
● 写public就表示只包括公开的方法。
返回值类型:
● 必填项。
● * 表示返回值类型任意。
全限定类名:
● 可选项。
● 两个点“…”代表当前包以及子包下的所有类。
● 省略时表示所有的类。
方法名:
● 必填项。
● 表示所有方法。
● set表示所有的set方法。
形式参数列表:
● 必填项
● () 表示没有参数的方法
● (…) 参数类型和个数随意的方法
● () 只有一个参数的方法
● (, String) 第一个参数类型随意,第二个参数是String的。
异常:
● 可选项。
● 省略时表示任意异常类型。
9.4 使用AOP
- Speing对AOP的实现主要有三种
第一种:Spring框架集合AspectJ框架实现AOP,基于注解。
第二种:Spring框架集合AspectJ框架实现AOP,基于XML。
第三种方式:Spring框架自己实现的AOP,基于XML配置方式。
实际开发中,都是Spring+AspectJ来实现AOP。
需要引入的依赖
<!--spring context依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.0-M2</version>
</dependency>
<!--spring aop依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.0.0-M2</version>
</dependency>
<!--spring aspects依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.0-M2</version>
</dependency>
然后,Spring配置文件中添加context命名空间和aop命名空间。
第一步:定义目标类以及目标方法
package org.example;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void generate(){
System.out.println("生成订单……");
}
}
定义切面类
package org.example;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyaspectJ {
//此处切点是目标类的方法。不是切面类的方法
@Before("execution( * org.example.UserService.*(..))")
// 这就是需要增强的代码(通知)
public void befor(){
System.out.println("我是一个前置通知!");
}
@Around("execution(public * org.example.UserService.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知开始!");
joinPoint.proceed();
System.out.println("环绕通知结束!");
}
@AfterReturning("execution(public * org.example.UserService..*(..))")
public void after(){
System.out.println("后置通知");
}
@AfterThrowing("execution(public * org.example.UserService..*(..))")
public void thowsadvice(){
System.out.println("异常通知");
}
@After("execution(public * org.example.UserService..*(..))")
public void zuiztongzhi(){
System.out.println("最终通知!");
}
}
第三步:目标类和切面类都纳入spring bean管理在目标类上添加@Component注解。在切面类上添加@Component注解。
第四步:在spring配置文件中添加组建扫描
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns: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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启组件扫描-->
<context:component-scan base-package="org.example"></context:component-scan>
<!--开启自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
<aop:aspectj-autoproxy proxy-target-class=“true”/> 开启自动代理之后,凡事带有@Aspect注解的bean都会生成代理对象。
proxy-target-class=“true” 表示采用cglib动态代理。
proxy-target-class=“false” 表示采用jdk动态代理。默认值是false。即使写成false,当没有接口的时候,也会自动选择cglib生成代理类。
package com.powernode.spring6.test;
import com.powernode.spring6.service.OrderService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AOPTest {
@Test
public void testAOP(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aspectj-aop-annotation.xml");
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
orderService.generate();
}
}
通知类型包括:
● 前置通知:@Before 目标方法执行之前的通知
● 后置通知:@AfterReturning 目标方法执行之后的通知
● 环绕通知:@Around 目标方法之前添加通知,同时目标方法执行之后添加通知。
● 异常通知:@AfterThrowing 发生异常之后执行的通知
● 最终通知:@After 放在finally语句块中的通知
接下来,编写程序来测试这几个通知的执行顺序:
当发生异常之后,最终通知也会执行,因为最终通知@After会出现在finally语句块中。
出现异常之后,后置通知和环绕通知的结束部分不会执行。
可以使用@Order注解来标识切面类,为@Order注解的value指定一个整数型的数字,数字越小,优先级越高。
9.5 全注解式开发
package com.powernode.spring6.service;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.powernode.spring6.service")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class Spring6Configuration {
}
9.6 基于XML配置方式的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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/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容器管理-->
<bean id="myaspectJ1" class="org.example.MyaspectJ1"></bean>
<bean id="userService1" class="org.example.UserService1"></bean>
<!--aop配置-->
<aop:config>
<!--切点-->
<aop:pointcut id="point" expression="execution( * org.example.UserService1.*(..))"/>
<!--切面-->
<aop:aspect ref="myaspectJ1">
<!--切点+通知-->
<!--method切面类中的增强方法-->
<aop:around method="around" pointcut-ref="point"></aop:around>
</aop:aspect>
</aop:config>
</beans>
切面类
package org.example;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
public class MyaspectJ1 {
//此处切点是目标类的方法。不是切面类的方法
// 这就是需要增强的代码(通知)
public void befor(){
System.out.println("我是一个前置通知!");
}
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知开始!");
joinPoint.proceed();
System.out.println("环绕通知结束!");
}
public void after(){
System.out.println("后置通知");
}
public void thowsadvice(){
System.out.println("异常通知");
}
public void zuiztongzhi(){
System.out.println("最终通知!");
}
}
目标类
package org.example;
import org.springframework.stereotype.Service;
public class UserService1 {
public void generate(){
System.out.println("生成订单……");
}
}
十、Spring6对事务的支持
10.1 什么是事务?
在一个业务流程当中,通常需要多条DML语句共同联合才能完成。这多条DMl语句必须同时成功,或者同时失败才能保证数据的安全。
多条DML语句要么同时成功,要么同时失败,这叫做事务。
- 事务的四个处理流程:
第一步:开启事务代码;
第二步:执行核心业务代码;
第三步:提交事务;
第四步:回滚事务; - 事务的四个特性:
A-原子性:事务是最小的工作单元,不可再分?
C-一致性:事务要么同时成功,要么同时失败,事务前事务后的总量不变。
I-隔离性:事务和事务之间因为有隔离性,才可以保证互不干扰。
D-持久性:持久性是事务结束的标志。 - Spring实现事务的两种方式
编程式事务:通过编写代码的方式实现事务的管理。
声明式事务:注解方式,基于xml配置方式。