Keep Calm and Carry On

Spring框架基础

一、Spring介绍

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。

轻量级:

控制反转:对象的控制权反转为Spring容器控制;

面向切面:

容器框架:Spring就是一个容器,一个管理bean的工厂,org.springframework.context.ApplicationContext接口用于完成容器的配置,初始化,管理bean。一个Spring容器就是某个实现了ApplicationContext接口的类的实例。也就是说,从代码层面,Spring容器其实就是一个ApplicationContext。

二、Spring简单入门

1. 导入Spring的核心jar包

核心jar包
spring-core-3.2.2.RELEASE.jar
包含Spring框架基本的核心工具类,Spring其它组件要都要使用到这个包里的类,是其它组件的基本核心。
spring-beans-3.2.2.RELEASE.jar
所有应用都要用到的,它包含访问配置文件、创建和管理bean,以及进行Inversion of Control(IoC) / DependencyInjection(DI)操作相关的所有类
spring-context-3.2.2.RELEASE.jar
Spring提供在基础IoC功能上的扩展服务,此外还提供许多企业级服务的支持,如邮件服务、任务调度、JNDI定位、EJB集成、远程访问、缓存以及各种视图层框架的封装等。
spring-expression-3.2.2.RELEASE.jar
Spring表达式语言
com.springsource.org.apache.commons.logging-1.1.1.jar
第三方的主要用于处理日志

2. 在使用spring之前

创建xxxService,并由xxxService调用相应的业务方法完成工作,而且每次创建一个XXXService都是一个新的对象

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test01() {
// Spring之前,service的用法,new一个service,调动它的方法
UserService userService1 = new UserServiceImpl();
userService1.save();
UserServiceImpl userService2 = new UserServiceImpl();
userService2.save();

// 两次创建的service是不同的对象
System.out.println(userService1.hashCode()); // 1586270964
System.out.println(userService2.hashCode()); // 1642360923
}

3. Spring配置文件beans.xml

src目录下创建配置未见beans.xml,配置文件的约束在Spring文档的文件内找到:

beans.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--配置一个bean(对象)-->
<bean id="userService" class="cn.zhuobo.spring.service.serviceImpl.UserServiceImpl" />
</beans>

4. 获取对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test02() {
// 1. 加载配置文件beans.xml
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

// 2. 根据配置文件配置的id获取对象
UserService userService1 = (UserService) context.getBean("userService");
userService1.save();
UserService userService2 = (UserService) context.getBean("userService");
userService2.save();

// 从spring容器中获取对象,两次获取的是同一个对象
System.out.println(userService1.hashCode()); // 1604125387
System.out.println(userService2.hashCode()); // 1604125387
}

三、控制反转和依赖注入

1. IoC(Inversion of Control)

控制反转不是一种技术,而是一种设计思想,意味着对象的控制权被交给IoC容器,而不是传统的直接在对象内部控制。传统上在对象内部直接new另一个对象,并由程序主动地创建依赖对象,维护对象间的依赖关系(比如Customer对象有一个依赖对象Order,有应用程序类创建这两个对象,并主动将Order注入(set)到Customer)。在IoC中,所有的对象由IoC容器控制,在系统运行到适当的时候,将对象交给需要的另一个对象。所有的对象都由IoC容器控制,不再是应用程序,IoC容器控制着对象的创建、销毁,对象不再管理那些被引用的对象的生命周期。应用程序(或者一个对象)不再主动创建对象,而是被动地接受依赖对象。

传统:

控制反转:

2. DI(Dependency Injection)

DI即依赖注入,组件之间的依赖关系有IoC容器在运行之间决定,也就是应用程序需要的资源不再自己创建,而是有IoC容器创建并注入要需要依赖注入的组件(对象),由IoC容器为某个需要依赖注入的对象注入外部资源。“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”

所谓控制反转,那么控制的什么反转了呢?其实就是:获得依赖对象的方式反转了!

