创作不易,各位看官点赞收藏.
Spring:是一个开源框架,它由[Rod Johnson](https://baike.baidu.com/item/Rod Johnson)创建。它是为了解决企业应用开发的复杂性而创建的。Spring 使用基本的 JavaBean 来完成以前只可能由EJB完成的事情。Spring 的用途不仅限于服务器端的开发,从简单性、可测试性和松耦合的角度而言,任何 Java 应用都可以从 Spring 中受益。
Spring官网:https://spring.io/
Spring 的7大模块:
IOC 底层主要使用的是 xml 文件解析、工厂模式、反射来实现的。IOC 思想就是基于 IOC 容器,IOC 容器底层就是一个对象工厂,是一种思想并不是一种技术。
<bean id="user" class="com.entity.User">bean>
class Factory{
public static User getUser(){
// 进行xml解析,获取类的全限定类名(class属性对应的值)
String clazzValue = .....;
Class clazz = Class.forName(clazzValue); // 通过反射获取对象的Class对象
return (User)clazz.newInstance(); // 通过反射,无参构造器来创建一个对象
}
}
IOC 工厂的两个接口:BeanFactory、ApplicationContext。这两个接口的作用都可以通过加载配置文件,通过工厂这个过程去创建对象。
@Test
public void test1(){
// 它在加载配置文件时不会创建对象,它使用的是懒汉式加载,在使用某个对象的时候才会去创建这个对象
BeanFactory applicationContext = new ClassPathXmlApplicationContext("services.xml");
// 创建并获取对象
UserDaoImpl impl = applicationContext.getBean("userDaoImpl", UserDaoImpl.class);
}
@Test
public void test2(){
// 它在加载配置文件时就创建了对象,采用的是饿汉式
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("services.xml");
// 获取对象
UserDaoImpl impl = applicationContext.getBean("userDaoImpl", UserDaoImpl.class);
}
它有两个重要的实现类:FileSystemXmlApplicationContext、ClassPathXmlApplication 这两个类都是用于加载配置文件的,就是加载配置文件的路径不同。前者加载文件路径必须是绝对路径,而后者加载文件路径是相对于 src 路径下的。
Bean管理:在Spring容器中Bean管理包含两个步骤,Spring 创建对象、Spring 注入属性。
<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="user1" class="com.xiaotanke.entity.User" >bean>
beans>
// 设置类的属性和属性的set方法
public class User {
private String name;
private int age;
private boolean isMan;
private List<String> hobbies;
private String[] array;
private Map<String,String> map;
private User user;
private Properties properties;
public void setProperties(Properties properties) {this.properties = properties;}
public void setUser(User user) {this.user = user;}
public void setArray(String[] array) {this.array = array;}
public void setName(String name) { this.name = name;}
public void setAge(int age) {this.age = age;}
public void setMan(boolean man) {isMan = man;}
public void setHobbies(List<String> hobbies) {this.hobbies = hobbies;}
public void setMap(Map<String, String> map) {this.map = map;}
}
<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="user1" class="com.xiaotanke.entity.User" >
<property name="name" value="zhangsan"/>
<property name="age" value="10"/>
<property name="man" value="true"/>
<property name="hobbies">
<list>
<value>参数一value>
<value>参数二value>
<value>参数三value>
list>
property>
<property name="array">
<array>
<value>值1value>
<value>值2value>
<value>值3value>
array>
property>
<property name="map">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
map>
property>
<property name="user" ref="user2"/>
<property name="properties">
<props>
<prop key="学号">001prop>
<prop key="身份证号">002prop>
<prop key="姓名">张三prop>
props>
property>
bean>
<bean id="user2" class="com.xiaotanke.entity.User"/>
beans>
<bean id="user3" class="com.xiaotanke.entity.User">
<constructor-arg name="name" value="张三"/>
<constructor-arg name="age" value="10"/>
<constructor-arg name="isMan" value="true"/>
<constructor-arg name="hobbies">
<list>
<value>参数一value>
<value>参数二value>
<value>参数三value>
list>
constructor-arg>
<constructor-arg name="array">
<array>
<value>值1value>
<value>值2value>
<value>值3value>
array>
constructor-arg>
<constructor-arg name="map">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
map>
constructor-arg>
<constructor-arg name="user" ref="user1"/>
<constructor-arg name="properties">
<props>
<prop key="姓名">张三prop>
props>
constructor-arg>
bean>
<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="user4" class="com.xiaotanke.entity.User" p:name="张三" p:age="10" p:user-ref="user1"/>
beans>
<bean id="user5" class="com.xiaotanke.entity.User">
<property name="name">
<value>
value>
property>
bean>
<bean id="user6" class="com.xiaotanke.entity.User">
<property name="users">
<list>
<ref bean="user1"/>
<ref bean="user2"/>
<ref bean="user3"/>
list>
property>
bean>
在Spring中有两种类型的bean,普通bean和工厂bean (FactoryBean)。
<bean id="user" class="com.xiaotanke.entity.User"/>
工厂bean:配置一种类型,但是在返回的时候可以返回与配置不同的其它类型的对象。需要在类中实现FactoryBean接口,并重写接口的方法。
public class Student implements FactoryBean<User> {
/**
* 设置这个类再获取时的类型
*/
@Override
public User getObject() throws Exception {
return new User();
}
/**
* 获取时对象的Class对象
*/
@Override
public Class<?> getObjectType() {
return User.class;
}
/**
* 设置对象在Spring容器中是否单例
*/
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}
<bean id="s" class="com.xiaotanke.entity.Student"/>
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean.xml");
// 从Spring容器中获取s这个student对象时,它返回的是一个User类型的对象,说明这是一个工厂bean
User s = context.getBean("s", User.class);
System.out.println(s);
}
在Spring中可以设置创建对象是单实例和多实例,默认情况下是单例 bean。
单例对象:在Spring中只创建了一个对象,全局上下文中获取都是同一个对象,在容器初始化时就会创建对象。(默认是单例对象)
<bean id="user101" class="com.xiaotanke.entity.User" scope="singleton"/>
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean.xml");
User user1 = context.getBean("user101", User.class);
User user2 = context.getBean("user101", User.class);
System.out.println(user1 == user2); // 输出为true,说明两个对象是相同的
}
原型对象:从Spring容器中获取对象时,会新创建一个对象并返回,这个对象是在获取对象是才会创建。
<bean id="user101" class="com.xiaotanke.entity.User" scope="prototype"/>
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean.xml");
User user1 = context.getBean("user101", User.class);
User user2 = context.getBean("user101", User.class);
System.out.println(user1 == user2); // 输出为false,说明获取对象时新创建了一个对象
}
注意事项:
在Spring中,Bean的生命周期就是对象的创建到对象的销毁的这个过程,主要包含几个步骤。
<bean id="admin" class="com.xiaotanke.entity.Admin">bean>
<bean id="admin" class="com.xiaotanke.entity.Admin">
<property name="name" value="张三"/>
bean>
public void initMethod(){
System.out.println("3、初始化方法");
}
<bean id="admin" class="com.xiaotanke.entity.Admin" init-method="initMethod">
<property name="name" value="张三"/>
bean>
public void destroyMethod(){
System.out.println("5、销毁bean对象");
}
<bean id="admin" class="com.xiaotanke.entity.Admin" init-method="initMethod" destroy-method="destroyMethod">
<property name="name" value="张三"/>
bean>
初始化对象的后置处理器:创建一个类实现 BeanPostProcessor 接口并重写接口方法,这是在初始化对象前后会执行对应的方法。将这个类放到Spring容器中,在这个容器中的所有对象在初始化前后都会执行对应的方法,处处理器需要放到 Spring 容器中才会生效。
public class MyBeanPostProcessor implements BeanPostProcessor {
// 初始化前执行的方法
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化bean对象前执行方法");
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
// 初始化后执行的方法
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化bean对象后执行方法");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
bean的生命周期:
<bean id="myBeanPostProcessor" class="com.xiaotanke.hander.MyBeanPostProcessor"/>
<bean id="emp1" class="com.xiaotanke.autowire.Emp" autowire="byName"/>
<bean id="emp2" class="com.xiaotanke.autowire.Emp" autowire="byType"/>
<bean id="dept" class="com.xiaotanke.autowire.Dept"/>
引入外部文件:通过引入外部文件来配置bean对象的属性。
<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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${user}"/>
<property name="password" value="${password}"/>
bean>
beans>
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306
user=root
password=1234567
创建对象:使用注解创建对象并把对象放在Spring容器中,下面是几个常见的注解。
/**
* @Component 创建一个普通bean对象
* @Controller 创建一个前端控制器对象
* @Servic 创建一个服务层的对象
* @Repository 创建一个dao层的对象
*
* 它们的功能都是一样的,都是创建一个对象,只是使用的场景不一样
*/
注意事项:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>5.3.20version>
dependency>
<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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.xiaotanke"/>
beans>
获取对象并使用
// 创建了一个对象,对象的名称默认是类名称的首字母小写,也可以通过value属性来指定一个对象的名称
@Component(value = "test")
public class ComponentTest {
}
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
// 获取对象
ComponentTest componentTest = context.getBean("test", ComponentTest.class);
System.out.println(componentTest);
}
配置组件扫描
<context:component-scan base-package="com" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
context:component-scan>
简单属性注入:在属性名前面使用Value注解,也可以在set方法上使用Value注解。一般属性简单使用注解,属性复杂还是使用xml配置。
@Component
public class Admin {
@Value("张三")
private String name;
}
引用属性注入:将已经在Spring容器的对象赋值给某个属性,有两种方式。
@Controller
public class ControllerTest {
/**
* ‘@Autowired’:可以标注在属性上、方法上和构造器上,来完成自动装配。
* 默认是根据属性类型,spring自动将匹配到的属性值进行注入
* 当标注的属性是接口时,其实注入的是这个接口的实现类, 如果这个接口有多个实现类,(会先按照类型注入,存在多种类型就会按照名称注入)
* 只使用@Autowired就会报错,因为它默认是根据类型找,然后就会找到多个实现类bean,
* 所有就不知道要注入哪个。然后它就会根据属性名去找。
* 所以如果有多个实现类可以配合@Qualifier(value=“类名”)来使用
*/
@Autowired
@Qualifier(value = "serviceTest") // Qualifier需要和Autowired一同使用
private ServiceTest serviceTest;
/**
* 在方法上使用,会注入到参数上
*/
@Autowired
@Qualifier(value = "serviceTest")
public void test1(ServiceTest serviceTest){
}
}
@Service
public class ServiceTest {
/**
* 这个注解数java的注解,它是按照名称进行注入,如果名称相同就会按照类型注入(先名称后类型)
* 可以通过name属性来指定注入对象的名称
*/
@Resource(name = "repositoryTest")
private RepositoryTest repositoryTest;
}
Spring中的配置类:用于替代Spring的配置文件,可以通过这个配置类来实现对象的创建等功能。
// 使用这个注解表示是一个配置类,会被Spring容器识别
@Configuration
// 这个注解是配置组件的扫描包
@ComponentScan(basePackages = {"com.xiaotanke.annotation","com.xiaotanke.component"})
public class SpringConfig {
/**
* 这个注解是创建了一个对象
* bean的名称就是带注释的方法的名称。如果指定,则忽略方法名
*/
@Bean
public ControllerTest controllerTest(){
return new ControllerTest();
}
}
public static void main(String[] args) {
// 加载配置类,与加载配置文件作用一样,参数是配置类的Class对象
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
ControllerTest controllerTest = context.getBean("controllerTest", ControllerTest.class);
System.out.println(controllerTest);
}
可以通过配置类去开启组件扫描,不用再创建一个 xml 文件去开启组件扫描。
// 标志是一个配置类
@Configuration
// 需要扫描的包,可以配置多个
@ComponentScan({"com.jx","com"})
public class SpringConfig {
}
获取组件:
public static void main(String[] args) {
// 加载配置类
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
// 获取组件 。。。。
}
AOP 的底层使用的动态代理实现的,有两种情况的动态代理(JDK原生动态代理、CGLIB动态代理)。
JDK 动态代理的是代理接口,代理对象和被代理对象实现同一个接口,然后通过代理对象完成被代理对象需要完成的任务并加入自己的逻辑任务。
// 被代理对象实现的接口
public interface Rent {
void rent();
}
// 被代理对象
public class RentServiceImpl implements Rent{
@Override
public void rent() {
System.out.println("我需要租房!!!!");
}
}
// 代理对象生成类
public class ProxyFactory implements InvocationHandler {
// 被代理对象
private Object target;
public void setTarget(Object target) {
this.target = target;
}
// 生成代理对象
public Object newInstanceProxy(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
}
// 对被代理对象做增强
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理类前置增强");
// 执行被代理对象执行的方法
Object result = method.invoke(this.target, args);
System.out.println("代理类后置增强");
return result;
}
}
// 测试
public static void main(String[] args) {
// 被代理的接口
Rent service = new RentServiceImpl();
// 代理对象工厂类
ProxyFactory factory = new ProxyFactory();
// 设置被代理对象接口
factory.setTarget(service);
// 生成接口代理对象
Rent proxy = (Rent) factory.newInstanceProxy();
// 代理对象执行对应的方法
proxy.rent();
}
CGLIB动态代理基本原理是使用子类去继承被代理类或者接口,然后通过子类去对被代理类进行增强。
// 被代理对象类
public class RentServiceImpl {
public void rent(){
System.out.println("我要租房子!!!");
}
}
// CGLIB 代理工厂类
public class CGLIBFactory implements MethodInterceptor {
// 被代理对象
private Object target;
public void setTarget(Object target) {
this.target = target;
}
// 产生动态代理对象
public Object newInstance(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
// 代理增强
/**
* @param o 代理对象
* @param method 被代理的方法
* @param objects 参数
* @param methodProxy 代理方法
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("前置增强");
// 执行代理方法
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("后置增强");
return result;
}
}
public static void main(String[] args) {
// 生成代理对象工厂
CGLIBFactory factory = new CGLIBFactory();
// 被代理对象
RentServiceImpl rentService = new RentServiceImpl();
factory.setTarget(rentService);
RentServiceImpl o = (RentServiceImpl) factory.newInstance();
o.rent();
}
/**
* 1、连接点:可以被增强的方法
* 2、切入点:实际被增强的方法
* 3、通知(增强):增强的逻辑方法,通知有多种类型(前置通过、后置通知、环绕通知、异常通知、最终通知)
* 前置通知:在切入点方法之前执行
* 后置通过:在切入点方法执行完毕后执行
* 环绕通知:在切入点方法执行的前后都会执行
* 异常通知:在切入点方法出现异常的时候会执行
* 最终通知:这个无论怎样都会执行,与finally类似
* 4、切面:是一个动作,把通知用在切入点的过程
*/
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.9.1version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>5.3.20version>
dependency>
<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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.xiaotanke"/>
<aop:aspectj-autoproxy proxy-target-class="true"/>
beans>
/**
* 增强类
*/
@Component
@Aspect
public class MyAspectj {
/**
* execution()切入点表达式
* execution(* com.xiaotanke.service.UserServiceImpl.*(..))
* *:表示修饰符,所有的修饰符和返回类型
* com.xiaotanke.service:切入点的全限定路径
* UserServiceImpl:切入点类
* *(..):切入的方法及其参数列表
*/
/**
* 前置通知:在被增强方法之前前执行
*/
@Before(value = "execution(* com.xiaotanke.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("添加user方法的前置通知");
}
/**
* 后置通知:无论如何都会执行的方法,在方法执行后执行
*/
@After(value = "execution(* com.xiaotanke.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("添加user方法后的后置通知");
}
/**
* 环绕通知:被增强方法的前后执行
* @param joinPoint 被增强方法信息接口,通过这个参数可以获取被增强方法信息
* @throws Throwable 异常
*/
@Around(value = "execution(* com.xiaotanke.service.UserServiceImpl.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("添加user方法的前置环绕通知");
// 执行方法
Object result = joinPoint.proceed();
System.out.println("添加user方法的后置环绕通知");
return result; // 返回执行结果
}
/**
* 返回通知:增强方法执行后执行,能获取方法的返回值,returing对应的值和参数值名称相同
*/
@AfterReturning(value = "execution(* com.xiaotanke.service.UserServiceImpl.*(..))",returing="result")
public void afterReturning(Object result){
System.out.println("添加user方法的返回通知"+result);
}
/**
* 异常通知:当被增强方法出现异常时执行,但是后面的最终通知也会继续执行,可以获取目标的异常信息
* throwing属性值和参数名称需要相同
*/
@AfterThrowing(value = "execution(* com.xiaotanke.service.UserServiceImpl.*(..))",throwing="exception")
public void afterThrowing(Throwable exception){
System.out.println("添加user方法的异常通知"+exception);
}
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
UserServiceImpl userServiceImpl = context.getBean("userServiceImpl", UserServiceImpl.class);
// 执行被增强方法
userServiceImpl.add();
}
未出现异常时的执行顺序:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TVILQXlX-1690514070885)(https://s2.loli.net/2022/07/30/1EHtk5CPfzL2cv9.png)]
出现异常时的执行顺序:最终通知始终会执行的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jsP4pTHA-1690514070885)(https://s2.loli.net/2022/07/30/HLbJ4kuZsncxaqF.png)]
抽取公共的切入点:
/**
使用一个方法来作为公共的切入点
*/
@Pointcut(value = "execution(* com.xiaotanke.service.UserServiceImpl.*(..))")
public void point(){}
// 通知直接可以使用这个方法来引用公共的切入点
@Before(value = "point()")
public void before(){
System.out.println("添加user方法的前置通知");
}
对个增强类对同一个方法进行增强,可以设置增强方法的优先级:
/**
* 设置增强的优先权,整数数值越小,优先级越高,
* 前置通知优先级越高越先执行
* 后置通知是优先级越高,越后执行
*/
@Component
@Aspect
@Order(1)
public class AspectjTwo {
@Before(value = "execution(* com.xiaotanke.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("添加user方法的前置通知(优先级一)");
}
@Around(value = "execution(* com.xiaotanke.service.UserServiceImpl.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("添加user方法的前置环绕通知(优先级一)");
// 执行方法
Object result = joinPoint.proceed();
System.out.println("添加user方法的后置环绕通知(优先级一)");
return result; // 返回执行结果
}
}
通过配置文件来实现AspectJ:
<bean id="userServiceImpl" class="com.test3.UserServiceImpl"/>
<bean id="userServiceProxy" class="com.test3.UserServiceProxy"/>
<aop:config>
<aop:pointcut id="point" expression="execution(* com.test3.UserServiceImpl.add(..))"/>
<aop:aspect ref="userServiceProxy">
<aop:before method="before" pointcut-ref="point"/>
<aop:after-returning method="afterReturning" pointcut-ref="point"/>
aop:aspect>
aop:config>
测试:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
UserServiceImpl service = context.getBean("userServiceImpl", UserServiceImpl.class);
service.add();
}
事务:在一组逻辑业务中,要么都成功,要么都失败。如果有一个步骤失败了,其他成功是步骤都要回滚。事务有四个特性:原子性、一致性、隔离性、持久性。
原子性:在操作中不可分割,要么都成功要么都失败
一致性:数据操作之前和操作之后的总量是不变的,数据保持一致
隔离性:两个事务去操作同一条数据,他们之间不会产生影响的
持久性:数据会永远持久化保存数据库中
在Spring中,分为编程式事务和声明式事务,一般在开发中都使用声明式事务。
编程式事务:手动开启、提交、回滚事务。
声明式事务:底层通过AOP方式在方法前使用编程式事务的方法开启事务,在方法后提交或回滚。用配置文件的方法或注解方法控制事务。
在Spring中的事务管理API中,PlatformTransactionManager
接口配置了不同框架的事务管理,通过接口的不同实现类去进行事务的管理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VzDVaJ6f-1690514070886)(https://s2.loli.net/2022/08/02/Dg6wmeIs5yXjMWZ.png)]
基于注解:
<context:component-scan base-package="com.xiaotanke"/>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="1234567"/>
bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/xiaotanke/mapper/*.xml"/>
bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
bean>
<context:component-scan base-package="com.xiaotanke"/>
<bean id="transactionManager" class="org.springframework.jdbc.support.JdbcTransactionManager"/>
<tx:annotation-driven transaction-manager="transactionManager"/>
@Transactional
注解,可以作用在类上表示类中所有方法都加上了事务,可以作用在方法上表示这个方法加上了事务。加上事务的方法表示如果方法中出现了异常或者不正常中断,那么方法之前已经完成的任务就会回滚到初始状态。@Transactional
public boolean transfer(Integer money) {
UserMapper userMapper = sqlSessionTemplate.getMapper(UserMapper.class);
// 查询到user
User user1 = userMapper.query(101);
User user2 = userMapper.query(102);
// 开始转账
if (action(user1,user2,100)) {
userMapper.update(user1);
System.out.println(1/0); // 出现异常,之前更新的user1的数据就会回滚
userMapper.update(user2);
return true;
}else {
System.out.println("转账失败,余额不足");
}
return false;
}
@Transaction注解常见属性:
基于xml方式:创建事务管理器,配置事务通知,通过aop切入到方法上。
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<tx:advice id="advice">
<tx:attributes>
<tx:method name="insert*"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="txPoint" expression="execution(* com.xiaotanke.service.impl.UserServiceImpl.*(..))"/>
<aop:advisor advice-ref="advice" pointcut-ref="txPoint"/>
aop:config>
事务传播行为:指的是在一个存在事务的方法被另一个存在事务的方法调用时,事务进行处理。在Spring中一共有7种传播行为。
/**
* 事务的传播行为:
* 1、Propagation.REQUIRED(默认):在事务A方法1中去调用事务B方法2,那么方法2会加入到方法1的事务中,执行方法1的事务。
* 2、Propagation.REQUIRES_NEW:在事务A的方法1中去调用事务B方法2,那么方法1的事务作为外层事务,方法2的事务作为内层事务
* 方法2会创建一个自己的事务,两个事务不会干扰,如果方法2执行成功,但是方法1执行失败,方法2 依然可以进行提交,方法1就会执行回滚操作。
* 3、Propagation.SUPPORTS:在事务A的方法1中去调用事务B的方法2,如果在方法1中有事务在运行,那么方法2也会加入到方法1的事 务,如果方法1中没有事务,那么方法2就支持事务。
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean transfer(Integer money) {}
事务之间可以同时操作一条或多条数据,如果不考虑事务的隔离级别就会出现脏读、不可重复读、幻读问题。通过设置事务的隔离级别来解决对应的问题。
脏读:一个未提交的事务读取到另一个未提交事务的数据。如果一个事务进行了事务回滚,那么另一个事务读取到的数据就是脏数据。
不可重复读:一个未提交的事务读取到一个已提交事务的数据,导致事务每次读取到的数据不一样。
幻读:一个事务在前后两次查询的结果不一样。
/**
* 事务隔离级别:
* 1、Isolation.READ_UNCOMMITTED:读未提交,存在脏读、不可重复度、幻读问题
* 2、Isolation.READ_COMMITTED:读已提交,存在不可重复度、幻读问题
* 3、Isolation.REPEATABLE_READ:可重复读,存在幻读问题
* 4、Isolation.SERIALIZABLE:序列化,一个事务未完成时,另一个事务需要进行等待,不存在问题。
* 5、Isolation.DEFAULT:使用数据库默认隔离级别,默认值
*/
// 默认的隔离级别是存储方式默认隔离界别,mysql默认隔离级别是可重复读
@Transactional(propagation = Propagation.NOT_SUPPORTED,isolation = Isolation.SERIALIZABLE)
public boolean transfer(Integer money) {}
Spring5中已经移除了Log4jConfigListener
,官方建议使用Log4j2。
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-apiartifactId>
<version>2.18.0version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.18.0version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-slf4j-implartifactId>
<version>2.18.0version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.36version>
dependency>
log4j2.xml
文件,并编写文件
<configuration monitorInterval="5">
<Properties>
<property name="LOG_PATTERN" value="%date{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
<property name="FILE_PATH" value="E:\code\idea\study\ssm_L\Spring2.0\Spring-Transaction" />
<property name="FILE_NAME" value="Spring-Transaction" />
Properties>
<appenders>
<console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${LOG_PATTERN}"/>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
console>
<File name="FileLog" fileName="${FILE_PATH}/${FILE_NAME}.log" append="true">
<PatternLayout pattern="${LOG_PATTERN}"/>
File>
appenders>
<loggers>
<logger name="org.mybatis" level="info" additivity="false">
<AppenderRef ref="Console"/>
logger>
<Logger name="org.springframework" level="all" additivity="false">
<AppenderRef ref="Console"/>
Logger>
<root level="all">
<appender-ref ref="Console"/>
<appender-ref ref="FileLog"/>
root>
loggers>
configuration>
在传统Juit中需要每次都加载配置文件,我们可以将Junit配置到Spring中,这样就可以直接注入Spring容器中的对象。
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.3.22version>
dependency>
测试类:
/**
* 整合Junit4
*/
@RunWith(SpringJUnit4ClassRunner.class) // 单元测试的版本
@ContextConfiguration("classpath:application.xml") // 加载配置文件
public class JunitTest {
/**
* 可以直接注入
*/
@Autowired
private UserService userServiceImpl;
@Test
public void test1(){
userServiceImpl.transfer(100);
}
}
Spring Resource:提供低级别资源的访问能力,例如获取项目的根路劲等等操作。
Resource接口:是 Spring 提供的资源访问策略,它本身不提任何资源访问实现,但是有很多实现类去实现了对应的方法。例如:UrlResource、ClassPathResource、FileSystemResource、ServletContextResource、InputStreamResouce、ByteArrayResource。
public static void main(String[] args) throws IOException {
String urlPath = "https://photo.16pic.com/00/93/69/16pic_9369314_b.png?imageView2/0/format/jpg";
Resource urlResource = new UrlResource(urlPath);
// 获取相关信息
System.out.println("URL=> " + urlResource.getURL());
System.out.println("fileName=> " + urlResource.getFilename());
System.out.println("描述信息=> " + urlResource.getDescription());
// 文件流
InputStream inputStream = urlResource.getInputStream();
FileOutputStream outputStream = new FileOutputStream("test.png");
byte[] buffer = new byte[1024];
while (inputStream.read(buffer) != -1){
outputStream.write(buffer);
}
}
public static void main(String[] args) throws IOException {
// 文件存放到项目的路劲下
String path = "file/test.txt";
Resource resource = new ClassPathResource(path);
System.out.println("fileName=> " + resource.getFilename());
System.out.println("描述信息=> " + resource.getDescription());
InputStream inputStream = resource.getInputStream();
byte[] bytes = new byte[1024];
while (inputStream.read(bytes) != -1){
System.out.println(new String(bytes));
}
}
public static void main(String[] args) throws IOException {
// 文件在系统的绝对路径
String path = "E:\\code\\idea\\projects\\spring-boot-practice\\boot-01\\src\\main\\resources\\file\\test.txt";
Resource resource = new FileSystemResource(path);
System.out.println("fileName=> " + resource.getFilename());
System.out.println("描述信息=> " + resource.getDescription());
InputStream inputStream = resource.getInputStream();
byte[] bytes = new byte[1024];
while (inputStream.read(bytes) != -1){
System.out.println(new String(bytes));
}
}