0%

Spring基础大全

一、概念

  • 轻量级的开源JavaEE框架
  • IOC:控制反转,把创建对象过程交给Spring进行管理
  • Aop:面向切面,不修改源代码进行功能增强

二、IOC容器

2.1 IOC底层原理

  • XML解析、工厂模式、反射

2.2 IOC容器实现方式

2.2.1 两个接口

  • BeanFactory:IOC容器基本实现,是Spring内部的使用接口,不提供开发人员使用
  • ApplicationContext:BeanFactory的子接口,提供更多更强大的功能,一般由开发人员进行使用
    • ApplicationContext实现类:FileSystemApplicationContext/ClassPathXmlApplicationContext

2.3 Bean管理

2.3.1 两个操作

  • Spring创建对象
  • Spring注入属性

2.3.2 操作Bean管理对象(基础操作/基于XML)

(1)创建/获取对象

1
<bean id="user" class="com.company.spring5.User"></bean>
  • 在Spring配置文件中,使用bean标签,标签里面加对应属性,就可以实现对象创建
  • bean标签属性
    • id:唯一标识,不可以加特殊符号
    • class:类全路径(包类路径)
  • 创建对象时候,默认也是执行无参数构造方法完成对象创建
1
2
3
4
5
6
7
8
// 获取XML文件名获取应用上下文
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

// 根据bean的name属性获取到对象
User user = context.getBean("user", User.class);

// 调用对象方法
user.add();

(2)注入属性

  • DI:依赖注入,就是注入对象属性值

(3)注入方式

  • 第一种:使用set方法进行注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建类
public class Book{
private String bname;
private String bauthor;

// 采用set方法进行属性注入
public void setBname(String bname){
this.bname=bname;
}

public void setBauthor(String bauthor){
this.bauthor=bauthor;
}
}
1
2
3
4
5
6
7
8
<!--
使用property完成属性注入
name:类里面属性名称
value:向属性注入的值
-->
<bean id="book" class="com.company.spring5.User">
<property name="bname" value="活着"></property>
</bean>
  • 第二种:使用有参数构造进行注入