依赖注入例子:

  1. 在前面的UserService.java中添加一个属性username,并提供属性的setter、getter方法;
  2. 在beans.xml配置依赖注入数据:
1
2
3
4
5
6
7
8
9
<!--配置一个bean-->
<!--Spring创建对象的原理:
1. 解析xml,获取id,类名,属性
2. 通过反射,用类名创建对象
3. 根据属性,给创建的对象注入相关资源
-->
<bean id="userService" class="cn.zhuobo.spring.service.serviceImpl.UserServiceImpl" >
<property name="username" value="tanzhuobo" />
</bean>

在这个例子里就是spring自己创建了一个字符串对象username,并主动注入到需要username这个对象的UserService对象中。

四、加载Spring容器的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Spring容器加载的两种方式
*/
@Test
public void test01() {
// 第一种凡是:ClassPathXmlApplicationContext,是最常用的方式
ApplicationContext context1 = new ClassPathXmlApplicationContext("beans.xml");
UserService userService1 = (UserService) context1.getBean("userService");
userService1.save();

// 第二种方式:文件系统路径(绝对路径)
ApplicationContext context2 = new FileSystemXmlApplicationContext("D:\\javaProject\\spring\\src\\beans.xml");
UserService userService2 = (UserService) context2.getBean("userService");
userService2.save();

}

五、装配bean的三种方式

1. 所谓的装配bean,就是在beans.xml中配置一个bean标签

  1. 使用构造方法创建bean
1
2
3
<bean id="userService" class="cn.zhuobo.spring.service.serviceImpl.UserServiceImpl" >
<property name="username" value="tanzhuobo" />
</bean>
  1. 使用静态工厂创建bean

    写一个静态工厂,StaticUserServiceFactory.java,用来创建bean

1
2
3
4
5
6
public class StaticUserServiceFactory {

public static UserService getUserService() {
return new UserServiceImpl();
}
}

​ 在beans.xml装配一个bean

1
2
3
<!--第二种装配bean方法:使用静态工厂
指定静态工厂、以及方法-->
<bean id="userService2" class="cn.zhuobo.spring.service.StaticUserServiceFactory" factory-method="getUserService" />
  1. 使用实例工厂

    也就是创建一个工厂,但是工厂提供的创建bean的方式不是静态的,InstanceUserServiceFactory.java

1
2
3
4
5
6
public class InstanceUserServiceFactory {

public UserService getUserService() {
return new UserServiceImpl();
}
}

​ 在beans.xml中装配一个bean

1
2
3
4
5
6
7
8
<!--第三种方式:使用实例工厂
1. 先装配一个实例工厂(由于方法是非静态的,要先创建工厂对象)
2. 再装配工厂对象可以产生的bean
-->
<bean id="factory" class="cn.zhuobo.spring.service.InstanceUserServiceFactory" />
<bean id="userService3" factory-bean="factory" factory-method="getUserService">
<property name="username" value="tan3" />
</bean>

2. bean的作用域

在bean标签内可以配置scope属性,表示该bean在容器内是单例还是多例的。

说明
singleton 在Spring IoC容器中仅存在一个Bean实例,Bean以单例方式存在,默认值
prototype 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时 ,相当于执行new XxxBean()

六、bean的生命周期

七、依赖注入

依赖注入,为bean(被注入对象)创建依赖对象,配置依赖对象(可以狭义的解释为为bean的属性赋值)。

1. 手动装配,使用xml

1. 构造方法注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--根据第一个构造方法依赖注入-->
<bean id="student1" class="cn.zhuobo.spring.domain.Student">
<constructor-arg name="name" value="zhuobo1" />
<constructor-arg name="age" value="18" />
</bean>

<!--根据第二个构造方法依赖注入-->
<bean id="student2" class="cn.zhuobo.spring.domain.Student">
<constructor-arg name="name" value="zhuobo2" />
<constructor-arg name="password" value="password2" />
</bean>

