【Java学习】 Spring的基础理解 IOC、AOP以及事务
一、简介
maven依赖:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.0.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.0.RELEASE</version> </dependency>
1.spring全家桶的结构构图:
最下边的是测试单元 其中spring封装了自己的测试单元
-
Core Container
上边一层(Core Container)是核心容器,也是spring框架的基础 也是核心
- core:提供了框架的基本组成部分 包括ioc和依赖注入功能
- beans:提供了BeanFactory 实现了工厂模式。方便解耦
- context:上下文内容
- expression:提供了强大的表达式语言,用于在运行时查询和操作对象。
3.Data Access/Integration 数据访问/集成
- 该模块包含JDBC、ORM、OXM、JMS和事务处理模块
- JDBC:提供了JDBC的抽象层,可以更方便的处理数据库
- ORM:模块提供了流行的对象关系型映射的API的集成
- OXM:模块提供了对OXM实现的支持(啥是OXM)
- JMS:包含了生产和消费消息的功能
- 事务:毋庸置疑,可以实现特殊接口类以及所有的pojo支持编程式和声明式事务管理。
4.Web
Web层由Web、Servlet、Web-Sockethe和Web-Portlet组成
- Web模块:提供面向web的基本功能和面向web的应用上下文
- Servlet模块:为web应用提供了模型视图看着(MVC)和RestWeb服务的实现。Spring的MVC框架可以将代码与web表单进行分离。
- Web-Socket
- Web-Portlet
5.AOP
Aop模块提供了面向切面编程的实现,允许自定义放啊拦截器和切入点,对代码继续宁解耦,可以减少模块间的耦合度,方便扩展和提高可维护性
6.Instrumentation
7.Messaging
8.Aspects
也是面向切面编程
9.Test
二、IOC:
2.1理论:IOC也就是控制反转
其基本理解就是Spring将创建对象的过程转交给了IOC容器。,在其它类中我们只需要调用即可,不需要重新创建对象。
在我们传统的三层架构模型中,目录结构分别为pojo、dao、和service
当我们在service中调用dao时,常会使用到
private UserDao userDao=new UserDao();
这就是我们通常的思维,需要创建对象才可以使用,但是引入spring后,便可以使用<bean>标签就可以创建对象。
2.2 Spring 中IOC的实现提供了两种方式:
2.2.1BeanFactory:这是IOC的基本实现,是Spring的内部接口,不会提供给开发人员使用。
加载配置文件时,不会创建对象,只有在使用的时候才会创建对象(一会可以代码解释 注意的是getBean的动作)
2.2.2ApplicationContext 它是BeanFactory接口的子接口,提供了更加强大的功能开发人员进行加载配置文件的时候就进行了创建。
//1.引入jar包
//2.编辑配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
配置对象 id为唯一限定名 一般为类名的小写 class是类所在的位置
<bean id="teacher" class="com.qiang.pojo.Teacher"></bean>
</beans>
//3.测试
@Test
public void testTeacherCreate(){
//加载配置文件
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationConfig.xml");
Teacher teacher = ac.getBean("teacher", Teacher.class);
System.out.println(teacher);
}
//在以上代码中 如果使用的是ApplicationContext 则在进行加载配置文件时 就已经在IOC容器中创建好了teacher对象
//如果是使用的是 BeanFactory ac 创建对象,则在getbean时才会创建对象(不可操作,因为该接口不对开发人员透明)
2.2.3 ApplicationContext接口的实现类的继承关系
由上图可以看出,我们能使用的只有ClassPathXMlApplicationContext和FileSystemXmlApplicationContext来解析我们的xml配置文件。
其中Class开头的可以去classpath路径下去寻找
File开头的可以加载磁盘路径下的配置文件
之后我们还可以使用AnnotationconfigApplicationContext可以使用注解方式来创建容器。
2.3 IOC对Bean的管理
2.3.1 创建bean
bean就是我们所说的Java对象,在之前所学的创建Java对象,一般是使用new关键字 调用类中的构造器来创建对象。
因此创建bean的方法就可以有三种方式:使用默认的无参构造、使用简单工厂方式创建、使用静态工厂方式创建
A:使用默认的无参构造
<bean id="唯一标识" class="类的全限定名"> </bean>
B :使用普通工厂类创建bean实例
1.建立普通类teacher,包含个别属性,并添加get、set方法
2.创建工厂类
public class SchoolFactory {
Teacher teacher=new Teacher();
public Teacher getInstance(){
return teacher;
}
}
在工厂类中实例化对象,并添加一个普通方法可以获取到对象
3.配置文件
<!-- 普通工厂类创建bean实例-->
<bean id="factory" class="com.qiang.pojo.factory.SchoolFactory" ></bean>
<bean id="teacher1" factory-bean="factory" factory-method="getInstance"></bean>
第一个bean是一个实例化的对象也就是对象工厂
第二个bean 是利用对象工厂来创建的对象实例化
factory标签指的是 是哪个工厂对象,对应上边的id
factory-method指的是调用可以获取对象实例的方法(普通方法)
4.测试:
/**
* 测试普通工厂类创建bean实例
*/
@Test
public void testTeacherFactory1(){
//加载配置文件
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationConfig.xml");
Teacher teacher1 = ac.getBean("teacher1", Teacher.class);
System.out.println(teacher1);
}
如果在程序中可能需要频繁的创建某个类的实例对象,采用工厂模式会更好
2.3.2 依赖注入 也就是注入属性
依赖注入也就是对属性的注入,分为三种:构造器注入、set注入以及p标签注入
其中的构造注入,需要走的是带参构造
set注入,走的是set方法和无参构造
p标签注入和set注入基本一样,只不过头文件中需要引入
对象工厂接口中最基本的是BeanFactory,它可以支持懒加载,也就是在加载配置文件时,不会创建实例对象,而是在需要调用时才会创建。
如今使用的是applicationContext来解析配置文件,他的底层接口也是BeanFactory,但是它可以在加载配置文件的时候,就直接创建了bean实例。
以Student类作为演示
public class Student {
private String sname;
private int age;
private String sex;
public Student(String sname, int age, String sex) {
this.sname = sname;
this.age = age;
this.sex = sex;
System.out.println("这是三个参数的无参构造");
}
public Student(String sname, int age) {
this.sname = sname;
this.age = age;
System.out.println("这是第一个属性为name的两个参数的构造");
}
public Student( int age,String sname) {
this.sname = sname;
this.age = age;
System.out.println("这是第一个属性为age的两个参数的构造");
}
public Student() {
System.out.println("这是无参构造");
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Student{" +
"sname='" + sname + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
A 构造器注入 <constructor-arg>
<bean id="stu1" class="com.qiang.pojo.Student">
<constructor-arg name="sname" value="zs"></constructor-arg>
<constructor-arg name="age" value="15"></constructor-arg>
</bean>
看我们的实体类中就可以发现,我们的含有两个参数的构造器,有两个,在这个时候我们使用的构造器的构造函数就不知道使用的是哪个带参的构造器
因此我们可以使用 index来标识下标 就可以指定先执行那个代餐的构造
其中constructor-arg中也可以使用type来标识参数类型来确定属性
如果是八大基本数据类型,则可以直接写关键字,如果是其他类型,则需要添加类的全限定路径
如果其中还含有其他对象类型的参数,
如此时的student类中包含属性 private Grade grade;
构造器:
<bean>
<construct-arg type="com.qiang.Grade" ref="grade"></construct-arg>
</bean>
<bean id="grade" class="com.qiang.Grade"></bean>
B set方法注入 此时需要在类中对属性添加set方法 以及无参构造
<bean id="student2" class="com.qiang.pojo.Student"> <property name="id" value="2"></property> <property name="name" value="李四"></property> </bean>
C :p标签注入 此时就要添加对应的set方法以及无参构造 以及添加头文件
D:也可以set注入和构造器注入 混合使用 但是需要有对应的构造方法
2.3.3 不同属性类型对应的写法:
由于我们的属性的在不同的使用场景下可能有不同的属性类型,如集合、数组等情况,因此可能会需要使用到不同的标签,下面只演示使用set方法注入的情况 那就意味着,我们需要添加构造方法和无参构造。
实体类
public class Order {
private String [] cources;
private List<String> lists;
private Map<String,String> maps;
private Set<String> sets;
配置文件
<!-- 测试不同属性类型的属性注入-->
<bean id="order" class="com.qiang.pojo.Order">
<!-- 数组集合使用array标签-->
<property name="cources">
<array>
<value>美羊羊</value>
<value>兰羊羊</value>
</array>
</property>
<!-- list集合类型使用list标签-->
<property name="lists">
<list>
<value>舒克</value>
<value>贝塔</value>
</list>
</property>
<!-- set集合使用set标签-->
<property name="sets">
<set>
<value>mysql</value>
<value>javase</value>
<value>javaweb</value>
</set>
</property>
<property name="maps">
<map>
<entry key="java" value="我们在学习的语言"></entry>
<entry key="web" value="前端的"></entry>
</map>
</property>
数组类型的使用<array><value></value></array>
list类表类型的使用:<list><value></value></list>
set集合类型的使用<set><value></value></set>
map集合类型的使用<map><entry key="" value=""></entry></map>
2.4 Bean的作用域: scope属性
- singleton:默认值 单例模式 每次获取的bean都是同一个对象
- prototype:每次获取bean都会被重新实例化
- request:每次请求都会重新实例化对象,但是在同一请求下获取的情况下的bean是单例的
- session 每次会话内的bean是单例的
- application:整个应用程序对象内的bean实例都是单例模式的
- websocket:同一个websocket对象内的对象是单例的。
Singleton
<!-- 测试单例模式-->
<bean id="stu3" class="com.qiang.pojo.Student" scope="singleton"></bean>
测试类:
@Test
public void testStudent3(){
//加载配置文件
BeanFactory ac=new ClassPathXmlApplicationContext("applicationConfig2.xml");
Student stu1 = ac.getBean("stu3", Student.class);
System.out.println(stu1);
Student stu2 = ac.getBean("stu3", Student.class);
System.out.println(stu2);
System.out.println(stu1==stu2);
}
结果:
com.qiang.pojo.Student@10a035a0
com.qiang.pojo.Student@10a035a0
true
prototype
<!-- 测试多例模式-->
<bean id="stu4" class="com.qiang.pojo.Student" scope="prototype"></bean>
测试类
@Test
public void testStudent4(){
//加载配置文件
BeanFactory ac=new ClassPathXmlApplicationContext("applicationConfig2.xml");
Student stu1 = ac.getBean("stu4", Student.class);
System.out.println(stu1);
Student stu2 = ac.getBean("stu4", Student.class);
System.out.println(stu2);
System.out.println(stu1==stu2);
}
结果:
com.qiang.pojo.Student@10a035a0
com.qiang.pojo.Student@67b467e9
false
2.5 bean的生命周期
2.5.1 一般理解下的bean的生命周期:
- 通过构造器创建bean实【此时执行的是无参构造】
- 为bean属性设置值以及对其他的bean的引用【set注入】
- 调用bean的初始化方法,在配置文件中配置
- bean对象可以使用了 【已经获取到了对象】
- 当容器关闭时,调用bean的销毁方法【在配置文件中配置】
实体类对象
public class People implements Serializable {
private String oid;
public People() {
System.out.println("第一步:执行无参构造");
}
public String getOid() {
return oid;
}
public void setOid(String oid) {
this.oid = oid;
System.out.println("第二步: 调用set方法给属性设置值......");
}
public void initMethod(){
System.out.println("第三步:执行初始化方法............");
}
public void destroyMethod(){
System.out.println("第五步:执行销毁方法............");
}
@Override
public String toString() {
return "People{" +
"oid='" + oid + '\'' +
'}';
}
}
配置文件 添加初始化方法 和销毁方法
<bean id="people" class="com.qiang.pojo.People"
init-method="initMethod"
destroy-method="destroyMethod">
</bean>
其中的标签init-method、destroy-method 中的方法是在实体类中自定义的
测试类
/**
* 测试bean的生命周期
*/
@Test
public void testBeanLive(){
//加载配置文件
ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("applicationConfig2.xml");
//获取bean实例
People people = ac.getBean("people", People.class);
System.out.println("第四步:获取bean实例对象 。。。");
System.out.println(people);
//手动销毁
ac.close();
}
结果:
第一步:执行无参构造
第三步:执行初始化方法............
第四步:获取bean实例对象 。。。
People{oid='null'}
第五步:执行销毁方法............
2.5.2 添加后置处理器的生命周期的方法
- 第一步:执行无参数的构造方法 。。。
- 第二步: 调用set方法给属性设置值......
- 在初始化之前执行的方法
- 第三步:执行初始化方法............
- 在初始化之后执行的方法
- 第四步:获取bean实例对象 。。。
- 第五步:执行销毁方法............
在实体类中 实现了BeanPostProcessor接口
并重写了
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之前执行的方法");
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之后执行的方法");
return bean;
}
2.6 自动装配:
装配的意思是 就是怎么去创建对象
spring的装配方式有三种:
在xml中显示的装配 也就是使用<bean>标签
在Java中的显示配置 new关键字
自动装配机制
Spring的自动装配你有两个角度的实现
分别是 组件扫描和 自动装配
组件扫描:spring 回自动发现应用上下文中所创建的bean
自动装配:spring自动满足bean之间的依赖,也就是使用IOC和DI
自动装配的实现方式有两种,一种是通过xml的bena标签 也可以使用注解方式
2.6.1 通过XMl中的标签 自动装配
A:byName 按名称自动装配
通俗理解:在xml配置文件中 使用bean标签 通过bynae自动诸如和,每次遇到名称为 byname属性值时就自动创建对象
实体类Dog
private String color;
private int age;
配置文件
<bean id="d1" class="com.qiang.pojo.Dog" autowire="byName">
<property name="age" value="15"></property>
</bean>
测试类:
@Test
public void testDog1(){
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationConfig.xml");
Dog d1 = ac.getBean("d1", Dog.class);
System.out.println(d1.getAge());
}
结果:
15
B.byType 按类型自动装配
配置文件
<bean id="d1" class="com.qiang.pojo.Dog" autowire="byType">
<property name="age" value="21"></property>
</bean>
测试类:
@Test
public void testDog1(){
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationConfig.xml");
Dog d1 = ac.getBean(Dog.class);
System.out.println(d1.getAge());
}
结果:
21
2.6.2根据注解开发
spring中的注解注入的有:@Autowired、@Resources @Qualifier @Service @Commonent @Controller @Repository
在使用注解开发时,需要现在配置文件中开启组件扫描功能
<!--开启组件扫描-->
<context:component-scan base-package="cn.liushao"></context:component-scan>
开启组件扫描 便可以自动查找其中的自动注入
不同的注解的区分:
- Autowired是自动注入,自动从spring的上下文找到合适的bean来注入。
- Resource用来指定名称注入。
- Qualifier和Autowired配合使用,指定bean的名称。
- Service,Controller,Repository分别标记类是Service层类,Controller层类,Dao层的类,spring扫描注解配置时,会标记这些类要生成bean。
- Component是一种泛指,标记类是组件,spring扫描注解配置时,会标记这些类要生成bean。
@Autowired注解
默认按类型装配springbean 默认情况下,必须要求依赖对象必须存在。如果容器中有多个相同类型的bean,则框架会抛出异常。
@Qualifier
此注解用来消除依赖注入冲突的。我们可以消除需要注入那个bean的问题
通过该注解,我们可以使用特定的Spring Bean一起装配,Spring框架可以从多个相同类型并满足装配要求的bean中找到我们想要的。
@Resource是按名称装配:
分析: 由于注解@Autowired是默认按类型装配的,一个类型的可能会有多个实现方法
因此在演示的时候 就可以选择一个接口,有多个实现类来作为演示
1.构建一个接口
public interface TeacherService {
public void sayName();
public void saysex();
}
2.创建多个实现类(以三个举例)
@Component
public class TeacherServiceImpl1 implements TeacherService {
@Override
public void sayName() {
System.out.println("A");
}
}
@Component
public class TeacherServiceImpl1 implements TeacherService {
@Override
public void sayName() {
System.out.println("B");
}
}
@Component
public class TeacherServiceImpl1 implements TeacherService {
@Override
public void sayName() {
System.out.println("C");
}
}
3.创建两外一个类,可以调用该类的实现类
public class TeacherController {
@Autowired
//创建对象
private TeacherService teacherService;
public void soutResult(){
teacherService.sayName();
}
}
4.修改配置文件 开启扫描
<!-- 测试注解开发-->
<context:component-scan base-package="com.qiang"></context:component-scan>
5.测试:
@Test
public void testAopAno(){
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationConfig.xml");
TeacherController contro = ac.getBean("teacherController", TeacherController.class);
contro.soutResult();
}
6.观察结果:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'teacherController': Unsatisfied dependency expressed through field 'teacherService'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.qiang.service.TeacherService' available: expected single matching bean but found 3: teacherServiceImpl1,teacherServiceImpl2,teacherServiceImpl3
分析:由于在service接口有多个实现类,使用autowired是按类型注入,可能会找不到使用哪个 因此可以搭配使用@Qualifier注解
修改第三步:
public class TeacherController {
@Autowired
@Qualifier("teacherServiceImpl1")
//创建对象
private TeacherService teacherService;
public void soutResult(){
teacherService.sayName();
}
}
继续进行测试 结果为:
A
结果显示正常
继续修改第三步:
public class TeacherController {
@Resource(name = "teacherServiceImpl1")
//创建对象
private TeacherService teacherService;
public void soutResult(){
teacherService.sayName();
}
}
继续进行测试 结果为:
A
结果显示正常
通过上述例子可以看出,如果一个类需要由多个实例变量时,可以搭配使用 @Autowired
@Qualifier("teacherServiceImpl1"),也可以单独使用 @Resource(name = "teacherServiceImpl1"
三、AOP
3.1AOP简介:
AOP通俗理解就是面向切面编程,是对面向对象的一种补充、将那些与业务无关的,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模板,这个模板就被称为”切面“,使用AOP减少了系统中的重复代码、降低了模块间的耦合度,同时提高了系统的可维护性。
AOP的底层是动态代理,分别为JDK动态代理和CGLIB动态代理
3.2术语:
连接点:可以被增强的方法
切入点: 实际真正被增强的方法,称为切入点
通知(增强):又被叫做增强,实际增强的逻辑部分称为通知[增强]
切面(是个动作):把通知应用到切入点的过程。
其中:通知有五种:
前置通知(before) 后置(返回)通知(after returning) 、环绕通知(Around)、异常通知(after-throwing)
通知通俗理解就是AOP暴漏给我们的方法,我们在这个方法中直接定义需要扩展的代码即可,但是其执行顺序,交给了spring来处理。
3.3 AOP的准备工作:
首先介绍一下Aspect J 这个是一个独立的AOP模型,但不是Spring框架的内容。
因此在演示的时候需要导入相关的jar包,或者使用maven文件
切入点表达式:知道对那个类的方法进行增强
execution( [权限修饰符] [返回值类型] [类的全路径] [方法名][参数列表])
3.4 AOP通知
3.4.1基于XML的配置通知
A 第一种方式:
1.添加jar包
2.建造实体类和代理对象类
普通类应包含一个普通方法,该普通方法也就是要增强的那个方法
public class Student implements Serializable {
public void add(){
System.out.println("这个只是一个普通的方法");
}
}
public class StudentProxy {
//配置前置通知
public void before(){
System.out.println("前置通知。。。。。");
}
//配置后置返回通知
public void afterreturning(){
System.out.println("后置返回通知....");
}
//配置最终通知
public void after(){
System.out.println("最终通知....");
}
//配置环绕通知
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知前");
proceedingJoinPoint.proceed();
System.out.println("环绕通知后....");
}
//配置异常通知
public void afterThrow(){
System.out.println("异常通知....");
}
}
3.修改配置文件
在修改配置文件时应注意,头文件也需要进行修改
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd"
//注入
<bean id="student" class="com.qiang.pojo.Student"></bean>
<bean id="studentProxy" class="com.qiang.pojo.StudentProxy"></bean>
<aop:config >
<!--切入点-->
<aop:pointcut id="pc" expression="execution(* com.qiang.pojo.Student.add(..))"/>
<!--配置切面-->
<aop:aspect ref="studentProxy">
<!--将增强应用到具体的方法上-->
<!--前置通知-->
<aop:before method="before" pointcut-ref="pc"></aop:before>
<!--后置返回通知-->
<aop:after-returning method="afterreturning" pointcut-ref="pc"></aop:after-returning>
<!--最终通知-->
<aop:after method="after" pointcut-ref="pc"></aop:after>
<!--环绕通知-->
<aop:around method="around" pointcut-ref="pc"></aop:around>
</aop:aspect>
以上没有演示异常通知,异常通知在程序发生异常时才会发生。
测试:
@Test
public void testAopXmlDemo1(){
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationConfig.xml");
Student student = ac.getBean("student", Student.class);
student.add();
}
结果:
前置通知。。。。。
环绕通知前
这个只是一个普通的方法
环绕通知后....
最终通知....
后置返回通知....
分析:
前置通知:在连接点之前执行的通知
后置返回通知:一般在方法的结尾,必然会多一个返回值
环绕通知:包含了前置通知和后置通知
异常通知:处理异常数据,事务回滚
3.4.2基于注解的配置通知
B:第二种方式
1.引入jar包
2.创建实体类
@Component
public class User {
public void add(){
System.out.println("这是一个普通方法");
}
}
3.创建代理类
@Component
@Aspect //生成代理对象
public class UserProxy {
//前置通知
@Before(value = "execution(* org.qiang.aop.anno.pojo.User.add(..))")
public void before(){
System.out.println("前置通知。。。");
}
//后置返回通知
@AfterReturning(value = "execution(* org.qiang.aop.anno.pojo.User.add(..))")
public void afterReturning(){
System.out.println("后置返回通知afterReturning....");
}
//环绕通知
@Around(value = "execution(* org.qiang.aop.anno.pojo.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("环绕之前....");
// 被增强的方法执行了
proceedingJoinPoint.proceed();
System.out.println("环绕之后....");
}
// //异常通知 只有手动创造了异常才可以触发这个通知
// @AfterThrowing(value = "execution(* org.qiang.aop.anno.pojo.User.add(..))")
// public void afterThrowing(){
// System.out.println("异常通知 afterThrowing......");
// }
//最终通知
@After(value = "execution(* org.qiang.aop.anno.pojo.User.add(..))")
public void after(){
System.out.println("最终通知....");
}
}
//修改配置文件
<!-- 开启组件扫描-->
<context:component-scan base-package="org.qiang.aop.anno"></context:component-scan>
<!-- 开启Aspect生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
//测试:
@Test
public void testAopAnno(){
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationConfig2.xml");
User user = ac.getBean("user", User.class);
user.add();
}
结果:
环绕之前....
前置通知。。。
这是一个普通方法
环绕之后....
最终通知....
后置返回通知afterReturning....
C:第三种方式抽取重复代码
只需要修改代理类对象就可以了
@Component
@Aspect //生成代理对象
public class UserProxy {
// 抽取切入点
@Pointcut(value = "execution(* org.qiang.aop.anno.pojo.User.add(..))")
public void pointcutDemo(){
}
//前置通知
@Before(value = "pointcutDemo()")
public void before(){
System.out.println("前置通知。。。");
}
//后置返回通知
@AfterReturning(value = "pointcutDemo()")
public void afterReturning(){
System.out.println("后置返回通知afterReturning....");
}
//环绕通知
@Around(value = "pointcutDemo()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("环绕之前....");
// 被增强的方法执行了
proceedingJoinPoint.proceed();
System.out.println("环绕之后....");
}
// //异常通知 只有手动创造了异常才可以触发这个通知
// @AfterThrowing(value = "pointcutDemo()")
// public void afterThrowing(){
// System.out.println("异常通知 afterThrowing......");
// }
//最终通知
@After(value = "pointcutDemo()")
public void after(){
System.out.println("最终通知....");
}
}
D : AOP 底层是动态代理默认的是JDK动态代理的方法,也可以通过
<aop: aspectj-autoproxy proxy-target-class="true"></aop :aspect-autoproxy>
标签来修改,其默认值是false是JDK动态代理的格式,改为true就可以使用CGLIB代理格式。
3.5 Spring的事务管理
Spring框架将固定的冗余部分的套路代码进行了封装,对程序员仅提供简单的XML配置就可以完成事务的管理,不需要在编写事务管理代码。这也就是Spring的非常重要功能--声明式事务
声明式事务是基于AOP实现的(动态代理)。程序员只需要调用持久层代码和业务逻辑代码,将开启事务的代码反正该了前置通知中,将事务回滚和事务提交的代码放在了后置通知中。
使用事物可以保证操作前后数据的完整性,事务的四个特性:ACID 原子性、一致性、隔离性和持久性
编程式事务:整个事务的操作都是由程序员进行手动管理,手动提交,手动回滚
声明式事务:整个事务由其他框架进行管理,我们使用事务的时候只需要进行简单的声明或
者配置即可。
Spring中的Tx模块就包含了对声明式事务的封装,以下是我们的日常的手动提交事务的写法:
public void testdemo1() throws SQLException {
Connection conn= DriverManager.getConnection("","","");
//关闭自动提交
conn.setAutoCommit(false);
try {
PreparedStatement ptst=conn.prepareStatement("insert into bank values=(?,?)");
ptst.executeUpdate();
}catch (Exception e){
//再发生异常时,就会进行事务的回滚
conn.rollback();
}
}
回顾Aop切面编程,可以发现共通点,在关闭自动提交的部分,可以用前置通知来代替,try代码块的部分就可以理解为是切入点,事务提交的部分可以使用后置通知来实现,而对于出现异常,事务回滚的操作就可以使用异常通知来处理。
也就是:
在org.springframework.jdbc.datasource.DataSourceTransactionManager 中的
方法:
protected void doBegin(){
conn.setAutoCommit(false)
}
protected void doBegin() {
}
protected void doRollback() {
}
添加事务通知:
<tx:advice id="tt" transaction-manager="transactionmanager"> <tx:attributes> <tx: method name="方法名"> </tx:attributes> </ tx: advice> <aop:config> <aop:pointcut id="pt" expression="execution(* 类的全路径.方法(. .))" /> <aop:advisor advice-ref="tt" pointcut-ref="pt"> </aop:advisor> </aop:config>
声明式事务的四个基础属性介绍:
<tx:method>标签下有属性配置,也可以用在注解上: