Spring
1.spring概述
spring全家桶:spring,springmvc,springboot,springcloud
spring:spring的出现是为了解决企业开发难度,减轻对项目模块之间的管理,类和类之间的管理,帮助开发人员创建对象,管理对象之间的关系,spring的核心技术ioc,aop,能实现模块之间,类之间的解耦合
依赖:一个类使用另一个类中的方法
2.框架
框架怎么学:框架是一个软件,其他人写好的软件
(1)知道框架能干什么,mybatis–访问数据库,对表中的数据进行增删改查
(2)框架的语法:框架要完成一个功能,需要一定的步骤支持
(3)框架的内部实现:框架内部怎么做,原理是什么
(4)通过学习,实现一个框架
3.spring框架的第一个核心功能–ioc
IOC(Inversion of Control):控制反转,是一个理论,概念,思想
描述:把对象的创建,赋值,管理工作都交给代码之外的容器实现,也就是对象的创建是由其他外部资源完成的
控制:创建对象,对对象的属性赋值,对象之间的关系管理
反转:把原来由开发人员管理,创建对象的权限转交给代码之外的容器进行实现,由容器代替开发人员管理对象,创建对象,给属性赋值
正转:由开发人员在代码中,使用new关键字创建对象,开发人员主动管理对象
容器:是一个服务器软件,一个框架(spring)
为什么使用ioc:目的就是为了减少对代码的改动,同时又能实现不同的功能,实现解耦合
3.1.java创建对象的方式
- 构造方法,new Student();
- 反射
- 序列化
- 克隆
- ioc:容器创建对象
- 动态代理
3.2.ioc的体现
servlet:
- 创建类继承HttpServlet
- 在web.xml中注册servlet,使用myservlet,com.lkw.MyServlet
- 没有创建servlet对象,因为没有使用new关键字
- servlet是tomcat服务器能够帮你创建的,tomcat也称为容器,tomcat作为容器,里面存放的有servlet对象,Listener,Filter对象
3.3.ioc技术的实现
DI是ioc的技术实现
DI(Dependency Injection):依赖注入,只需要在程序中提供要使用的对象名称就可以,至于创建对象,赋值,查找都由容器内部实现
spring是使用DI实现了ioc的功能,spring底层创建对象,使用的是反射机制
3.4.使用spring创建对象的案例:
实现步骤:
- 创建maven项目
- 加入maven的依赖:spring的依赖,版本5.2.5,junit单元测试依赖
- 创建类(接口和它的实现类),和没有使用框架一样,就是普通的类
- 创建spring需要使用的配置文件,声明类的信息,这些类由spring创建和管理
- 测试spring创建对象
- 调用对象的方法
spring-context和spring-webmvc是spring中的两个模块
spring-context:是ioc功能的,创建对象的
spring-webmvc:做web开发使用的,是servlet的升级
spring-webmvc中也可以用到spring-context中创建对象的功能的
创建对象的代码:
public void testStudent02(){
//声明spring的配置文件
String config = "beans.xml";
//创建spring容器对象
//spring默认创建对象的时机:在spring创建容器的时候,会将beans标签中的所有bean对象创建出来
//spring创建对象默认调用的是无参数的构造方法
ApplicationContext app = new ClassPathXmlApplicationContext(config);//对象是在这句代码这里完成对象创建的
Student student =(Student) app.getBean("MyStudent");
//调用对象的方法
student.fn();
}
对以上过程的理解:以上过程就体现了spring的ioc,即控制反转,就是将对象的创建交给spring容器来管理,可以把application当作是spring容器,然后所有的对象都要放在标签中,也就是只要是放在标签中的对象,spring容器在创建的时候都会调用相关类的构造方法创建对象
####4.使用spring完成对象的创建和相关属性的赋值
4.1.使用spring创建对象,并对实体类对象的属性完成赋值的步骤:
第一步:创建maven项目,加入spring的依赖和juint依赖
第二步:创建实体类对象Student
public class Student {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
第三步:在resources目录下,创建一个application.xml文件中,将Student类放到spring容器中
<bean id="student01" class="com.lkw.test01.entity01.Student">
<!--在bean标签的内部为Student的属性注入值-->
<property name="name" value="lkw"></property>
<property name="age" value="20"></property>
</bean>
<!--以上方式使用set注入的方式,这是使用最多的方式
需要注意的是:以上是对Student类对象的简单数据类型进行set注入,前提是在这个实体类中有相关属性的set方法才可以,就拿上面的代码来说,name和age是Student对象中的属性,是简单数据类型,并且有相关的set方法,这样就可以在bean标签的内部使用property标签完成属性的赋值,既然是这样,那么就可以通过name属性的值,推出相关的对象的set方法的名称
-->
第四步:在test目录下创建相应的测试方法
public class TestStudent01 {
/*
使用set注入的方式为对象的属性赋值
* */
@Test
public void test01(){
//获取容器对象
String config = "di01/application.xml";
ApplicationContext app = new ClassPathXmlApplicationContext(config);
//获取学生对象
Student student =(Student) app.getBean("student01");
System.out.println(student);
}
}
4.2.使用spring创建对象,并使用set注入的方式对对象的引用数据类型的属性进行赋值
<bean id="student01" class="com.lkw.test01.entity02.Student">
<!--在备案标签的内部为Student的属性注入值-->
<property name="name" value="lkw"></property>
<property name="age" value="20"></property>
<!--为引用数据类型赋值-->
<property name="school" ref="school01"></property>
</bean>
<!--创建school对象-->
<bean id="school01" class="com.lkw.test01.entity02.School">
<!--先对School对象进行赋值-->
<property name="name" value="XUPT"></property>
<property name="address" value="郭杜街道"></property>
</bean>
如果属性是引用数据类型,需要再声明一个bean标签,然后依照之前的方式,对简单数据类型进行赋值,然后在要使用的类的bean标签中引用已经定义好的引用数据类型的id
4.3.使用spring创建对象,并使用构造方法对对象的属性进行赋值
<bean id="student03" class="com.lkw.test01.entity03.Student">
<!--方法一:使用Student的构造方法创建对象-->
<!-- <constructor-arg name="myname" value="lkw"></constructor-arg>-->
<!-- <constructor-arg name="myage" value="20"></constructor-arg>-->
<!-- <constructor-arg name="myschool" ref="school03"></constructor-arg>-->
<!--方法二:使用index来创建对象-->
<!-- <constructor-arg index="0" value="lkw"></constructor-arg>-->
<!-- <constructor-arg index="1" value="20"></constructor-arg>-->
<!-- <constructor-arg index="2" ref="school03"></constructor-arg>-->
<!--方法三:使用index的省略形式,但是需要注意的是书写的顺序必须和构造方法上的形参的顺序一致-->
<constructor-arg value="lkw"></constructor-arg>
<constructor-arg value="20"></constructor-arg>
<constructor-arg ref="school03"></constructor-arg>
<!--总结:建议使用第一种方式创建对象-->
</bean>
<!--School对象-->
<bean id="school03" class="com.lkw.test01.entity03.School">
<!--为school对象进行初始化-->
<property name="name" value="Princeton"></property>
<property name="address" value="USA"></property>
</bean>
4.4.使用spring创建对象,并使用spring的语法规则(byName)对属性进行赋值
<!--使用spring提供的语法规则对对象中的引用数据类型的属性进行赋值-->
<!--表示使用通过属性名的方式进行自动注入,就是说bean的id和要被注入的类中的这个属性名一致-->
<bean id="student04" class="com.lkw.test01.entity04.Student" autowire="byName">
<!--简单数据类型没有办法完成赋值操作-->
<property name="name" value="lkw"></property>
<property name="age" value="20"></property>
</bean>
<bean id="school" class="com.lkw.test01.entity04.School" ><!--因为在Student对象中有一个School类型的变量,变量名叫做school,因此在使用
byname的形式进行属性值的注入的时候,要确保,School的bean标签的id值等于Student类的这个school变量的变量名
-->
<!--对里面的属性进行赋值-->
<property name="name" value="Duke"></property>
<property name="address" value="USA"></property>
</bean>
4.5.使用spring创建对象,并使用spring的语法规则(byType)对属性进行赋值
<!--使用spring提供的语法规则对对象中的引用数据类型的属性进行赋值-->
<!--使用spring中按照语法规则(通过类型)对属性进行赋值-->
<!--
byType(按照类型注入):java类中引用类型的数据类型和spring容器中(配置文件)<bean>的class属性是同源关系的,这样的bean能够赋值给引用数据类型
同源就是一类的意思:
1.java类中引用数据类型和bean的class的值是一样的
2.java类中引用数据类型和bean的class的值是父子关系
3.java类中应用数据类型和bean的class的值是接口和实现类的关系
-->
<bean id="student05" class="com.lkw.test01.entity05.Student" autowire="byType">
<!--简单数据类型没有办法完成赋值操作-->
<property name="name" value="lkw"></property>
<property name="age" value="20"></property>
</bean>
<bean id="school" class="com.lkw.test01.entity05.School" >
<!--对里面的属性进行赋值-->
<property name="name" value="Duke"></property>
<property name="address" value="USA"></property>
</bean>
注意:以上byName和byType都是专门用来创建引用数据类型的对象的,对简单数据类型不适用
多个配置文件
在spring中,为了便于对类进行管理,通常情况下会创建多个配置文件,这样做可以提高程序的效率,还可以实现解耦合
主配置文件:
<!--包含关系的配置文件-->
<!--application表示主配置文件:包含其他的配置文件,主配置文件一般是不定义对象的
语法:<import resource="其他配置文件的路径"/>
关键字:"classpath:"表示类路径(class文件所在目录)
在spring配置文件中指定其他文件的位置,需要使用classpath,告诉spring去哪里加载配置文件
-->
<!-- <import resource="classpath:di06/spring-school.xml"></import>-->
<!-- <import resource="classpath:di06/spring-student.xml"></import>-->
<!--
可以使用通配符的形式一次性指定多个文件,使用*表示匹配所有
但是需要注意的是:
1.主配置文件不能包含在通配符的包含范围之内,因为那样的话,spring创建容器对象的时候,当容器扫描到主配置文件的时候,会再次创建主配置文件,这样就形成了死循环
2.使用通配符的形式指定多个文件的时候,需要将所有的配置文件放在一个directory下面,不能直接放在resources目录下面,否则无法扫描成功
-->
<import resource="classpath:/di06/spring-*.xml"></import>
Student的配置文件:
<bean id="student06" class="com.lkw.test01.entity06.Student" autowire="byType">
<property name="name" value="lkw"></property>
<property name="age" value="20"></property>
</bean>
School配置文件:
<bean id="school06" class="com.lkw.test01.entity06.School">
<!--对school的属性进行赋值-->
<property name="name" value="XUPT"></property>
<property name="address" value="xian"></property>
</bean>
基于注解的DI
通过注解完成java对象的创建,属性赋值
使用注解的步骤:
-
java如maven的依赖,spring-context
-
在类中加入spring的注解(多个不同功能的注解)
-
在spring配置文件中,加入一个组件扫描器的标签,说明注解在你的项目中的位置
-
学习的注解:
@Component:在spring中相当于bean标签的作用 @Repository:创建dao对象,用来访问数据库 @Service:创建service对象,处理业务逻辑,可以有事务功能 @Controller:创建Controller对象,接收请求,显示处理结果 @Value:对简单数据类型的变量进行赋值 @Autowired:对引用数据类型进行赋值,默认是byType的方式,它是spring框架提供的注解 @Resource:对引用数据类型进行赋值,默认是byName的方式,如果byName不成功的话,就会使用byType注解,它是jdk中的注解
注解的使用
@Autowired
1.创建实体类对象
School:
@Component("school01")//使用Component声明类来代替使用bean标签
public class School {
@Value("Standford")
private String name;
@Value("USA")
private String address;
@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
Student:
/*
* 使用component注解创建对象,等同于<bean>的功能,
* 属性:value:就是对象的名称,也就是bean的id值,value的值是唯一的,创建的对象在整个容器中就一个
* 位置:这个注解使用在类的上面
* Component(value="student01"),就等同于<bean id="student01" class="com.lkw.entity01"></bean>
*
* 在spring中和Component注解的功能一致,创建对象的注解还有:
* 1.@Repository:(用在持久层上面):放在dao的实现类上面,表示创建dao对象,dao对象是能够访问数据库的
* 2.@Service(用在业务层上面):放在service的实现类上面,创建service对象,service对象是做业务处理的,可以有事务等功能
* 3.@Controller(用在控制器上面):放在控制器(处理器)类的上面,创建控制器对象的,控制器对象,能够接收用户提交的参数,显示请求的处理结果
* @Repository,@Service,@Controller这三个注解是对项目进行分层的
*
* 使用时机:就是上面的这三个和@Component的作用是一样的,但是还拥有其他额外的功能,当要创建的类有特殊作用的时候,比如控制层的类,就是用@Controller
* 注解来标明控制层的类,以此类推,当不属于这些类的时候,就使用Component
*
*
* */
@Component(value = "student01")
public class Student {
/*
简单数据类型的赋值:使用@Value注解
使用这个注解在类中不需要定义set方法
*/
@Value(value="lkw")
private String name;
@Value(value="20")
private int age;
/*
给引用数据类型赋值,使用Autowired注解,该注解是spring框架提供的,实现引用数据类型的赋值
spring中通过注解给引用数据类型赋值,使用的是自动注入的原理,支持byName和byType,但是Autowired默认是byType自动注入
Autowired使用在引用数据类型的属性之上,无需set方法,推荐使用
Autowired(required=true):Autowired有一个参数值,是boolean类型的,默认为true
required=true:表示引用类型赋值失败,程序报错,并终止执行,这种情况下要求该bean必须存在
required=false:引用数据类型如果赋值失败,程序正常执行,引用类型是null
* */
// @Autowired//默认是byType的方式完成注入
// private School school;
/*使用byName的方式完成属性的注入
* 步骤:
* 1.加上Autowired注解
* 2.再加一个Qualifier(value="bean的id"):表示使用指定名称的bean完成赋值
* */
@Autowired
/*Auto*/
@Qualifier("school01")
private School school;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
applicationContext.xml
<!--声明组件扫描器(component-scan),组件就是java对象
base-package:指定注解在你的项目中的包名
component-scan工作方式:spring会扫描 遍历base-package指定的包
把包中和子包中的所有类,找到类中的注解,按照注解的功能创建对象,或给属性赋值
-->
<context:component-scan base-package="com.lkw.entity01"></context:component-scan>
<context:component-scan base-package="com.lkw.entity02"></context:component-scan>
<!--指定多个包名的三种方式-->
<!--第一种:使用多次组件扫描器,指定不同的包名-->
<!-- <context:component-scan base-package="com.lkw.entity01"></context:component-scan>-->
<!-- <context:component-scan base-package="com.lkw.entity02"></context:component-scan>-->
<!--第二种方式:使用分隔符(;或者,)分隔多个包名-->
<!-- <context:component-scan base-package="com.lkw.entity01,com.lkw.entity02"></context:component-scan>-->
<!--第三种方式:指定父包-->
<!-- <context:component-scan base-package="com.lkw"></context:component-scan>-->
@Resource注解的使用
Student:
@Component(value = "student02")
public class Student {
@Value(value="lkw")
private String name;
@Value(value="20")
private int age;
/*
* 引用数据类型的赋值:
* @Resource:作用和@Autowired一样,都是给引用数据类型赋值,@Resource注解都有byName和byType,默认是byName
* 来自jdk的注解,spring框架提供了这个注解的功能支持,可以使用它完成引用数据类型的赋值,使用的也是自动注入原理
* 默认是byName:先使用byName自动注入,如果byName赋值失败,再使用byType
*
*
* 位置:
* 1.在属性的定义上面,无需set方法,推荐使用
* 2.在set方法上面
*
* 在resource注解中可以只使用byName注解完成属性的赋值
* 使用name属性=bean的id(也就是Component的value属性)
*
* */
@Resource(name="school02")
private School school;//这里先去entity02包下通过name去找school,结果发现失败,于是就使用byType,然后发现在entity02包下有一个School类型的对象
//然后将这个对象自动装配到Student类里面
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
School类不变
使用配置文件完成类的属性的赋值
配置文件:test.properties
myname=lkk
myage=22
Student:
public class Student {
//使用属性配置文件完成类的属性的赋值
@Value("${myname}")
private String name;
@Value("${myage}")
private int age;
}
总结:如果单纯使用配置文件的方式来为对象的属性进行赋值的话,那就不需要使用注解,也不用组件扫描器,组件扫描器是搭配注解一起使用的
4.spring框架的第二个核心技术–aop
4.1.动态代理
动态代理是指,程序在整个运行过程中根本就不存在目标类的代理类,目标对象的代理对象只是有代理生成工具(不是真实定义的类)在程序运行的时候由JVM根据反射等机制动态生成的,代理对象与目标对象的代理关系在==程序运行时==才确立
4.2.动态代理的分类
4.2.1.JDK动态代理
动态代理的实现方式常有两种,使用JDK和Proxy,与通过CGLIB生成代理,jdk的动态要求目标对象必须实现接口,这是java设计上的要求,java的包中提供了三个类支持代理模式:Proxy,Method和InvocationHandler
使用JDK的Proxy实现代理,要求目标类与代理类实现相同的接口,若目标类不存在接口,则无法使用该方式实现,但对于无接口的类,要为其创建动态代理,就要使用CGLIB
4.2.2.CGLIB动态代理(了解)
CGLIB(Code Generation Library)是一个开源项目,是一个强大的,高性能的,高质量的Code生成类库,它可以在运行期扩展java类与实现java接口,它广泛被许多AOP框架使用,比如SpringAop
CGLIB代理的生成原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代理对象,所以,使用CGLIB生成动态代理,要求目标类必须能够被继承,即不能是final的类,cglib的代理效率要高于jdk
总结:
有接口并且目标类和代理类实现相同的接口----JDK动态代理
使用jdk中的Proxy,Method,InvocationHandler创建代理对象,jdk动态代理要求目标类必须实现接口
有无接口都可以使用,但是必须要有继承关系----CGLIb
原理是继承,通过继承目标类创建子类,子类就是代理对象,要求目标类不能是final的,方法也不能是final的
4.3.动态代理的作用:
- 在目标类的源代码不变的情况下,增加功能
- 减少代码的重复使用
- 专注于业务逻辑代码
- 解耦合,让你的业务功能和日志,事务非业务功能分离
4.4.Aop(Aspect Orient Programming)
4.4.1.aop
就是面向切面编程,基于动态代理的,可以使用jdk,cglib两种代理方式,aop就是动态代理的规范化,把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式,使用动态代理
Aspect:切面,给你的目标类则更加的功能,就是切面,像打印日志,提交事务都是切面,切面的特点就是一般都是==非业务方法==,可以独立使用
4.4.2.怎么理解面向切面编程
(1)需要在分析项目功能的时候,找出切面
(2)合理的安排切面的执行时间(在目标方法之前还是之后)
(3)合理安排切面执行的位置,在哪个类,哪个方法增加增强功能
4.4.3.术语
(1)Aspect:切面,表示增强的功能,就是一堆代码,完成某一个功能,非业务功能,常见的切面功能有日志,事务,统计信息,参数检查,权限验证
(2)JoinPoint:连接点,连接业务方法和切面的位置,就是某个类中的业务方法
(3)PointCut:切入点,指多个连接点的集合,多个方法
(4)目标对象:给哪个类的方法增加功能,这个类就是目标类
(5)Advice:通知,通知表示切面功能的执行时间
4.4.4.切面三个关键的要素:
- 切面的功能代码,就是切面要实现的功能
- 切面的执行位置,使用PointCut表示切面的执行位置
- 切面的执行时间,使用Advice表示时间,在目标方法之前,还是目标之后
4.4.5.aop的实现
aop是一个规范,是动态的一个规范化,一个标准
aop的技术实现框架:
1.spring:spring在内部实现了aop框架,能做aop的工作,但是一般不使用
2.aspectJ:一个开源的专门做aop的框架,spring框架中集成了aspectJ框架,通过spring就能使用aspectJ的功能
aspectJ框架实现aop的两种方式:
1.使用xml的配置文件:配置全局事务
2.使用注解:我们在项目中要做aop功能,一般都使用注解,aspectJ中有5个注解
(1)切面的执行时间,这个执行时间在规范中叫做Advice(通知,增强)在aspectJ框架中使用注解来表示,也可以在xml配置文件中的标签
- @Before
- @AfterRetruning
- @Around
- @AfterThrowing
- @After
(2)表示切面执行的位置,使用的是切入点表达式
4.4.6.切入点表达式的语法
4.4.7.切入点表达式的使用(spring_pro/spring06-aspect)
1.通知:
第一步:创建新模块
第二步:创建一个接口,接口中定义业务方法
第三步:创建接口的实现类,在实现类中重写抽象方法
第四步:定义切面类,在切面类中书写相应的切入点表达式
SomeService类:
public interface SomeService {
void doSome(String name,Integer age);
}
SomeServiceImpl类:
public class SomeServiceImpl implements SomeService{
@Override
public void doSome(String name, Integer age) {
System.out.println("============执行doSome方法============");
}
}
MyAspect类:
//前置通知---在业务方法执行之前执行的方法,一个业务方法可以由若干个前置通知方法
/*@Before(value="execution(public void com.lkw.ba01.SomeServiceImpl.doSome(String,Integer))")
public void doBefore(){
System.out.println("前置方法,在目标方法执行之前执行");
}*/
/*@Before(value="execution(* *..do*(..))")
public void doBefore(){
System.out.println("前置方法,在目标方法执行之前执行");
}*/
//连接点:JoinPoint
/*
* 指定方法中的参数:JoinPoint
*JoinPoint:业务方法,要加入切面功能的业务方法
* 作用是:可以在通知方法中获取方法执行时的信息,例如方法名称,方法的实参
* 如果你的切面功能中需要用到方法的信息,就加入JoinPoint
* 这个JoinPoint蚕食的值是由框架赋予的,必须时第一个位置的参数
* */
/*@Before(value="execution(public void com.lkw.ba02.SomeServiceImpl.doSome(String,Integer))")
public void doBefore(JoinPoint jp){
//获取业务方法的相关信息
//获取业务方法的完整定义
System.out.println("方法的签名(定义)"+jp.getSignature());
System.out.println("方法的名称:"+jp.getSignature().getName());
//获取方法的实参
Object args[] = jp.getArgs();
for (Object arg : args) {
System.out.println("参数:"+arg);
}
System.out.println("前置方法,在目标方法执行之前执行");
}*/
/*
* 定义后置通知方法,方法时实现切面功能的
* 方法定义的要求:
* 1.访问修饰符:public
* 2.方法没有返回值
* 3.方法名称自定义
* 4.方法可以有参数,也可以没有参数
* 如果有参数,参数不是自定义的,有几个参数类型可以使用
*
* */
/**
* @AfterReturning:后置通知
* 属性:1.value:切入点表达式
* 2.returning 自定义的变量,表示目标方法的返回值的,自定义的变量名必须和方法的形参名一样
* 位置:在方法定义的上面
*
* 特点:
* 1.在目标方法之后执行的
* 2.能够获取到目标方法的返回值,可以根据返回值做出不同的处理
* 3.可以修改这个返回值
*/
/**
* String res = someService.doOther("kkk",20);
* 后置通知表达式相当于函数的参数的传递,有简单数据类型和引用数据类型传参的区别
* 简单数据类型传递参数是值传递,引用数据类型传参是引用传递
* 在函数中对简单数据类型进行修改,不会影响最终函数的执行结果,即不影响函数之外定义的简单类型的变量的值
* 但是如果是引用数据类型的话,会影响最终的函数之外的变量的结果
*
*/
@AfterReturning(value = "execution(* *..someService.doOther(..))",returning = "res")
public void doAfterReturning(Object res){
System.out.println("方法的返回值:"+res);
if(res != null){
res = "hello";//因为这里传入的参数的类型是简单数据类型,所以最终的结果不会改变,但如果是引用数据类型的话,结果就不发生改变
}
}
/**
* 环绕通知方法的定义格式:
* 1.public
* 2.必须有一个返回值,推荐使用Object
* 3.方法名称自定义
* 4.方法有参数,固定的参数ProceedingJoinPoint
*/
/**
* @Around: 环绕通知
* 属性:value:切入点表达式
* 位置:在方法定义的上面
*
* 特点:
* 1.它是功能最强的通知
* 2.在目标方法的前后都能增强功能
* 3.控制目标方法是否被调用执行
* 4.执行原来的目标方法的执行结果,影响最后的调用结果
*
* 环绕通知,等同于jdk动态代理,InvocationHandler接口
* 参数:ProceedingJoinPoint 就等同于Method,作用:执行目标方法
* 返回值:就是目标方法的执行结果,可以被修改
*
* 环绕通知:经常做事务,在目标方法执行之前开启事务,在目标方法执行结束后,提交事务
*/
@Around(value="execution(* *..*.doFirst(..))")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
//可以通过ProceedingJoinPoint对象来获得方法的相关信息
System.out.println("目标方法的名称为:"+pjp.getSignature().getName());
//实现环绕通知
Object res = null;
//在目标方法之前,输出时间
System.out.println("在目标方法之前,输出时间"+new Date());
res = pjp.proceed();//就相当于method.invoke();
if(res != null){
//可以通过环绕通知修改返回的值,这里是和后置通知不同的地方,因为后置通知是在方法调用结束之后,在将修改后的值传入到后置通知的方法之中,如果是简单
//数据类型肯定结果不会改变,但是如果是引用数据类型,结果会受到影响
//而环绕通知是在方法返回之前修改的返回值的结果,所以无论是简单数据类型还是引用数据类型都会受到影响
res = "qwer";
}
System.out.println("在目标方法执行之后,提交事务");
return res;
}
@AfterThrowing:异常通知
*
* 属性:1.value:切入点表达式
* 2.throwing 自定义的变量,表示目标方法抛出的异常对象,这个变量的名称要和方法的参数名称一致
* 特点:
* 1.在目标方法抛出异常的时候执行
* 2.可以做异常的监控程序,监控目标方法执行时是不是有异常,如果有异常,可以发送邮件,短信进行通知
*/
@AfterThrowing(value = "execution(* *..*.doSecond(..))",throwing = "ex")
public void doThrowing(Exception ex){
System.out.println("异常信息:"+ex.getMessage());
}
/**
* 最终通知方法的定义格式:
* 1.public
* 2.方法没有返回值
* 3.方法名称自定义
* 4.方法没有参数,有的话只能是JoinPoint
*
* @After:最终通知
*
* 属性:1.value:切入点表达式
* 特点:该通知一般用在所有业务方法执行完毕之后,用来清除资源
* 并且无论发生什么异常,该方法都会执行,相当于finally子句
*/
@After(value="execution(* *..*.*(..))")
public void doAfter(){
System.out.println("无论发生什么情况,该代码都会执行");//即使抛出异常,该方法依然会执行
}
总结:使用最多的是前置,后置,环绕通知,异常和最终使用的不多,重点掌握前三个
5.mybatis和spring集成
用到的技术是ioc
为什么使用ioc:能把mybatis和spring继承在一起,像一个框架一样,是因为ioc能创建对象,可以把mybatis框架中的对象交给spring同意创建,开发人员从spring获取对象,开发人员就不用同时面对两个或多个框架了,就面对的是spring
5.1.mybatis的使用步骤
1.定义dao接口,PlayerDao
2.定义mapper文件PlayerDao.xml
3.定义mybatis的主配置文件mybatis.xml
4.创建dao的代理对象
PlayerDao dao = SqlSession.getMapper(PlayerDao.class);
List<Player> players = dao.selectPlayer();
要使用dao对象,需要使用getMapper方法
怎么使用getMapper对象,需要哪些条件呢?
1.获取SqlSession对象,需要使用SqlSessionFactory的openSession方法来创建SqlSession对象
2.创建SqlSessionFactory对象,通过读取mybatis的主配置文件,能创建SqlSessionFactory对象
需要使用SqlSessionFactory对象,使用Factory能获取SqlSession,有了SqlSession就能有dao,目的就是获取dao对象
Factory创建需要读取主配置文件,需要配置数据库的连接信息,在以后的项目中不使用mybatis自带的数据库连接池,代之使用独立的数据库连接池类,把数据库连接池也交给spring进行管理
通过以上的说明,我们需要让spring创建以下对象:
1.独立的连接池类对象,使用阿里的druid连接池
2.SqlSessionFactory对象
3.dao对象
需要学习的就是上面三个对象的创建语法,使用xml的bean标签
注意:以上三个步骤都是固定的,只是根据项目的需求,做一些小的改动即可
5.2.mybatis和spring的集成步骤
spring和mybatis的集成:
1.新建maven项目
2.加入maven依赖
(1)spring依赖
(2)mybatis依赖
(3)mysql驱动
(4)spring的事务的依赖
(5)mybatis和spring集成的依赖:mybatis官方使用的,用来在spring项目中创建mybatis的
SqlSessionFactory,dao对象的
3.创建实体类
4.创建dao接口和mapper文件
5.创建mybatis主配置文件
6.创建Service接口和实现类。属性是dao
7.创建spring的配置文件:声明mybatis的对象交给spring管理
(1)数据源
(2)SqlSessionFactory
(3)dao对象
(4)声明自定义的service
8.创建测试类,创建service对象,通过service对象调用dao方法访问数据库
6.spring的事务处理
6.1.什么是事务
事务是指一组sql语句的集合,集合中有多条sql语句,可能是可能是增删改查,我们希望这些sql要么全部执行成功要么全部执行失败,这些sql语句作为一个整体
6.2.什么时候想到使用事务
当我们的操作涉及到多个表,或者多个sql语句的增删改查的时候,要保证这些语句都成功才能完成我们要求的功能的时候,考虑到使用事务
6.3.java代码中写程序,控制事务,此时事务应该放在哪里呢
service类的业务方法上,因为业务方法会调用多个dao方法,执行多个sql语句
6.4.通常使用jdbc访问数据库,还有mybatis访问数据该如何处理事务呢
jdbc访问数据库,处理事务:Connection conn; conn.commit(); conn.rollback();
mybatis访问数据库,处理事务:SqlSession.commit() SqlSession.rollback();
hibernate访问数据库,处理事务:Session.commit();Session.rollback();
6.5.上面对于不同的技术处理事务有什么不足之处
(1)不同的数据库访问技术,处理事务的对象,方法不同,需要了解不同数据库访问技术使用事务的原理
(2)需要掌握多种数据库中事务的处理逻辑,什么时候提交事务,什么时候回滚事务
(3)需要掌握事务的多种方法
总结:就是多种数据库访问技术,有不同的事务处理机制
6.6.怎么解决不足
spring提供一种处理事务的统一模型,能使用统一步骤,方式完成多种不同数据库访问技术的事务处理
6.7.处理事务,需要怎么做,做什么
spring的处理事务的模型,使用的步骤都是固定的,把事务使用的信息提供给spring就可以了
(1)事务内部提交,回滚事务,使用的事务管理器对象,代替你完成commit和rollback
事务管理器适应个接口和它的众多实现类
接口:PlatformTransactionManager,定义了事务重要方法,commit,rollback
实现类:spring把每一种数据库访问技术对应的事务处理类都创建好了
mybatis访问数据库—spring创建好的是DataSourceTransactionManager
Hibernate访问数据库----spring创建好的是HibernateTransactionManager
怎么使用:你需要告诉spring你使用的是哪种数据库的访问技术,在spring的配置文件中使用声明就可以了
例如,想要使用mybatis访问数据库,你应该在xml文件中这样配置:
<bean id="xxx" class="com...DataSourceTransactionManager"></bean>
(2)你的业务方法需要什么样的事务,要说明需要的事务类型
说明方法需要的事务:
-
事务的隔离级别:
1.读未提交读(Read Uncommitted):也称为脏读,一个事务读取到另一个事务未提交的内容
2.不可重复读(Read Committed):一个事务读到另一个事务已提交的内容,此级别解决了读未提交读的问题,即其他事务没有提交的内容对本事务不可见,这种方式读取到的数据比较真实,但是会出现不可重复读的现象,就是第一次读取数据的时候可能只有1条数据,但是同时用户又向表中插入了一条数据,第二次读取的时候,发现读到的数据就变成了2条,就是不可重复读就是说每一次读到的数据可能不一致,因为用户可能一直在向表中插入数据
3.可重复读(Repeatable Read):也称为幻读或虚读,一个事务读取到另一个事务已提交的内容(主要是数据的插入),此级别解决了读未提交读和不可重复读的问题,即事务A能读到事务B提交的数据插入的内容,即使是B事务对数据进行修改了,A事务读取到的依然还是开启事务时的数据
4.可串行化(Serializable):最高的隔离级别,通过强制事务排序,使之不可能相互冲突,从而解决幻读问题,效率最低,但是读取到的数据最真实,就是一个事务在读取数据的时候,其他所有事务只有排队等待,只有上一个操作完毕之后才能继续其他事务,这就相当于是java中的synchronized(同步机制)
mysql的默认隔离级别:可重复读
-
事务的传播行为:控制业务方法是不是有事务的,是什么样的事务,7个传播行为,表示你的业务方法调用时,事务在方法之间时如何使用的
上面的只需要掌握1,2,4即可
-
事务的超时时间:表示一个方法的最长的执行时间,如果方法执行时超过了时间,事务就回滚,单位时秒,整数值,默认是-1
(3)事务提交,回滚的时机
1.当你的业务方法执行成功,没有抛出异常,当方法执行完毕之后,spring在方法执行后提交事务,事务管理器commit
2.当你的业务方法抛出运行时异常或者ERROR,spring执行回滚,调用事务的rollback
3.当你的业务方法抛出非运行时异常的(主要是受查异常),提交事务
总结spring的事务
-
管理事务的是事务管理和它的实现类
-
spring的事务是一个统一的模型
(1)指定要使用哪些事务管理器的实现类,使用
(2)制定哪些类,哪些方法需要加入事务的功能
(3)指定方法需要的隔离级别,传播行为,超时
你需要告诉spring,你的项目中类信息,方法的名称,方法的事务传播行为
(4)spring框架中提供的事务处理方案
1.适合小型项目使用的,注解方案
spring框架自己用aop实现给业务方法的增加事务的功能,使用@Transactional注解增加事务
@Transactional注解是spring框架自己的注解,放在public方法的上面,表示当前方法具有事务
可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等
2.使用@Transactional注解的步骤:
-
需要声明事务管理器对象
-
开启事务注解驱动,告诉spring框架,我要使用注解的方式管理事务
spring使用aop机制,创建@Transactional所在的类代理对象,给方法加入事务的功能
spring给业务方法加入事务:
在你的业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务,使用aop的环绕通知
@Around("你要增加的事务功能的业务方法名称") Object myAround(){ 开启事务,spring给你开启 try{ buy(1001,10); spring的事务管理.commit(); }catch(Exception e){ spring的事务管理rollback(); } }
-
在你的方法上面加入@Transactional注解
7.web项目中使用容器对象
7.1.普通的javase项目和web项目的区别
1.javase项目是有main方法的,执行代码是执行main方法的,在main方法里面创建容器对象
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
2.文本项目是在tomcat服务器上运行的,tomcat一启动,项目一直运行下去
需求:web项目中容器对象只需要创建一次,把容器对象放到全局作用域ServletContext域当中去
怎么实现:
使用监听器 当全局作用域对象被创建的时候 创建容器 存入ServletContext
监听器作用:
(1)创建容器对象,执行ApplicationContext ctx = new ClassPathXmlApplicationContext(“applicationContext.xml”);
(2)把容器对象放入到ServletContext,ServletContext.setAttribute(key,ctx);
监听器可以自己创建,也可以使用框架中提供好的ContextLoaderListerner