<!--通过索引+数据类型使用构造方法依赖注入-->
<bean id="student3" class="cn.zhuobo.spring.domain.Student">
<constructor-arg index="0" type="java.lang.String" value="zhuobo3"/>
<constructor-arg index="1" type="java.lang.Integer" value="19" />
</bean>

2. setter方法注入

1
2
3
4
5
<bean id="student4" class="cn.zhuobo.spring.domain.Student" >
<property name="name" value="zhuobo4" />
<property name="password" value="password4" />
<property name="age" value="18" />
</bean>

3. p命名空间注入

2. Spring表达式

1
2
3
4
5
#{123}、#{'jack'} : 数字、字符串
#{beanId} :另一个bean引用
#{beanId.propName} :操作数据
#{beanId.toString()} :执行方法
#{T(类).字段|方法} :静态方法或字段

3. 集合注入

集合的注入都是在property添加子标签:

数组:array

Set:<set>

List:<list>

Map:<map>,使用子标签 <entry>描述

Properties:<props>,使用子标签 <prop key='key'>value</prop>描述

  1. 数组:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 类
public class Student {
private String name;
private String[] courses;
}

// 配置一个bean,并为集合属性注入数据
<bean id="student" class="cn.zhuobo.spring.domain.Student">
<property name="name" value="zhuobo" />
<property name="courses">
<array>
<value>Math</value>
<value>English</value>
<value>Computer Science</value>
</array>
</property>
</bean>
  1. List
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// class
public class Student {
private String name;
private List<String> courses;
}

// 配置一个bean,并为集合属性注入数据
<bean id="student" class="cn.zhuobo.spring.domain.Student">
<property name="name" value="zhuobo" />
<property name="courses">
<list>
<value>Math</value>
<value>English</value>
<value>Computer Science</value>
</list>
</property>
</bean>
  1. Set
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// class
public class Student {
private String name;
private List<String> courses;
private Set<String> cars;
}

<bean id="student" class="cn.zhuobo.spring.domain.Student">
<property name="cars">
<set>
<value>BMW</value>
<value>Audi</value>
<value>Ferrari</value>
<value>Porsche</value>
</set>
</property>
</bean>
  1. Map
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Student {
private String name;
private Map<String, String> info;
}
// 为map属性注入数据
<bean id="student" class="cn.zhuobo.spring.domain.Student">
<property name="info">
<map>
<entry key="name" value="zhuobo"/>
<entry key="id" value="14332017"/>
</map>
</property>
</bean>
  1. Properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Student {
private String name;
private Properties mySqlInfos;
}
// 为map属性注入数据
<bean id="student" class="cn.zhuobo.spring.domain.Student">
<property name="mySqlInfos">
<props>
<prop key="username">zhuobo</prop>
<prop key="password">password</prop>
<prop key="url">mysql:jdbc://localhost:3306/database</prop>
</props>
</property>
</bean>

4. 注解注入

使用注解来取代xml配置,在类上添加注解就相当于在xml写了一个手动装配了一个bean标签,Spring的注解注入默认是不开启的,开启注解注入如下:

  1. @Component:没有配置id,需要根据类型获取;
1
2
3
// 如果@Component没有配置id,那么就可以使用getBean通过类型去获取对象
// 参数可以是实现类类型,获取接口类型
UserService userService = (UserService) context.getBean(UserService.class);
  1. @Component(“id”):配置了id,可以使用getBean,根据参数id获取bean;

  2. web开发@Component的衍生注解:

    @Repository(“名称”):dao层

    @Service(“名称”):service层

    @Controller(“名称”):web层

@AutoWired:根据数据类型自动注入,如果是接口,spring根据类型从容器中找到合适的bean注入

@Qualifier(“名称”):指定自动注入的id名称,与@AutoWired结合使用

@Resource(“名称”):相当于上面两个注解结合使用的简写

@ PostConstruct:自定义初始化

