文章目录
- 1. Spring
- 2. Spring IOC
- 3. Spring中依赖注入的数值问题
- 4. Sping依赖注入方式
- 5. Spring 管理第三方bean
- 6. Spring中的FactoryBean(了解)
- 7. Spring中bean的作用域
- 8.Spring中bean的生命周期
- 9. Spring中的自动装配
- 10.Spring中注解(重要)
- 11. Spring中组件扫描
- 12. Spring中完全注解开发
- 13.Spring 集成 Junit4
- 14. Spring AOP前奏
- 15. Spring中的AOP(重要)
- 16. AspectJ详解(重要)
- 17. Spring中的JdbcTemplate
- 18. Spring声明式事务管理(重要)
- 19. Spring5新特性
1. Spring
Spring是一个为了简化企业级开发的开源框架。
Spring的特性:
- Spring是一个IOC和AOP容器框架。
- Spring最受认同的是控制反转的**依赖注入(DI)**风格。 Inversion of Control (IoC) 控制反转是一个通用概念,可以用多种不同的方式表达。 依赖注入只是控制反转的一个具体例子。
- AOP:Aspect-Oriented Programming,面向切面编程
- 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API。
- 容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期。
- 组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
- 一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring 自身也提供了SpringMVC和持久层的JDBCTemplate)。
搭建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>
-
编写核心配置文件【applicationContext.xml/beans.xml/spring.xml】
配置文件路径:src/main/resources
-
示例代码
<?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"> <!-- 将对象装配到IOC容器中--> <bean id="Stu" class="Spring.pojo.Student"> <property name="stuId" value="1"></property> <property name="stuName" value="Sivan"></property> </bean> </beans>
-
-
使用核心类库
public void test(){ //使用Spring之前 Student student = new Student(); //使用Spring之后 //创建ioc容器 ApplicationContext iocObj = new ClassPathXmlApplicationContext("applicationContext.xml"); //通过容器对象id获取需要对象。 Student Stu = (Student)iocObj.getBean("Stu",Student.class); System.out.println(Stu); }
Spring中getBean()的三种方式
getBean(String beanId)
:通过beanId获取对象。【但是需要类型转换】getBean(Class<T> clazz)
:通过反射获取对象。【但是容器中有多个相同bean,会报错】getBean(String beanId,Class<T> clazz)
:整合前两种方法。
bean标签详解
- 属性
- id:bean的唯一标识
- class:定义bean的类型【Class全类名】
- 子标签
- property:为对象中的属性赋值【set注入方式】
- name:设置属性名称
- value:设置属性数值
- ref:ref = "dataSource"就是引用
dataSource
的bean
spring容器会在引用后进行验证,验证当前的xml是否存在引用的bean
- property:为对象中的属性赋值【set注入方式】
2. Spring IOC
IOC:将对象的控制权反转给Spring
-
BeanFactory接口
IOC容器的基本实现,是Spring的内部使用接口,面向Spring本身。
-
ApplicationContext接口
BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者。
-
ConfigurableApplicationContext接口
提供关闭或刷新容器对象的方法。
-
ClassPathXmlApplicationContext与FileSystemXmlApplicationContext实现类
基于类路径检索xml文件/基于文件系统检索xml文件。
-
-
-
3. Spring中依赖注入的数值问题
字面量数值(value)
- 数据类型:基本数据类型或包装类
- 语法:value属性或value标签
CDATA区
<bean id="Stu2" class="Spring.pojo.Student">
<property name="stuId" value="2"></property>
<property name="stuName">
<!-- xml中使用CDATA区来解决出现特殊字符的情况-->
<value><![CDATA[<<Xin>>]]></value>
</property>
</bean>
外部已声明bean及级联属性赋值问题
使用外部已声明bean的语法:ref
。
级联属性更改数值之后会影响外部声明的bean。(ref中使用的是引用)
示例代码:
<!-- 被ref引用的外部bean-->
<bean id="Dept01" class="Spring.pojo.Dept">
<property name="deptId" value="1"></property>
<property name="deptName" value="财务部"></property>
</bean>
<bean id="Emp01" class="Spring.pojo.Employee">
<property name="id" value="1"></property>
<property name="email" value="123@qq.com"></property>
<property name="lastName" value="WWW"></property>
<property name="salary" value="100.00"></property>
<property name="dept" ref="Dept01"></property>
<!-- 使用级联属性赋值-->
<property name="dept.deptName" value="研发部"></property>
</bean>
内部bean
概念:在bean标签中定义另一个完整的bean。
内部bean不会直接装配到IOC容器中。所以无法从容器中直接获取。
<!-- 使用内部bean-->
<bean id="Emp02" class="Spring.pojo.Employee">
<property name="id" value="2"></property>
<property name="email" value="123@qq.com"></property>
<property name="lastName" value="XXX"></property>
<property name="salary" value="110.00"></property>
<property name="dept">
<bean class="Spring.pojo.Dept">
<property name="deptName" value="XXX部门"></property>
<property name="deptId" value="1"></property>
</bean>
</property>
</bean>
集合
如果传入bean中的属性是集合,就不能简单的用<property name = “deptId” value = “List”>来使用。
应该用以下方式:
-
List集合
<!-- 测试List集合--> <utils:list id="list01"> <ref bean="Emp01"></ref> <ref bean="Emp02"></ref> </utils:list> <bean id="Dept02" class="Spring.pojo.Dept"> <property name="deptId" value="1"></property> <property name="deptName" value="研发部"></property> <property name="empList" ref="list01"></property> </bean>
-
Map集合
<!-- 测试Map集合--> <utils:map id="map01"> <entry key="101" value-ref="Emp01"></entry> <entry > <key><value>102</value></key> <ref bean="Emp02"></ref> </entry> </utils:map> <bean id="EmpMap" class="Spring.pojo.Dept"> <property name="deptId" value="101"></property> <property name="deptName" value="研发部门"></property> <property name="empMap" ref="map01"></property> </bean>
4. Sping依赖注入方式
-
调用set方法注入
语法:<property>标签
-
使用构造器注入
语法:<constructor-arg>
-
使用p名称空间注入
需要先导入:xmlns:p=“http://www.springframework.org/schema/p”
语法:在bean标签中,使用p:XXX。也是使用set方法注入。
示例代码:
<bean id="Stu3" class="Spring.pojo.Student">
<constructor-arg name="stuId" value="3"></constructor-arg>
<constructor-arg name="stuName" value="WWW"></constructor-arg>
</bean>
<bean id="Stu4" class="Spring.pojo.Student" p:stuId="4" p:stuName="XXX" ></bean>
5. Spring 管理第三方bean
以Spring管理Druid数据源为例。
-
导入jar包
<!--导入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.28</version> </dependency>
-
编写db.properties配置文件
#这里名字最好使用一个前缀,防止与Mysql冲突 db.driverClassName=com.mysql.cj.jdbc.Driver db.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8 db.username=root db.password=root
-
编写applicationContext.xml
//加载外部属性文件db.properties <context:property-placeholder location="classpath:db.properties"></context:property-placeholder> //装配数据源 <bean id="Druid" 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>
-
测试
ApplicationContext ioc = new ClassPathXmlApplicationContext("ApplicationContext_druid.xml"); DruidDataSource druid = ioc.getBean("Druid", DruidDataSource.class); System.out.println("druid = " + druid); DruidPooledConnection connection = druid.getConnection(); System.out.println("connection = " + connection);
6. Spring中的FactoryBean(了解)
Spring中两种bean
- 一种是普通bean
- 另一种是工厂bean【FactoryBean】
- 作用:如需我们程序员参与到bean的创建时,使用FactoryBean。bean返回的类型由getObject()方法决定。
FactoryBean使用步骤
-
实现FactoryBean接口
-
重写方法【三个】
package factory; import pojo.Dept; import org.springframework.beans.factory.FactoryBean; public class MyFactoryBean implements FactoryBean<Dept> { /** * getObject():参数对象创建的方法 * @return * @throws Exception */ @Override public Dept getObject() throws Exception { Dept dept = new Dept(101,"研发部门"); //..... return dept; } /** * 设置参数对象Class * @return */ @Override public Class<?> getObjectType() { return Dept.class; } /** * 设置当前对象是否为单例 * @return */ @Override public boolean isSingleton() { return true; } }
-
装配工厂bean
在applicationContext.xml中使用bean标签装配工厂bean。
-
测试使用
7. Spring中bean的作用域
语法
- 在bean标签中添加
scope
属性即可
四个作用域
- singleton【默认值】:单例【在容器中只有一个对象】
- 对象创建时机:创建容器对象时,创建对象。
- prototype:多例【在容器中有多个对象】
- 对象创建时机:getBean()方法被调用时,创建对象。
- request:请求域
- 当前请求有效,离开请求域失效
- 当前请求:URL不变即为当前请求
- session:会话域
- 当前会话有效,离开当前会话失效
- 当前会话:当前浏览不关闭不更换即为当前会话
8.Spring中bean的生命周期
bean的生命周期
① 通过构造器或工厂方法创建bean实例
<bean id="Student" class="Spring.pojo.Student"
init-method="initMethod"
destroy-method="destroyMethod">
<property name="id" value="1"></property>
<property name="name" value="Xin"></property>
</bean>
② 为bean的属性设置值(比如setid
方法)和对其他bean的引用
③ 调用bean的初始化方法
④ bean可以使用了
⑤ 当容器关闭时,若使用ConfigurableApplicationContext
初始化容器,则可以调用bean的销毁方法context.close
。
bean的后置处理器
-
作用:在调用初始化方法前后对bean进行额外的处理。
-
实现:
-
实现BeanPostProcessor接口
-
重写方法
- postProcessBeforeInitialization(Object, String):在bean的初始化之前执行
- postProcessAfterInitialization(Object, String):在bean的初始化之后执行
-
在容器中装配后置处理器
<!-- 装配后置处理器--> <bean class="Spring.pojo.processor.Myprocessor"></bean>
-
-
注意:装配后置处理器会为当前容器中每个bean均装配,不能为局部bean装配后置处理器
9. Spring中的自动装配
Spring中提供两种装配方式
- 手动装配:自己使用bean中的property属性进行装配。
- 自动装配:使用Autowire属性。
Spring自动装配语法及规则
-
在bean标签中添加属性:Autowire即可
-
byName:对象中属性名称与该属性容器中的beanid进行匹配,如果一致,则自动装配成功
-
byType:对象中属性类型与该属性容器中的beanclass进行匹配,如果唯一,则自动装配成功
-
匹配0个:未装配
-
匹配多个,会报错
expected single matching bean but found 2: deptDao,deptDao2(期望匹配单个bean,但找到两个bean)
<!--将deptDao装配到容器中--> <bean id="deptDao" class="Spring.dao.impl.DeptImpl"></bean> <!--自动装配——byName,属性名与该属性的beanid进行匹配。--> <bean id="deptService" class="Spring.service.impl.DeptServiceImpl" autowire="byName"></bean> <!-- 自动装配——byType,属性类型与class一致--> <bean id="deptService2" class="Spring.service.impl.DeptServiceImpl" autowire="byType"></bean>
-
-
-
注意:基于XML方式的自动装配,只能装配非字面量数值
总结
- 基于xml自动装配,底层使用set注入
- 最终:不建议使用byName、byType,建议使用注解方式自动装配。
10.Spring中注解(重要)
使用注解装配对象到IOC
位置:在类的上面标识。
注意:
- Spring本身不区分四个注解【注解本质一样,都是Component】,目的是提高代码可读性。
- Java中的约定:约束(Maven包结构、Java命名规则等)>注解>XML>代码
- 关于beanid:不使用value,默认将类名首字母小写作为beanid。也可以使用value属性/省略value从而设置beanid。
-
装配对象的四个注解:
-
@Component(组件):装配普通组件到IOC中。
-
@Repository(仓库):装配持久化层到IOC中。
-
@Service(业务):装配业务逻辑层到IOC中。
-
@Controller(控制):装配控制层/表示层组件到IOC中。
-
-
使用注解装配对象的步骤:
-
使用组件扫描(目的是扫描到注解)
//注意命名空间。 <?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-3.0.xsd"> <!--开启组件扫描 base-package:设置扫描注解包名【当前包及其子包】--> <context:component-scan base-package="Spring"></context:component-scan> </beans>
-
使用注解标识组件(标识的是实现类Impl,因为只有实现类才可以创建对象)
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //使用Component注解 Dept dept = context.getBean("dept", Dept.class); System.out.println("dept = " + dept); //使用Repository注解 DeptDaoImpl deptdaoimpl = context.getBean("deptdaoimpl", DeptDaoImpl.class); System.out.println("deptdaoimpl = " + deptdaoimpl); //使用Service注解 DeptServiceImpl deptserviceimpl = context.getBean("deptserviceimpl", DeptServiceImpl.class); System.out.println("deptserviceimpl = " + deptserviceimpl); //使用Controller注解 DeptController deptController = context.getBean("deptController", DeptController.class); System.out.println("deptController = " + deptController);
-
使用注解管理对象之间的依赖关系
依赖关系:比如Service层依赖Dao层的对象。
使用**@Autowired注解**来管理对象之间的依赖关系(装配对象中的属性)。
-
使用方式:直接注解到属性上。
-
装配原理:反射机制
-
装配方式
-
先按照byType进行匹配(寻找接口的实现类)
-
匹配1个:匹配成功,正常使用
-
匹配0个:
-
默认【@Autowired(required=true)】报错
/*expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} */
-
@Autowired(required=false),不会报错
-
-
匹配多个
-
再按照byName进行唯一筛选
-
筛选成功【对象中属性名称==beanId】,正常使用
-
筛选失败【对象中属性名称!=beanId】,报如下错误:
//expected single matching bean but found 2: deptDao,deptDao2
-
-
-
-
-
@Autowired中required属性
-
true:表示被标识的属性必须装配数值,如未装配,会报错。
-
false:表示被标识的属性不必须装配数值,如未装配,不会报错。
-
-
@Qualifier注解
- 作用:配合@Autowired使用,可以设置装配属性的beanid,而不是必须与属性名相同。
- 注意:不能单独使用,需要与@Autowired一起使用。
-
@Value注解
- 作用:装配对象中的属性(针对字面量数值)
11. Spring中组件扫描
组件扫描:扫描自己写的注解,可以让程序识别。
默认情况下会扫描包下面的所有包。如果想要扫描100个包中个个别几个,可以使用包含扫描/排除扫描。
包含扫描/排除扫描
- 注意:
- 使用包含/排除扫描之前,必须设置
use-default-filters="false"
【关闭当前包及其子包的扫描】 - type
- annotation:设置被扫描注解的全类名
- assignable:设置被扫描实现类的全类名
- 使用包含/排除扫描之前,必须设置
<context:component-scan base-package="Spring" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
<context:include-filter type="assignable" expression="Spring.service.impl.DeptServiceImpl"/>
<context:exclude-filter type="assignable" expression="Spring.controller.DeptController"/>
</context:component-scan>
12. Spring中完全注解开发
完全取消xml配置过程。
完全注解开发步骤
-
创建配置类
-
在配置类上添加注解
- @Configuration:标识当前的类是一个配置类,用来代替xml。
- @ComponentScan:设置组件扫描的当前包以及子包。
示例代码:
@ComponentScan(basePackages = "Spring") @Configuration public class SpringConfig { }
-
使用 AnnotationConfigApplicationContext 创建容器对象
示例代码:
@Test public void test(){ ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); Object deptdaoimpl = context.getBean("deptdaoimpl"); System.out.println("deptdaoimpl = " + deptdaoimpl); }
13.Spring 集成 Junit4
Spring提供了对Junit的集成,所以可以在测试类中直接注入IOC容器中的对象。
集成步骤
-
导入jar包支持
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.1</version> <scope>test</scope> </dependency>
-
指定Spring配置文件路径/Config类【使用@ContextConfiguration】
-
指定Spring环境下的Junit4 的运行器【使用@RunWith】
示例代码:
@ContextConfiguration(classes = SpringConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class TextJunit {
@Autowired
DeptDao deptdao;
@Test
public void test(){
deptdao.addDept(new Dept());
}
}
14. Spring AOP前奏
代理模式
-
代理模式:比如房屋中介、房屋App等都是代理。
-
我们(目标对象)与中介(代理对象)不能相互转换,因为是平级关系。
为什么需要代理?
-
需求:实现【加减乘除】计算器类
- 在加减乘除方法中,添加日志功能【在计算之前,记录日志。在计算之后,显示结果。】
-
实现后发现问题如下
- 日志代码比较分散,可以将代码提取到日志类中。
- 日志代码比较混乱,非核心代码与核心业务代码书写一处。
-
总结:我们不期望在核心业务代码中书写日志代码。
-
此时:使用代理模式解决问题:
【先将日志代码横向提取到日志类中,再动态织入回到业务代码中】
-
手动实现动态代理步骤
注意:代理对象与实现类【目标对象】是“兄弟”关系,不能相互转换
-
实现方式
- 基于接口实现动态代理: JDK动态代理
- 基于继承实现动态代理: Cglib、Javassist动态代理
-
基于接口实现动态代理的具体步骤:
-
创建类(比如
MyProxy
)【为了创建代理对象的工具类】 -
提供属性【目标对象,也就是实现类】
-
提供方法(比如
getProxyObject()
)【为目标对象创建代理对象】实现动态代理关键步骤
- 一个类:Proxy
- 概述:Proxy代理类的基类【类似Object】
- 作用:newProxyInstance():创建代理对象
- 一个接口:InvocationHandler
- 概述:实现【动态织入效果】关键接口
- 作用:invoke(),执行invoke()实现动态织入效果
- 一个类:Proxy
-
提供有参构造器【避免目标对象为空】
-
package com.atguigu.beforeaop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* MyProxy为获取代理对象的工具类。
*/
public class MyProxy {
/**
* 目标对象【目标客户】
*/
private Object target;
public MyProxy(Object target){
this.target = target;
}
/**
* 获取目标对象的,代理对象
* @return
*/
public Object getProxyObject(){
Object proxyObj = null;
/**
第一个参数:类加载器【ClassLoader loader】,目标对象的类加载器
第二个参数:目标对象实现接口:Class<?>[] interfaces,目标对象实现所有接口
第三个参数:InvocationHandler (动态织入的关键方法)
*/
//创建类加载器
ClassLoader classLoader = target.getClass().getClassLoader();
//代理对象要知道目标对象实现了什么样的接口
Class<?>[] interfaces = target.getClass().getInterfaces();
//创建代理对象
proxyObj = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
//执行invoke()实现动态织入效果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取方法名【目标对象】
String methodName = method.getName();
//执行目标方法之前,添加日志
MyLogging.beforeMethod(methodName,args);
//获取目标对象的目标方法。
Object rs = method.invoke(target, args);
//执行目标方法之后,添加日志
MyLogging.afterMethod(methodName,rs);
return rs;
}
});
return proxyObj;
}
//不推荐以下方法:使用匿名内部类
// class invocationImpl implements InvocationHandler{
// }
}
@Test
public void testBeforeAop(){
// int add = calc.add(1, 2);
// System.out.println("add = " + add);
//目标对象
Calc calc = new CalcImpl();
//代理工具类
MyProxy myProxy = new MyProxy(calc);
//获取代理对象,与目标对象CalcImpl不能相互转换,两者是兄弟关系。
Calc calcProxy = (Calc)myProxy.getProxyObject();
//使用代理对象执行方法
// int add = calcProxy.add(1, 2);
int div = calcProxy.div(2, 1);
}
15. Spring中的AOP(重要)
AspectJ框架【AOP框架】
- AspectJ框架是Java社区中最完整最流行的AOP框架
- 在Spring2.0以上的版本中,可以使用基于AspectJ注解或基于XML配置的AOP。
使用AspectJ步骤
-
导入相关jar包
<!-- 添加AspectJ--> <!--spirng-aspects的jar包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.1</version> </dependency>
-
配置文件
- 开启组件扫描
- 开启AspectJ注解支持
<!-- 开启组件扫描--> <context:component-scan base-package="com.atguigu"></context:component-scan> <!-- 开启AspectJ注解支持--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
切面类(MyLogging类)添加注解
@Component //标识当前类为一个组件。 @Aspect //将当前类标识为切面类(非核心业务提取类)。
-
在切面类中添加通知注解
@Component @Aspect public class MyLogging { @Before(value = "execution(public int Spring.AOP.CalcImpl.add(int ,int))") public void beforeMethod(JoinPoint joinPoint){ //获取当前方法名称 String name = joinPoint.getSignature().getName(); //获取方法形参 Object[] args = joinPoint.getArgs(); System.out.println("当前调用"+ name + "方法,参数是"+ Arrays.toString(args)); } }
-
测试
@Test public void test1(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Calc calc = context.getBean("calc", Calc.class); //错误的,代理对象不能转换为目标对象【代理对象与目标对象是兄弟关系】 //CalcImpl calc = context.getBean("calc", CalcImpl.class); calc.add(1,2); }
Spring中AOP概述
- AOP:Aspect-Oriented Programming,面向切面编程【面向对象一种补充】
- 优势:
- 解决代码分散问题
- 解决代码混乱问题
- 优势:
- OOP:Object-Oriented Programming,面向对象编程
AOP更针对某一个方法,进行横向的扩展。比如添加日志等。
Spring中AOP相关术语
- 横切关注点:非核心业务代码【日志】,称之为横切关注点
- 切面(Aspect):将横切关注点提取到类中,这个类称之为切面类
- 通知(Advice):将横切关注点提取到类中之后,横切关注点更名为:通知
- 目标(Target):目标对象,指的是需要被代理的对象【实现类(CalcImpl)】
- 代理(Proxy):代理对象可以理解为:中介
- 连接点(Joinpoint):通知方法需要指定通知位置,这个位置称之为:连接点【此时在通知之前】
- 切入点(pointcut):通知方法需要指定通知位置,这个位置称之为:切入点【此时在通知之后】
16. AspectJ详解(重要)
AspectJ切入点表达式
-
语法:
@Before(value = "execution(权限修饰符 返回值类型 包名.类名.方法名(参数类型))")
-
通配符
[ * ]:代表任意权限修饰符+返回值类型 和 任意包名+类名+方法名。
[ … ] :代表任意参数类型及参数个数。
通配符常用的方法是:
@Before(value = "execution(* Spring.AOP.CalcImpl.* (..))")
-
重用切入点表达式
@Pointcut("execution(* Spring.AOP.CalcImpl.* (..))") public void myPoint(){ } @After("myPoint()") public void AfterMethod(JoinPoint joinPoint){ System.out.println("后置"); }
AspectJ中JoinPoint对象
-
JoinPont【切入点对象】
-
作用:
-
获取方法名称
//获取方法签名【方法签名=方法名+参数列表】 joinPoint.getSignature(); //获取方法名称 String methodName = joinPoint.getSignature().getName();
-
获取参数
Object[] args = joinPoint.getArgs();
-
AspectJ中通知
指定方法:切入点表达式设置位置
-
前置通知
@Before
- 执行时机:指定方法执行之前【如目标方法中有异常,会执行】
-
后置通知
@After
- 执行时机:指定方法所有通知执行之后【如目标方法中有异常,会执行】
-
返回通知
@AfterReturning
-
执行时机:指定方法返回结果时执行,【如目标方法中有异常,不执行】
-
注意事项:@AfterReturning中returning属性与入参中参数名一致
示例代码:
@AfterReturning(value = "myPoint()",returning = "rs") public void AfterReturning(JoinPoint joinPoint,Object rs){ //获取当前方法名称 String name = joinPoint.getSignature().getName(); //获取方法形参 Object[] args = joinPoint.getArgs(); System.out.println("【返回通知】当前调用"+ name + "方法,返回结果是"+ rs); }
-
-
异常通知
@AfterThrowing
-
执行时机:指定方法出现异常时执行,【如目标方法中没出现异常,不执行】
-
注意事项:@AfterThrowing中throwing属性与入参中参数名一致
示例代码
@AfterThrowing(value = "myPoint()",throwing = "ex") public void AfterThrowing(JoinPoint joinPoint , Exception ex){ //获取当前方法名称 String name = joinPoint.getSignature().getName(); //获取方法形参 Object[] args = joinPoint.getArgs(); System.out.println("【异常通知】当前调用"+ name + "方法,出现的异常是"+ ex); }
总结
- 有异常:前置通知=》异常通知=》后置通知
- 无异常:前置通知=》返回通知=》后置通知
-
-
环绕通知
@Around
该通知可以整合前四个通知。
注意:
- 参数中必须使用ProceedingJoinPoint,目的是使用
proceed()
方法 - 环绕通知必须将目标对象的方法返回结果作为返回值。
示例代码
@Around("myPoint()") public Object AroundMethod(ProceedingJoinPoint pjp){ String name = pjp.getSignature().getName(); Object[] args = pjp.getArgs(); //定义方法返回值 Object rs = null; try { //前置通知 System.out.println("【前置通知】当前调用"+ name + "方法,参数是"+ Arrays.toString(args)); //触发目标对象的方法 rs = pjp.proceed(); //返回通知 System.out.println("【返回通知】当前调用"+ name + "方法,返回结果是"+ rs); } catch (Throwable ex) { //异常通知 System.out.println("【异常通知】当前调用"+ name + "方法,出现的异常是"+ ex); }finally { //后置通知 System.out.println("【后置通知】当前调用"+ name + "方法,参数是"+ Arrays.toString(args)); } return rs; }
- 参数中必须使用ProceedingJoinPoint,目的是使用
定义切面优先级
两个切面类中如果都存在前置等切面,则可以使用@Order来定义切面类的优先级。
@Order(value = index),其中index值越小,优先级越高
@Component
@Aspect
@Order(value = 1)
public class MyValidate {}
============================
@Component
@Aspect
@Order(2)
public class MyLogging {}
基于XML方式配置AOP
在一些老的项目中,可能没有使用基于注解配置,还在使用XML的方式进行配置切面类。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置计算器实现类-->
<bean id="calculator" class="com.atguigu.spring.aop.xml.CalculatorImpl"></bean>
<!--配置切面类-->
<bean id="loggingAspect" class="com.atguigu.spring.aop.xml.LoggingAspect"></bean>
<!--AOP配置-->
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut id="pointCut"
expression="execution(* com.atguigu.spring.aop.xml.Calculator.*(..))"/>
<!--配置切面-->
<aop:aspect ref="loggingAspect">
<!--前置通知-->
<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:config>
</beans>
17. Spring中的JdbcTemplate
JdbcTemplate简介
Spring提供的jdbcTemplate是一个小型的持久化层框架,可以简化jdbc代码。
JdbcTemplate使用步骤
- 导入jar包
//重要的是导入该jar包。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.1</version>
</dependency>
<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>
<!--导入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.28</version>
</dependency>
-
编写配置文件
-
db.properties:设置连接数据库属性
-
applicationContext.xml:spring配置文件
- 加载外部属性文件【db.properties】
- 装配数据源【DruidDataSources】
- 装配持久化层框架【JdbcTemplate】
-
示例代码
db.properties
db.driver=com.mysql.cj.jdbc.Driver db.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8 db.username=root db.password=root
applicationContext_JdbcTemplate.xml
<!-- 装配外部属性文件--> <context:property-placeholder location="classpath:db.properties"></context:property-placeholder> <!-- 装配数据源--> <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${db.driver}"></property> <property name="url" value="${db.url}"></property> <property name="password" value="${db.password}"></property> <property name="username" value="${db.username}"></property> </bean> <!-- 装配持久化层框架--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="druidDataSource"></property> </bean>
装配自己内部的bean可以使用注解,但是第三方的bean要使用xml去装配,比如jdbcTemplate。因为装配类需要在类上面去写注解。
-
-
使用核心类库(API调用)
JdbcTemplate常用API
JdbcTemplate默认:自动提交事务,不像Mybatis一样手动提交事务。
- jdbcTemplate.update(String sql,Object… args):通用的增删改方法
- jdbcTemplate.batchUpdate(String sql,List<Object[]> args):通用批处理增删改方法
- jdbcTemplate.queryForObject(String sql,Class clazz,Object… args):查询单个数值
- String sql = “select count(1) from tbl_xxx”;
- jdbcTemplate.queryForObject(String sql,RowMapper rm,Object… args):查询单个对象
- String sql = “select col1,col2… from tbl_xxx”;
- jdbcTemplate.query(String sql,RowMapper rm,Obejct… args):查询多个对象
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext_Jdbc.xml");
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "insert into tbl_employee(last_name,email,salary,dept_id) values(?,?,?,?)";
List<Object[]> list = new ArrayList<>();
list.add(new Object[]{"qq0","qq.com",123,123});
list.add(new Object[]{"qq1","qq.com",123,123});
list.add(new Object[]{"qq2","qq.com",123,123});
list.add(new Object[]{"qq3","qq.com",123,123});
//演示批量添加操作。
jdbcTemplate.batchUpdate(sql,list);
//查询单个数值
String sql = "select count(1) from tbl_employee";
Integer integer = jdbcTemplate.queryForObject(sql, Integer.class);
System.out.println("integer = " + integer);
//查询单个对象
String sql = "select id,last_name,email,salary from tbl_employee where id = ?";
//创建RowMapper
RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, 1);
System.out.println("employee = " + employee);
//查询多个对象
String sql = "select id,last_name,email,salary from tbl_employee";
RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
List<Employee> employeeList = jdbcTemplate.query(sql, rowMapper);
使用JdbcTemplate搭建Service&Dao层
-
Service层依赖Dao层
-
Dao层依赖JdbcTemplate
示例代码:
//text: /** * @Author 不知名网友鑫 * @Date 2022/10/2 **/ @ContextConfiguration(locations = "classpath:applicationContext_Jdbc.xml") @RunWith(SpringJUnit4ClassRunner.class) public class TestDaoService { @Autowired DeptService deptService; @Test public void test(){ List<Dept> depts = deptService.selectDeptS(); System.out.println("depts = " + depts); } } //DeptService /** * @Author 不知名网友鑫 * @Date 2022/10/2 **/ @Service("deptService") public class DeptServiceImpl implements DeptService{ @Autowired DeptDao deptDao; @Override public List<Dept> selectDeptS() { List<Dept> depts = deptDao.selectDept(); return depts; } } //DeptDao /** * @Author 不知名网友鑫 * @Date 2022/10/2 **/ @Repository("deptDao") public class DeptDaoImpl implements DeptDao{ @Autowired JdbcTemplate jdbcTemplate; @Override public List<Dept> selectDept() { String sql = "select dept_id,dept_name from tbl_dept"; RowMapper<Dept> rowMapper = new BeanPropertyRowMapper<>(Dept.class); List<Dept> query = jdbcTemplate.query(sql, rowMapper); return query; } }
18. Spring声明式事务管理(重要)
回顾事务:
- 事务四大特征【ACID】
- 原子性:事务是最小单元,不可分割。
- 一致性:事务要么同时提交,要么同时回滚。
- 隔离性:事务与事务彼此隔离,不能有关联。多个事务并发执行不会互相干扰。
- 持久性:事务对数据1的修改应该被写在持久化容器中。
- 事务三种行为
- 开启事务:connection.setAutoCommit(fasle);
- 提交事务:connection.commit();
- 回滚事务:connection.rollback();
Spring中事务管理
案例:去结账。
具体步骤:1. 生成订单 2. 生成订单详情 3. 更改库存&销量 4. 清空购物车
如果其中一步出错,我们希望整个流程一起回滚,而不是提交部分,此时我们需要进行事务管理。
-
编程式事务管理【传统事务管理】
- 获取数据库连接Connection对象
- 取消事务自动提交【开启事务】
- 执行操作【业务代码】
- 正常完成操作时手动提交事务
- 失败完成操作时回滚事务
- 关闭相关资源
问题:事务管理的代码与核心业务代码相耦合。
- 事务管理代码分散
- 事务管理代码混乱
-
声明式事务管理【使用AOP管理事务】
方式:先横向提取事务管理代码,再动态织入。
使用声明式事务管理
不用事务管理代码,发现:同一个业务中,会出现局部成功及局部失败的现象【不正常】
-
导入Jar包,添加AspectJ的jar包。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.1</version> </dependency>
-
编写配置文件
- 配置事务管理器
- 开启事务注解支持
<!-- 配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="druidDataSource"></property> </bean> <!-- 开启事务注解支持transaction-manager默认值:transactionManager,事务管理器id为transactionManager时, 可以不写 注意annotation-driven是tx标签--> <tx:annotation-driven></tx:annotation-driven>
-
在需要事务管理的业务方法上,添加注解**@Transactional**
@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); }
Spring声明式事务管理属性
@Transactional注解属性
事务传播行为【Propagation】
-
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
-
Propagation常用属性:
REQUIRED
传播行为:当 bookService 的 purchase()方法被另一个事务方法 checkout()调用时,它默认会在现有的事务内运行。因此在 checkout()方法的开始和终止边界内只有一个事务。
如果余额不足,就一本书也买不了。
REQUIRES_NEW
传播行为:
表示该方法必须启动一个新事务,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。
事务隔离级别【Isolation】
假设现在有两个并发的事务:Transaction01和Transaction02。
脏读【读取到了未提交的事务】:
①Transaction01 将某条记录的 AGE 值从 20 修改为 30,但是没有提交。
②Transaction02 读取了 Transaction01 更新后的值:30。
③随后Transaction01 回滚,AGE 值恢复到了 20。
④Transaction02 读取到的 30 就是一个无效的值。
不可重复读 【多次从一个字段中读取到的数据不相同】
①Transaction01 读取了 AGE 值为 20。
②Transaction02 将 AGE 值修改为 30,并且提交数据。
③Transaction01 再次读取 AGE 值为 30,和第一次读取不一致。
幻读【多次从一个表中读取的行不相同】
①Transaction01 读取了 STUDENT 表中的一部分数据。
②Transaction02 向 STUDENT 表中插入了新的行。
③Transaction01 读取了 STUDENT 表时,多出了一些行。
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会互相影响。
隔离级别越高,数据一致性就越好,但并发性越弱。一个事务与其他事务之间的隔离等级【1,2,4,8】
隔离等级
- 读未提交【1】:READ UNCOMMITED
- 事务1可以读取事务2未提交的数据。存在问题:脏读。
- 读已提交【2】:READ COMMITTED
- 存在问题:可能出现不可重复读
- 可重读【4】:REPEATABLE READ(字段锁)
- 存在问题:可能出现幻读
- 串行化【8】SERIALIZABLE(表的锁)
其他属性
- 事务超时【timeout】
类型:int,单位:second。
默认值:-1【未设置强制回滚】
设置超时时间,到达指定时间后,会强制事务回滚。
-
事务只读【readonly】
一般事务方法中,只有查询操作时,才将事务设置为只读。
-
事务回滚【rollbackFor/noRollbackFor】
遇见回滚/不回滚的异常类。
示例代码:
//当前事务传播行为是自己属于一个事务。
@Transactional(propagation = Propagation.REQUIRES_NEW,
timeout = 1,
noRollbackFor = ArithmeticException.class)
public void purchase(String username, String isbn) {
//查询book价格
Integer price = bookshopDao.findBookPriceByIsbn(isbn);
//测试事务超时
// try {
// Thread.sleep(5000);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
//修改库存
bookshopDao.updateBookStock(isbn);
//修改余额
bookshopDao.updateUserAccount(username, price);
//演示遇到算数异常时,事务不回滚
int i = 1/0;
}
基于XML方式配置声明式事务管理
<!-- 配置事务切面 -->
<aop:config>
<aop:pointcut expression="execution(* com.atguigu.tx.component.service.BookShopServiceImpl.purchase(..))" id="txPointCut"/> <!-- 将切入点表达式和事务属性配置关联到一起 -->
<aop:advisor advice-ref="myTx" pointcut-ref="txPointCut"/>
</aop:config>
<!-- 配置基于 XML 的声明式事务 -->
<tx:advice id="myTx" transaction-manager="transactionManager">
<tx:attributes>
<!-- 设置具体方法的事务属性 -->
<tx:method name="find*" read-only="true"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="purchase"
isolation="READ_COMMITTED"
no-rollback-for="java.lang.ArithmeticException,java.lang.NullPointerException" propagation="REQUIRES_NEW"
read-only="false"
timeout="10"/>
</tx:attributes>
</tx:advice>
19. Spring5新特性
添加了新注解
-
以@Nullable 为例
位置:可以书写在方法&属性&参数前面。
作用:表示当前方法或属性可以为空,消除了空指针异常。
Spring5整合Log4j2
-
导入jar包
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.11.2</version> <scope>test</scope> </dependency>
-
编写配置文件(不需要自己编写)
-
log4j2.xml
日志级别以及优先级排序:OFF > FATAL > WHAR > INFO > DEBUG > TRACE > ALL
高级别会打印低级别的内容。
-
Spring5整合Junit5
-
导入jar包【注意:将Junit4的jar包删除】
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.7.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.1</version> </dependency>
-
使用注解进行整合。
//方式一: @ContextConfiguration(locations = "classpath:applicationContext.xml") @ExtendWith(SpringExtension.class) //方式二: @SpringJUnitConfig(locations = "classpath:applicationContext.xml")