文章目录
- 1、Spring框架介绍
- 2、IOC容器
- 2.1、IOC的底层原理
- 2.2、IOC接口-BeanFactory
- 2.3、IOC操作bean管理
- 2.4、Bean管理操作有两种方式
- 2.4.1、IOC操作bean管理-基于xml方式(demo01、demo02
- 2.4.1.1、IOC操作bean管理--基于xml方式创建对象
- 2.4.1.2、IOC操作bean管理--基于xml方式注入属性
- 2.4.1.3、IOC操作bean管理--注入属性--外部bean
- 2.4.1.4、IOC操作bean管理--注入属性--内部bean
- 2.4.1.5、IOC操作bean管理--内部bean的级联赋值
- 2.4.1.6、IOC操作bean管理--注入集合属性
- 2.4.1.7、IOC操作bean管理-FactoryBean
- 2.4.1.8、IOC操作bean管理--bean的作用域
- 2.4.1.9、IOC操作bean管理--bean的生命周期
- 2.4.1.10、IOC操作bean管理--bean的生命周期的后置处理器
- 2.4.1.11、IOC操作bean管理-自动装配
- 2.4.2、IOC操作bean管理-基于注解方式(demo03 demo04)
- 3、AOP
- 4、JdbcTemplate
- 5、事务管理
Author:宇哥 WeChat/QQ: Warmheart170712 / 939638113
整理不易,重在分享, 欢迎转发收藏,助力面试上岸,学编程找宇哥
1、Spring框架介绍
1、Spring是轻量级的开源的JavaEE框架
2、Spring可以解决企业应用开发的复杂性
3、Spring有两个核心部分,IOC和AOP
IOC:控制反转,把创建对象的过程交给spring容器进行管理
AOP:面向切面,不修改源代码的基础上进行功能的增强
4、Spring的特点
方便解耦,简化开发
支持AOP编程
方便程序测试
方便和其他框架进行整合
方便进行事务操作
降低API开发难度
2、IOC容器
2.1、IOC的底层原理
什么是IOC:
控制反转:inversion of control
把对象的创建和对象之间的调用都交给spring管理
使用IOC的目的,为了降低耦合度
入门案例就是IOC的实现
IOC的底层原理:
底层技术:XML解析技术,设计模式,反射
见图过程
2.2、IOC接口-BeanFactory
IOC思想基于IOC容器实现,IOC容器底层就是对象工厂
Spring提供IOC容器实现两种方式-两个接口
BeanFactory:
概念:是IOC容器的基本实现,是Spring内部的接口,
一般不提供给开发人员使用
特点:在加载配置文件的时候,不会创建对象,
在获取对象或者使用对象的时候才会创建
ApplicationContext:
概念:是BeanFactory的子接口,提供了更多更强大的功能,
一般开发人员使用
特点:在加载配置文件的时候就会配置文件中的对象进行创建
2.3、IOC操作bean管理
什么是bean管理
bean管理指的是两个操作:Spring创建对象和Spring注入属性
Spring创建对象两种方式:基于xml配置文件和基于注解方式
2.4、Bean管理操作有两种方式
2.4.1、IOC操作bean管理-基于xml方式(demo01、demo02
2.4.1.1、IOC操作bean管理–基于xml方式创建对象
<bean id="user" class="cn.diautowried.User"></bean>
在Spring配置文件中:
使用bean标签,标签里面添加对应属性,就可以实现对象的创建
bean标签中的常用属性
id属性:唯一标识,表示给对象其标识或者别名
class属性:类的全路径(包类路径)
创建对象的时候,默认使用的是无参构造
2.4.1.2、IOC操作bean管理–基于xml方式注入属性
DI:依赖注入,就是注入属性
2.4.1.2.1、第一种方式:使用set方法进行属性注入
在spring配置文件中配置对象创建,配置属性注入
创建book对象,set方法进行属性注入:如下
property:表示属性的标签
name:表示实体类中的属性名
value:表示为属性名赋值
<bean id="book" class="cn.diautowried.Book">
<!--在bean标签中使用property标签进行属性注入-->
<property name="bookName" value="围城"></property>
<property name="bookAauthor" value="钱钟书"></property>
</bean>
2.4.1.2.2、第二种方法:使用有参构造进行属性注入
在spring配置文件中进行配置
使用有参构造注入属性:
constructor-arg:构造-参数
name:表示实体类中的属性名
value:表示为属性名赋值
<bean id="order" class="cn.diautowried.Order">
<constructor-arg name="orderName" value="华为手机"></constructor-arg>
<constructor-arg name="orderAdddress" value="山西太原"></constructor-arg>
</bean>
2.4.1.2.3、第三种注入方式:p名称空间注入—set注入的简化版
1 配置文件中添加p名称空间约束
xmlns:p="http://www.springframework.org/schema/p"
2:进行属性注入,在bean标签中进行操作
<!--p名称空间注入 set注入简化版-->
<bean id="book1" class="cn.diautowried.Book" p:bookName="我们仨" p:bookAauthor="杨绛"></bean>
3 属性说明:
p名称空间属性注入--set注入简化版本
p:属性名=“属性值”
2.4.1.3、IOC操作bean管理–注入属性–外部bean
1 创建UserService 和 UserDao 对象
2 将UserDao注入到UserService中
name属性值:类中的属性的名称
ref属性值:是UserDao对象的bean标签的属性值
<bean id="userService" class="cn.zzz.service.UserService">
<property name="userDao" ref="userDaoImpl"></property>
</bean>
外部bean:
<bean id="userDaoImpl" class="cn.jdkproxy.UserDaoImpl"></bean>
2.4.1.4、IOC操作bean管理–注入属性–内部bean
1 创建Dept类 和 Emp类,一对多关系:部门和员工,
一个部门有多个员工,一个员工属于一个部门
将部门对象注入到员工员工对象中
<bean id="emp" class="cn.innertbean.Emp">
<!--设置两个普通属性-->
<property name="eName" value="Anny"></property>
<property name="eGender" value="女"></property>
<!--设置对象属性-->
<property name="dept">
<bean id="dept" class="cn.innertbean.Dept">
<property name="dName" value="开发部"></property>
</bean>
</property>
</bean>
2.4.1.5、IOC操作bean管理–内部bean的级联赋值
1 创建bean:emp
<bean id="emp" class="cn.innertbean.Emp">
<!--设置两个普通属性-->
<property name="eName" value="Jack"></property>
<property name="eGender" value="男"></property>
<!--级联赋值-->
<property name="dept" ref="dept"></property>
</bean>
<!--创建bean dept-->
<bean id="dept" class="cn.innertbean.Dept">
<property name="dName" value="理发部"></property>
</bean>
2.4.1.6、IOC操作bean管理–注入集合属性
2.4.1.6.0、注入集合属性
1 注入数组、注入List、注入Map、注入Set
创建类,定义数组,list、map、set类型属性,生成的set方法
2 创建实体类Stu,生成属性的set方法
public class Stu {
//1、数组类型属性
private String[] courses;
//2、List集合类型属性
private List<String> list;
//3、map集合类行书
private Map<String,String> map;
//4、set集合的属性
private Set<String> set;
//学生学习多门课程
private List<Course> courseList;
3 配饰spring.xml
在spring配置文件中进行配置
<!--完成集合类型属性的注入-->
<bean id="stu" class="cn.pojo.Stu">
<!--数组类型的属性注入-->
<property name="courses">
<array>
<value>java课程</value>
<value>数据库课程</value>
</array>
</property>
<!--list类型属性注入-->
<property name="list">
<list>
<value>IDEA</value>
<value>Eclispse</value>
<value>HbuilderX</value>
</list>
</property>
<!--map类型属性注入-->
<property name="map">
<map>
<entry key="mysql" value="免费"></entry>
<entry key="oracle" value="收费"></entry>
</map>
</property>
<!--set类型属性注入-->
<property name="set">
<set>
<value>SpringAlibaba</value>
<value>SpringCloud</value>
</set>
</property>
</bean>
输出属性结果
数组: [java课程, 数据库课程]
List集合:[IDEA, Eclispse, HbuilderX]
Map集合: {mysql=免费, oracle=收费}
Set集合 [SpringAlibaba, SpringCloud]
2.4.1.6.1、在集合里面设置对象类型值
List集合设置对象类型
private List<Course> courseList;
使用Stu和Course
配置spring的配置文件
需求:在集合list里面设置对象类型值
<bean id="stu" class="cn.pojo.Stu">
<!--注入list集合类型,值是对象-->
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
<ref bean="course3"></ref>
</list>
</property>
</bean>
创建多个course对象:
<bean id="course1" class="cn.pojo.Course">
<property name="courseName" value="前端课程"></property>
</bean>
<bean id="course2" class="cn.pojo.Course">
<property name="courseName" value="后端课程"></property>
</bean>
<bean id="course3" class="cn.pojo.Course">
<property name="courseName" value="数据库课程"></property>
</bean>
2.4.1.7、IOC操作bean管理-FactoryBean
1 Spring有两种类型的bean
一种是普通bean
一种是工厂bean,也就是FactoryBean
2 普通bean:在spring配置文件中定义bean的类型,
返回类型就是此类型
工厂bean:在spring配置文件中定义bean的类型,
返回类型可以不一致(可以返回其他类型)
3、工厂bean案例
步骤1:创建类,设置这个类为工厂bean。实现接口FactoryBean
步骤2:实现接口中的方法,在此方法中定义返回的bean的类型
4 自定义的工厂bbean,定义返回的bean对象是Course类型对象
public class MyBean implements FactoryBean<Course> {
//定义返回的bean对象是其他对象
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setCourseName("社会与科学");
return course;
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return false;
}
}
5 配置文件配置myBean对象
<bean id="myBean" class="cn.factorybean.MyBean"></bean>
6 工厂bean - 测试bean的返回类型是其他类型--FactoryBean
应该返回MyBean类型的对象,实际返回时Course类型对象
@Test
public void testMyBean(){
//1、创建Spring的核心容器,并加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("spring4.xml");
//2、获取对象
Course course = context.getBean("myBean", Course.class);
System.out.println(course);
}
7 返回Course类型对象
2.4.1.8、IOC操作bean管理–bean的作用域
在spring中,设置创建bean实例是单实例还是多实例
在默认情况下,spring容器创建的对象是单实例
如何设置单实例和多实例:
在spring配置文件中bean标签里有属性scope,用于设置单实例和多实例
第一个值:默认值singleton,表示的是单实例对象
第二个值:设置protoType。表示的是多实例对象
<!--测试作用域 单实例 和 多实例-->
<bean id="user1" class="cn.pojo.User" scope="singleton">
<property name="username" value="张三"></property>
</bean>
<bean id="user2" class="cn.pojo.User" scope="prototype">
<property name="username" value="张三"></property>
</bean>
singleton和protoType的区别:
singleton是单实例对象,
设置scope的属性值是singleton,
单实例对象会在spring配置文件加载的时候就创建
prototy是多实例对象
设置scope的属性值是prototype,
不是在加载spring配置文件的时候创建对象,
而是在调用getBean方法的时候创建多实例对象
拓展:scope的其他属性值:
request:将创建好的对象放在request域对象中,
一次请求共享数据
session:将创建好的对象放在session域对象中,
一次会话共享数据
2.4.1.9、IOC操作bean管理–bean的生命周期
1、生命周期:
从对象创建到对象销毁的过程
2、bean的生命周期---简化五步
(1)、通过构造器创建bean实例,也就是执行无参构造
(2)、为bean的属性设置值,以及对其他bean的调用
(调用set方法)
(3)、调用bean的初始化方法,(初始化的方法需要进行配置)
(4)、bean可以使用,对象可以获取
(5)、当容器在关闭的时候,会调用bean的销毁方法
(销毁的方法需要进行配置)
3、bean的生命周期代码案例
步骤1:创建Phone类,
设置属性名,
生成set方法、
构造方法,
配置初始化、
销毁方法。
以及一个普通方法
/**
* 测试bean的生命周期
*/
public class Phone {
private String phoneName;
//第一步:无参构造执行,创建对象
public Phone() {
System.out.println("第一步:创建手机的无参构造执行了");
}
//第二步:调用set方法设置对象的属性值
public void setPhoneName(String phoneName) {
this.phoneName = phoneName;
System.out.println("第二步:调用set方法设置对象的属性值");
}
//第三步:创建初始化方法 初始化方法需要自行配置
public void initBean(){
System.out.println("第三步:执行初始化的方法");
}
//第四步:创建bean实例创建完成并且调用方法
public void actionBean(){
System.out.println("第四步:创建bean实例创建完成并且调用方法");
}
//第五步:关闭容器执行销毁beam方法
public void destoryBean(){
System.out.println("第五步:容器关闭bean实例被销毁");
}
}
4 配置spring配置文件
属性:
init-method="initBean" :配置初始化方法
destroy-method="destoryBean" :配置销毁方法
<!--测试bean的生命周期-->
<bean id="phone" class="cn.beanlife.Phone" init-method="initBean" destroy-method="destoryBean">
<property name="phoneName" value="华为mate40"></property>
</bean>
5 测试方法
@Test
public void testLife(){
//1、创建spring的核心容器。并加载配置文件
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring6.xml");
//2、通过容器获取对象
Phone phone = context.getBean("phone", Phone.class);
//3、调用方法
phone.actionBean();
//4、关闭容器,手动让bean实例销毁
context.close();
}
6 执行结果
2.4.1.10、IOC操作bean管理–bean的生命周期的后置处理器
1 后置处理器介绍
Bean的后置处理器的作用:
Bean的设计模式用到了模板方法设计模式。
在Bean的各个阶段都会有一些功能增强,
这些功能都是由Bean后处理器提供的
实现接口BeanPostProcessor,
重写方法
在初始化方法执行之前执行:
postProcessBeforeInitialization
在初始化方法执行之后执行:
postProcessAfterInitialization
2 bean的生命周期---详细七步
(1)、通过构造器创建bean实例,也就是执行无参构造
(2)、为bean的属性设置值,以及对其他bean的调用
(调用set方法)
(3)、把bean实例传递到bean后置处理器的方法
即:postProcessBeforeInitialization()
(4)、调用bean的初始化方法,(初始化的方法需要进行配置)
(5)、把bean实例传递到bean后置处理器的方法
即:postProcessAfterInitialization()
(6)、bean可以使用,对象可以获取
(7)、当容器在关闭的时候,会调用bean的销毁方法
(销毁的方法需要进行配置)
3 bean的后置处理器方法
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化执行之前执行的方法,把bean实例传递到bean后置处理器的方法");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化执行之后执行的方法,把bean实例传递到bean后置处理器的方法");
return bean;
}
}
5 spring.xml文件中配置自定义的后置处理器
<!--测试bean的生命周期 后置处理器执行-->
<bean id="myBeanPost" class="cn.beanlife.MyBeanPost"></bean>
6 测试代码输出结果
2.4.1.11、IOC操作bean管理-自动装配
1、什么是自动装配?什么是手动装配
手动装配:
是程序员在配置文件中手动的进行属性的赋值,
即value属性值和ref属性值
自动装配:
根据指定的装配规则,根据属性名称或者属性类型,
Spring自动将匹配的属性值进行注入
2 实体类用于自动装配
public class Emp {
//部门属性
private Dept dept;
{
3、演示自动装配: 两种方式:根据名称注入,根据类型注入
2.1、自动装配 通过属性名称 注入属性
根据属性名称自动注入:autowire="byName",
注意:特点:注入值-的bean的id值和类中的属性名称一致
<bean id="emp1" class="cn.autowire.Emp" autowire="byName"></bean>
<bean id="dept" class="cn.autowire.Dept"></bean>
2.2、自动装配 通过属性类型 注入属性
根据属性类型自动注入:autowire="byType"
<bean id="emp2" class="cn.autowire.Emp" autowire="byType"></bean>
<bean id="dept" class="cn.autowire.Dept"></bean>
4 配置spring.xml
<!--1、原始方式 手动装配属性-->
<bean id="emp" class="cn.autowire.Emp">
<!--手动装配属性-->
<property name="dept" ref="dept"></property>
</bean>
<!--2、自动装配 通过属性类型 注入属性-->
<bean id="emp1" class="cn.autowire.Emp" autowire="byName"></bean>
<!--3、自动装配 通过属性名称 注入属性-->
<bean id="emp2" class="cn.autowire.Emp" autowire="byType"></bean>
<!--创建Dept的bean对象-->
<bean id="dept" class="cn.autowire.Dept"></bean>
5 测试代码
@Test
public void TestAuto(){
//1、创建spring的核心容器。并加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("spring7.xml");
//2、通过容器获取对象
//手动装配
Emp emp = context.getBean("emp", Emp.class);
//自动装配-通过属性名称注入
Emp emp1 = context.getBean("emp1", Emp.class);
//自动装配-通过属性类型进行注入
Emp emp2 = context.getBean("emp2", Emp.class);
//3、输出
System.out.println(emp);
System.out.println(emp1);
System.out.println(emp2);
}
6 输出结果
2.4.2、IOC操作bean管理-基于注解方式(demo03 demo04)
2.4.2.1、常用创建bean对象的注解
1、什么是注解
注解是代码的特殊标记:
格式:@注解名(属性名=属性值,属性名=属性值)
使用注解:注解作用在类上面,方法上面,属性上面
使用注解的目的:简化xml设置
2、spring针对Bean管理中创建对象提供注解
@Component:笔试普通的bean
@Service:表示业务层
@Controller:表示控制层
@Repository:表示持久层
以上四个注解功能是一样的,都可以用来创建bean实例
注解中的value值可以自定义,相当于bean标签中的id属性值
<bean id="userService" class=""/>
value值可以省略不写,默认值是类名称的首字母小写
2.4.2.2、基于注解方式–实现对象创建
1 创建类,在类上添加创建对象的注解
@Service(value = "userService")
public class UserService {
public void add(){
System.out.println("service add...");
}
}
2 配置spring.xml
开启组件扫描
1、如果扫描多个包,多个包之间使用逗号隔开
2、扫描包的上层目录
<context:component-scan base-package="cn.anno"></context:component-scan>
设置组件扫描-设置包含哪些注解
use-default-filters="false" :
表示不使用默认的filter,自行配置filter
context:include-filter :
设置扫描哪些些内容
type属性:注解
expression:只扫描对应表达式的注解,
<context:component-scan base-package="cn.anno" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
设置组件扫描-设置排除哪些注解
默认扫描所有注解,排除execlude
context:exclude-filter :设置不扫描哪些些内容
type:注解
expression:不扫描对应表达式的注解,
<context:component-scan base-package="cn.anno">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
3 测试代码
@Test
public void testComponent(){
ApplicationContext context =
new ClassPathXmlApplicationContext("spring1.xml");
UserService userService =
context.getBean("userService", UserService.class);
System.out.println(userService);
userService.add();
}
测试结果
2.4.2.3、基于注解方式–实现属性注入
2.4.2.3.1、@AutoWired :根据属性类型进行注入
@AutoWired :根据属性类型进行注入
@Autowired注解是按照类型(byType)装配依赖对象,
默认情况下它要求依赖对象必须存在,
如果允许 null 值,可以设置它的required 属性为 false。
2.4.2.3.2、@Qualifier:根据属性名称进行注入
@Qualifier:根据属性名称进行注入
在@AutoWired注解通过类型注入的前提下,
如果被注入的接口有多个实现类对象,
那么就可以按照名称(byName)来装配,
可以结合@Qualifier 注解一起使用
如果不使用@Qualifier注解进行value属性的标识,
那么容器会为此接口的多个实现类创建bean对象
此时,容器不知道改注入哪个实现类的bean对象,
会报异常
NoUniqueBeanDefinitionException:
No qualifying bean of type 'cn.anno.dao.UserDao'
available: expected single matching bean
but found 3: userDaoImpl1,userDaoImpl2,userDaoImpl3
2.4.2.3.3、@Resource: 可以根据类型注入,可以根据名称注入
@Resource: 可以根据类型注入,可以根据名称注入
@Resource不是spring的注解,而是javax的注解。
@Resource默认按照byName自动注入
@Resource 有两个重要的属性:name和type
Spring将@Resource注解的:
name属性解析为bean的名字,
type属性则解析为bean的类型。
如果使用 name 属性,则使用 byName 的自动注入策略,
如果使用 type 属性时则使用 byType 自动注入策略。
如果既不制定 name 也不制定 type 属性,
这时将通过反射机制使用 byName 自动注入策略。
2.4.2.3.4、@Value 注入普通类型属性
@Value注解:
注入普通类型属性,为普通属性赋值
属性value的值,会赋值给name
@Value(value = "武磊")
private String name;
2.4.2.4、基于注解方式–实现完全注解开发
1 创建配置类,替代spring.xml文件
@Configuration:注解
表示标识此类是配置类
@ComponentScan("cn.spring")注解:
表示开启注解扫描
括号中的值,表示开启扫描此包下的所有注解
@Configuration//作为配置类。替代xml核心配置文件
@ComponentScan("cn.spring")
public class SpringConfig {}
2 编写测试类
@Test
public void test1(){
//1、创建核心容器,加载配置类
AnnotationConfigApplicationContext ccontex =
new AnnotationConfigApplicationContext(SpringConfig.class);
//2、通过容器获取bean对象
UserService userService = ccontex.getBean("userService", UserService.class);
//3、调用方法
userService.add();
System.out.println(userService);
}
2.4.2.5 容器的不同表现形式
1 BeanFactory
2 Application
3 ClassPathXmlApplicationContext
4 AnnotationConfigApplicationContext
5 FactoryBean
3、AOP
3.1 什么是AOP(概念)
什么是AOP(概念)
官方:面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离
从而使得业务逻辑各部分之间的耦合度降低,
提高程序的可重用性,同时提高开发效率
通俗:不修改源代码的方式,在主干功能里添加新的功能
3.2、AOP底层原理
3.2.1、AOP底层使用动态代理,有两种情况的动态代理
AOP底层使用动态代理,有两种情况的动态代理
第一种:有接口情况--使用JDK动态代理
创建接口实现类的代理对象,增强类的方法
第二种:没有接口情况--使用CGLIB动态代理
创建子类的代理对象,增强类的方法
JDK动态代理
CGLIB动态代理
3.2.2、JDK动态代理和CGLIB动态代理的区别
JDK动态代理和CGLIB动态代理的区别
概念:
JDK代理:使用的是反射机制生成一个实现代理接口的匿名类,
在调用具体方法前调用InvokeHandler来处理。
CGLIB代理:使用字节码处理框架asm,
对代理对象类的class文件加载进来,
通过修改字节码生成子类。
效率:
JDK创建代理对象效率较高,执行效率较低;
CGLIB创建代理对象效率较低,执行效率高。
关系:
JDK动态代理机制是委托机制,只能对实现接口的类生成代理,
通过反射动态实现接口类;
CGLIB则使用的继承机制,针对类实现代理,
被代理类和代理类是继承关系,
所以代理类是可以赋值给被代理类的,
因为是继承机制,不能代理final修饰的类。
实现:
JDK代理是不需要依赖第三方的库,只要JDK环境就可以进行代理,
需要满足以下要求:
1.实现InvocationHandler接口,重写invoke()
2.使用Proxy.newProxyInstance()产生代理对象
3.被代理的对象必须要实现接口
CGLIB 必须依赖于CGLIB的类库,需要满足以下要求:
1.实现MethodInterceptor接口,重写intercept()
2.使用Enhancer对象.create()产生代理对象
使用场景:
1)如果目标对象实现了接口,
默认情况下会采用JDK的动态代理实现AOP,
可以强制使用CGLIB实现AOP
2)如果目标对象没有实现了接口,
必须采用CGLIB库,
spring会自动在JDK动态代理和CGLIB之间转换
3.2.3、AOP的JDK动态代理实现案例
1 创建接口 UserDao
public interface UserDao {
public int add(int a,int b);
public String update(String id);
}
2 创建接口的实现类
public class UserDaoImpl implements UserDao{
@Override
public int add(int a, int b) {
return a+b;
}
@Override
public String update(String id) {
return id;
}
}
3 创建UserDaoImpl的代理类,实现功能增强
创建代理对象代码,做功能增强
public class UserDaoProxy implements InvocationHandler {
被代理对象是userDaoImpl
创建的是谁的代理对象,就需要把谁传递过来
通过有参构造进行传递
private UserDaoImpl userDaoImpl;
public UserDaoProxy(UserDaoImpl userDaoImpl) {
this.userDaoImpl = userDaoImpl;
}
增强逻辑
@Override proxy代理对象本身 被增强的方法 执行增强方法传递的参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在方法执行之前执行
System.out.println("在方法执行之前执行......"+"被增强的方法的名称:"+method.getName()+"......传递的参数"+ Arrays.toString(args));
//被增强的方法add执行,
Object result =method.invoke(userDaoImpl, args);
System.out.println("add方法执行了..."+result);
//在方法执行之后执行
System.out.println("方法之后执行");
return result;
}
}
4 创建测试代码
public class TestJDKProxy {
public static void main(String[] args) {
//1、获取被代理实现类的接口UserDao的字节码文件对象
Class[] interfaces = {UserDao.class};
//2、创建被代理接口的实现类UserDaoImpl的对象
UserDaoImpl userDaoImpl = new UserDaoImpl();
//3、创建UserDao接口的实现类的代理对象
//下面的代码相当于 UserDao userDao = new UserDaoImpl();
UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(TestJDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDaoImpl));
//执行目标方法
int total = userDaoProxy.add(1, 2);
System.out.println("total:"+total);
}
}
JDK动态代理,调用Proxy类的newProxyInstance()方法
调用:newProxyInstance(ClassLoader loader, 类< ? >[] interfaces, InvocationHandler h)
返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
方法参数1:ClassLoader loader
--类加载器
方法参数2:类<?>[] interfaces
--增强方法所在的类,这个类实现的接口,
即被代理类的父接口
方法参数3:InvocationHandler h
--实现InvocationHandler接口,
创建代理对象,写增强方法
5 执行结果
3.3、AOP相关术语
1 AOP的先关术语
连接点:类里面的那些方法可以增强,这些方法就被成为连接点
切入点: 实际被增强的方法,称为切入点
通知(增强):
实际增强的逻辑部分,被称为通知(增强)
通知有多种昂类型:5种类
前置通知,
后置通知、
环绕通知,
异常通知,
最终通知:
切面:把通知应用到切入点的过程,就是切面
3.4、AOP操作准备
3.4.1、Spring和AspectJ的关系
Spring和AspectJ的关系
1、Spring框架一般基于AspectJ实现AOP操作
2、什么是AspectJ
Aspect不是Spring的组成部分,独立的AOP框架,
一般把Aspect和Spring框架一起使用,进行AOP操作
3.4.2、基于AspectJ实现AOP的操作
3.4.3、在项目中引入AOP的相关依赖
1 步骤1:在项目中引入AOP的相关依赖
<!--spring-AOP依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--Aspect切面依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.9.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
3.4.4、切入点表达式
切入点表达式
切入点表达式的作用:知道对哪个类里面的哪个方法进行增强
切入点表达式语法:
execution([权限修饰符][返回类型][类的路径][方法名称][参数列表])
举例1:对cn.aop.zzy包中的UserDao类里面的add方法做增强
execution(* cn.aop.zzy.UserDao.add(..))
举例2:对cn.aop.zzy包中的UserDao类里面的所有方法做增强
execution(* cn.aop.zzy.UserDao.*(..))
举例3:对cn.aop.zzy包中的所有类,以及里面的所有方法做增强
execution(* cn.aop.zzy.*.*(..))
3.6、AOP操作–基于AspectJ注解
3.6.1 基于AspectJ注解
1 创建被增强类
创建被增强类,被增强方法
public class User {
public void add(){
System.out.println("我是被增强的方法,执行了");
}
}
2 创建增强类,编写增强逻辑
@Component
@Aspect //生成代理对象
public class UserProxy {
//前置通知:在目标方法执行之前执行
//增强的方法 before注解表示作为前置通知,是在目标方法执行之前执行
@Before(value = "execution(* cn.anno.User.add(..))")
public void before(){
System.out.println("before。。。在目标方法执行之前执行");
}
//后置通知:在目标方法执行之后执行
@After(value = "execution(* cn.anno.User.add(..))")
public void after(){
System.out.println("after。。。在目标方法执行之后执行");
}
//返回通知:在目标方法返回值之后执行
@AfterReturning(value = "execution(* cn.anno.User.add(..))")
public void afterReturning(){
System.out.println("afterReturning。。。在目标方法返回值之后执行");
}
//异常通知:在目标方法执行发生异常执行
@AfterThrowing(value = "execution(* cn.anno.User.add(..))")
public void afterThrowing(){
System.out.println("afterThrowing。。。在目标方法执行发生异常执行");
}
//环绕通知,在目标方法执行之前和执行之后执行
@Around(value = "execution(* cn.anno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("around 前。。。目标方法执行之前执行");
//被增强的方法执行
proceedingJoinPoint.proceed();
System.out.println("around 后。。目标方法执行之后执行");
}
}
3 进行通知配置
1、在spring文件中,开启注解扫描
<context:component-scan base-package="cn.anno"></context:component-scan>
2、使用注解创建User和UserProxy对象加上@Component
3、在增强类上面添加@Aspect注解@Aspect //生成代理对象
4、在配置文件中开启生成代理对象的配置
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
5、配置不同类型的通知在增强类里面,
在作为通知方法上面添加通知类型的注解,
并使用切入点表达式配置
4 编写测试类
@Test
public void testBefore(){
//1.创建IOC容器,加载核心配置文件,
ApplicationContext context =
new ClassPathXmlApplicationContext("spring1.xml");
//2.通过容器获取对象
User user = context.getBean("user", User.class);
user.add();
}
4 测试结果
目标方法无异常时,通知的执行顺序:
around 前。。。目标方法执行之前执行
before。。。在目标方法执行之前执行
我是目标方法,也就是被增强的方法。。。。我执行了
around 后。。目标方法执行之后执行
after。。。在目标方法执行之后执行
afterReturning。。。在目标方法执行返回之后执行
目标方法发生异常时,通知的执行顺序:
around 前。。。目标方法执行之前执行
before。。。在目标方法执行之前执行
after。。。在目标方法执行之后执行
afterThrowing。。。在目标方法执行发生异常执行
3.6.2 相同切入点提取
相同切入点抽取:
pointCut()作用:
作为切入点的承载方法,在通知方法上直接引用即可
@Pointcut(value = "execution(* cn.anno.User.add(..))")
public void pointCut(){}
//前置通知
@Before(value = "pointCut()")
public void before(){
System.out.println("before。。。在目标方法执行之前执行");
}
3.6.3 增强的优先级
@Order(value = 3):
设置增强的优先级(同一个目标方法有多个增强的情况)
在增强类上面添加注解@Order(数字类型值),
数字类型值越小,优先级越高
4、JdbcTemplate
4.1、什么是JdbcTemplate
spring框架对JDBC进行封装,
使用JDBCTemplate方便实现对数据库操作
4.2、准备工作
1 引入JDBCTemplate和事务的依赖
<!--事务-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--spring-jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
2 在spring配置文件中配置数据库连接池
<!--配置德鲁伊连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="clone">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///user_db"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
3 配置JDBCTemplate对象,为其注入DataSource
<!--配置JDBCTemplate 查看源码-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入DataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
4.3、基于4.2的准备工作进行CRUD
4.3.1、新增操作
API方法:
update方法用于增删改的操作
update(String sql,Object[]... args)
有两个参数:
第一个参数:sql语句
第二个参数:可变参数,设置sql语句的值
@Repository
public class UserDaoImpl implements UserDao {
//注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
//添加的方法
@Override
public void add(User user) {
//编写SQL语句
String sql1 = "insert into t_user values(null ,?,?)";
//获取参数值
Object[] args = {user.getUserName(), user.getUserStatus()};
//进行新增操作,返回操作数
int update = jdbcTemplate.update(sql1,args);
//输出行记录
System.out.println(update);
}
}
4.3.2、修改和删除操作
API方法:
update方法用于增删改的操作
update(String sql,Object[]... args)
有两个参数:
第一个参数:sql语句
第二个参数:可变参数,设置sql语句的值
@Repository
public class UserDaoImpl implements UserDao {
//注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
//修改的方法
@Override
public void update(User user) {
//注意:sql语句中的字段名称要与数据库表中的字段名称一致
String sql2 = "update t_user set user_name=?,user_status=? where user_id=?";
Object[] args = {user.getUserName(), user.getUserStatus(),user.getUserId()};
int update = jdbcTemplate.update(sql2,args);
System.out.println(update);
}
//删除的方法
@Override
public void delete(Integer id) {
String sql3 = "delete from t_user where user_id=?";
int update = jdbcTemplate.update(sql3,id);
System.out.println(update);
}
}
4.3.3、查询操作
4.3.3.1、查询返回某个值—查询所有记录总数
API方法:
jdbcTemplate.queryForObject(String sql, Class<T> requiredType)
此方法有连个参数:
第一个参数:sql语句
第二个参数:返回类型Class
//查询所有记录
@Override
public int getAll() {
//编写sql语句
String sql4 = "select count(*) from t_user";
//执行查询操作,第一个参数是sql。第二个参数是返回值的类型class
Integer count = jdbcTemplate.queryForObject(sql4, Integer.class);
//返回结果
return count;
}
4.3.3.2、查询返回某对象
API方法:
jdbcTemplate.queryForObject(String sql, RowMapper<T> rowMapper ,Object... args )
此方法有三个参数:
第一个参数:sql语句
第二个参数:RowMapper是接口,可以返回不同类型的数据,
使用接口中的实现类,可以完成数据的封装
第三个参数:传递sql语句中问号的值
//通过id查询
@Override
public User selectUserById(Integer id) {
//编写sql
String sql5 = "select * from t_user where user_id=?";
//执行查询操作
/*
jdbcTemplate.queryForObject(String sql, RowMapper<T> rowMapper ,Object... args )
第一个参数:sql语句
第二个参数:RowMapper是接口,可以返回不同类型的数据,使用接口中的实现类,可以完成数据的封装
第三个参数:传递sql语句中问号的值
*/
User user =
jdbcTemplate.queryForObject(sql5, new BeanPropertyRowMapper<User>(User.class), id);
return user;
}
4.3.3.3、查询返回某集合
API方法:
jdbcTemplate.query(String sql, RowMapper<T> rowMapper ,Object... args )
此方法有三个参数:
第一个参数:sql语句
第二个参数:RowMapper是接口,可以返回不同类型的数据,
使用接口中的实现类,可以完成数据的封装
第三个参数:传递sql语句中问号的值
//返回集合数据
@Override
public List<User> findAllUser() {
//编写sql
String sql6 = "select * from t_user";
//执行查询操作
List<User> userList = jdbcTemplate.query(sql6, new BeanPropertyRowMapper<User>(User.class));
return userList;
}
4.3.3.4、批量操作—新增
API方法:
batchUpdate(String sql,List<Object[]> batchArgs)
两个参数:
第一个参数:sql语句
第二个参数:list集合,添加多条记录数据
//批量添加
@Override
public void batchAddUser(List<Object[]> batchArgs) {
String sql7 = "insert into t_user values(null,?,?)";
int[] ints = jdbcTemplate.batchUpdate(sql7,batchArgs);
System.out.println(Arrays.toString(ints));
}
4.3.3.5、批量操作—修改和删除
API方法:
batchUpdate(String sql,List<Object[]> batchArgs)
两个参数:
第一个参数:sql语句
第二个参数:list集合,添加多条记录数据
//批量修改
@Override
public void batchUpdateUser(List<Object[]> batchArgs) {
String sql8 = "update t_user set user_name=?,user_status=? where user_id=?";
int[] ints = jdbcTemplate.batchUpdate(sql8,batchArgs);
System.out.println(Arrays.toString(ints));
}
//批量删除
@Override
public void batchDelete(List<Object[]> batchArgs) {
String sql9 = "delete from t_user where user_id=?";
int[] ints = jdbcTemplate.batchUpdate(sql9,batchArgs);
System.out.println(Arrays.toString(ints));
}
5、事务管理
5.1、什么是事务
什么是事务
事务是数据库操作的最基本的单元,
是逻辑上的一组操作,要么都成功,
如果有一个失败,所有操作都失败
典型场景。银行转账
5.2、事务的四个也行ACID
事务的四个也行ACID
原子性:
原子性意味着数据库中的事务执行是作为原子。
即不可再分,整个语句要么执行,要么不执行
一致性:
一致性即在事务开始之前和事务结束以后,
数据库的完整性约束没有被破坏。
隔离性:
事务的执行是互不干扰的,
一个事务不可能看到其他事务运行时,中间某一时刻的数据
持久性:
意味着在事务完成以后,
该事务所对数据库所作的更改便持久的保存在数据库之中,
并不会被回滚
5.3、新建事务操作环境
5.4、Spring事务管理操作
事务添加到JavaEE三层架构里的Service层,也就是业务逻辑层
在Spring中进行事务管理--两种方式
编程时事务:不建议使用,代码臃肿,不易维护
声明式事务:使用方便
声明式事务:
1、基于注解方式
2、基于xml配置文件方式
3、在Spring进行声明式事务管理。底层用到了AOP原理
4、提供可一个接口,代表事务管理器,
这个接口针对不同的框架提供了不同的实现类
5.5、Spring声明式事务–注解方式
步骤1、在Spring配置文件中引入事务的名称空间tx
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
步骤2、在Spring配置文件中配置事务管理器,并且注入数据源
<!--创建事务管理器-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
步骤3:在配置文件中开启事务注解
<!--开启事务注解 指明事务管理器-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>
步骤4、在service类上或者方法上添加事务注解--@Transactional
1、@Transactional,这个注解可以添加到类上面,也可以添加到方法上面
2、如果把这个注解添加到类上面,这个类里面的所有方法都添加事务
3、如果把这个方法添加到方法上,只为这个方法添加事务
@Service
@Transactional
public class AccountService {
@Autowired
private AccountDao accountDao;
//转账的方法
public void accountMoney(){
//luncy少100
accountDao.reduceMoney();
//模拟意向
int a = 1/0;
//maey多100
accountDao.addMoney();
}
}
步骤5、测试结果
测试过程中发生了异常。事务的提交失败,
那么事务就会进行回滚,数据库中的lucy和mary的money值,
没有发生变化
5.6、@Transactional注解事务管理的参数配置
@Transactional注解源码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
属性1:propagation。事务的传播行为
多事务方法直接进行调用,这个过程中的事务管理有七种方式,
前两种行为常用:REQUIRED和REQUIRED_NEW
@Transactional(propagation = Propagation.REQUIRED)
属性2:ioslation、事务的隔离级别
在多事务操作之间不会互相产生影响,
不考虑隔离会产生很多问题,问题如下:
问题:
脏读:一个未提交的事务读取到了另一个未提交事务的数据
幻读:一个事务读取到另一个提交事务的添加数据
不可重复读:一个未提交的事务读取到了另一个已提交事务修改的数据
解决方案:
通过设置事务的隔离级别,就能解决读的问题
读未提交:
读已提交
可重复读:
串行化:
//事务 事务的隔离级别:可重复度
@Transactional(isolation = Isolation.REPEATABLE_READ)
属性3:timeou。超时时间
事务需要在一定的时间内提交,如果超过设定时间,事务就会回滚
默认值是-1.设置时间以单位秒计算
属性4:readOnly、是否只读
读:表示查询,写:表示增删改
readOnly默认值是false,表示可以增删改查
设置readOnly值是true,只能进行查询操作
属性5:rollbackFor、回滚
设置出现纳西异常,进行回滚操作
属性6:norollbackFor、不回滚
设置出现哪些异常,不进行回滚操作
5.7、Spring声明式事务–xml方式
<!--开启注解扫描-->
<context:component-scan base-package="cn.zzz"></context:component-scan>
<!--配置德鲁伊连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="clone">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///user_db"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--配置JDBCTemplate 查看源码-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入DataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<tx:method name="accountMoney" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* cn.zzz.service.AccountService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"></aop:advisor>
</aop:config>
</beans>
测试结果
测试过程中发生了异常。事务的提交失败,
那么事务就会进行回滚,数据库中的lucy和mary的money值,
没有发生变化
5.8、Spring声明式事务–完全注解方式
1 创建配置类,配置数据源,JdbcTemplate ,事务管理器
@Configuration//配置类
@ComponentScan(basePackages = "cn.zzy")//注解扫描
@Transactional//开启事务
public class TxConfig {
//创建DruidDataSource对象
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql:///user_db");
druidDataSource.setUsername("root");
druidDataSource.setPassword("root");
return druidDataSource;
}
//创建JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DruidDataSource druidDataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//在IOC容器中根据数据类型,找到DruidDataSource,并注入
jdbcTemplate.setDataSource(druidDataSource);
return jdbcTemplate;
}
//创建事务管理器DataSourceTransactionManager对象
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DruidDataSource druidDataSource){
DataSourceTransactionManager dataSourceTransactionManager =
new DataSourceTransactionManager();
//在IOC容器中根据数据类型,找到DruidDataSource,并注入
dataSourceTransactionManager.setDataSource(druidDataSource);
return dataSourceTransactionManager;
}
}
2 创建Service Dao 以及D奥Impl
@Service
@Transactional
public class AccountService {
@Autowired
private AccountDao accountDao;
//转账的方法
public void accountMoney(){
/*注解方式:声明式事务*/
//luncy少100
accountDao.reduceMoney();
//模拟意向
int a = 1/0;
//maey多100
accountDao.addMoney();
}
}
public interface AccountDao {
//多钱的方法
public void addMoney();
//少钱的方法
public void reduceMoney();
}
@Repository
public class AccountDaoImpl implements AccountDao{
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void addMoney() {
String sql = "update t_account set money=money+? where username=?";
//设置让mary的钱多100
jdbcTemplate.update(sql, 100,"mary");
}
@Override
public void reduceMoney() {
String sql = "update t_account set money=money-? where username=?";
//设置让lucy的钱少100
jdbcTemplate.update(sql, 100,"lucy");
}
}
public class test1 {
@Test
public void testAnnoTx(){
//创建核心容器对象
ApplicationContext context =
new AnnotationConfigApplicationContext(TxConfig.class);
//通过容器获取对象
AccountService accountService = context.getBean("accountService", AccountService.class);
//调用方法
accountService.accountMoney();
}
}
、
测试结果:
测试过程中发生了异常。事务的提交失败,
那么事务就会进行回滚,数据库中的lucy和mary的money值,
没有发生变化