专栏目录
文章目录
〇、启示录
0.1 OCP开闭原则
0.2 DIP依赖倒置
0.3 IoC控制反转
0.4 Spring特点
- 轻量
- 大小与开销都是轻量级
- 非侵入式,对象不依赖于具体类
- 控制反转
- 不new对象
- 交出对象关系
- 面向切面
- 面向切面编程,允许通过分离应用的业务辑和系统级服务进行内聚性的开发
- 容器
- 可以包含并管理对象的配置和生命周期
- 框架
- 将简单的组件配置,组合成为复杂的应用
0.5 创建Spring项目
- 使用maven的pom.xml引入依赖
<!-- 配置仓库--> <project> <!-- 使用提前版本需要设置仓库--> <repositories> <repository> <id>repository.spring.snapshot</id> <name>Spring Snapshot Repository</name> <url>https://repo.spring.io/snapshot</url> </repository> </repositories> <!--引入依赖 6.0--> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.10</version> </dependency> </dependencies> </project>
- 创建对象—Bean包中创建对象类
- 创建配置文件<*.xml>
<beans> <bean id="唯一标识符,不能重复" class="类的全路径"/> </beans>
- 写Spring程序
class Demo{ @Test public void test(){ // 1.获取spring容器对象 // ApplicationContext是一个接口 // 从根目录开始搜索spring配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring配置文件的目录"); // 2.根据bean的id生成对象 Object name = applicationContext.getBean("Bean的id"); } }
- 通过无参构造方法,使用反射机制实现
- 创建后存储到Map中
- ClassPathXmlApplicationContext可以同时绑定多个xml文件
- getBean方法传递id值错误会出现异常而报错
- applicationContext.getBean(“Bean的id”,返回类型[Data.class]);
- ApplicationContext父接口为BeanFactory(IoC顶级接口)
- Spring底层IoC实现:XML解析+工厂模式+反射机制
0.6 Log4j日志框架
- maven引入Log4j2依赖
- log4j-core,log4j-slf4j2-impl
- 写log4j配置文件
<configuration> <loggers> <!-- ALL < TRACE < DEBUG <INFO <WARN <ERROR <FATAL < OFF --> <root level="DEBUG"> <appender-ref ref="spring6log"/> </root> </loggers> <appenders> <console name="name" target="SYSTEM_OUT"> <PatternLayout patten="日志格式"/> </console> </appenders> </configuration>
- 使用Log4j
class Log{ @Test public void test(){ // 1.创建日志记录器对象 Logger logger=LoggerFactory.getLogger(); logger.level(str); } }
一、IoC控制反转
-
控制反转
- 控制反转是一种思想
- 降低程序耦合度,提高程序扩展力,达到OCP原则,达到DIP原则
- 控制反转反转的是什么
- 将对象的创建权力交出去,交给第三方容器负责
- 将对象和对象之间关系的维护交出去,交给第三方容器负责
- 如何实现
- 依赖注入–>DI
-
依赖注入
- 实现了控制反转的思想
- Spring沟通过依赖注入的方式完成Bean的管理
- Bean管理–>Bean对象的创建,Bean对象中属性的赋值(对象间关系的维护)
- 依赖
- 对象和对象之间的关联关系
- 注入
- 一种数据传递行为,通过注入行为来让对象和对象产生关系
- 实现方式
- set注入
- 构造注入
- 实现了控制反转的思想
1.1 依赖注入
1.1.1 Set方法注入 – 重点
先构造对象,再注入,必须存在set方法
<bean>
<property name="spring调用对应的set方法" ref="对应的传参"/>
</bean>
-
注入外部Bean
<beans> <bean id="bean1" class="#"/> <bean id="bean2" class="#"> <property name="bean1" ref="#"/> </bean> </beans>
-
注入内部Bean—不常用
<beans> <bean id="bean2" class="#"> <property name="bean1"> <bean class="#"/> </property> </bean> </beans>
-
简单类型
- String,int,enum,CharSequence,Number,Date,Temperal(Java8提供的时间类型),URI,URL,Locale,Class等等(基本类型,包装类)
- 一般实际开发不会将Data是为简单类型,而用ref
<!--简单类型用Value进行赋值--> <bean id="#" class="#"> <property name="#" value="#"/> <!-- 若要Data简单类型,对格式有特殊要求--> <property name="#" value="日期 月 ..."/> </bean>
-
级联注入
- property的配置顺序不能颠倒
- 级联对象必须提供get方法
<beans> <bean id="class1" class="#"/> <bean id="class2" class=""> <property name="student" value="#"/> <property name="class1" ref="#"/> <property name="class1.name" value="#"/> </bean> </beans>
-
注入数组
<beans> <bean id="class1" class="#"/> <bean id="class2" class=""> <property name="class1"> <array> <ref name="#"/> <ref name="#"/> <ref name="#"/> </array> </property> </bean> </beans>
-
注入List集合,Set集合,Map类,Properties
<beans> <bean id="class1" class="#"/> <bean id="class2" class=""> <property name="class1"> <list><!--有序可重复--> <value>#</value> <value>#</value> <value>#</value> </list> <set><!--无序不可重复--> <ref>#</ref> <ref>#</ref> <ref>#</ref> </set> <map> <entry key="#" value="#"/> <entry key="#" value="#"/> <entry key="#" value="#"/> </map> <props><!--只能是String类型--> <prop key="#" >value</prop> <prop key="#" >value</prop> <prop key="#" >value</prop> </props> </property> </bean> </beans>
-
注入null和空串
<elem> <!-- 不注入默认null--> <!--手动注入null--> <property name="#"> <null/> </property> <!--注入空串--> <property name="#" value=""/> <property name="#"> <value/> </property> </elem>
-
注入特殊符号
<properties> <!--1. 使用实体符号代替特殊符号--> <property name="#" value="a > b"/> <!--使用<![CDATA[]]>标签--> <property> <value><![CDATA["#"]]></value> </property> </properties>
实体符号
特殊符号 转义字符 [>] > [<] < ['] &apos ["] " [&] &
构造注入
创建对象同时赋值
<bean id="#" class="#">
<!-- 构造方法的两个传参-->
<!-- index指定参数下标-->'
<!-- ref指定参数对应id-->
<constructor-arg index="0" ref="#"/>
<constructor-arg index="1" ref="#"/>
<!-- 通过参数名字指定传参-->
<constructor-arg name="#" ref="#"/>
<!-- 不指定下标,不指定名字-->
<!-- 根据类型进行注入-->
<constructor-arg ref="#"/>
</bean>
1.1.2p命名空间注入 — 底层还是set注入
-
在spring配置文件头添加p命名空间
xmlns:p="http://www.springframework.org/scema/p"
<beans xmlns="#" xmlns:xsi="#" xmlns:p="http://www.springframework.org/scema/p" xsi:schemaLocation="#"> </beans>
-
使用p命名空间
<beans> <bean id="#" class="#" p:name="#" p:age="#" p:type-ref="#"/> </beans>
1.1.3 c命名空间注入 — 底层基于构造注入
-
在spring配置文件头添加c命名空间
xmlns:c="http://www.springframework.org/scema/c"
<beans xmlns="#" xmlns:xsi="#" xmlns:c="http://www.springframework.org/scema/c" xsi:schemaLocation="#"> </beans>
-
使用
<beans> <bean id="#" class="#" c:name="#" c:下标="#"/> </beans>
1.1.4 util命名空间 — 让配置复用,针对集合
在spring配置文件头添加util命名空间 xmlns:util="http://www.springframework.org/scema/util
, xsi:schemaLocaton="新增将beans-->util"
<beans xmlns="#"
xmlns:xsi="#"
xmlns:util="http://www.springframework.org/scema/util"
xsi:schemaLocation="#">
</beans>
-
使用
<beans> <util:properties id="prop"> <prop key="#">#</prop> <prop key="#">#</prop> <prop key="#">#</prop> </util:properties> <bean id="#" class="#"> <property name="#" ref="prop"/> </bean> </beans>
1.1.5 基于XML的自动装配 — 基于set注入
-
根据名字进行装配
<beans> <bean id="#" class="#" autowire="byName"/> <!--根据名字装配的目标bean的id需要是对应的set方法名--> <bean id="名称(方法名)" class="#"/> </beans>
-
根据类型进行装配
<beans> <bean id="#" class="#" autowire="byType"/> <!--根据名字装配的目标bean的id需要是对应的set方法名--> <bean class="#"/> </beans>
1.1.6 引入外部配置文件
使用context时spring默认首先搜索windows的命名,建议外部配置文件内容命名添加前缀
<beans>
<!-- 引入context命名空间-->
<!-- 1. xmlns:context="#"-->
<!-- 赋值将xsi:schemaLocation将beans改为context-->
<context:property-placeholder location="#"/>
<bean id="#" class="#">
<!-- 取值-->
<property name="#" value="${name}"/>
<property name="#" value="${name}"/>
<property name="#" value="${name}"/>
</bean>
</beans>
1.2 Bean的作域
1.2.1 singleton
Spring默认情况下
Bean时单例的,在Spring上下文初始化时实例化
每次调用getBean都返回同一个单例
1.2.2 prototype
singleton–>单例
prototype–>多例的
<bean id="#" class="#" scope="#"/>
1.2.3 其他scope
request --> 一次请求 仅限web应用
session --> 一次会话 web应用
global session --> 一次会话 portlet应用专用
application --> 一个应用对应一个Bean 仅限web应用
websocket --> 一个websocket生命周期一个Bean 仅限web应用
自定义scope --> 很少使用
1.2.4 自定义scope
例子:作用域:线程 --> 一个线程一个Bean
-
自定义Scope,实现scope接口
- spring内置了线程范围的类:org.springframework.context.support.SimpleThreadScope,可直接使用
-
将自定义的Scope注册到Spring容器中
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map> <entry key="自定义scope名字"> <bean class="org.springframework.context.support.SimpleThreadScope"/> </entry> </map> </property> </bean>
-
使用Scope
<bean id="#" class="#" scope="自定义scope"/>
二、Gof—四人组 --> 设计模式
- 设计模式:一种可以被重复利用的解决方案
- GoF(Gang of Four) --> 四个作者合著的《Design Patterns : Elements of Reusable Object-Oriented Software》《设计模式》
- 描述了23中设计模式
- 创建型:解决对象创建问题
- 单例模式
- 工厂方法模式
- 抽象工厂模式
- 建造者模式
- 原型模式
- 结构型:一些类或对象组合在一起的经典结构
- 代理模式
- 装饰模式
- 适配器模式
- 组合模式
- 享元模式
- 外观模式
- 桥接模式
- 行为型:解决类或对象之间的交互问题
- 策略模式
- 模板方法模式
- 责任链模式
- 观察者模式
- 迭代子模式
- 命令模式
- 备忘录模式
- 状态模式
- 访问者模式
- 中介者模式
- 解释器模式
2.1 GoF工厂模式
2.1.1 三种状态
- 简单工厂模式(Simple Factory)
- 静态工厂方法模式
- 工厂方法模式(Factory Method)
- 23种设计模式之一
- 抽象工厂模式(Abstract Factory)
- 23种设计模式之一
2.1.2 简单工程模式
包括三个角色
- 抽象产品角色 —> 抽象类
- 具体产品角色 —> 实现抽象类
- 工厂类角色 —> 静态方法
//* 抽象产品角色 ---> 抽象类
public abstract class Weapon {
public abstract void attack();
}
//* 具体产品角色 ---> 实现抽象类
public class Tank extends create.abstractFactory.Weapon.Weapon {
@Override
public void attack() {
System.out.println("Tank");
// ...
}
}
//* 工厂类角色 ---> 静态方法
public class WeaponFactory {
public static Weapon get(String weaponType) {
// ...
if (weaponType.equals("Tank")) {
return new Tank();
} else if (equals()) {
System.out.println("elseIF");
} else {
System.out.println("else");
}
}
}
解决问题
-
客户端只需要对工厂索要
-
简单工厂模式实现了职责分离,客户端不需要关系生产细节
-
客户端只负责消费,工厂类负责生产.生产者和消费者分离
-
缺点
- 扩展时,需要修改工厂类,违背OCP
- 工厂类责任比重大,不能出问题,一旦出问题,全部瘫痪–>全能类,上帝类
2.1.3 工厂方法模式
解决了简单工厂模式的OCP问题
一个工厂对应生产一种产品
解决了工厂是全能类的问题
包括三个角色
- 抽象产品角色 —> 抽象类
- 具体产品角色 —> 实现抽象类
- 抽象工厂角色 —> 抽象静态方法
- 具体工厂角色 —> 实现抽象工厂类
//* 抽象产品角色 ---> 抽象类
public abstract class Weapon {
public abstract void attack();
}
//* 具体产品角色 ---> 实现抽象类
public class Tank extends create.abstractFactory.Weapon.Weapon {
@Override
public void attack() {
System.out.println("Tank");
// ...
}
}
//* 抽象工厂角色 ---> 抽象静态方法
public abstract class WeaponFactory {
public abstract Weapon get();
}
//* 具体工厂角色 ---> 实现抽象工厂类
public class TankFactory extends create.abstractFactory.Factory.WeaponFactory {
@Override
public static create.abstractFactory.Weapon.Weapon get() {
return new Tank();
}
}
优点
- 扩展产品时,符合OCP原则,只需要增加产品角色类和对应的工厂角色类
缺点
- 每次增减产品都要增加类,是的系统中类的个数增加,增加系统复杂度和依赖度
2.1.4 抽象工厂模式
优点
- 当⼀个产品族中的多个对象被设计成⼀起⼯作时,它能保证客户端始终只使⽤同⼀个产品族
中的对象。
缺点
- 产品族扩展⾮常困难,要增加⼀个系列的某⼀产品,既要在AbstractFactory⾥加代码,⼜要
在具体的⾥⾯加代码****
2.2 Bean的实例化方式
Spring为Bean提供实例化方式
- 构造方法实例化
- 简单工厂模式实例化
- 通过factory-bean实例化
- 通过FactoryBean接口实例化
2.2.1 构造方法
通过绑定类的全路径,自动调用无参构造方法实例化对象
<bean id="#" class="#"/>
2.2.2 简单工厂模式
静态方法
在配置文件中指定某个类中的某个方法
factory-method指定工厂类的某个静态方法
<bean id="#" class="#" factory-method="get"/>
2.2.3 factory-bean实例
工厂模式,实例方法
告诉Spring要调用哪个对象的哪个方法
通过factory-bean + factory-method 调用
<beans>
<bean id="" class=""/>
<bean id="" class="" factory-bean="" factory-method=""/>
</beans>
2.2.4 FactoryBean接口
实现FactoryBean接口,不需要描述factory-bean, factory-method
可简化factory-bean实例化方法
这个方法可以对普通Bean进行加工处理
public class PersonFactory extends FactoryBean<Person> {
@Override
// 重写方法
public Person getObject() {
return new Person();
}
}
<bean id="person" class="PersonFactory"/>
2.2.5 BeanFactory 和 FactoryBean 的区别
IoC的顶级容磁
BeanFactory --> Bean工厂 是工厂,负责创建Bean对象
FactoryBean是一个Bean
辅助Spring实例化其他bean对象
Spring有两类Bean
- 普通Bean
- 工厂Bean
2.3 Bean的生命周期
生命周期 --> 对象从创建到最终销毁的过程
2.3.1 Bean生命周期 --> 5步
- 实例化Bean
- Bean属性赋值
- 调用set方法
- 初始化Bean
- 使用init方法,需要指定init-method
- 使用Bean
- 销毁Bean
- 使用destroy方法,需要制定destroy-method
2.3.2 Bean生命周期 --> 7步
- 实例化Bean
- Bean属性赋值
- 调用set方法
- 执行"Bean后处理器"的before方法
- 初始化Bean
- 使用init方法,需要指定init-method
- 执行"Bean后处理器"的after方法
- 使用Bean
- 销毁Bean
- 使用destroy方法,需要制定destroy-method
2.3.3 Bean生命周期 --> 10步
- 实例化Bean
- Bean属性赋值
- 调用set方法
- 检查Bean是否实现了Aware相关接口,并设置相关依赖
- 执行"Bean后处理器"的before方法
- 检查Bean是否实现了InitializingBean接口,并实现方法
- 初始化Bean
- 使用init方法,需要指定init-method
- 执行"Bean后处理器"的after方法
- 使用Bean
- 检查Bean是否实现了DisposableBean接口,并实现了方法
- 销毁Bean
- 使用destroy方法,需要制定destroy-method
2.3.4 Bean的作用域不同,管理方式不同
Spring容器只对Singleton的Bean进行完整的生命周期管理
prototype的Bean,只负责初始化完毕 --> 前八步
2.3.5 自己new的对象如何让Spring管理
new的对象之后通过DefaultListableBeanFactory方法加入Spring管理
public class Test {
public static void main(String[] args) {
Object object = new Object();
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
defaultListableBeanFactory.registerSingleton("id", object);
}
}
2.4 Bean的循环依赖
循环依赖 --> A对象中有B对象,B对象中有A对象 (例如丈夫对象和妻子对象)
2.4.1 singleton + setter
没有问题 单例的,每个对象只有唯一的一个
Spring管理Bean两个阶段
-
Spring容器加载时,实例化Bean,任意一个Bean实例化后,马上曝光[属性赋值之前]
-
曝光之后,在进行属性的赋值
核心解决方案–>实例化对象和对象属性的赋值分为两个阶段
2.4.2 prototype + setter
BeanCurrentlyInCreationException异常,Bean处于创建中异常
都是prototype时出现
2.4.3 基于构造注入
不可以,在构造过程中要赋值,赋值就要构造,循环–>创建中异常
2.4.4 底层代码
2.5 反射机制
2.5.1 分析方法四要素
- 调用哪个对象
- 调用哪个方法
- 传递什么参数
- 返回什么结果
Class<?> clazz=CLass.forName(“”);
Method doSome = clazz.getDeclaredMethod(“methodName”,Class…);
doSome.invoke(对象,参数…);
//无参对象
Object obj=clazz.Construct();
//根据属性名获取其类型
clazz.getDeclaredField(Str)–>getType();
三、手写Spring框架
3.1 准备阶段
- maven项目,配置pom.xml
- dom4j --> 解析xml
- junit --> 单元测试
- 创建Bean类,写入spring.xml [使用者角度]
3.2 开发阶段
- 创建ApplicationContext接口–>解析xml
- classPathXmlApplicationContext实现ApplicationContext接口–>实例化Bean–>Map
- SAXReader[dom4j解析xml核心对象]
- 获取指向文件的输入流
InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(configLocation)
- 读取文件
Document document = reader.read(in)
- 使用xpath获取所有bean标签
List<Node> beans = document.selectNodes("//bean")
- 遍历列表,曝光实例化Bean
- 向下转型Element,为了获取更多方法
Element beanElt = (Element) node
- 获取bean的id,class属性
String id = beanElt.attributeValue("id")
*
获取无参构造方法Class<?> aClass = Class.forName(className) ; Constructor<?> defaultCon = aClass.getDeclaredConstructor()
- 调用无参构造方法实例化
Object bean = defaultCon.newInstance()
- 曝光Bean并存入Map
singletonObjects.put(id,bean)
- 向下转型Element,为了获取更多方法
- 遍历列表,给Bean赋值
- 向下转型Element,为了获取更多方法
Element beanElt = (Element) node
- 获取bean的id,class属性
String id = beanElt.attributeValue("id")
- 获取无参构造方法
Class<?> aClass = Class.forName(className)
- 获取bean下的property
List<Element> properties = ele.elements("property")
- 遍历property
- 获取name属性,value属性,ref属性
String propertyName = property.attributeValue("name")
- 获取属性类型
Field declaredField = aClass.getDeclaredField(propertyName)
- 获取set方法名
String setMethodName="set"+propertyName.toUpperCase().charAt(0)+propertyName.substring(1)
- 获取set方法
Method setMethod = aClass.getDeclaredMethod(setMethodName, declaredField.getType())
- 调用set方法
- value为空
String ref = property.attributeValue("ref")
- ref为空
- 获取simpleName
String propertyType = declaredField.getType().getTypeName()
- switch进行类型转换
- 对属性赋值
- 获取simpleName
- value为空
- 获取name属性,value属性,ref属性
- 向下转型Element,为了获取更多方法
- 实现getBean方法
四、IoC注解式开发
简化sml配置
Spring6倡导全注解式开发
4.1 回顾注解
4.1.1 自定义注解
public @interface name{}
- 标注注解的注解,元注解
@Target(value = { ElementType})
- 标注注解最终保存在class文件中,并且可以被反射机制读取
Retention(RetentionPolicy)
@Target(ElementType.TYPE)
public @interface Component {
String value();
}
4.1.2 反射注解
@Component(属性名 = 属性值)
public class User {
}
public class ReflectAnnotation {
public static void main(String[] args) {
Class<?> aClass = Class.forName("User");
//判断类上是否有注解
if (aClass.isAnnotationPresent(Component.class)) {
//获取类上的注解
Component annotation = aClass.getAnnotation(Component.class);
System.out.println(annotation.value());
}
}
}
4.1.3 组件扫描
只知道包的名字,扫描包下的所有类,当类上具有特定注解的时候,实例化该对象,存放到Map中
class ComponentScan {
public static void main(String[] args) {
Map<String, Object> beanMap = new HashMap<>();
String packageName = "web";
String packagePath = packageName.replaceAll("\\.", "/");
//系统加载器
URL url = ClassLoader.getSystemClassLoader().getResource(packagePath);
String path = url.getPath();
File file = new File(path);
File[] files = file.listFiles();
Arrays.stream(files).forEach(f -> {
String className = packageName + f.getName().split("\\.")[0];
//通过反射机制解析注解
Class<?> aClass = Class.forName(className);
if (aClass.isAnnotationPresent(Component.class)) {
Component annotation = aClass.getAnnotation(Component.class);
String id = annotation.value();
Object object = aClass.newInstance();
beanMap.put(id, object);
}
});
}
}
4.2 声明Bean的注解
步骤:
- 依赖aop --> spring context
- 添加context命名空间
- 给Spring开个那就指定要扫描的包中的类
<context:component-scan base-package="#"/>
- 给Bean类上使用注解
负责声明Bean的注解,常见四个
-
@Component --> 组件
public @interface Component{ String value() default ""; }
-
@Controller --> 控制器 --> 表示层
public @interface Controller{ @AliasFor{ //别名 annotation = Component.class; } }
-
@Service --> 业务 --> 用在Service层
public @interface Service{ @AliasFor{ //别名 annotation = Component.class; } }
-
@Repository --> dao --> 持久层
public @interface Repository{ @AliasFor{ //别名 annotation = Component.class; } }
4.3 多包扫描
-
用逗号隔开
<context:conmponent-scan base-package="#,#"/>
-
直接指定多个包的共同父包,但会牺牲效率
4.4 选择性实例化Bean
区别实现Controller,Service,Repository选择性实例化注解的bean
-
添加use-default-filters=“false” --> 所有注解Bean实例化失效
<context:component-scan base-package="#" use-default-filters="false"> <!-- 添加生效的--> <context:include-filter type="annotation" expressioni="Type"/> </context:component-scan>
-
添加use-default-filters=“true” --> 所有注解Bean实例化生效
<context:component-scan base-package="#" use-default-filters="true"> <!-- 添加失效的--> <context:exclude-filter type="annotation" expressioni="Type"/> </context:component-scan>
4.5 负责注入的注解
Bean属性赋值的注解:
-
@Value --> 注入简单类型 不需要提供setter
public class Value{ @Value("String") private String driver; @Value("name") private String name; @Value("password") private String password; @Value("30") private int age; // 也可以使用在方法上 public Value(@Value("") String name){} }
-
@Autowired --> 注入非简单类型,根据类型
public class Autowired{ @Autowired private User user; }
-
@Qualifier --> 与@Autowired一起使用,负责注入名字
public class Qualifier{ @Autowired @Qualifier("user") private User user; }
-
@Resource
- 可以进行非简单类型注入,是JDK扩展包中,属于标准注解
- 默认根据名称装备byName,为指定名字会将属性名作为name,否则根据类型装配
- 只能用在setter上
- 依赖–>javax.annotation
- spring6支持JakartaEE9规范
- spring5支持JavaEE规范
public class Resource{ @Resource("nameBean") private Name name; }
4.6 全注解开发
@Configuration
@ComponentScan({"#", "#"})
class Spring6 {
}
class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spring6.clss);
Student student = context.getBean("student");
}
}
五、JdbcTemplate
Spring提供的JDBC模板类,对JDBC的封装,简化JDBC
同样也可以让Spring继承ORM框架:Mybatis,Hibernate等
5.1 环境准备
依赖:
spring-context
mysql
spring-jdbc
junit
<beans>
<bean id="#" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="#" ref="#"/> <!--自己的数据源-->
</bean>
</beans>
5.2 增删改
class Test {
public static void main(String[] args) {
String sql;//SQL语句
int count = jdbcTemplate.update(sql, "", "参数");
}
}
5.3 查找
class Test {
public static void main(String[] args) {
String sql;
// 单个查找
User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), "参数");
// 多个查找
List<User> user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class));
// 查一个值
int count = jdbcTemplate.queryForObject(sql, int.class);
}
}
5.4 批量添加
class Test {
public static void main(String[] args) {
String sql;
//准备数据
Object[] objects1 = {"", ""};
Object[] objects2 = {"", ""};
Object[] objects3 = {"", ""};
// 添加list集合
List<Object[]> list = new List<>();
list.add(objects1);
list.add(objects2);
list.add(objects3);
// 执行
int tf = jdbcTemplate.batchUpdate(sql, list.class);
}
}
5.5 批量增删改
class Test {
public static void main(String[] args) {
String sql;
//准备数据
Object[] objects1 = {"", ""};
Object[] objects2 = {"", ""};
Object[] objects3 = {"", ""};
// 添加list集合
List<Object[]> list = new List<>();
list.add(objects1);
list.add(objects2);
list.add(objects3);
// 执行
int tf = jdbcTemplate.batchUpdate(sql, list.class);
}
}
5.6 回调函数
import java.sql.PreparedStatement;
import java.sql.ResultSet;
class Test {
public static void main(String[] args) {
String sql;
jdbcTemplate.execute(sql, new PreparedStatementCallback<User>() {
@Override
public User doInPreparedStatement(PreparedStatement ps) throws SQLExceptiion, DataAccessException {
ps.setInt(1, 2);//给第几个问好赋什么值
ResultSet rs = ps.executeQuery();
if (rs.next()) {
int id = rs.getInt("id");
String realName = rs.getString("real_name");
int age = rs.getInt("age");
user = new User(id, realName, age);
}
return user;
}
});
}
}
5.7 使用Druid连接池
依赖 --> druid
<beans>
<bean id="#" class="DruidDataSource">
<property name="#" ref="#"/> <!--自己的数据源-->
</bean>
</beans>
六、GoF代理模式
6.1 理论
代理模式是GoF设计模式之一,属于结构型设计模式
作用:
- 当一个对象需要受到保护的时候,可以考虑使用代理对象去完成某个行为
- 需要给某个对象的功能进行功能增强的时候,可以考虑找一个代理进行增强
- A和B无法直接交互的时候,可以考虑使用代理
角色:
- 代理类 --> 代理主题
- 目标类 --> 目标对象
- 代理类和目标类的公共接口(抽象主题)
特点:
6.2 静态代理
当提出新的需求,实现新的功能时:
- 硬编码,在每个业务接口中每个方法直接添加新功能
- 违背了OCP原则
- 代码没有得到复用
- 编写业务类的子类,子类继承弗雷,对每个方法进行重写
- 导致耦合度高
- 代码没有得到复用
- 代理模式
- 创建代理对象继承接口
- 代理类中private目标类
// 公共接口
public interface OrderService {
// 生成订单
void generate();
}
// 目标类
public class OrderServiceImpl implements OrderService {
@Override
public void generate() {
System.out.println("generate");
}
}
// 代理类
public class OrderServiceProxy implements OrderService {
private OrderServiceImpl orderService;
public OrderServiceProxy(OrderServiceImpl orderService) {
//可添加增强内容
this.orderService = orderService;
}
}
优点:
- 解决了OCP原则
- 降低耦合度
缺点:
- 类爆炸,需要创建的代理类太多,不好维护
6.3 动态代理
- 可以解决静态代理的类爆炸问题
- 添加了字节码生成技术,在内存中为我们动态生成一个class字节码,该字节码就是代理类
- 可以解决代码复用问题
常见动态生成类的技术:
- JDK动态代理技术: 只能代理接口
- CGLIB动态代理技术: 高性能高质量的代码生成类库,通过继承方法实现(借助了字节码处理框架ASM)
- Javassist动态代理技术
6.3.1 JDK代理
// 公共接口
public interface OrderService {
// 生成订单
void generate();
}
// 目标类
public class OrderServiceImpl implements OrderService {
@Override
public void generate() {
System.out.println("generate");
}
}
//使用代理
public class client {
public static void main(String[] args) {
//创建目标对象
OrderService target = new OrderServiceImpl();
/*
新建代理对象,在内存中动态生成了代理类的字节码,new对象,通过字节码是实例化了对象
newProxyInstance
1. 类加载器ClassLoader 必须和目标类的加载器是同一个 class.getCLass().getClassLoader()
2. 代理类实现接口Class<?>[] 代理类和目标类实现同一个接口 class.getCLass().getInterface()
3. 调用处理器InvocationHandler 传需要增强的程序/增强代码,是个接口,就要实现类
该实现类实现InvocationHandler接口,实现invoke方法
向下转型
*/
OrderService proxyObj = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new OrderInvocationHandler(target));
//调用代理对象
proxyObj.generate();
}
}
//代理增强类
public class OrderInvocationHandler implements InvocationHandler {
/*当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke方法被调用
三个参数
1. Object proxy 代理对象引用
2. Method method 目标对象的目标方法
3. Object[] args 目标方法上的实参
*/
private Object object;
public OrderInvocationHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("InvocationHandler");
//目标对象,目标方法,目标参数,返回值
method.invoke(object, args);
return null;
}
}
可以对newProxyInstance()方法进行proxyUtils进行封装
6.3.2 CGLIB代理
既可以代理接口,也可以代理类
需要引入依赖 --> cglib
public class client {
public static void main(String[] args) {
// 创建字节码增强器
// CGLIB库当中的核心对象,依靠他生成代理类
Enhancer enhancer = new Enhancer();
//告诉CGLIB父类是谁,告诉CGLIB目标类是谁
enhancer.setSuperclass(Object.class);
//设置回调(等同于JDK代理的调用处理器)
//实现接口MethodInterceptor
// methodProxy.invokeSuper(target,objects);
enhancer.setCallback("???");
//创建代理对象
//在内存中生成UserService类的子类,代理类字节码
//创建代理对象
Object object = enhancer.create();
// 调用方法
object.method();
}
}
底层实际是生成了一个继承目标类的类
jdk高于8汇报错
- 添加参数 -> Modify options -> Add VM
- –add-opens java.base/java.lang=ALL-UNNAMED
- –add-opens java.base/sun.net.util=ALL-UNNAMED
七、AOP面向切面编程
7.1 AOP介绍
-
AOP是一种编程技术
-
AOP是对OOP(面向对象)的补充延申
-
底层通过动态代理实现
-
Spring的AOP动态代理:JDK动态代理+CGLIB动态代理
- 代理接口,默认使用JDK
- 代理某个类,且这个类没有实现接口,会默认使用CGLIB
-
每个系统中都会有一些系统服务,例如日志,事务管理等,这些服务统称为交叉业务
-
因为交叉业务是通用的,如果每个业务都掺杂交叉业务,会存在问题
- 交叉业务代码没有得到复用,若修改则需要修改多处
- 程序员无法专注于核心业务代码的编写,要额外处理交叉业务
AOP便解决上述问题,将与核心业务无关的代码提取出来,形成独立的组件,以横向交叉的方式应用于业务流程中的过程
优点
- 代码复用性强
- 代码易于维护
- 是开发者更关注业务逻辑
7.2 AOP七大术语
- 连接点 Join point --> 位置
- 整个程序执行过程中,可以织入切面的位置(方法执行前后,异常抛出之后等)
- 切点 Pointcut --> 方法
- 程序执行流程中,真正织入切面的方法(一个切点对应多个连接点)
- 通知 Advice --> 增强{具体代码)
- 前置通知 --> 方法前
- 后置通知 --> 方法后
- 环绕通知 --> 前后都有
- 异常通知 --> 在catch中
- 最终通知 --> 在finally中
- 切面 Aspect
- 切点 + 通知
- 织入 Weaving
- 把通知应用到目标对象上的过程
- 代理对象 Proxy
- 一个目标对象被织入通知后产生的新对象
- 目标对象 Target
- 被织入的对象
7.3 切点表达式
- 格式
execution([访问权限修饰符]返回类型[全限定类名]方法名(形参)[异常])
- 访问控制权限修饰符
- 可选项
- 没写就是四个权限都包括
- public只表示是公开
- 返回值类型
- 必填项
- 全限定类名
- 可选项
- "…"表示当前包以及子包的所有类
- 省略表示所有类
- 方法名
- 必填
- "*"表示所有方法
- "set*"表示所有set方法
- 形参
- 必填项目
- ()表示无参
- (*)表示只有一个参数的方法
- (…)表示参数类型和个数随意的方法
- (*,String)第一个参数类型随意,第二个是String类型
- 异常
- 可选项
- 省略是表示任意异常类型
八、Spring的AOP
Spring对AOP的实现包括以下三种:
- Spring框架结合AspectJ框架实现的AOP,基于注解方式
- Spring框架结合AspectJ框架实现的AOP,基于XML方式
- Spring框架自己实现的AOP,基于XML方式
8.1 准备工作
使用Spring+Aspect的AOP,需要引入依赖
- spring-context
- spring-aop
- spring-aspect
在Spring的配置文件中添加context命名空间和aop命名空间
8.2 实现
创建目标类,纳入IoC管理
@Service("userService")
public class UserService {
public void getName() {
System.out.println("name");
}
}
切面(通知+切点),纳入IoC管理
@Component("logAspect")
@Aspect //切面类需要使用该注解进行标注
public class LogAspect {
@Before("切点表达式")
public void addUp() {
System.out.println("我是通知");
}
}
Spring配置文件开启自动代理
<!--proxy-target-class="true" 强制使用CGLIB代理-->
<aop:aspectj-autoproxy proxy-target-class="false"/>
8.3 所有通知类型
- 前置通知 --> @Before
- 后置通知 --> @AfterReturning
- 环绕通知 --> @Around
class Test{ @Around("#") public void aroundAdvice(ProceedingJoinPoint joinPoint){ //前环绕 //执行目标 jointPoint.proceed(); //后环绕 } }
- 异常通知 --> @AfterThrowing
- 最终通知 --> @After
前环绕–>前置通知–>目标方法–>异常通知–>(后置通知)–>最终通知–>(后环绕)
8.4 切面顺序
可以通过@Order注解控制,Order数字越小优先级越高
8.5 通用切点
定义方法,添加注解Pointcut定义通用切点表达式
class Test {
//定义通用的切点表达式
@Pointcut("#")
public void Name() {
}
@Before("Name()")
public void get() {
}
}
8.6 连接点
除了环绕通知,其实所有的通知都可以添加Join Point参数,可以直接使用这个连接点的方法
8.7 全注解开发
新建一个类即可,代替spring配置文件
@Configuration //代替XMl文件
@ComponentScan("#") //组件扫描
@EnableAspectJAutoProxy(proxyTargeetClass = true) //启用aspectj的自动代理
public class Test {
}
8.8 基于XML方式实现
<!--添加命名空间-->
<beans>
<bean id="#" class="#"/>
<bean id="#" class="#"/>
<!-- aop配置-->
<aop:config>
<!-- 切点表达式-->
<aop:pointcut id="myPointcut" expression="表达式"/>
<!-- 切面:通知+切点-->
<aop:aspect ref="#">
<aop:around method="#" piontcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
</beans>
8.9 AOP编程式事务
通过aop的环绕通知编写切面,将事务管理加入
class Test {
@Around("#")
public void Advice(ProceedingJoinPoint joinPoint) {
try {
//前环绕-->开启事务
joinPoint.proceed();
//后环绕-->提交事务
} catch (Throwable e) {
//出现异常-->回滚事务
e.printStackTrace();
}
}
}
8.10 AOP安全日志
使用aop的通知实现安全日志操作(把用户的所有增删改操作进行日志记录)
九、事务
9.1 概念
- 什么是事务
- 多条DML(增删改)要么同时成功,要么同时失败
- 处理过程
- 开启事务-start transaction
- 执行核心业务代码
- 提交事务(核心业务处理过程中没有异常)-commit transaction
- 回滚事务(核心业务处理过程中出现异常)-rollback transaction
- 四个特性-ACID
- 原子性:事务是最小单位,不可再分
- 一致性:要么同时成功,要么同时失败,事务前后总量不变
- 隔离性:事务和事务之间有隔离性,保证互不干扰
- 持久性:事务结束的标志
9.2 Spring对事务的支持
Spring实现事务的两种方式:
- 编程式事务 --> 编写代码的方式实现事务管理
- 声明式事务 --> 基于注解 // 基于XML
Spring事务管理API
PlatformTransactionManager接口:spring事务管理器的核心接口
- DataSourceTransactionManager:支持jdbcTemplate,MyBatis,Hibernate等事务管理
- JtaTransactionManager:支持分布式事务管理
9.2.1 基于注解
配置事务管理器
<!--命名空间-->
<beans xmlns:tx="#">
<!-- 事务管理器-->
<bean id="#" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 数据源-->
<property name="#" ref="#"/>
</bean>
<!--事务启动器 告诉spring框架采用注解方式管理事务-->
<tx:annotation-driven transaction-manager="事务管理器的id"/>
</beans>
在方法或者类上添加注解->@Transactional
9.2.2 基于XML
未完待续...
9.3 事务属性
9.3.1 事务属性有哪些
9.3.2 事务传播行为
- REQUIRED:支持当前事务,若没有事务,则新建一个
- SUPPORTS:支持当前事务,若没有事务,则按非事务方式执行
- MANDATORY:必须在一个事务中,没有事务则抛出异常
- REQUIRES_NEW:开启新的事物,如果事务已经存在,则将存在的事务挂起
- NOT_SUPPORTED:以非事务的方式运行,如果事务已经存在,如果事务已经存在
- NEVER:如果事务已经存在,如果事务已经存在,则抛出异常
- NESTED:如果当前有事务进行,则将该方法应当运行在一个嵌套式事务中,被嵌套的事务可以独立于外层事务进行提交回滚.若外层事务不存在,则如REQUIRED一样
9.3.3 事务隔离级别
@Transactional(isolation = Isolation)
防止多事务并发
数据库读取数据三大问题:
- 脏数据:读到但是没有提交 --> 读缓存
- 不可重复读:同一个事务中,两次读取的数据不一样 --> 数据不一样
- 幻读:读到的数据是假的 --> 数据不存在
隔离的四个级别:
- 读未提交:READ_UNCOMMITTED
- 存在脏读问题,能够读取到其他事务未提交的数据
- 读提交:READ_COMMITTED
- 解决脏读问题,其他事务提交后才能读取到,但存在不可重复读的问题
- 可重复读:REPEATABLE_READ
- 解决了不可重复读,只要当前事务不结束,读到的数据一直都是一样的,但存在幻读
- 序列化:SERIALIZABLE
- 解决了幻读问题,事务排队执行,不支持并发
9.3.4 事务超时
@Transactional(timeout = 10)
超过设定时间,如果该事务中所有的DML语句还没有执⾏完毕的话,最终结果会选择回滚
默认值-1,表示没有时间限制。
在当前事务当中,最后⼀条DML语句执⾏之前的时间。如果最后⼀条DML语句后⾯很有很多业务逻辑,这些业务代码执⾏的时间不被计⼊超时时间。
9.3.5 只读事务
@Transactional(readOnly = true)
当前事务设为只读,在该事务中只允许select语句执行,DIU均不可执行
该特性启动spring的优化策略,提高select语句执行效率
9.3.6 设置异常回滚
@Transactional(rollbackFor=NumberFormatException.class)
只有发生特定异常的时候,才执行回滚
9.3.7 设置异常不回滚
@Transactional(noRollbackFor=NumberFormatException.class)
只有不发生特定异常的时候,才执行回滚
9.3.8 全注解开发
编写一个类来代替XML配置文件
import javax.management.MXBean;
@Configration //代替xml文件
@ComponentScan("#") //组件扫描
@EnableTransactionManagement //开启事务注解
public class TransactionManageConfig {
@Bean(name = "id") //德鲁伊数据池
public DataSource getDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring6");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
@Bean(name = "jdbcTemplate") //jdbc
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
@Bean("txManage") //事务管理器
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);