Spring优点
- 方便解耦,简化开发 Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护交给Spring管理
- 方便集成各种框架 Spring可以整合很多框架,比如Mybatis
- 方便程序的测试 Spring支持JUnit4,可以通过注解很方便的测试Spring程序
- 支持AOP(面向切面编程) Spring提供了面向切面编程
- 声明式事务 只需要配置就可以完成对事务的管理,无需手动编程
使用Spring需要导入的jar
- Spring-core:包含Spring框架基本的核心工具类,Spring的其他组件都要用到这个包中的类,是其他组件的基本核心
- Spring-beans:包含访问配置文件、创建和管理bean以及进行IOC或者DI操作相关的所有类
- Spring-context:Spring提供在基础IOC功能上的扩展服务,此外还提供许多企业级服务的支持,例如邮件服务、任务调度等
- Spring-expression:定义了Spring的表达式语句
- commons-logging:处理日志信息
- 使用SPring框架,只需要倒入Spring的四个基础jar和处理日志信息的commons-logging即可
IOC
IOC是指在程序开发过程中,对象实例的创建不再由调用者管理,而是由Spring容器创建,Spring容器会负责控制程序之间的关系,而不是由代码直接控制,因此,控制权由程序代码转移到了Spring容器,控制权发生了反转,即控制反转。 Spring IOC提供了两种IOC容器,分别是BeanFactory和ApplicationContext。
BeanFactory
BeanFactory是基础的IOC容器,是一个接口,提供了完整的IOC服务,BeanFactory是一个管理Bean的工厂,他主要负责初始化各种bean,并调用它们的生命周期方法 BeanFactory接口有多个实现类,最常见的是XmlBeanFactory,它根据Xml配置文件中的定义装配Bean。 创建XmlBeanFactory对象,传入xml文件
BeanFactory beanFactory = new XmlBeanFactory(newFileSystemResource("D://applicationContext.xml"));
ApplicationContext
ApplicationContext是BeanFactory的字接口,也被称为应用上下文,不仅提供了BeanFactory的所有功能,还添加了对国际化、资源访问、事件传播等方面的支持。
ApplicationContext提供了两个实现类
- ClassPathXmlApplicationContext 该类从类路径ClassPath中寻找制定的xml文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation);
configLocation参数用来指定Spring配置文件的名称和位置
- FileSystemXmlApplicationContext 该类从指定的文件系统路径中寻找指定的xml文件
* ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);
ClassPathXmlApplicationContext和FileSystemXmlApplicationContext的区别 在读取Spring的配置文件时,ClassPathXmlApplicationContext从ClassPath中读取配置文件,而FileSystemXmlApplicationContext通过configLocated参数的值从文件系统中读取xml文件。
在Java项目中,通常使用ClassPathXmlApplicationContext类实例化ApplicationContext容器,而在web项目中,ApplicationContext容器的实例化工作交给web服务器完成,web服务器实例化ApplicationContext容器通常使用基于ContextLoaderListener实现的方式,只需要编辑web。xml文件即可
<!--指定Spring配置文件的位置,有多个配置文件时,以逗号分隔-->
<context-param>
<param-name>contextConfigLocation</param-name>
<!--spring将加载spring目录下的applicationContext.xml文件-->
<param-value>
classpath:spring/applicationContext.xml
</param-value>
</context-param>
<!--指定以ContextLoaderListener方式启动Spring容器-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
BeanFactory和ApplicationContext的区别 如果Bean的某一个属性没有注入,则使用BeanFactory加载后,在第一次调用getBean方法时会抛出异常,而ApplicationContext会在初始化时检查,有利于检查所依赖的属性是否注入。
依赖注入(DI)
依赖注入和控制反转含义相同,他们是从两个角度描述同一个概念。 当某个对象实例需要另外一个对象实例时,传统的方法是由调用者创建被调用者的实例,比如使用new,而使用Spring框架后,被调用者的实例不再有调用者创建,而是交给了Spring容器,者称为控制反转。 Spring容器在创建被调用实例时,会自动将调用者需要的对象实例注入为调用者,这样,通过 Spring容器获得被调用者实例,成为依赖注入。
依赖注入的实现方式
- 属性setter注入
//创建类A
public class A {
public A() {
System.out.println("正在初始化类A,调用无参构造器A。。。");
}
public void print() {
System.out.println("调用了类A的print方法。。。");
}
}
//创建类B
public class B {
private A a;
public void setA(A a) {
this.a = a;
}
public void print() {
a.print();
}
}
//配置Beans.xml
<bean id="a" class="com.ssm.ioc.A"/>
<bean id="b" class="com.ssm.ioc.B">
<property name="a" ref="a"/>
</bean>
//测试
@Test
public void test() {
B b = (B) context.getBean("b");
b.print();
}
- 构造构造器注入
//配置Beans.xml
<bean id="a" class="com.ssm.ioc.A"/>
<bean id="b" class="com.ssm.ioc.B">
<constructor-arg name="a" ref="a"/>
</bean>
//测试
@Test
public void test() {
B b = (B) context.getBean("b");
b.print();
}
- 静态工厂方式实例化
//创建工厂类
public class MyBeanFactory {
public static A createCoffer() {
return new A();
}
}
//配置Beans.xml
<bean id="a" class="com.ssm.ioc.MyBeanFactory" factory-method="createA"/>
//测试
@Test
public void test() {
A a = (A) context.getBean("a");
}
- 实例工厂实例化
//创建工厂类
public class MyBeanFactory {
public MyBeanFactory() {
System.out.println("A工厂实例化中。。。");
}
public A createBean() {
return new A();
}
}
//配置Beans.xml
<bean id="myBeanFactory" class="com.ssm.ioc.MyBeanFactory"/>
<bean id="a" factory-bean="myBeanFactory" factory-method="createBean"/>
//测试
@Test
public void test() {
A a = (A) context.getBean("a");
}
Bean的会话状态
- 有状态会话的Bean 每个用户有自己特有的一个实例,在用户的生存期内,bean保持可用户的信息,即"有状态",一旦用户灭亡(调用结束或实例结束),bean的生命周期也会结束。
- 无状态会话的Bean bean一旦被实例化就被加到会话池中,各个用户可以公用,即使用户死亡,bean的生命周期也不一定结束,他可能依然存在于会话池中,供其他用户使用,由于没有特定的用户,也就没办法保存用户的状态,所以叫无状态Bean,但无状态Bean并非没有状态,如果它有自己的属性,那么这些属性就会受到所有调用它的用户的影响。
Servlet的线程安全问题
Servlet体系结构是建立在Java多线程机制上的,它的生命周期是由web容器负责的,一个Servlet类Application中只有一个实例存在,也就是有多个线程在使用这个实例,这是单例模式的使用。Servlet本身是无状态的,无状态的单例是线程安全的,但是如果在Servlet中使用了实例变量,那么就会变成有状态了,是非线程安全的。
如何解决Servlet的线程安全问题
- 避免使用实例变量
- 避免使用非线程安全集合
- 在多个Servlet中对某个外部对象的修改进行加锁操作
Spring中Bean的作用域
- 在Spring配置文件中,使用的scope属性设置Bean的作用域
- singleton 单例模式,使用singleton定义的Bean在Spring容器中只有一个实例,这也是Bean的默认作用域,所有的Bean请求,只要id与该Bean定义相匹配,就只会返回Bean的同一个实例。适用于无回话状态的Bean,例如(DAO层、Service层)。
- prototype 原型模式,每次通过Spring容器获取prototype定义的Bean时,容器都会创建一个新的Bean实例,适用于需要需要保持会话状态的Bean(比如Struts2的Action类)。
- request 在一次HTTP请求中,容器会返回该Bean的同一个实例,而对于不同的HTTP请求,会返回不同的实例,该作用域仅在当前HttpRequest内有效
- session 在一次HttpSession中,容器会返回该Bean的同一个实例,而对于不同的HTTP请求,会返回不同的实例,该作用域仅在当前HttpSession内有效
- global Session 在一个全局的session中,容器会返回该Bean的同一个实例,该作用域仅在使用portlet context时有效。
Spring Bean的生命周期
- Spring容器可以管理singleton作用域Bean的生命周期,在此作用域下,Spring能够精确的知道该Bean何时被创建,何时初始化完成,以及何时被销毁。
- 而对于prototype作用域的Bean,Spring只负责创建,当容器创建了Bean的实例后,Bean的实例就交给客户端代码管理,Spring容器不再跟踪其生命周期,每次客户端请求prototype作用域的bean时,Spring容器都会创建一个新的Bean,并且不会管那些被配置成prototype作用域的Bean的生命周期。
当一个Bean被加载到Spring容器时,就具有了生命周期,而Spring容器在保证一个Bean能够使用之前,会进行很多工作
Bean的生命周期的执行过程
- 根据配置文件的配置调用Bean的构造方法或者工厂方法实例化Bean
- 利用依赖注入完成Bean中所有属性值的配置注入
- 如果Bean实现了BeanNameAware接口,则Spring调用Bean的setBeanName方法传入当前Bean的id值
- 如果Bean实现了BeanFactoryAware接口,则Spring调用setBeanFactory方法传入当前工厂实例的引用
- 如果Bean实现了ApplicationContextAware接口,则Spring调用setApplicationContext方法传入当前ApplicationContext实例的引用
- 如果BeanPostProcessor和Bean关联,则Spring将调用该接口的预初始化方法postProcessBeforeInitialization对Bean进行加工,Spring AOP就是利用它实现的
- 如果Bean实现了InitializingBean接口,则Spring调用afterPropertiesSet方法
- 如果在配置文件中通过init-method属性指定了初始化方法,则调用该初始化方法
- 如果BeanPostProcessor 和Bean关联,则Spring将调用该接口的初始化方法postPrecessAfterInitialization,此时,Bean已经可以使用
- 如果在中指定了该Bean的作用范围为scope="singleton",则将该Bean放入Spring IOC的缓存池中,将触发Spring对该Bean的生命周期管理,若在中指定了该Bean的作用范围为scope="prototype",则将该Bean交给调用者,调用者管理该Bean的生命周期,Spring不再管理该Bean
- 如果Bean实现了DisposableBean接口,则Spring会调用destroy方法将Spring中的Bean销毁,如果在配置文件中通过destroy-method属性指定了Bean的销毁方法,则Spring将调用该方法对Bean进行销毁
Spring基于XML装配Bean
设值注入和构造器注入
在实例化Bean的过程中,首先调用默认的构造方法实例Bean对象,然后通过java的反射机制调用setter方法对属性进行注入,因此设置注入要求一个Bean的对应类必须满足以下两点要求
- 必须提供一个默认的无参构造方法
- 必须为需要注入的属性提供setter方法
使用设值注入时,在Spring配置文件中,需要使用元素的子元素元素为每个属性注入值,而使用构造注入时,在配置文件中,主要使用标签定义构造方法的参数,可以使用value属性设置该参数的值。
Annotation(注解)注入
JDK1.5之后,提供了Annotation功能,Spring也提供了对Annotation的全面支持,Spring3中定义了一系列注解
- @Component:可以使用此注解描述Spring中的Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean),并且可以用在任何层次
- @Repository:用于将数据访问层(DAO层)的类标示为Spring中的Bean
- @Service:通常用作在业务层(Service层),用于将业务层的类标示为Spring中的Bean
- @Controlle:通常作用在控制层,用于将控制层的类标示为Spring中的Bean
- @Autowired:用于Bean的属性变量、属性的Set方法及构造函数进行标注,配合对应的注解处理器完成Bean的自动配置工作,默认按照Bean的类型进行装配
- @Resource:作用与Autowired一样,区别是@Autowired默认按照Bean的类型装配,@而Resource默认按照Bean的实例类型进行装配,@Resource有name、type属性,Spring将name属性解析为Bean实例名称,将type属性解析为Bean的梳理类型,如果指定name属性,则按照实例名称进行装配,如果指定type属性,按照Bean类型进行装配,如果都不指定,则先按照Bean实例名称装配,如果不能装配,则按照Bean的类型进行装配,如果都不能匹配,抛出NoSuchBeanDefinitionException异常
- @Qualifier:与@Autowired配合使用,会将默认的按照Bean配型装配修改为按Bean的实例名称装配,Bean的实例名称由@qualifier注解的参数指定
@Resource和@Autowired的区别
- @Resource和@Autowired都是做bean的注入时使用
- @Resource不是Spring中的注解,但是Spring支持该注解,而@Autowired是Spring的注解
- @Autowired是按照类型(byType)来装配Bean的,不回去匹配name,默认情况下他要求依赖对象必须存在,如果需允许null,可以设置它的required属性为false,如果想让@Autowired按照名称(byName)来装配,则需要配合@Qualifier一起使用
@Resource注解装配步骤
- 如果同时指定了name和type属性,则从Spring上下文中找到唯一匹配的Bean进行装配,找不到抛出异常
- 如果指定了name,则从上下文中查找名称(id)匹配的Bean进行装配,找不到抛出异常
- 如果指定了type,则从上下文查找类型匹配的唯一Bean进行装载,找不到或者是找到了多个,抛出异常
- 如果即没有指定name,也没有指定type,则默认按照byName进行查找并装载,如果没有匹配,则按照byType进行匹配并装载
自动装配
自动装配是指Spring容器可以自动装配(autowire)相互协作的Bean之间的关系,将一个Bean注入其他Bean的Property中。 要使用自动装配,就需要配置元素的autowire值,autowire属性有五个
- byName:根据Property的name自动装配,如果一个Bean的name和另外一个Bean中的Property相同,则会自动装配这个Bean到Property中
- byType:根据Property的数据类型自动装配,如果一个Bean的数据类型兼容另外一个Bean中Property的数据类型,则自动装配
- constructor:根据构造方法的参数的数据类型,进行byType模式的自动装配
- autodetect:如果发现默认的构造方法,则用constructor,否则有byType
- no:默认情况下,不实用自动装配,Bean依赖必须通过ref元素定义
AOP面向切面编程
AOP概述
- AOP和OOP类似,也是一种编程模式,Spring AOP是基于AOP编程模式的一个框架,它的使用有效减少了系统间的重复代码,达到了模块间解耦的作用。
- AOP的全程是"Aspect Oriented Programming",即面向切面编程,它将业务逻辑的各部分进行隔离,使开发人员在编写业务逻辑时可以专心核心业务,从而提高开发基础。
- AOP采取横向抽取机制,取代了传统的纵向继承体系,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
- 目前最流行的AOP有Spring AOP和AspectJ
- Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。
- AspectJ是一个基于Java语言的AOP框架,从Spring2.0开始,Spring AOP引入了对AspectJ的支持。
AOP相关术语
名称 | 说明 |
---|---|
Joinpoint(连接点) | 指那些被拦截到的点,在Spring中,可以被动态代理拦截目标类的方法 |
Pointcut(切入点) | 指要对哪些Joinpoint进行拦截,即被拦截的连接点 |
Advice(通知) | 指拦截到Joinpoint之后要做的事情,即对切入点增强的内容 |
Target(目标) | 指代理的目标对象 |
Weaving(植入) | 指把增强代码应用到目标上,生成代理对象的过程 |
Proxy(代理) | 指生成的代理对象 |
Aspect(切面) | 切入点和通知的结合 |
JDK动态代理是通过JDK中的Java.lang.reflect.Proxy实现的
使用JDK中的Proxy实现动态代理
//创建接口
public interface CustomerDao {
public void add(); // 添加
public void update(); // 修改
public void delete(); // 删除
public void find(); // 查询
}
//创建接口实现类
public class CustomerDaoImpl implements CustomerDao {
public void add() {
System.out.println("添加客户...");
}
public void update() {
System.out.println("修改客户...");
}
public void delete() {
System.out.println("删除客户...");
}
public void find() {
System.out.println("修改客户...");
}
}
//创建切面类
public class MyAspect {
public void myBefore() {
System.out.println("方法执行之前");
}
public void myAfter() {
System.out.println("方法执行之后");
}
}
//创建代理类
import com.mengma.dao.CustomerDao;
import com.mengma.dao.CustomerDaoImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyBeanFactory {
public static CustomerDao getBean() {
//准备目标类
final CustomerDao customerDao = new CustomerDaoImpl();
//创建切面类实例
final MyAspect myAspect = new MyAspect();
//使用代理类,进行增强
return (CustomerDao) Proxy.newProxyInstance(
//当前类加载器
MyBeanFactory.class.getClassLoader(),
//所创建实例的实现类接口
new Class[]{CustomerDao.class},
//需要增强的方法
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
myAspect.myBefore();
Object obj = method.invoke(customerDao, args);
myAspect.myBefore();
return obj;
}
}
);
}
}
//创建测试类
import com.mengma.dao.CustomerDao;
import com.mengma.jdk.MyBeanFactory;
import org.junit.Test;
public class TestAOP {
@Test
public void test() {
CustomerDao customerDao = MyBeanFactory.getBean();
customerDao.add();
customerDao.delete();
customerDao.find();
customerDao.update();
}
}
输出结果 evernotecid://73D3823C-134C-4C14-AE11-0F871EA0D1DE/appyinxiangcom/27476071/ENResource/p4
在调用目标类的方法前后,成功调用了增强的代码使用JDK自带的Proxy实现动态代理的局限性
- JDK动态代理必须要实现一个或多个接口,如果不希望实现接口,可以使用CGLIB代理
- CGLIB代理底层是通过使用一个小而快的字节码处理框架ASM(Java 字节码操控框架)转换字节码并生成新的类。因此CGLIB要依赖于ASM的jar包
- Spring的core核心包中已经集成了CGLIB所需要的包
CGLIB实现动态代理
//创建目标类
public class GoodsDao {
public void add() {
System.out.println("添加商品...");
}
public void update() {
System.out.println("修改商品...");
}
public void delete() {
System.out.println("删除商品...");
}
public void find() {
System.out.println("修改商品...");
}
}
//创建切面类
public class MyAspect {
public void myBefore() {
System.out.println("方法执行之前");
}
public void myAfter() {
System.out.println("方法执行之后");
}
}
//创建代理类
import com.mengma.dao.GoodsDao;
import com.mengma.jdk.MyAspect;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyBeanFactory {
public static GoodsDao getBean() {
//创建目标类
final GoodsDao goodsDao = new GoodsDao();
//创建切面类实例
final MyAspect myAspect = new MyAspect();
//生成代理类,CGLIB在运行时,生成指定对象的子类,增强
Enhancer enhancer = new Enhancer();
//确定需要增强的类
enhancer.setSuperclass(goodsDao.getClass());
//添加回调函数
enhancer.setCallback(new MethodInterceptor() {
//intercept相当于jdk invoke
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
myAspect.myBefore();
Object obj = method.invoke(goodsDao, args);
myAspect.myAfter();
return obj;
}
});
GoodsDao goodsDaoProxy = (GoodsDao) enhancer.create();
return goodsDaoProxy;
}
}
//创建测试类
@Test
public void testCGLIB() {
GoodsDao goodsDao = com.mengma.cglib.MyBeanFactory.getBean();
goodsDao.add();
goodsDao.delete();
goodsDao.find();
goodsDao.update();
}
运行结果
Spring通知类型
通知(Advice)是对目标切入点进行增强的内容。Spring为通知(Advice)提供了Advice接口,Spring通知按照在目标类方法的连接点位置,可分为五种类型
名称 | 说明 |
---|---|
MethodBeforeAdvice(前置通知) | 在方法之前自动执行的通知成为前置通知,可以应用于权限管理功能 |
AfterReturningAdvice(后置通知) | 在方法执行之后自动执行的通知成为后置通知,可用于关闭流、上传文件、删除临时文件等功能 |
MethodInterceptor(环绕通道) | 在方法前后自动执行的通知成为环绕通知,可以应用于日志、事务管理等功能 |
ThrowsAdvice(异常通知) | 在方法抛出异常时自动执行的通知成为异常通知,可以应用于处理异常记录日志等功能 |
IntroductionInterceptor(引介通知) | 在目标类中添加一些新的方法和属性,可以应用于修改旧版本程序(增强类) |
生命Spring AOP
Spring创建了一个AOP代理的基本方法是使用ProxyFactoryBean,这个类对应的切入点和通知提供了完整的控制能力,并可以生成指定的内容
ProxyFactoryBean类中的常用可配置属性
属性名称 | 描述 |
---|---|
target | 代理的目标对象 |
proxyInterfaces | 代理要实现的接口,如果有多个接口,则可以使用list标签赋值 |
proxyTargetClass | 是否对类代理而不是接口,设置为true时,使用CGLIB代理 |
interceptorNames | 需要植入目标的Advice |
singleton | 返回的代理是否为单例,默认为true(单例) |
optimize | 设置为true时,强制使用CGLIB |
环绕通知案例
//倒入Spring-aop依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
//创建切面类
//需要实现接口,确定哪个通知,及告诉Spring应该执行哪个方法
public class MyAspect implements MethodInterceptor {
public Object invoke(MethodInvocation mi) throws Throwable {
System.out.println("方法执行之前");
// 执行目标方法
Object obj = mi.proceed();
System.out.println("方法执行之后");
return obj;
}
}
在上述代码中,MyAspect实现了MethodInterceptor接口,并重写了invoke方法,MethodInterceptor是Spring AOP的jar包提供的,而invoke方法用于确定目标方法mi,并告诉Spring要在目标方法前后执行哪些方法。
//创建Spring配置文件
<?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 id="customerDao" class="com.mengma.dao.CustomerDaoImpl"/>
<!-- 通知 advice -->
<bean id="myAspect" class="com.mengma.factorybean.MyAspect"/>
<!--生成代理对象 -->
<bean id="customerDaoProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!--代理实现的接口 -->
<property name="proxyInterfaces" value="com.mengma.dao.CustomerDao"/>
<!--代理的目标对象 -->
<property name="target" ref="customerDao"/>
<!--用通知增强目标 -->
<property name="interceptorNames" value="myAspect"/>
<!-- 如何生成代理,true:使用cglib; false :使用jdk动态代理 -->
<property name="proxyTargetClass" value="true"/>
</bean>
</beans>
首先配置了目标类和通知,然后使用ProxyFactoryBean类生成代理对象。
//创建测试类
@Test
public void testAOP() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
CustomerDao customerDao = (CustomerDao) applicationContext.getBean("customerDaoProxy");
customerDao.update();
customerDao.find();
customerDao.delete();
customerDao.add();
}
执行结果
Spring使用AspectJ开发AOP,基于XML和Annotation
使用AspectJ开发AOP的两种方式
- 基于XML的声明式
- 基于Annotation的声明式
基于XML的声明式开发
基于XML的声明式是指通过Spring配置文件的方式定义切面、切入点及声明通知,而所有的切面和通知都必须定义在aop:config元素中。
//倒入spring-aspects依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
//创建切面类
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
//切面类
public class MyAspect {
// 前置通知
public void myBefore(JoinPoint joinPoint) {
System.out.print("前置通知,目标:");
System.out.print(joinPoint.getTarget() + "方法名称:");
System.out.println(joinPoint.getSignature().getName());
}
// 后置通知
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print("后置通知,方法名称:" + joinPoint.getSignature().getName());
}
// 环绕通知
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
throws Throwable {
System.out.println("环绕开始"); // 开始
Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法
System.out.println("环绕结束"); // 结束
return obj;
}
// 异常通知
public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("异常通知" + "出错了" + e.getMessage());
}
// 最终通知
public void myAfter() {
System.out.println("最终通知");
}
}
//创建Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!--目标类 -->
<bean id="customerDao" class="com.mengma.dao.CustomerDaoImpl"/>
<!--切面类 -->
<bean id="myAspect" class="com.mengma.aspectJ.xml.MyAspect"/>
<!--AOP 编程 -->
<aop:config>
<aop:aspect ref="myAspect">
<!--配置切入点,通知最后增强哪些方法-->
<aop:pointcut expression="execution ( * com.mengma.dao.*.* (..))" id="myPointCut"/>
<!--前置通知,关联通知Advice和切入点PointCut-->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<!--后置通知,在方法返回后执行,就可以获得返回值returning属性-->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="joinPoint"/>
<!--环绕通知-->
<aop:around method="myAround" pointcut-ref="myPointCut"/>
<!--抛出通知:用于处理程序发生异常,可以接收当前方法产生的异常 -->
<!-- *注意:如果程序没有异常,则不会执行增强 -->
<!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
<!--最终通知:无论程序发生任何事情,都将执行 -->
<aop:after method="myAfter" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
//创建测试类
@Test
public void testAspectJForXml() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
CustomerDao customerBean = (CustomerDao) applicationContext.getBean("customerDao");
customerBean.add();
}
执行结果
基于Annotation的声明式
AspectJ允许使用注解定义切面、切入点和增强处理,而Spring框架可以识别并根据注解生成AOP代理
名称 | 说明 |
---|---|
@Aspect | 用于定义一个切面 |
@Before | 用于定义前置通知,相当于BeforeAdvice |
@AfterReturning | 用于定义后置通知,相当于AfterReturningAdvice |
@Around | 用于定义环绕通知,相当于MethodInterceptor |
@AfterThrowing | 用于定义抛出异常,相当于ThrowAdvice |
@After | 用于定义最终final通知,不管是否异常,该通知都会执行 |
DeclareParents | 用于定义引介通知,相当于IntroductionInterceptor |
开发案例
//创建切面类
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
//切面类
@Aspect
@Component
public class MyAspect {
// 用于取代:<aop:pointcut
// expression="execution(*com.mengma.dao..*.*(..))" id="myPointCut"/>
// 要求:方法必须是private,没有值,名称自定义,没有参数
@Pointcut("execution(*com.mengma.dao..*.*(..))")
private void myPointCut() {
}
// 前置通知
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint) {
System.out.print("前置通知,目标:");
System.out.print(joinPoint.getTarget() + "方法名称:");
System.out.println(joinPoint.getSignature().getName());
}
// 后置通知
@AfterReturning(value = "myPointCut()")
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print("后置通知,方法名称:" + joinPoint.getSignature().getName());
}
// 环绕通知
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
throws Throwable {
System.out.println("环绕开始"); // 开始
Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法
System.out.println("环绕结束"); // 结束
return obj;
}
// 异常通知
@AfterThrowing(value = "myPointCut()", throwing = "e")
public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("异常通知" + "出错了" + e.getMessage());
}
// 最终通知
@After("myPointCut()")
public void myAfter() {
System.out.println("最终通知");
}
}
//在CustomerDaoImpl目标类中添加注解@Repository("customerDao")
//创建ApplicationContext.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描含com.mengma包下的所有注解-->
<context:component-scan base-package="com.mengma"/>
<!-- 使切面开启自动代理 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
/。创建测试类
@Test
public void testAspectJForAnnottion() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
CustomerDao customerDao = (CustomerDao) applicationContext.getBean("customerDao");
customerDao.add();
}
执行结果
Spring JDBCTemplate
Spring JDBCTemplate概述
- Spring框架针对数据库开发中的应用提供了JDBCTemplate类,该类是Spring对JDBC支持的核心,提供了对所有数据库操作功能的支持。
- Spring框架提供的JDBC支持主要由四个包组成,分别是core(核心包)、object(对象包)、dataSource(数据源包)和support(支持包),JdbcTemplate类在核心包中,作为Spring JDBC的核心,JdbcTemplate类包含了所有数据库操作的基本方法。
- JdbcTemplate类继承自抽象类JdbcAccessor,同时实现了JdbcOperations接口,直接父类JdbcAccessor为子类提供了一些访问数据库时使用的公共属性
JdbcAccessor提供的公共属性
- DataSource:获取数据库连接,具体实现时还可以引入对数据库连接的缓冲池和分布式事务的支持,它可以作为访问数据库资源的标准接口
- SQLExceptionTranslator:SQLExceptiontranslator接口负责对SQLException进行转译工作,通过必要的设置或者获取SQLExceptionTranslator中的方法,可以使JdbcTemplate在需要处理SQLException时,委托SQLExceptionTranslator的实现类完成相关的转译工作。
- JdbcOperations接口定义了在JdbcTemplate类中可以使用的操作集合,包括添加、修改、查询和删除工作
Spring中JDBC的相关配置
<?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 id="dataSource" class="org.springframework.jdbc.dataSource.DriverManagerDataSource">
<!--数据库驱动-->
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<!--连接数据库的url-->
<property name= "url" value="jdbc:mysql://localhost/spring" />
<!--连接数据库的用户名-->
<property name="username" value="root" />
<!--连接数据库的密码-->
<property name="password" value="root" />
</bean>
<!--配置JDBC模板-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.jdbcTemplate">
<!--默认必须使用数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置注入类-->
<bean id="xxx" class="xxx">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
...
</beans>
Spring事务管理
Spring的事务管理是基于AOP实现的,而AOP是以方法为单位的,Spring的事务属性分别为传播行为、隔离界别、只读和超时属性,这些属性提供了事务的方法和描述策略
Spring事务管理的三个核心接口
- PlatformTransactionManager:Spring提供的平台事务管理器,用于管理事务,提供了三个方法:getTransaction用于获取事务状态信息,commit用于提交事务,rollback用于事务的回滚,在项目中,Spring将xml中配置的事务详细信息封装到对象TransactionDefinition中,然后通过事务管理器的getTransaction方法获取事务的状态(TransactionStatus),并对事务进行下一步的操作
- TransactionDefinition接口是事务定义的接口,提供了获取事务相关信息的方法
方法 | 描述 |
---|---|
getName | 获取事务对象名称 |
getIsolationLevel | 获取事务的隔离级别 |
getPropagationBehavior | 获取事务的传播行为 |
getTimeout | 获取事务的超时时间 |
isReadOnly | 获取事务是否只读 |
事务的传播行为是指在同一个方法中,不同操作前后所使用的事务
属性名称 | 值 | 描述 |
---|---|---|
PROPAGATION_REQUIRED | required | 支持当前事务。如果 A 方法已经在事务中,则 B 事务将直接使用。否则将创建新事务 |
PROPAGATION_SUPPORTS | supports | 支持当前事务。如果 A 方法已经在事务中,则 B 事务将直接使用。否则将以非事务状态执行 |
PROPAGATION_MANDATORY | mandatory | 支持当前事务。如果 A 方法没有事务,则抛出异常 |
PROPAGATION_REQUIRES_NEW | requires_new | 将创建新的事务,如果 A 方法已经在事务中,则将 A 事务挂起 |
PROPAGATION_NOT_SUPPORTED | not_supported | 不支持当前事务,总是以非事务状态执行。如果 A 方法已经在事务中,则将其挂起 |
PROPAGATION_NEVER | never | 不支持当前事务,如果 A 方法在事务中,则抛出异常 |
PROPAGATION.NESTED | nested | 嵌套事务,底层将使用 Savepoint 形成嵌套事务 |
在事务管理过程中,传播行为可以控制是否需要创建事务以及如何创建事务,通常情况下,数据的查询不会改变事务的传播行为,所以不需要进行事务管理,而对于数据的增加、修改和删除等操作,必须进行事务管理,如果没有指定事务的传播行为,Spring默认的传播行为是required。
- TransactionStatus,描述事务的状态,它描述了某一时间点上事务的状态信息,包含六个操作
名称 | 说明 |
---|---|
flush | 刷新事务 |
hasSavepoint | 获取事务是否在保存点 |
isCompleted | 获取事务是否完成 |
isNewTransaction | 获取是否是新事务 |
isRollbackOnly | 获取是否回滚 |
setRollbackonly | 设置事务回滚 |
Spring的事务管理由两种方式
- 传统的编程式事务管理,通过编写代码实现的事务管理
- 基于AOP技术实现声明式的事务
基于AOP的声明式事务的实现方式
- 基于XML方式的声明式事务
- 通过Annotation注解方式的事务管理
基于XML方式的声明式事务
以银行转帐为例展示Spring的声明式事务
//新建一个数据库
CREATE DATABASE spring;
USE spring;
CREATE TABLE account (
id INT (11) PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(20) NOT NULL,
money INT DEFAULT NULL
);
INSERT INTO account VALUES (1,'zhangsan',1000);
INSERT INTO account VALUES (2,'lisi',1000);
//创建 c3p0-db.properties
jdbc.driverClass = com.mysql.jdbc.Driver
jdbc.jdbcUrl = jdbc:mysql://localhost:3306/spring
jdbc.user = root
jdbc.password = root
//实现 DAO
//创建 AccountDao 接口
//创建一个名为 com.mengma.dao 的包,在该包下创建一个接口 AccountDao
public interface AccountDao {
// 汇款
public void out(String outUser, int money);
// 收款
public void in(String inUser, int money);
}
//创建DAO层接口实现类
//创建一个名为 com.mengma.dao.impl 的包,在该包下创建实现类 AccountDaoImpl
import org.springframework.jdbc.core.JdbcTemplate;
import com.mengma.dao.AccountDao;
public class AccountDaoImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
// 汇款的实现方法
public void out(String outUser, int money) {
this.jdbcTemplate.update("update account set money =money-? where username =?", money, outUser);
}
// 收款的实现方法
public void in(String inUser, int money) {
this.jdbcTemplate.update("update account set money =money+? where username =?", money, inUser);
}
}
//实现 Service
//创建 Service 层接口
//创建一个名为 com.mengma.service 的包,在该包下创建接口 AccountService
public interface AccountService {
// 转账
public void transfer(String outUser, String inUser, int money);
}
//创建 Service 层接口实现类
//创建一个名为 com.mengma.service.impl 的包,在该包下创建实现类AccountServiceImpl
import com.mengma.dao.AccountDao;
public class AccountServiceImpl {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(String outUser, String inUser, int money) {
this.accountDao.out(outUser, money);
this.accountDao.in(inUser, money);
}
}
//创建 Spring 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!-- 加载properties文件 -->
<context:property-placeholder location="classpath:c3p0-db.properties" />
<!-- 配置数据源,读取properties文件信息 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}" />
<property name="jdbcUrl" value="${jdbc.jdbcUrl}" />
<property name="user" value="${jdbc.user}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- 配置jdbc模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置dao -->
<bean id="accountDao" class="com.mengma.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
<!-- 配置service -->
<bean id="accountService" class="com.mengma.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<!-- 事务管理器,依赖于数据源 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 编写通知:对事务进行增强(通知),需要编写切入点和具体执行事务的细节 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 给切入点方法添加事务详情,name表示方法名称,*表示任意方法名称,propagation用于设置传播行为,read-only表示隔离级别,是否只读 -->
<tx:method name="find*" propagation="SUPPORTS"
rollback-for="Exception" />
<tx:method name="*" propagation="REQUIRED" isolation="DEFAULT"
read-only="false" />
</tx:attributes>
</tx:advice>
<!-- aop编写,让Spring自动对目标生成代理,需要使用AspectJ的表达式 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut expression="execution(* com.mengma.service.*.*(..))"
id="txPointCut" />
<!-- 切面:将切入点与通知整合 -->
<aop:advisor pointcut-ref="txPointCut" advice-ref="txAdvice" />
</aop:config>
</beans>
// 创建测试类
public class AccountTest {
@Test
public void test() {
// 获得Spring容器,并操作
String xmlPath = "applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
xmlPath);
AccountService accountService = (AccountService) applicationContext
.getBean("accountService");
accountService.transfer("zhangsan", "lisi", 100);
}
}
执行结果
模拟转账失败
//将transfer方法修改为如下
public void transfer(String outUser, String inUser, int money) {
this.accountDao.out(outUser, money);
//模拟断电
int i = 1/0;
this.accountDao.in(inUser, money);
}
执行结果
由此可知,表中数据并没有发生改变,则表示事务不能正常提交,Spring事务管理生效基于Annotation实现声明式事务
//修改applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
">
<!-- 加载properties文件 -->
<context:property-placeholder location="classpath:c3p0-db.properties"/>
<!--扫描含com.mengma包下的所有注解-->
<context:component-scan base-package="com.mengma"/>
<!-- 配置数据源,读取properties文件信息 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置jdbc模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 事务管理器,依赖于数据源 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 注册事务管理驱动 -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
//加注解
import com.mengma.dao.AccountDao;
import com.mengma.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false)
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(String outUser, String inUser, int money) {
this.accountDao.out(outUser, money);
this.accountDao.in(inUser, money);
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import com.mengma.dao.AccountDao;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
// 汇款的实现方法
public void out(String outUser, int money) {
this.jdbcTemplate.update("update account set money =money-? where username =?", money, outUser);
}
// 收款的实现方法
public void in(String inUser, int money) {
this.jdbcTemplate.update("update account set money =money+? where username =?", money, inUser);
}
}
执行结果和使用XML实现结果相同