1
2
3
4
5
6
7
8
9
10
11
// 创建类
public class Orders{
private String oname;
private String address;

// 有参数构造进行属性注入
public Orders(String oname,String address){
this.oname = oname;
this.address = address;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--
使用constructor-arg完成有参数构造注入
name:类里面属性名称
value:向属性注入的值
index:类中属性的下标,如下里"0"表示oname,"1"表示address
如果不在bean中添加属性注入标签,则表示默认按照无参构造方式注入对象
所以如果不在类中显式的定义无参构造,则xml文件会报错,报错内容为bean中的class
-->
<bean id="orders" class="com.company.spring5.Orders">
<constructor-arg name="oname" value="蛋炒饭"></constructor-arg>
<constructor-arg name="address" value="东华">
<!--
<constructor-arg index="1" value="东华"></constructor-arg>
-->
</bean>
  • 第三种:p名称空间注入(了解)
1
2
3
4
5
6
7
8
9
10
11
<!--
在beans标签中添加p名称空间
xmlns:p="http://www.springframework.org/schema/p"
-->
<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">

<bean id="user" class="com.company.User" p:name="gy" p:age="10"></bean>
</beans>

(4) 注入其他类型属性

  • 字面量
    • null值
    • 属性值包含特殊符号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!--
null标签为属性设置空值
-->
<bean id="book" class="com.company.spring5.User">
<property name="bname">
<null/>
</property>
</bean>

<!--
属性值包含特殊符号
<<>>会让属性值内容表示为标签
解决方法:
1. 把<>进行转义,如&lt、&gt
2. 把带特殊符号内容谢到CDATA
-->
<bean id="book" class="com.company.spring5.User">
<property name="bname">
<value><![CDATA[<<南京>>]]></value>
</property>
</bean>

(4) 注入外部bean

  • 比如Service层调用Dao层对象,需要在Bean中进行注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--
在这里进行Service层对象的注入
这样在Controller层,就可以调用userService对象提供的方法来处理我们的请求
-->
<bean id="userService" class="com.company.spring5.userService">
<!--
注入userDao对象
name属性:类里面属性名称
ref属性:创建userDao对象bean标签id值
-->
<property name="userDao" ref="userDaoImpl">
</property>
</bean>
<bean id="userDaoImpl" class="com.company.spring5.userDaoImpl">
</bean>

(5) 注入内部bean和级联赋值

  • 一对多关系:部门和员工

    • 一个部门有多个员工,一个员工属于一个部门
    • 在实体类之间表示一对多关系,员工表示所属部门,使用对象类型属性
  • 实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 部门类,生成set方法方便注入
public class Dept {
private String dname;

public void setDname(String dname) {
this.dname = dname;
}
}

// 员工类
public class Emp {
private String ename;
private String gender;

private Dept dept;

public void setEname(String ename) {
this.ename = ename;
}

public void setGender(String gender) {
this.gender = gender;
}

public void setDept(Dept dept) {
this.dept = dept;
}
}
  • 注入内部bean
1
2
3
4
5
6
7
8
9
10
11
<bean id="emp" class="com.company.Emp">
<property name="ename" value="jack"></property>
<property name="gender" value="man"></property>

<!--内部注入部门属性,以嵌套bean的方式实现-->
<property name="dept">
<bean id="dept" class="com.company.Dept">
<property name="dname" value="保安部"></property>
</bean>
</property>
</bean>
  • 级联赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!--第一种写法-->
<bean id="emp" class="com.company.Emp">
<property name="ename" value="jack"/>
<property name="gender" value="man"/>

<!--引入外部bean的方式,为外部bean的属性赋值(级联赋值)-->
<property name="dept" ref="dept">
</property>
</bean>
<bean id="dept" class="com.company.Dept">
<property name="dname" value="保安部"/>
</bean>

<!--第二种写法-->
<bean id="emp" class="com.company.Emp">
<property name="ename" value="jack"/>
<property name="gender" value="man"/>

<!--引入外部bean的方式,为外部bean的属性赋值-->
<property name="dept" ref="dept"/>
<!--需要Dept类中属性有get方法-->
<property name="dept.dname" value="技术部"/>
</bean>
<bean id="dept" class="com.company.Dept">
</bean>

2.3.3 操作Bean管理对象(注入集合类型)

(1)注入数组/List/Map/Set类型

  • 为学生类注入集合属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Stu {
// 数据类型属性
private String[] courses;

// List类型属性
private List<String> list;

// Map类型属性
private Map<String,String> maps;

// Set类型属性
private Set<String> sets;

// 生成set方法
public void setSets(Set<String> sets) {
this.sets = sets;
}

public void setCourses(String[] courses) {
this.courses = courses;
}

public void setList(List<String> list) {
this.list = list;
}

public void setMaps(Map<String, String> maps) {
this.maps = maps;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!--
数组对应array>value标签
List对应list>value标签
Map对应map>entry[key,value]标签
Set对应set>value标签
-->
<bean id="stu" class="com.company.Stu">
<property name="courses">
<array>
<value>Java</value>
<value>数据结构</value>
</array>
</property>

<property name="list">
<list>
<value>张三</value>
<value>李四</value>
</list>
</property>

<property name="maps">
<map>
<entry key="Java" value="世界上最好的语言"/>
<entry key="Php" value="世界上最好的语言"/>
</map>
</property>

<property name="sets">
<set>
<value>Mysql</value>
<value>Redis</value>
</set>
</property>
</bean>

(2)集合中注入对象类型值

1
2
3
4
5
6
7
8
9
public class Stu {
// 集合中对象类型注入
private List<Course> courses;

//set方法注入属性
public void setCourses(List<Course> courses) {
this.courses = courses;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--
List中的对象属性
list>ref.bean标签引入外部的对象
-->
<bean id="stu" class="com.company.Stu">
<property name="courses">
<list>
<ref bean="course1"/>
<ref bean="course2"/>
</list>
</property>
</bean>

<bean id="course1" class="com.company.Course">
<property name="cname" value="Java"/>
</bean>

<bean id="course2" class="com.company.Course">
<property name="cname" value="Mysql"/>
</bean>

(4)把集合注入部分提取出来(公共属性)

  • 在spring配置文件中引入util名称空间
  • 使用util:list创建属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!--
添加xmlns:util
在xsi:schemaLocation中添加http://www.springframework.org/schema/util/spring-util.xsd
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

<!--提取公共属性-->
<util:list id="bookList">
<value>Java从入门到入土</value>
<value>Php从入门到入土</value>
</util:list>

<bean id="book" class="com.company.Book">
<!--
使用公共list实现属性注入,需要使用ref完成list引入
ref对应到util的id属性
-->
<property name="list" ref="bookList"/>
</bean>
</beans>

2.3.4 操作Bean管理对象(工厂Bean)

  1. Spring有两种类型bean,一种普通bean,另外一种工厂bean
  2. 普通bean:在配置文件中定义bean类型就是返回类型
  3. 工厂bean:在配置文件定义bean类型可以和返回类型不一样
    1. 第一步:创建类,让这个类作为工厂bean,实现接口FactoryBean
    2. 第二步:实现接口里面的方法,在实现的方法中定义返回的bean类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 创建工厂类,实现接口FactoryBean
public class MyBean implements FactoryBean<User> {
@Override
public boolean isSingleton() {
return false;
}

// 工厂可以获取到指定对象,这里获取的是User对象
@Override
public User getObject() throws Exception {
User user = new User();
user.setName("gy");
return user;
}

@Override
public Class<?> getObjectType() {
return null;
}
}

1
2
3
4
5
<!--
注入工厂对象,注入后会调用getObject方法获取到指定的对象
所以才说这里定义的bean,在返回的时候获取的对象可以不一样
-->
<bean id="myBean" class="com.company.factorybean.MyBean"/>
1
2
3
4
5
6
7
8
// 测试方法,根据applicationContext获取到的对象应该使用具体的类,这里使用的是User,所以根据注入的工厂bean获取到的的就是User对象
@Test
public void test1(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml");
User myBean = applicationContext.getBean("myBean", User.class);
System.out.println(myBean);

}

2.3.5 操作Bean管理对象(Bean作用域)

(1)bean的性质

  • Spring中,默认情况下,创建的bean是单实例对象,也就是注入的一个bean被两个引用变量获取到后,引用的地址都是相同的

(2)设置单实例/多实例

  • 在spring配置文件标签里面有属性scope用于设置bean是单实例还是多实例
  • scope属性值
    • 第一个:默认值,singleton,表示实例为单实例对象
    • 第二个:prototype,表示是多实例对象
1
2
3
<bean id="book" class="com.company.spring5.User" scope="prototype">
<property name="bname" value="活着"></property>
</bean>

(3)singleton和prototype区别

  • singleton表示单实例,prototype表示多实例
  • 设置scope值是singleton时候,加载spring配置文件(java的ApplicationContext对象创建的时候)时,就会创建单实例对象,单实例对象不管获取多少次地址都是相同的
  • 设置scope值是prototype时,不是在加载spring配置文件时创建,而是在调用getBean方法的时候才创建多实例对象,多实例对象的地址都是不一样的

2.3.6 操作Bean管理对象(Bean生命周期)

(1)生命周期

  • 从对象创建到对象销毁的过程

(2)bean生命周期

  • 通过构造器创建bean实例(无参数构造)
  • 为bean的属性设置值和对其他bean的引用(调用set方法)
  • 把bean实例传递bean前置处理器
  • 调用bean的初始化的方法(需要进行配置)
  • 把bean实例传递bean后置处理器
  • bean可以被获取到了
  • 当容器关闭时候,调用bean的销毁的方法(需要进行配置销毁的方法)

(3)演示bean生命周期

  • 实体类,配置无参构造,初始化方法initMethod,销毁方法destory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Order {
private String oname;

public Order(){
System.out.println("第一步:通过无参构造创建bean实例");
}

public void setOname(String oname) {
this.oname = oname;
System.out.println("第二步:调用set方法设置属性");
}

public void initMethod(){
System.out.println("第三步:执行初始化方法");
}

public void destory(){
System.out.println("第四步:执行销毁方法");
}
}

  • bean注入
1
2
3
4
5
6
7
<!--
在bean中添加init-method、destroy-method属性
并添加上我们在类中实现的方法
-->
<bean id="myOrder" class="com.company.Order" init-method="initMethod" destroy-method="destory">
<property name="oname" value="123"/>
</bean>
  • 测试
1
2
3
4
5
6
7
8
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");

// 获取bean实例对象
Object myBean = context.getBean("myOrder");


// 手动让bean实例销毁,此方法只有ClassPathXmlApplicationContext有
context.close();
  • 结果

(4)后置处理器

  • bean实例初始化前后的操作,具体操作如下
  • 创建类,实现接口BeanPostProcessor,创建后置处理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 继承BeanPostProcessor的类可以重写方法
// 这两个方法可以在bean实例初始化前后被调用,可以对bean实例进行操作
public class MyBeanPost implements BeanPostProcessor {
// 初始化前的操作
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return null;
}

// 初始化后的操作
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return null;
}
}
  • 配置bean
1
2
3
<bean id="myOrder" class="com.company.Order" init-method="initMethod" destroy-method="destory">
<property name="oname" value="123"/>
</bean>

2.4 xml自动装配

  • 根据指定装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入
1
2
3
4
5
6
7
8
<!--
bean标签属性autowire可以实现自动装配
autowire属性值有byName和byType
byName要求bean中的id要和对应类的属性名一致,才能自动装配该对象
byType要求类型一致,才能自动装配该对象,注意要有两个相同类型的bean就会报错
-->
<bean id="emp" class="com.company.Emp" autowire="byType"/>
<bean id="dept" class="com.company.Dept"/>

2.5 IOC操作bean管理(外部属性文件)

  • 使用情况:一般在spring项目要配置properties配置文件,填写数据库信息,这里就需要使用xml以引入外部文件的方式,配置连接数据库的bean
  • 直接配置数据库信息

    • 配置德鲁伊druid连接池
  • 引入外部属性文件配置数据库连接池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--
先引入druid.jar包,在xml中引入命名context空间
xmlns:util="http://www.springframework.org/schema/util"
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
通过context引入properties配置文件
在druid中通过${}引入配置文件中的信息,注入数据库连接池信息
-->

<context:property-placeholder location="classpath:application.properties"/>


<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"/>
<property name="url" value="${prop.url}"/>
<property name="username" value="${prop.userName}"/>
<property name="password" value="${prop.password}"/>
</bean>

2.6 IOC操作Bean管理(基于注解方式)

2.6.1 Spring提供的注解

  • @Component
  • @Service
  • @Controller
  • @Repository
  • 以上的注解功能都是一样的,都可以用来创建bean实例,主要是方面理解

2.6.2 注解方式创建对象

  • 第一步:引入spring-aop包
  • 第二步:在bean配置文件中,开启组件扫描
1
<context:component-scan base-package="包地址 包地址 ..." />
  • 第三步:在类上添加注解,spring即可扫描到该类并完成对象的注入

  • 默认注入bean的id为类名称首字母小写:如UserService=>userService

  • 注解Repository(value=”userSerivice”)中的value值等同于bean的id值

2.6.3 注解方式注入属性

  • 提供的注解
    • @Autowired:根据属性类型进行自动装配
    • @Qualifier:根据属性名称进行注入,@Qualifier(value=”注入类的value/id值”),需要和@Autowired一起使用,就是叠加在一起作用在属性上
    • @Resource:可以根据类型/名称注入,@Resource默认按照属性类型进行自动装配,@Resource(name=””)即可以按照属性名称进行注入
    • @Value:完成普通属性的注入,如@Value(value=”123”),可以为String类型属性注入属性值
  • 以上操作都需要在bean配置文件中开启扫描才能运行,意思是在获取注入的对象时,仍然要用ApplicationContext获取到bean配置文件

2.6.4 完全注解开发

  • 第一步:创建配置类config/,这一步完成后就可以删除bean配置文件,因为这一步的作用和bean配置文件是一样的
1
2
3
4
5
@Configuration
@ComponentScan(basePackages={"com.company"}) //配置需要扫描的包名
public class SpringConfig{

}
  • 第二步:获取对象
1
2
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class)
UserService userService = context.getBean("userService",UserService.class)

三、AOP

3.1 概念

  • 面向切面编程:在不修改源代码的情况下在主干功能里添加新的功能模块

3.2 AOP底层原理

3.2.1 动态代理

  • Spring5已经对动态代理做了封装,可以直接调用,需要的知识点:反射

  • 第一种:有接口情况,使用JDK动态代理

    • 创建接口实现类代理对象,增强类的方法
  • 第二种:没有接口情况,使用CGLIB动态代理

3.2.2 AOP(JDK动态代理)

  • JDK动态代理,使用Proxy类里面的方法创建代理对象
    • 调用newProxyInstance方法
  • newProxyInstance方法包含的三个参数
    • 第一个参数:类加载器
    • 第二个参数:增强方法所在的类,这个类实现的接口,支持多个接口
    • 第三个参数:实现接口InvocationHandler,创建代理对象,写增强的方法
  • 实现步骤:
    • 创建接口,定义方法,如UserDao
    • 创建接口实现类,实现方法,如UserDaoImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class JDKProxy{
public static void main(String[] args){
Class[] interfaces = {UserDao.class};
// 写法一:匿名内部类
/*
Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces,new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
return null;
}
});
*/
UserDaoImpl userDao = new UserDaoImpl();

// 写法二:创建代理对象
UserDao dao = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces,new UserDaoProxy(userDao));
dao.add(1,2); // 相加的方法

}
}


class UserDaoProxy implements InvocationHandler{
// 写Onject是为了代码更通用
private Object obj;

// 获取到代理对象所代理的对象
public UserDaoProxy(Object obj){
this.obj = obj;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
System.out.println("方法执行前..."+method.getName+
"\n传递的参数..."+Arrays.toString(args));

// 执行增强方法
Object res = method.invoke(obj,args);

System.out.println("方法执行后...")
return res;
}
}

3.3 AOP专业术语

  • 连接点:类中可以被增强的方法,这些方法称为连接点
  • 切入点:真正被增强的方法,称为切入点
  • 通知(增强):实际增强的逻辑部分
    • 前置通知
    • 后置通知
    • 环绕通知
    • 异常通知
    • 最终通知 finally
  • 切面:动作,如权限判断
    • 把通知应用到切入点的过程

3.4 AOP操作准备

3.4.1 概述

  • Spring框架一般都是基于AspectJ实现AOP操作
  • AspectJ不是Spring组成部分,独立的AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作
  • 基于AspectJ实现AOP操作
    • 基于xml配置文件实现
    • 基于注解方式实现(使用)
  • 需要引入依赖包

3.4.2 切入点表达式

1
2
3
4
5
6
7
8
9
// execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
// 例1:对BookDao类里的add进行增强
execution(* com.company.dao.BookDao.add(...));

// 例2:对BookDao类里的所有方法进行增强
execution(* com.company.dao.BookDao.*(...));

// 例2:对包dao中所有类所有方法进行增强
execution(* com.company.dao.*.*(...));

3.5 AspectJ注解

3.5.1 操作步骤

  • 创建类,定义方法,如User类
  • 创建增强类,编写增强逻辑,如UserProxy
    • 在增强类中,创建方法,让不同方法代表不同通知类型
  • 进行通知的配置
    • 在Spring配置文件中,开启注解扫描
    • 使用注解创建User(@Component)和UserProxy对象
    • 在增强类上面添加注解@Aspect
    • 在Spring配置文件中开启生成代理对象
1
2
3
4
5
<!--开启注解扫描-->
<context:component-scan base-package="包地址 包地址 ..." />

<!--开启Aspect生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  • 配置不同类型的通知
    • 在增强类中,在通知方法上添加通知类型注解,使用切入点表达式进行配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Component
@Aspect
public class UserProxy{
// 前置通知
@Before(value = "execution(* com.company.User.add([参数]))")
public void before(){
System.out.println("before......")
}

// 最终通知,方法之后执行
@After(value = "execution(* com.company.User.add([参数]))")
public void after(){
System.out.println("after......")
}

// 后置通知,返回值之后执行,有异常时不执行
@AfterReturning(value = "execution(* com.company.User.add([参数]))")
public void afterReturning(){
System.out.println("afterReturning......")
}

// 异常通知,有异常才执行
@AfterThrowing(value = "execution(* com.company.User.add([参数]))")
public void afterThrowing(){
System.out.println("afterThrowing......")
}

// 环绕通知,此注解下的方法,在被增强方法执行之前和之后都执行
@Around(value = "execution(* com.company.User.add([参数]))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("around before......");

// 被增强方法执行
proceedingJoinPoint.proceed();

System.out.println("around after......");
}

}

3.5.2 相同切入点抽取

  • 将方法增强的切入点写成公共的部分,方便调用和切入点的统一修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Pointcur(value = "execution(* com.company.User.add([参数]))")
public void pointdemo(){

}

@Component
@Aspect
public class UserProxy{
// 前置通知,加入方法名称
@Before(value = "pointdemo()")
public void before(){
System.out.println("before......")
}
}

3.5.3 增强类设置优先级

  • 多个增强类可以增强同一个方法,因此可以为增强类设置优先级,
  • 方法:在增强类上面添加注解@Order(数值型),数字类型值越小,优先级越高
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
@Aspect
@Order(1)
public class PersonProxy{
@Pointcur(value = "execution(* com.company.User.add([参数]))")
public void pointdemo(){

}

// 前置通知,加入方法名称
@Before(value = "pointdemo()")
public void before(){
System.out.println("before......")
}
}

3.6 AspectJ配置文件

1
2
3
4
5
6
7
8
9
10
<aop:config>
<!--配置切入点-->
<aop:pointcut id="p" expression="execution(* com.company.Book.buy(...))"/>

<!--配置切面,ref对应注入的增强类-->
<aop:aspect ref="bookProxy">
<!--配置切面,ref对应前面的切入点id-->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>

3.7 完全注解开发

1
2
3
4
5
6
@Configuration
@ComponentScan(basePackages={"com.company"}) //配置需要扫描的包名
@EnableAspectJAutoProxy(proxyTargetClass=true) // 默认为true,开启Aspect生成代理对象
public class AopConfig{

}

四、JdbcTemplate

4.1 准备工作

  • 引入依赖包
    • mysql-connect
    • spring-jdbc
    • spring-tx:处理事务的包
  • 配置数据库连接池(druid)
  • 注入JdbcTemplate对象
1
2
3
4
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入连接池对象属性-->
<property name="dataSource" ref="dataSource"></property>
</bean>
  • 开启组件扫描

4.3 CURD实现

4.3.1 添加操作

  • 创建实体类User,创建Dao层UserDao,创建Service层UserService
1
2
3
4
5
6
7
8
9
10
11
12
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;

@Override
public void add(User user){
String sql = "insert into user values(?,?,?)";

Object[] args = {user.getUserId(),user.getUserName(),user.getPassword()}
jdbcTemplate.update(sql,args);
}
}

4.3.2 修改/删除操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;

@Override
public void update(User user){
String sql = "update user set user_name=?,password=? where user_id=?";

Object[] args = {user.getUserName(),user.getPassword(),user.getUserId()}
jdbcTemplate.update(sql,args);
}

@Override
public void delete(String id){
String sql = "delete from user where user_id=?";

jdbcTemplate.update(sql,id);
}
}