@ PreDestroy:自定义销毁

  1. @scope(prototype):多例,默认是单例

八、AOP(面向切面)

Aspect Oriented Programming(面向切面编程),通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP实现的目的是对业务处理过程的切面进行提取,提取处理工程的某个步骤或者阶段,以获得逻辑过程各个部分之间低耦合性的隔离效果。

1)AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。

2)利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

3)AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码

4) 经典应用:事务管理、性能监视、安全检查、缓存、日志等

5) Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码

1. AOP实现原理

a.aop底层将采用代理机制进行实现。

b.接口 + 实现类 :spring采用 jdk 的动态代理Proxy。

c. 实现类:spring 采用 cglib字节码增强。

2. AOP术语

1.target:目标类,需要被代理的类。例如:UserService
2.Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法
3.PointCut 切入点:已经被增强的连接点。例如:addUser()
4.advice 通知/增强,增强代码。例如:after、before
5.Weaving(织入):是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程.
6.proxy 代理类
7.Aspect(切面): 是切入点pointcut和通知advice的结合
一个线是一个特殊的面。
一个切入点和一个通知,组成成一个特殊的面。

3. 手动代理

1. JDK动态代理

这种方法需要目标类有接口以及实现类

目标类:

切面类:

工厂类:创建一个工厂类,工厂类的静态方法返回一个UserService接口,通过创建代理对象的方法,返回一个代理对象

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 MyBeanFactory {
public static UserService createUserService() {
// 1. 创建目标对象
final UserService userService = new UserServiceImpl();
// 2. 生命切面类对象
final MyAspect myAspect = new MyAspect();

// 3. 把切面的两个方法应用到目标对象
// 3.1 创建jdk代理
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
MyBeanFactory.class.getClassLoader(),
userService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 执行增强代码
myAspect.before();

// 执行目标对象的方法,并传递参数
Object object = method.invoke(userService, args);

// 执行增强方法
myAspect.after();

return object;
}
});
// 返回的是代理对象
return userServiceProxy;
}
}

测试:

2. cglib增强字节码

  1. 目标对象只有类,没有接口(目标对象没有实现接口),或者有接口由实现类都可以使用cglib字节码增强框架增强对象;
  2. 使用cglib字节码增强框架,在运行时创建目标类的子类,从而对目标类进行增强。

导入jar包:spring-core.jar整合了cglib字节码增强框架;

工厂类:

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 static UserService createUserService1() {
// 1. 创建目标对象
final UserService userService = new UserServiceImpl();
// 2. 生命切面类对象
final MyAspect myAspect = new MyAspect();

// 3. 把切面的两个方法应用到目标对象

// 创建增强对象
Enhancer enhancer = new Enhancer();
// 设置增强对象的父类,也就是要被增强的对象
enhancer.setSuperclass(userService.getClass());

enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

myAspect.before();

// Object obj = method.invoke(userService, objects);
// 或者这样子执行方法
Object obj = methodProxy.invokeSuper(o, objects);
myAspect.after();

return obj;

}
});
UserService userServiceProxy = (UserService) enhancer.create();
return userServiceProxy;
}

4. AOP的5中通知类型

AOP联盟为通知Advice定义了org.aopalliance.aop.Advice

Spring按照通知Advice在目标类方法的连接点位置,可以分为5类

​ •前置通知 org.springframework.aop.MethodBeforeAdvice

​ •在目标方法执行前实施增强

​ •后置通知 org.springframework.aop.AfterReturningAdvice

​ •在目标方法执行后实施增强

​ •环绕通知 org.aopalliance.intercept.MethodInterceptor

​ •在目标方法执行前后实施增强

​ •异常抛出通知 org.springframework.aop.ThrowsAdvice

​ •在方法抛出异常后实施增强

​ •引介通知 org.springframework.aop.IntroductionInterceptor

​ 在目标类中添加一些新的方法和属性

5. Spring编写半自动代理

