第一章 初识Spring
1.1 Spring简介
-
Spring是一个为简化企业级开发而生的开源框架。
-
Spring是一个IOC(DI)和AOP容器框架。
- IOC全称:Inversion Of Control【控制反转】
- 将对象控制权由程序员自己反转交个Spring
- DI全称:Dependency Injection【依赖注入】
- Spring管理对象与对象之间依赖关系
- AOP全称:Aspect-Oriented Programming【面向切面编程】
- IOC全称:Inversion Of Control【控制反转】
-
Spring简图
1.2 搭建Spring框架
-
导入jar包
<!--导入spring-context--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.1</version> </dependency> <!--导入junit4.12--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
-
创建POJO
-
编写核心配置文件
-
配置文件名称:applicationContext.xml【beans.xml或spring.xml】
-
配置文件位置:src/main/resources
-
示例代码
<bean id="empCui" class="com.atguigu.pojo.Employee"> <property name="id" value="1001"></property> <property name="lastName" value="cuicui"></property> <property name="email" value="[email protected]"></property> <property name="salary" value="100.5"></property> </bean>
-
-
使用核心类库【容器对象】
-
ApplicationContext
@Test public void testSpringHw(){ //创建Spring容器对象 ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); //通过容器对象,获取需要对象 Employee empCui = (Employee) ioc.getBean("empCui"); System.out.println("empCui = " + empCui); }
-
1.3 Spring特性
- 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API。
- 容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期。
- 组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
- 一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring 自身也提供了表述层的SpringMVC和持久层的JDBCTemplate)。
1.4 Spring中getBean()三种方式
-
getBean(String beanId):通过beanId获取对象
- 不足:返回值是Object,使用不灵活
-
getBean(Class clazz):通过Class获取对象
-
不足: 当容器中存在多个同类型的bean时,会报如下错误:
available: expected single matching bean but found 2: empCui,empTao
-
-
getBean(String beanId,Class clazz):通过beanId&Class获取对象
- 推荐使用
1.5 bean标签详解
-
属性
- id:唯一标识【任意定义,不可重复】
- class:装配对象全类名
-
子标签property
- name属性:设置对象中属性名称
- value属性:设置对象中属性数值
-
示例代码
<bean id="empCui" class="com.atguigu.pojo.Employee"> <property name="id" value="1001"></property> <property name="lastName" value="cuicui"></property> <property name="email" value="[email protected]"></property> <property name="salary" value="100.5"></property> </bean>
第二章 SpringIOC底层实现
2.1 BeanFactory与ApplicationContext
- BeanFactory:IOC容器的基本实现,是Spring内部的使用接口,是面向Spring本身的,不是提供给开发人员使用的。
- ApplicationContext: BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory。
2.2 IOC底层实现简图
-
BeanFactory【底层实现】
-
...
-
ApplicationContext【开发者实现】
-
ConfigurableApplicationContext
是ApplicationContext的子接口,包含一些扩展方法refresh()和close(),让ApplicationContext具有启动、关闭和刷新上下文的能力。
- ...
- ClassPathXmlApplicationContext【基于类路径检索xml】
- FileSystemXmlApplicationContext【基于系统路径检索xml】
- AnnotationConfigApplicationContext【基于完全注解研发】
- ...
-
-
-
第三章 Spring依赖注入数值问题
3.1 字面量数值
- 数据类型:基本数据类型及其包装类型、String等类型
- 语法结构:value属性或value标签可注入数值
3.2 CDATA区
-
语法:<![CDATA[]]>
-
作用:解决xml【支持各种xml】中特殊符号问题
-
示例代码
<bean id="empTao" class="com.atguigu.pojo.Employee"> <property name="id" value="1001"></property> <property name="lastName"> <value><![CDATA[<taotao>]]></value> </property> <property name="email" value="[email protected]"></property> <property name="salary"> <value>100.5</value> </property> </bean>
<select id="selectDeptAndEmpByDeptIdStep" resultMap="deptAndEmpRmStep"> <![CDATA[ select dept_id, dept_name from tbl_dept where dept_id < #{deptId} ]]> </select>
day06
3.3 外部已声明bean及级联属性赋值
- 级联属性赋值时,更改级联属性数值,也会同时更改外部bean的属性值
- 因为:引用类型赋值时,赋值的是引用
<!-- 测试外部bean-->
<bean id="dept1" class="com.atguigu.pojo.Dept">
<property name="deptId" value="101"></property>
<property name="deptName" value="研发部门"></property>
</bean>
<bean id="empXin" class="com.atguigu.pojo.Employee">
<property name="id" value="1002"></property>
<property name="lastName" value="xinxin"></property>
<property name="email" value="[email protected]"></property>
<property name="salary" value="200.5"></property>
<!-- 装配【注入】外部bean-->
<property name="dept" ref="dept1"></property>
<!-- 级联属性赋值-->
<property name="dept.deptName" value="教学部门"></property>
</bean>
3.4 内部bean
- 内部bean不会直接装配到ioc容器中,所以一般不会设置id属性值
<!-- 测试内部bean-->
<bean id="empNan" class="com.atguigu.pojo.Employee">
<property name="id" value="1003"></property>
<property name="lastName" value="nannan"></property>
<property name="email" value="[email protected]"></property>
<property name="salary" value="200.5"></property>
<property name="dept">
<bean class="com.atguigu.pojo.Dept">
<property name="deptId" value="102"></property>
<property name="deptName" value="教务部门"></property>
</bean>
</property>
</bean>
3.5 装配List
-
如需使用<util:list>标签,需要引入其名称空间及相关约束,快捷键如下:
<!-- 测试List-->
<bean id="dept2" class="com.atguigu.pojo.Dept">
<property name="deptId" value="102"></property>
<property name="deptName" value="教学部门"></property>
<property name="empList">
<list>
<ref bean="empXin"></ref>
<ref bean="empTao"></ref>
<!-- <bean class="com.atguigu.pojo.Employee"></bean>-->
</list>
</property>
</bean>
<!-- 定义list集合-->
<util:list id="empList">
<ref bean="empTao"></ref>
<ref bean="empXin"></ref>
</util:list>
<bean id="dept3" class="com.atguigu.pojo.Dept">
<property name="deptId" value="103"></property>
<property name="deptName" value="教学部门3"></property>
<!-- 引入外部已声明List-->
<property name="empList" ref="empList"></property>
</bean>
3.6 装配Map
<!-- 测试Map-->
<bean id="dept4" class="com.atguigu.pojo.Dept">
<property name="deptId" value="104"></property>
<property name="deptName" value="教学部门4"></property>
<property name="empMap">
<map>
<entry>
<key>
<value>1001</value>
</key>
<ref bean="empTao"/>
</entry>
<entry>
<key>
<value>1002</value>
</key>
<ref bean="empXin"></ref>
</entry>
</map>
</property>
</bean>
<util:map id="empMap">
<entry>
<key>
<value>1001</value>
</key>
<ref bean="empTao"></ref>
</entry>
<entry>
<key>
<value>1002</value>
</key>
<ref bean="empXin"></ref>
</entry>
</util:map>
<bean id="dept5" class="com.atguigu.pojo.Dept">
<property name="deptId" value="105"></property>
<property name="deptName" value="教学部门5"></property>
<property name="empMap" ref="empMap"></property>
</bean>
第四章 Spring依赖注入方式
4.1 回顾为属性赋值几种方式
- setXxx()方式
- 构造器方式
- 反射方式
4.2 Spring中三种依赖注入方式
-
set注入
-
语法:<property name="" value="">标签
-
属性
- name:设置属性名称
- value:设置属性数值【字面量数值】
- ref:设置属性数值【非字面量数值】
-
示例代码
<!-- 测试setter注入--> <bean id="dept1" class="com.atguigu.pojo.Dept"> <property name="deptId" value="101"></property> <property name="deptName" value="研发部门"></property> </bean>
-
-
构造器注入
-
语法:<constructor-arg name="deptId" value="102"></constructor-arg>
-
属性
- name:设置属性名称
- value:设置属性数值【字面量数值】
- ref:设置属性数值【非字面量数值】
-
注意:使用构造器注入方式,必须提供相应构造方法
-
示例代码
<!-- 测试构造器注入--> <bean id="dept2" class="com.atguigu.pojo.Dept"> <constructor-arg name="deptId" value="102"></constructor-arg> <constructor-arg name="deptName" value="教学部门"></constructor-arg> </bean>
-
-
p名称空间注入
-
语法
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 测试p名称空间注入--> <bean id="dept3" class="com.atguigu.pojo.Dept" p:deptId="103" p:deptName="教务部门"> </bean> </beans>
-
注意:p名称空间注入,本质是set注入。
-
第五章 Spring管理第三方bean【DruidDataSource】
5.1 Spring管理第三方bean步骤
- 导入相关jar包
- 装配bean
- 获取bean
5.2 示例代码
<!--导入druid的jar包-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--导入mysql的jar包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!--<version>8.0.26</version>-->
<version>5.1.37</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"
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
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 加载外部属性文件-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!-- 装配DruidDataSource-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${db.driverClassName}"></property>
<property name="url" value="${db.url}"></property>
<property name="username" value="${db.username}"></property>
<property name="password" value="${db.password}"></property>
</bean>
</beans>
@Test
public void testDruidDataSource() throws Exception{
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext_DruidDataSource.xml");
DruidDataSource dataSource = context.getBean("dataSource", DruidDataSource.class);
System.out.println("dataSource = " + dataSource);
//测试,通过dataSource获取Connection对象
DruidPooledConnection connection = dataSource.getConnection();
System.out.println("connection = " + connection);
}
第六章 Spring中FactoryBean
6.1 FactoryBean概述
- Spring提供两种Bean
- 一种普通Bean
- 另一种工厂Bean【FactoryBean】
- FactoryBean作用:参与Bean的创建时,使用FactoryBean
6.2 使用FactoryBean
-
实现FactoryBean接口
-
重写FactoryBean中三个方法
-
装配FactoryBean
-
测试
package com.atguigu.factorybean; import com.atguigu.pojo.Dept; import org.springframework.beans.factory.FactoryBean; /** * @author Chunsheng Zhang 尚硅谷 * @create 2022/4/20 14:08 */ public class MyFactoryBean implements FactoryBean<Dept> { /** * 参与bean的创建,并返回 * @return * @throws Exception */ @Override public Dept getObject() throws Exception { Dept dept = new Dept(101,"研发部门"); return dept; } /** * 返回bean的class即可 * @return */ @Override public Class<?> getObjectType() { return Dept.class; } public boolean isSingleton() { return true; } }
<?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"> <!-- 装配factoryBean--> <bean id="dept" class="com.atguigu.factorybean.MyFactoryBean"></bean> </beans>
@Test public void testFacoryBean(){ //创建容器对象 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext_factoryBean.xml"); Dept dept = context.getBean("dept", Dept.class); System.out.println("dept = " + dept); }
6.3 注意
- 通过工厂bean【FactoryBean】获取对象,不是工厂bean本身,而是getObject()方法返回的对象
第七章 Spring中bean的作用域
7.1 语法
- 在bean标签中添加scope属性即可
7.2 四种作用域
使用框架帮助我们创建对象时,使用无参构造器【所以必须提供无参构造器】
- singleton【默认值】:单例【当前容器中,只有一个对象】
- 创建容器对象时,创建装配对象
- prototype:多例【当前容器中,存在多个对象】
- 每次调用getBean()方法时,创建装配对象
- request:请求域
- 当前请求有效,离开当前请求失效
- 当前请求:当前URL【地址栏】未改变即为当前请求,如URL改变则不在当前请求。
- session:会话域
- 当前会话有效,离开当前会话失效
- 当前会话:默认情况,浏览器未关闭且未更换则在当前会话,如重启浏览器或更换浏览器则不在当前会话。
第八章 Spring中bean的生命周期
8.1 Spring生命周期
① 通过构造器或工厂方法创建bean实例【创建对象】
② 为bean的属性设置值和对其他bean的引用【为对象中属性赋值】
③ 调用bean的初始化方法【初始化】
④ bean可以使用了【使用】
⑤ 当容器关闭时,调用bean的销毁方法【销毁对象】
-
示例代码
/** * 初始化Dept */ public void initDept(){ System.out.println("3. 初始化Dept操作!"); } /** * 销毁Dept */ public void destroyDept(){ System.out.println("5. 销毁Dept操作!"); }
<bean id="dept" class="com.atguigu.pojo.Dept" init-method="initDept" destroy-method="destroyDept"> <property name="deptId" value="101"></property> <property name="deptName" value="研发部门"></property> </bean>
@Test public void testLifeCycle(){ //创建容器对象 ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("applicationContext_lifeCycle.xml"); Dept dept = context.getBean("dept", Dept.class); System.out.println("4. 使用dept = " + dept); //关闭容器对象 context.close(); }
8.2 Spring后置处理器
-
作用:在bean的初始化方法的前&后再继续添加方法的支持
-
实现
- 实现BeanPostProcessor接口
- 重写接口中的两个方法
- postProcessBeforeInitialization():在初始化方法之前执行
- postProcessAfterInitialization():在初始化方法之后执行
- 装配后置处理器【所有bean装配,不能为局部bean装配】
-
示例代码
package com.atguigu.mybeanprocess; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.lang.Nullable; /** * @author Chunsheng Zhang 尚硅谷 * @create 2022/4/20 15:18 */ public class MyBeanPostProcessor implements BeanPostProcessor { /** * 在初始化方法之前执行 * @param bean * @param beanName * @return * @throws BeansException */ public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("==>初始化方法【之前】执行!"); return bean; } /** * 在初始化方法之后执行 * @param bean * @param beanName * @return * @throws BeansException */ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("==>初始化方法【之后】执行!"); return bean; } }
<!-- 为所有bean装配后置处理器--> <bean class="com.atguigu.mybeanprocess.MyBeanPostProcessor"></bean>
-
添加后置处理器后的生命周期
① 通过构造器或工厂方法创建bean实例【创建对象】
② 为bean的属性设置值和对其他bean的引用【为对象中属性赋值】
执行处理器的postProcessBeforeInitialization()方法
③ 调用bean的初始化方法【初始化】
执行处理器的postProcessAfterInitialization()方法
④ bean可以使用了【使用】
⑤ 当容器关闭时,调用bean的销毁方法【销毁对象】
day07
第九章 Spring 自动装配
Spring提供两种装配风格
- 手动装配
- set注入
- 构造器注入
- p名称空间注入
- 自动装配:无需使用上述标签或名称空间,就可以自动为对象中属性注入数值。
9.1 自动装配【xml】
-
语法:在bean标签中,添加属性:autowire
-
byType:对象中属性类型与容器中class数值匹配。
-
如果唯一匹配,则匹配成功
-
如果匹配0个,则装配默认值null
-
如果匹配多个,则报如下错误
available: expected single matching bean but found 2: deptDao,deptDao2
-
-
byName:对象中属性名称与容器中id数值匹配
-
如果数值一致,则匹配成功
-
如果数值不一致,则装配默认值null
成员变量与属性关系:封装后的成员变量称之为属性
-
-
注意:
- 基于xml方式自动装配,底层使用set注入。
- 自动装配不能装配字面量数值
-
-
示例代码
<bean id="deptDao" class="com.atguigu.dao.impl.DeptDaoImpl"></bean> <bean id="deptMapper" class="com.atguigu.dao.impl.DeptMapper"></bean> <bean id="deptService" class="com.atguigu.service.impl.DeptServiceImpl" autowire="byName"></bean>
-
总结:最终不推荐使用byType&byName,推荐使用注解方式
第十章 Spring中注解【非常重要】
10.1 使用注解装配对象
-
@Component:标识普通组件
-
@Repository:标识持久化层组件
-
@Service:标识业务逻辑层组件
-
@Controller:标识【表述层、表示层、控制层】组件
-
注意:
- Spring框架本身不区分四个注解【在业务逻辑层使用@Repository也不会报错】,使用四个注解的优势:增强代码可读性。
- 注解位置:class上面
- 注解默认会将类名首字母小写作为beanId,也可以是value属性设置bean的Id【如只使用value属性时,value关键字可省略】
- 使用步骤
- 导入jar包【aop的jar包已经导入】
- 开启组件扫描【扫描注解标识组件】
- 可以直接使用注解标识组件
10.2 使用注解装配对象中属性【自动装配】
-
@Autowired注解
-
装配原理:反射机制【set注入&构造注入】
-
装配方式【byType&byName】
-
先按照byType进行匹配
-
匹配一个,则匹配成功
-
匹配0个
-
required=true,报如下错误:
available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations:
-
required=false,会装配默认值【null】,不会报【装配】错
-
-
匹配多个,再按照byName进行唯一筛选
- 筛选成功【对象属性与bean的Id一致】,则自动装配成功
- 筛选失败【对象属性与bean的Id不一致】,则按照byType风格报错
-
-
-
@Autowired注解属性:required属性
- required=true:表示当前被标识的属性,必须装配数值,如未装配数值,会报错
- required=false:表示当前被标识的属性,不必须装配数值,如未装配数值,不会报【装配】错,会将默认值【null】装配。
-
示例代码
// @Autowired(required = false) private DeptDao deptDao; @Override public void saveDept(Dept dept) { deptDao.insertDept(dept); } @Autowired public void setDeptDao(DeptDao deptDao) { this.deptDao = deptDao; }
-
-
设置自动装配名称的注解:@Qualifier
@Autowired(required = false) @Qualifier("deptDaoMybatis") private DeptDao deptDao;
-
设置自动装配字面量数值:@Value
@Value("101") private Integer deptId; @Value("研发部门") private String deptName;
第十一章 Spring组件扫描
<!-- base-package:设置扫描的当前包及其子包--> <context:component-scan base-package="com.atguigu.annotation"></context:component-scan>
11.1 Spring开启组件扫描-排除扫描
<context:component-scan base-package="com.atguigu.annotation">
<!-- 排除扫描[Controller] -->
<!-- <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>-->
<context:exclude-filter type="assignable" expression="com.atguigu.annotation.controller.DetpController"/>
</context:component-scan>
- type:设置排除扫描类型
- annotation:设置排除扫描注解全类名
- assignable:设置排除扫描对象全类名
11.2 Spring开启组件扫描-包含扫描
<!-- 假设当前项目中,共有100个包【其中:包含Controller(只扫描Controller)】-->
<context:component-scan base-package="com.atguigu.annotation" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
- type:设置排除扫描类型
- annotation:设置排除扫描注解全类名
- assignable:设置排除扫描对象全类名
- 注意:使用包含扫描时,需要关闭默认组件扫描
第十二章 Spring完全注解开发
-
完全注解开发【0xml】
-
示例代码
@ComponentScan(basePackages = "com.atguigu.annotation") @Configuration //标识当前类是一个配置类,代替xml配置文件 public class SpringConfig { }
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); Dept dept = context.getBean("dept", Dept.class); System.out.println("dept = " + dept);
第十三章 Spring继承Junit4
13.1 Spring整合Junit4步骤
-
导入jar包
- spring-test-5.3.1.jar
-
在测试类上面添加两个注解
@ContextConfiguration(locations = "classpath:application_annotation.xml") @RunWith(SpringJUnit4ClassRunner.class) //标识当前Junit版本【Junit4】 public class TestAnnotation { @Autowired private DeptService deptService; /** * 测试使用Spring装配对象中属性 */ @Test public void testAnnotationProperties() { deptService.saveDept(new Dept()); } }
第十四章 工厂模式
-
工厂模式:
- 生活中工厂:
- 服装厂:生产服装
- 笔记本电脑厂:生产电脑
- 生活中工厂:
-
程序中工厂:创建对象【管理对象】
-
示例代码
//获取连接 public static Connection getConnection() { Connection connection = threadLocal.get(); try { if(connection == null){ connection = dataSource.getConnection(); //将connection设置threadLocal threadLocal.set(connection); } } catch (SQLException e) { e.printStackTrace(); } return connection; }
day08
第十五章 AOP前奏
15.1 动态代理
- 代理:生活中【中介】
- 总结:目标对象不能转换为代理对象,因为是“兄弟”关系【宠物猫不能转换为宠物狗】
15.2 程序中
-
需求:在“计算器”加减乘除基础上,添加日志功能【验证】
-
日志:在计算之前,记录参数;在计算之后,记录返回结果
-
示例代码
public class CalcImpl implements Calc { @Override public int add(int a, int b) { System.out.println("==>add()方法,参数a:"+a+",b:"+b); int rs = a+b; System.out.println("==>add()方法,结果:rs"+rs); return rs; } }
-
发现问题
核心业务代码【加减乘除】,非核心业务代码【日志代码】
- 日志代码比较分散
- 核心业务代码与非核心业务代码,书写在一处,导致代码比较混乱
-
解决问题
- 思路:
- 先将日志代码【非核心业务代码】横向提取切面【工具类】
- 再将【非核心业务代码】动态织入到核心代码中
- 思路:
15.3 手动搭建动态代理
-
动态代理的方式
a)基于接口实现动态代理: JDK动态代理
b) 基于继承实现动态代理: Cglib、Javassist动态代理
-
JDK动态代理实现步骤
- Proxy类:代理类基类【类似Object】
- Proxy.newProxyInsatance():创建代理对象
- InvocationHandler接口:实现动态织入【动态将日志功能添加(织入)到业务代码中】
- invoke():执行目标对象相应方法【中介帮我们干活】
- Proxy类:代理类基类【类似Object】
- 实现动态代理步骤
- 提供目标对象
- 提供方法【获取代理对象】
- 有参构造器
15.4 搭建动态代理-示例代码
package com.atguigu.aopbefore;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author Chunsheng Zhang 尚硅谷
* @create 2022/4/24 8:53
*/
public class MyProxy {
// 提供目标对象
private Object target;
//有参构造器【target不为空】
public MyProxy(Object target){
this.target = target;
}
/**
* 提供方法【获取代理对象】
ClassLoader loader,类加载器
Class<?>[] interfaces,目标对象实现的所有接口集合
InvocationHandler h,实现动态织入对象【invoke()】
*/
public Object getProxy(){
Object obj = null;
//获取类加载器
ClassLoader classLoader = target.getClass().getClassLoader();
//获取目标对象所有接口
Class<?>[] interfaces = target.getClass().getInterfaces();
obj = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取方法名称
String methodName = method.getName();
//目标方法执行之前,日志
MyLogging.methodBefore(methodName,args);
//执行目标对象的相应方法【add()、sub()、mul()、div()】
Object rs = method.invoke(target, args);
//目标方法执行之后,日志
MyLogging.methodAfter(methodName,rs);
return rs;
}
});
return obj;
}
// private class MyInvo implements InvocationHandler{
//
// @Override
// public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// return null;
// }
//
// }
}
//目标对象
Calc calc = new CalcImpl();
//获取代理对象的工具类对象
MyProxy myProxy = new MyProxy(calc);
//获取代理对象
Calc calcProxy = (Calc)myProxy.getProxy();
//错误的,代理对象不能转换为目标对象,兄弟关系不能相互转换
// CalcImpl calcProxy = (CalcImpl)myProxy.getProxy();
calcProxy.add(1,2);
第十六章 AOP框架【重点】
16.1 AspectJ框架
- 概述:AspectJ是Java社区里最完整最流行的AOP框架。
- 在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。
16.2 使用AspectJ步骤
-
导入jar包
<!--spirng-aspects的jar包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.1</version> </dependency>
-
在spring配置文件【applicationContext.xml】中开启AspectJ注解支持
<!--配置自动扫描的包--> <context:component-scan base-package="com.atguigu.spring.aop"></context:component-scan> <!--开启AspectJ注解支持--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
在非核心业务类中【MyLogging】添加注解@Aspect,在方法上添加@Before
16.3 AOP概述
- OOP全称:Object-Oriented Programming【面向对象编程】
- 纵向继承机制
- AOP全称:Aspect-Oriented Programming【面向切面编程】
- 横向提取机制
16.4 AOP相关术语
-
横切关注点:非核心业务代码称之为:横切关注点【提取到切面之前】
- 日志功能
-
切面(Aspect):将非核心业务代码提取类,称之为切面类
- MyLogging
-
通知(Advice):非核心业务代码称之为:通知【提取到切面之后】
-
目标(Target):被通知的对象称之为目标对象
- CalcImpl
-
代理(Proxy):向目标对象应用通知之后创建的代理对象
//获取代理对象的工具类对象 MyProxy myProxy = new MyProxy(calc); //获取代理对象 Calc calcProxy = (Calc)myProxy.getProxy();
-
连接点(Joinpoint):通知的位置称之为连接点【通知之前】
-
切入点(pointcut):通知的位置称之为切入点【通知之后】
第十七章 AspectJ详解
17.1 切入点表达式
-
语法:execution(权限修饰符 返回值类型 全类名.方法名(参数列表类型))
execution(public int com.atguigu.aop.CalcImpl.add(int, int))
-
通配符
【*】可以代表如下语义
- 可以代表任意权限修饰符及返回值类型
- 可以代表任意全类名&方法名
【..】可以代表任意参数类型
-
示例代码
// @Before(value = "execution(public int com.atguigu.aop.CalcImpl.add(int, int))") // @Before(value = "execution(* *.*(..))") @Before("execution(* com.atguigu.aop.CalcImpl.*(..))") public void methodBefore(JoinPoint joinPoint){}
-
重用切入点表达式
-
提取可重用的切入点表达式
@Pointcut("execution(* com.atguigu.aop.CalcImpl.*(..))") public void myPointCut(){}
-
使用方法名(),重用切入点表达式
@Before("myPointCut()") public void methodBefore(JoinPoint joinPoint){}
-
17.2 JoinPoint对象
-
JoinPoint连接点对象【切入点对象】
-
常用方法
-
Signature signature = joinPoint.getSignature():获取方法签名
-
方法签名=方法名+参数列表
-
signature.getName():获取方法名
-
-
Object[] args = joinPoint.getArgs();
- 获取参数
-
17.3 AspectJ中5个通知
-
前置通知
-
语法:@Before
-
执行时机:在目标方法执行之前触发
-
如有异常是否执行:是
-
示例代码
@Pointcut("execution(* com.atguigu.aop.CalcImpl.*(..))") public void myPointCut(){} @Before("myPointCut()") public void methodBefore(JoinPoint joinPoint){ //获取方法名 Signature signature = joinPoint.getSignature(); // System.out.println("signature = " + signature); String methodName = signature.getName(); //获取参数 Object[] args = joinPoint.getArgs(); System.out.println("==>前置通知:"+methodName+"()方法,参数:"+ Arrays.toString(args)); }
-
-
后置通知
-
语法:@After
-
执行时机:在目标方法执行之后触发【所有通知之后】
-
如有异常是否执行:是
-
示例代码
/** * 方法之后 */ @After("myPointCut()") public static void methodAfter(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); System.out.println("==>后置通知:"+methodName+"()方法,参数:"+ Arrays.toString(args)); }
-
-
返回通知
-
语法:@AfterReturning
-
执行时机:返回结果之后出发
-
如有异常是否执行:否
-
示例代码
/** * 返回通知 */ @AfterReturning(value = "myPointCut()",returning = "rs") public void afterReturning(JoinPoint joinPoint,Object rs){ String methodName = joinPoint.getSignature().getName(); System.out.println("==>返回通知:"+methodName+"()方法,结果rs:"+ rs); }
-
-
异常通知
-
语法:@AfterThrowing
-
执行时机:出现异常时执行,未出现异常不执行
-
如有异常是否执行:是
-
示例代码
/** * 异常通知 */ @AfterThrowing(value = "myPointCut()",throwing = "ex") public void afterThrowing(JoinPoint joinPoint,Exception ex){ String methodName = joinPoint.getSignature().getName(); System.out.println("==>异常通知:"+methodName+"()方法,异常Ex:"+ ex); }
-
小结
- 有异常:前置通知->异常通知->后置通知
- 无异常:前置通知->返回通知->后置通知
-
-
环绕通知【前四个通知集合】
- 语法:@Around
- 注意事项:
- 必须使用ProceedingJoinPoint对象的proceed()方法,才可以事项环绕通知
- 环绕通知必须设置返回值,且返回值是目标方法的返回结果
-
示例代码
/** * 环绕通知 */ @Around("myPointCut()") public Object around(ProceedingJoinPoint pjp){ String methodName = pjp.getSignature().getName(); Object[] args = pjp.getArgs(); Object rs = null; try { //前置通知 System.out.println("==>前置通知:"+methodName+"()方法,参数:"+ Arrays.toString(args)); //触发目标对象的相应方法【加减乘除方法】 rs = pjp.proceed(); //返回通知 System.out.println("==>返回通知:"+methodName+"()方法,结果rs:"+ rs); } catch (Throwable throwable) { throwable.printStackTrace(); //异常通知 System.out.println("==>异常通知:"+methodName+"()方法,异常throwable:"+ throwable); } finally { //后置通知 System.out.println("==>后置通知:"+methodName+"()方法,参数:"+ Arrays.toString(args)); } return rs; }
17.4 AspectJ中切面优先级
-
语法:@Order(int index)
- index默认值:int存储最大值
- index数值越小优先级越高
-
注意:建议index使用正整数
-
示例代码
@Component @Aspect //@Order(value = 1) @Order(2) public class MyValidate {}
17.5 基于XML方式实现AspectJ框架
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置计算器实现类-->
<bean id="calcImpl" class="com.atguigu.spring.aop.xml.CalculatorImpl"></bean>
<!--配置切面类-->
<bean id="myLogging" class="com.atguigu.spring.aop.xml.LoggingAspect"></bean>
<bean id="myValidate" class="com.atguigu.aop.MyValidate"></bean>
<!--AOP配置-->
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut id="pointCut"
expression="execution(* com.atguigu.spring.aop.xml.Calculator.*(..))"/>
<!--配置切面-->
<aop:aspect ref="myLogging">
<!--前置通知-->
<aop:before method="beforeAdvice" pointcut-ref="pointCut"></aop:before>
<!--返回通知-->
<aop:after-returning method="returningAdvice" pointcut-ref="pointCut" returning="result"></aop:after-returning>
<!--异常通知-->
<aop:after-throwing method="throwingAdvice" pointcut-ref="pointCut" throwing="e"></aop:after-throwing>
<!--后置通知-->
<aop:after method="afterAdvice" pointcut-ref="pointCut"></aop:after>
<!--环绕通知-->
<aop:around method="aroundAdvice" pointcut-ref="pointCut"></aop:around>
</aop:aspect>
<!-- 配置切面【验证】-->
<aop:aspect ref="myValidate">
<aop:before method="validateData" pointcut-ref="pointCut"></aop:before>
</aop:aspect>
</aop:config>
</beans>
第十八章 JdbcTemplate持久化层框架
SSH【Spring+Struts2+Hibernate】
SSM【Spring+SpringMVC+Mybatis】
18.1 JdbcTemplate概述
- JdbcTemplate看作是一个小型的轻量级持久化层框架
18.2 搭建JdbcTemplate步骤
-
导入jar包
<!--spring-jdbc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.1</version> </dependency> <!--spring-orm--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.3.1</version> </dependency> <!--spring-context--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.1</version> </dependency> <!--导入druid的jar包--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <!--导入mysql的jar包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.37</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
-
编写配置文件【装配JdbcTemplate】
- 加载外部属性文件:db.properties
- 装配DataSource
- 装配JdbcTemplate
-
使用核心类库JdbcTemplate
18.3 JdbcTemplate常用API
-
增删改通用方法:update(String sql,Object... args);
-
批量增删改通用方法:batchUpdate(String sql,List<Object[]> list );
-
查询单个数值:queryForObject(String sql,Class clazz,Object... args);
-
查询单个对象:queryForObject(String sql,RowMapper
,Object... args); - RowMapper:映射器对象,将对象中属性与表中字段进行映射
-
查询多个对象:query(String sql,RowMapper
mapper,Object... args);
day09
第十九章 Spring声明式事务管理
19.1 回顾事务相关知识点
- 事务四大特征【特性】:ACID
- 原子性
- 一致性
- 隔离性
- 持久性
- 事务三种行为【操作】
- 开启事务:connection.setAutoCommit(false);
- 提交事务:connection.commit();
- 回滚事务:connection.rollback();
19.2 Spring中支持事务管理
-
编程式事务管理【使用原生的JDBC API进行事务管理的步骤】
-
获取数据库连接Connection对象
-
取消事务的自动提交【开启事务】
-
执行操作【核心业务代码】
-
正常完成操作时手动提交事务
-
执行失败时回滚事务
-
关闭相关资源
- 发现问题:
- 核心业务代码与事务管理代码【非核心业务】相耦合,导致代码分散及代码混乱问题
- 解决思路
- 使用AOP思想【框架】,将事务管理代码先横向提取,再动态织入。【声明式事务管理】
-
-
声明式事务管理
-
导入jar包【AspectJ】
<!--spirng声明式事务管理--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.1</version> </dependency>
-
编写配置文件
-
装配事务管理器【DataSourceTransactionManager】
-
开启事务注解支持:
<!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--配置数据源属性--> <property name="dataSource" ref="dataSource"></property> </bean> <!--开启事务注解支持 当事务管理器的id是transactionManager时,可以省略指定transaction-manager属性 --> <!-- <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>--> <tx:annotation-driven></tx:annotation-driven>
-
-
在需要事务管理的业务上添加注解即可【@Transactional】
-
19.3 Spring中声明式事务管理属性
@Transactional注解属性值
注解位置
- 类上
- 方法上
-
propagation【事务传播行为】
-
概述:当一个事务方法被另一个事务方法调用时,此时当前事务方法必须指定事务传播行为。
- 如:执行事务方法method()【事务是x】之后,调用事务方法method()2【事务是y】,此时需要设置method()2的执行事务是x还是y。
-
类型:Propagation枚举类型
-
REQUIRED【默认值】:如果有事务在运行,当前的方法就在这个事务内运行;否则就启动一个新的事务,并在自己的新事务内运行。
-
REQUIRES_NEW:当前的方法*必须*启动新事务,并在自己的事务内运行;如果之前有事务正在运行,应该将它挂起。
-
其他事务传播行为
传播属性 描述 REQUIRED 如果有事务在运行,当前的方法就在这个事务内运行;否则就启动一个新的事务,并在自己的新事务内运行。 REQUIRES_NEW 当前的方法*必须*启动新事务,并在自己的事务内运行;如果之前有事务正在运行,应该将它挂起。 SUPPORTS 如果有事务在运行,当前的方法就在这个事务内运行,否则可以不运行在事务中。 NOT_SUPPORTED 当前的方法不应该运行在事务中,如果有运行的事务将它挂起 MANDATORY 当前的方法必须运行在事务中,如果没有正在运行的事务就抛出异常。 NEVER 当前的方法不应该运行在事务中,如果有正在运行的事务就抛出异常。 NESTED 如果有事务正在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则就启动一个新的事务,并在它自己的事务内运行。
-
-
事务传播行为使用场景
/*去结账:结账时,账号余额支持买多少本书,就允许用户购买 如账户余额不充足怎么办? 需求1:不允许用户购买所有图书 */ @Transactional(propagation = Propagation.REQUIRED) public void purchase(String username, String isbn) { //查询book价格 Integer price = bookShopDao.findBookPriceByIsbn(isbn); //修改库存 bookShopDao.updateBookStock(isbn); //修改余额 bookShopDao.updateUserAccount(username, price); } /* 去结账:结账时,账号余额支持买多少本书,就允许用户购买 如账户余额不充足怎么办? 需求2:不允许用户购买最后一本(导致余额不足)的图书及其之后的所有图书, 之前的其他图书正常购买 */ @Transactional(propagation = Propagation.REQUIRES_NEW) public void purchase(String username, String isbn) { //查询book价格 Integer price = bookShopDao.findBookPriceByIsbn(isbn); //修改库存 bookShopDao.updateBookStock(isbn); //修改余额 bookShopDao.updateUserAccount(username, price); }
-
-
isolation【事务隔离级别】
-
概述:事务隔离级别就是事务与事务之间隔离等级
-
隔离级别如下
- READ UNCOMMITTED【读未提交】:1
- READ COMMITTED【读已提交】:2
- REPEATABLE READ【可重复读】:4
- SERIALIZABLE【串行化】:8
-
Oracle与Mysql隔离级别对比
Oracle MySQL READ UNCOMMITTED × √ READ COMMITTED √(默认) √ REPEATABLE READ × √(默认) SERIALIZABLE √ √ -
不同隔离级别可能出现现象
-
-
timeout【事务超时】
- 设置事务超时的强制回滚时间
- 类型:int,单位:秒
- 默认值:-1
-
readonly【事务只读】
- 默认值:false
- 设置当前事务是否只读
- 一般查询数据时,设置事务只读,提高一点性能
- 如增删改操作中设置只读,会报如下错误
- Connection is read-only. Queries leading to data modification are not allowed
-
rollbackFor|noRollbackFor【事务(是否)异常回滚】
- rollbackFor:设置回滚的指定异常Class
- noRollbackFor:设置不回滚的指定异常Class
-
示例代码
/** * //买书->查询book价格->修改库存->修改余额 * * @param username * @param isbn */ @Transactional(propagation = Propagation.REQUIRES_NEW, timeout = 3, readOnly = false, noRollbackFor = NullPointerException.class) public void purchase(String username, String isbn) { //查询book价格 Integer price = bookShopDao.findBookPriceByIsbn(isbn); try { String s = null; s.length(); } catch (Exception e) { e.printStackTrace(); } //测试事务超时 // try { // Thread.sleep(5000); // } catch (InterruptedException e) { // e.printStackTrace(); // } //修改库存 bookShopDao.updateBookStock(isbn); //修改余额 bookShopDao.updateUserAccount(username, price); }
19.4 基于XML方式使用声明式事务管理
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:druid.properties"></context:property-placeholder>
<!--配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="initialSize" value="${jdbc.initialSize}"></property>
<property name="maxActive" value="${jdbc.maxActive}"></property>
</bean>
<!--配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--配置数据源属性-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置BookShopDaoImpl-->
<bean id="bookShopDao" class="com.atguigu.spring.tx.xml.BookShopDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!--配置BookShopServiceImpl-->
<bean id="bookShopService" class="com.atguigu.spring.tx.xml.BookShopServiceImpl">
<property name="bookShopDao" ref="bookShopDao"></property>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--配置数据源属性-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置声明式事务-->
<tx:advice id="tx" transaction-manager="transactionManager">
<!--设置添加事务的方法-->
<tx:attributes>
<!--设置查询的方法的只读属性为true-->
<tx:method name="find*" read-only="true"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="purchase" propagation="REQUIRES_NEW" isolation="READ_COMMITTED"></tx:method>
</tx:attributes>
</tx:advice>
<!--AOP配置-->
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut id="pointCut"
expression="execution(* com.atguigu.spring.tx.xml.BookShopServiceImpl.purchase(..))"/>
<!--将事务方法和切入点表达式关联起来-->
<aop:advisor advice-ref="tx" pointcut-ref="pointCut"></aop:advisor>
</aop:config>
</beans>
第二十章 Spring5新特性
20.1 Spring5新增一些注解
名称 | 含义 | 可标注位置 |
---|---|---|
@Nullable | 可以为空 | @Target({ElementType.*METHOD*, ElementType.*PARAMETER*, ElementType.*FIELD*} |
@NonNull | 不应为空 | @Target({ElementType.*METHOD*, ElementType.*PARAMETER*, ElementType.*FIELD*}) |
@NonNullFields | 在特定包下的字段不应为空 | @Target(ElementType.*PACKAGE*) @TypeQualifierDefault(ElementType.*FIELD*) |
@NonNullApi | 参数和方法返回值不应为空 | @Target(ElementType.*PACKAGE*) @TypeQualifierDefault({ElementType.*METHOD*, ElementType.*PARAMETER*}) |
-
以@Nullable注解为例
@Nullable 注解可以使用在方法上面,属性上面,参数前面,表示方法返回可以为空,属性值可以为空,参数值可以为空。
此注解通常用来消除NullPointerException
20.2 Spring5整合Log4j2
-
导入jar包
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.11.2</version> <scope>test</scope> </dependency>
-
编写log4j2的配置文件:log4j2.xml
<?xml version="1.0" encoding="UTF-8"?> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出--> <configuration status="INFO"> <!--先定义所有的appender--> <appenders> <!--输出日志信息到控制台--> <console name="Console" target="SYSTEM_OUT"> <!--控制日志输出的格式--> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </console> </appenders> <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效--> <!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出--> <loggers> <root level="DEBUG"> <appender-ref ref="Console"/> </root> </loggers> </configuration>
20.3 Spring5整合Junit5
-
导入jar包:注意将junit4的相关jar包移除
<!--spring-test--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.1</version> </dependency> <!--junit5--> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.7.2</version> <scope>test</scope> </dependency>
-
在测试类上添加注解
- 方式一
@ExtendWith(SpringExtension.class) @ContextConfiguration(locations = "classpath:beans-tx.xml") public class Junit5Test {}
- 方式二
@SpringJUnitConfig(locations = "classpath:beans-tx.xml") public class Junit5Test {}