4.3.3 查询操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;

// 查询用户数
@Override
public int selctCount(){
String sql = "select count(*) from user";

Integer count = jdbcTemplate.queryForObject(sql,Integer.class);

return count;
}

// 根据id查询用户
@Override
public User findUserInfo(String id){
String sql = "select * from user where user_id=?";
// BeanPropertyRowMapper用于封装查询到的数据
User user = jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<User>(User.class),id);

return user;
}

// 查询返回集合
public List<User> findAll(){
String sql = "select * from user";

List<User> users = jdbcTemplate.query(sql,new BeanPropertyRowMapper<User>(User.class));

return users;
}
}

4.3.4 批量操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;

// 批量添加,List<Object[]>中的Object[]表示参数的集合
public int[] batchAdd(List<Object[]> barchArgs){
String sql = "insert into user values(?,?,?)";

// 返回的是影响的行数数组
int[] res = jdbcTemplate.batchUpdate(sql,batchArgs);

return res;
}

// 批量修改
public int[] batchUpdate(List<Object[]> barchArgs){
String sql = "update user set user_name=?,password=? where user_id=?";

// 返回的是影响的行数数组
int[] res = jdbcTemplate.batchUpdate(sql,batchArgs);

return res;
}

// 批量删除
public int[] batchDelete(List<Object[]> barchArgs){
String sql = "delete from user where user_id=?";

// 返回的是影响的行数数组
int[] res = jdbcTemplate.batchUpdate(sql,batchArgs);

return res;
}
}

五、事务管理

5.1 概念

  • 一组操作的组合
  • 事务的一些问题请转到数据库笔记中进行学习

5.2 事务特性ACID

  • 原子性
  • 一致性
  • 隔离性
  • 持久性

5.3 准备工作

  • 以银行转账操作为例
    • 创建数据库表,记录用户资金
    • 创建service、dao完成对象创建和注入
    • 创建jdbcTemplate对象
    • dao创建两个方法,某用户资金增加,另外用户资金减少
    • service创建转账方法,实现转账操作

5.4 事务操作

5.4 1 声明式事务管理

  • 基于注解方式
  • 基于xml方式

5.4.2 Spring事务管理API

  • Spring提供一个接口,代表事务管理器,这个接口针对不同框架提供不同的实现类

5.4.2 注解方式

  • 注入事务管理对象
1
2
3
4
5
<!--由于使用的式jdbc框架,所以注入DataSourceTransactionManager对象-->
<bean id="transactionManager" class="org.springframework.jdbc.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
  • 开启扫描
  • xml引入名称空间tx,同前面的引入操作
  • 开启事务注解
1
<tx:annotation-driven transacntion-manager="transactionManager"></tx:annotation-driven>
  • 在Service层添加事务注解@Transactional
    • @Transactional可以加到类上,也可以加到方法上
    • 加到类上,表示作用到类中的所有方法

5.4.3 注解的参数

(1)propagation:事务传播行为

  • 多事务方法直接进行调用,这个过程中事务是如何进行管理的,必须是对数据库中的数据产生变化的操作
  • 简单来说就是,就是添加了@Transaction事务管理的方法去调用未添加事务管理的方法,事务的操作过程是怎样的
  • 下表均已上图为例,在事务管理方法中调用非事务管理方法,非事务管理方法运行的情况
  • 配置方法:@Transactional(propagation = Propagation.REQUIRED)
传播属性 描述
REQUIRED(default) 有事务就在当前事务运行,没有事务就创建事务运行
REQUIRED_NEW 无论有没有事务,都会创建一个事务在里面运行
SUPPORTS 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中
NOT_SUPPORT 当前的方法不应该运行在事务中,如果有运行的事务,将它挂起
MANDATORY 当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常
NEVER 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常
NESTED 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行

(2)ioslation:事务隔离级别

  • 隔离性:多事务操作之间不会产生影响
  • 多事务带来的问题:脏读、不可重复读、虚读
    • 脏读:未提交的事务读取另一个未提交事务的数据
    • 不可重复读:两个事务同时读到一条数据,一个事务读到了另一个事务提交之前的数据(数据不一致了)
    • 虚读:一个未提交的事务读取到另一个提交事务的添加数据
  • 配置方式:@Transactional(ioslation= Ioslation.READ_UNCOMMITTED)
脏读 不可重复读 虚读
READ UNCOMMITTED(读未提交)
READ COMMITTED(读已提交)
REPEATABLE READ(可重复读/default)
SERIALIZABLE(串行化)

(3)timeout:超时时间

  • 事务需要在一定时间内进行提交,如果不提交就进行回滚,默认是-1,单位是秒

(4)readOnly:是否只读

  • 读:查询操作,写:增删改

  • 默认为false

(5)rollbackFor:回滚

  • 设置出现哪些异常进行事务回滚
  • 设置该属性值为异常的class即可

(6)noRollbackFor:不回滚

  • 设置出现哪些异常不进行事务回滚

5.5 事务完全注解开发

  • 创建配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Configuration
@ComponentScan(basePackages = "com.kuang")
@EnableTransactionManagement // 开启事务
public class TxConfig{
// 创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://user_db");
dataSource.setUsername("root");
dataSource.setPassword("root");

return dataSource;
}

//创建JdbcTemplate
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);

return jdbcTemplate;
}

// 创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}

六、Spring5新特性