1. 导入jar包

除了Spring的5个核心jar包之外,还需要导入2个jar包,分别是aop联盟、aop实现:

2. 目标类

3. 切面类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyAspect implements MethodInterceptor {

@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {

// 执行方法前的操作before advice
System.out.println("--start transaction--");

// 拦截方法
Object object = methodInvocation.proceed();

// 执行方法后的操作 after advice
System.out.println("--commit transaction--");

return object;
}
}

4. spring配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--业务类-->
<bean id="userService" class="cn.zhuobo.spring.service.serviceImpl.UserServiceImpl" />
<!--切面类-->
<bean id="myAspect" class="cn.zhuobo.spring.service.MyAspect" />

<!--代理对象-->
<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--业务类实现的接口,如有多个用array表示-->
<property name="interfaces" value="cn.zhuobo.spring.service.UserService"/>
<!--目标,也就是目标对象,业务类-->
<property name="target" ref="userService"/>
<!--切面类,多个用array表示-->
<property name="interceptorNames" value="myAspect" />

<!--配置是使用JDK动态代理还是cglib代理
默认是JDK动态代理,值为true表示使用cglib代理
-->
<property name="optimize" value="true" />
</bean>

5. 测试

1
2
3
4
5
6
7
@Test
public void test07() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 注意到获取的是代理对象
UserService userServiceProxy = (UserService) context.getBean("userServiceProxy");
userServiceProxy.deleteUser();
}

6. Spring全自动AOP

1. 导入jar包

1
spring-framework-3.0.2.RELEASE-dependencies\org.aspectj\com.springsource.org.aspectj.weaver\1.6.8.RELEASE.jar

2. 配置xml

3. 测试

注意到,这种配置方法是不会出现代理对象的,虽然说实际上还是代理对象在工作,根据 proxy-target-class配置使用什么代理对象。

1
2
3
4
5
6
7
8
@Test
public void test07() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

// 注意到与前面的半自动不同,这里不是获取代理对象,而是直接获取目标对象
UserService userServiceProxy = (UserService) context.getBean("userService");
userServiceProxy.deleteUser();
}

九、AspectJ

1. 介绍

  1. AspectJ是一个基于Java语言的AOP框架

  2. Spring2.0以后新增了对AspectJ切点表达式支持

  3. @AspectJ 是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面

  4. 新版本Spring框架,建议使用AspectJ方式来开发AOP

  5. 主要用途:自定义开发

2. 切入点表达式

切入点表达式用于描述作为切入点的方法

  1. Execution()
    语法:execution(修饰符 返回值 包.类.方法名(参数) throws异常)

    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 公共方法
    * 任意

    返回值,不能省略
    void 返回没有值
    String 返回值字符串
    * 任意

    包,[省略]
    com.gyf.crm 固定包
    com.gyf.crm.*.service crm包下面子包任意 (例如:com.gyf.crm.staff.service)
    com.gyf.crm.. crm包下面的所有子包(含自己)
    com.gyf.crm.*.service.. crm包下面任意子包,固定目录service,service目录任意包

    类,[省略]
    UserServiceImpl 指定类
    *Impl 以Impl结尾
    User* 以User开头
    * 任意

    方法名,不能省略
    addUser 固定方法
    add* 以add开头
    *Do 以Do结尾
    * 任意

    (参数)
    () 无参
    (int) 一个整型
    (int ,int) 两个
    (..) 参数任意

    throws ,可省略,一般不写。
  2. within():匹配包或者子包内的方法

    语法:within(com.zhuobo.service..*)

  3. this():匹配实现接口的代理对象中的方法

    语法:this(com.zhuobo.aop.user.UserDao)

  4. target():匹配实现接口的目标对象中的方法

    语法:this(com.zhuobo.aop.user.UserDao)

  5. args():匹配参数格式符合指定格式的方法

    语法:args(int, int )

  6. bean():指定的bean中的所有方法

    语法:bean(‘bean id’)

3. AspectJ通知类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
aop联盟定义通知类型,具有特性接口,必须实现,从而确定方法名称。
aspectj 通知类型,只定义类型名称,以及方法格式。
个数:6种,知道5种,掌握1中。
before:前置通知(应用:各种校验)
在方法执行前执行,如果通知抛出异常,阻止方法运行
afterReturning:后置通知(应用:常规数据处理)
方法正常返回后执行,如果方法中抛出异常,通知无法执行
必须在方法执行后才执行,所以可以获得方法的返回值。
around:环绕通知(应用:十分强大,可以做任何事情)
方法执行前后分别执行,可以阻止方法的执行
必须手动执行目标方法
afterThrowing:抛出异常通知(应用:包装异常信息)
方法抛出异常后执行,如果方法没有抛出异常,无法执行
after:最终通知(应用:清理现场)
方法执行完毕后执行,无论方法中是否出现异常

4. 基于xml的AspectJ的使用

1. 导入包

2. 编写业务类和切面类

业务类:

切面类:

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
public class MyAspect{

public void myBefore(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getName());
System.out.println("--BEFORE ADVICE--");
}

public void myAfter(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getName());
System.out.println("--AFTER ADVICE--");
}

public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println(joinPoint.getSignature().getName());
System.out.println("--before around ADVICE--");
Object object = joinPoint.proceed();
System.out.println("--after around ADVICE--");

return object;
}

public void myAfterReturning(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getName());
System.out.println("--myAfterReturning ADVICE--");
}

}

3. Spring的xml配置

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="userService" class="cn.zhuobo.spring.service.serviceImpl.UserServiceImpl" />
<!--切面类-->
<bean id="myAspect" class="cn.zhuobo.spring.service.MyAspect" />

<aop:config proxy-target-class="true">
<aop:aspect ref="myAspect">
<aop:pointcut id="myPointcut" expression="execution(* cn.zhuobo.spring.service.serviceImpl.UserServiceImpl.*(..))"/>

<!--前置通知-->
<aop:before method="myBefore" pointcut-ref="myPointcut" />

<!--后置通知:有异常就不会执行,可以返回目标对象的返回值-->
<aop:after-returning returning="returning" method="myAfterReturning" pointcut-ref="myPointcut" />

<!--相当于前置 + 后置通知-->
<aop:around method="myAround" pointcut-ref="myPointcut" />

<!--异常通知-->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="throwable" />

<!--最终通知:有无异常都会执行-->
<aop:after method="myAfter" pointcut-ref="myPointcut" />
</aop:aspect>
</aop:config>

4. 测试

1
2
3
4
5
6
7
8
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

// 直接根据ID获取bean,默认也是使用jdk实现的代理,而非使用cglib代理
UserService userService = (UserService) context.getBean("userService");
userService.deleteUser();
}

5. 给予注解的AspectJ的使用

1. 声明使用注解

1
2
3
4
<!--配置:扫描注解的包的范围-->
<context:component-scan base-package="cn.zhuobo.spring" />
<!--配置:使用注解注入-->
<aop:aspectj-autoproxy />

2. 使用注解配置业务类和切面类

原来的xml配置方式:

1
2
3
4
<!--业务类-->
<bean id="userService" class="cn.zhuobo.spring.service.serviceImpl.UserServiceImpl" />
<!--切面类-->
<bean id="myAspect" class="cn.zhuobo.spring.service.MyAspect" />

使用注解:

1
2
3
4
5
6
@Service("userService")
public class UserServiceImpl implements UserService

@Component
@Aspect // 使用@Aspect注解声明这是一个切面类
public class MyAspect{

3. 声明一个公共的切入点

@PointCut ,修饰方法 private void xxx(){} 之后通过“方法名”获得切入点引用

原来的xml配置方式:

1
<aop:pointcut id="myPointcut" expression="execution(* cn.zhuobo.spring.service.serviceImpl.UserServiceImpl.*(..))"/>

使用注解:在切面类里面创建一个方法,这个方法什么都不做,就只是添加一个注解,声明是公共的切入点

十、JDBC Template

JDBC Template是用于操作JDBC(Java DataBase Connectivity)的工具类,需要使用数据库连接池技术,其中两种比较常用的数据库连接池就是微软公司提供的dbcp、以及开源的c3p0。(c3p0有自动回收空闲连接的功能,而DBCP没有自动回收空闲连接的功能。控线连接指的是指定时间内,也就是长时间不使用的连接。DBCP的回收机制是如果连接数大于最大连接数,便回收连接。)

1. 在Spring中配置c3p0连接池,使用JDBC Template

1. 导入相关jar包

2. 在spring中配置

1
2
3
4
5
6
7
8
9
10
11
12
<!--配置一个DataSource,以供jdbc template使用-->
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="password" value="root"/>
<property name="user" value="root" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring" />
</bean>

<!--配置一个jdbc template,需要一个DataSource-->
<bean id="template" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="datasource"/>
</bean>

3. 测试

1
2
3
4
5
6
7
8
9
@Test
public void test04(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans02.xml");
JdbcTemplate template = (JdbcTemplate) context.getBean("template");

int rows = template.update("insert into t_user values (null, ?, ?)", "zhuobo2", "password2");

System.out.println("Affect rows :"+rows);
}

4. 将配置连接池的信息独立为一个文件

  1. 在类路径src目录下创建一个文件database.properties,文件内写数据库连接信息
1
2
3
4
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/spring
password=root
user=root
  1. 在spring配置文件中配置
1
2
3
4
5
6
7
8
9
10
11
<!--加载database.properties文件-->
<context:property-placeholder location="classpath:database.properties" />

<!--加载database.properties文件后使用 ${driverClass} 的方式获取值-->
<!--配置一个DataSource,以供jdbc template使用-->
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}" />
<property name="password" value="${password}"/>
<property name="user" value="${user}" />
<property name="jdbcUrl" value="${jdbcUrl}" />
</bean>

十一、事务管理

1. 事务回顾

简单事务:ABCD要么全部成功,要么全部失败;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ABCD 一个事务
Connection conn = null;
try{
//1 获得连接
conn = ...;
//2 开启事务
conn.setAutoCommit(false);
A
B
C
D
//3 提交事务
conn.commit();
} catche(){
//4 回滚事务
conn.rollback();
}

有保存点的事务:AB(必须),CD(可选)

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
Connection conn = null;
Savepoint savepoint = null; //保存点,记录操作的当前位置,之后可以回滚到指定的位置。(可以回滚一部分)
try{
//1 获得连接
conn = ...;
//2 开启事务
conn.setAutoCommit(false);
A
B
savepoint = conn.setSavepoint();
C
D
//3 提交事务
conn.commit();
} catche(){
if(savepoint != null){ //CD异常
// 回滚到CD之前
conn.rollback(savepoint);
// 提交AB
conn.commit();
} else{ //AB异常
// 回滚AB
conn.rollback();
}
}

2. 事务的传播行为

事务 描述
PROPAGATION_REQUIRED required , 必须 【默认值】 支持当前事务,A如果有事务,B将使用该事务。 如果A没有事务,B将创建一个新的事务。
PROPAGATION_SUPPORTS supports ,支持 支持当前事务,A如果有事务,B将使用该事务。 如果A没有事务,B将以非事务执行。
PROPAGATION_MANDATORY mandatory ,强制 支持当前事务,A如果有事务,B将使用该事务。 如果A没有事务,B将抛异常。
PROPAGATION_REQUIRES_NEW requires_new ,必须新的 如果A有事务,将A的事务挂起,B创建一个新的事务 如果A没有事务,B创建一个新的事务
PROPAGATION_NOT_SUPPORTED not_supported ,不支持 如果A有事务,将A的事务挂起,B将以非事务执行 如果A没有事务,B将以非事务执行
PROPAGATION_NEVER never,从不 如果A有事务,B将抛异常 如果A没有事务,B将以非事务执行
PROPAGATION_NESTED nested ,嵌套 A和B底层采用保存点机制,形成嵌套事务。

3. 手动管理事务

Spring底层使用TransactionTemplate对事务进行管理,还是比较麻烦的,可以直接使用TransactionTemplate,或者使用TransactionProxyFactoryBean生成代理管理事务,具体操作过程:

  1. Service提供一个TransactionTemplate属性
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
public class AccountServiceImpl implements AccountService {

private AcountDao accountDao; // 使用spring注入
private TransactionTemplate transactionTemplate; // 事务模板,手动使用事务

public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}

public void setAccountDao(AcountDao accountDao) {
this.accountDao = accountDao;
}

@Override
public void transfer(final String outer, final String inner, final Integer money) {
this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
accountDao.out(outer, money);
int num = 1/0; // 一个异常,出现异常这里的所有代码是一个事务,会回滚
accountDao.in(inner, money);
}
});
}
}
  1. 配置xml
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
<!--加载database.properties文件-->
<context:property-placeholder location="classpath:database.properties" />

<!--加载database.properties文件后使用 ${driverClass} 的方式获取值-->
<!--配置一个DataSource,以供jdbc template使用-->
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}" />
<property name="password" value="${password}"/>
<property name="user" value="${user}" />
<property name="jdbcUrl" value="${jdbcUrl}" />
</bean>

<!--配置一个dao,AccountDao继承了JdbcDaoSupport,提供DataSource即可注入,获取JDBCTemplate-->
<bean id="accountDao" class="cn.zhuobo.spring.dao.daoImpl.AcountDaoImpl">
<property name="dataSource" ref="datasource"/>
</bean>

<!--事务管理器,因为事务模板(TransactionTemplate)需要一个事务管理器属性-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource" />
</bean>

<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<!--需要一个事务管理器-->
<property name="transactionManager" ref="txManager"/>
</bean>

<!--配置一个service-->
<bean id="accountService" class="cn.zhuobo.spring.service.serviceImpl.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
<property name="transactionTemplate" ref="transactionTemplate" />
</bean>

4. 基于AOP的事务管理

1. 基于xml的配置

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
<context:property-placeholder location="classpath:database.properties" />

<!--配置一个DataSource,以供jdbc template使用-->
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}" />
<property name="password" value="${password}"/>
<property name="user" value="${user}" />
<property name="jdbcUrl" value="${jdbcUrl}" />
</bean>

<!--配置一个dao-->
<bean id="accountDao" class="cn.zhuobo.spring.dao.daoImpl.AcountDaoImpl">
<property name="dataSource" ref="datasource"/>
</bean>

<!--service-->
<!--配置一个service-->
<bean id="accountService" class="cn.zhuobo.spring.service.serviceImpl.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>

<!--事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource" />
</bean>

<!--基于AOP的事务配置:
1. 配置一个通知
2. 将通知应用到切点
-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/>
</tx:attributes>
</tx:advice>

<!--配置一个配置一个切点,以及切点应用的通知-->
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(* cn.zhuobo.spring.service.serviceImpl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"/>
</aop:config>

AccountService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AccountServiceImpl implements AccountService {

private AcountDao accountDao; // 使用spring注入
// 不用提供事务模板的属性
public void setAccountDao(AcountDao accountDao) {
this.accountDao = accountDao;
}

@Override
public void transfer(String outer, String inner, Integer money) {
accountDao.out(outer, money);
int num = 1 / 0; // 一个异常
accountDao.in(inner, money);
}
}

2. 注解配置事务

  1. 首先要先开启使用注解配置事务
  1. 在业务类上或者具体的业务类方法上使用注解