Spring核心
作者:互联网
一、核心基础
(一)基于XML的使用
1、Ioc配置
(1)Bean标签介绍
bean标签作用:用于配置被Spring容器管理的bean信息。默认情况下它调用的是类中的无参构造函数,如果没有无参构造,则不能创建。
bean标签属性:
标签 | 描述 |
id | 给对象在容器中提供一个唯一的标识,用于获取对象。 |
class | 指定类的全限定名。用于反射创建对象。默认下调用无参构造 |
init-method | 指定类中的初始化方法名称 |
destory-method | 指定类中的销毁方法,比如DataSource的配置中,一般需要配置destory-method="close" |
scope |
指定对象的作用范围: (1)singleton:默认值,单例,生命周期: a、创建:当应用加载,创建容器时,对象就被创建 b、存活:只要容器存在,一直存货 c、死亡:当应用卸载,容器销毁时,对象就被销毁 (2)prototype:多例,每次访问对象时,都会重新创建对象实例。生命周期如下: a、创建:访问对象时 b、存活:只要对象还在使用 c、死亡:当对象长时间不使用,被垃圾回收器回收 (3)request:将Spring创建的Bean对象存入到request中 (4)session:将Spring创建的Bean对象存入Session中 (5)global session:全局Session |
(2)Bean标签的实例化方式
a、使用默认无参构造函数(推荐)
在默认情况下,他会根据默认的无参构造函数来创建对象,如果不存在无参对象,则创建失败。
<?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="userService" class="com.lcl.galaxy.spring.service.impl.UserServiceImpl"/> </beans>
b、使用静态工厂创建
使用工厂类的静态方法创建一个对象,并将对象放入Spring容器中
package com.lcl.galaxy.spring.factory; import com.lcl.galaxy.spring.service.UserService; import com.lcl.galaxy.spring.service.impl.UserServiceImpl; public class StaticFactory { public static UserService createUserService(){ return new UserServiceImpl(); } }
<bean id="userService2" class="com.lcl.galaxy.spring.factory.StaticFactory" factory-method="createUserService"/>
c、实例工厂(了解)
先将工厂类放入Spring容器中,然后再引用工厂Bean
package com.lcl.galaxy.spring.factory; import com.lcl.galaxy.spring.service.UserService; import com.lcl.galaxy.spring.service.impl.UserServiceImpl; public class InstanceFactory { public UserService createUserService(){ return new UserServiceImpl(); } }
<bean id="instanceFactory" class="com.lcl.galaxy.spring.factory.InstanceFactory"/> <bean id="userService3" factory-bean="instanceFactory" factory-method="createUserService"/>
2、DI配置
DI是指Bean中的属性,依赖(属性)分为简单类型(String和8种基本类型)、对象类型、集合类型;
依赖注入是Spring IoC的具体实现;
(1)依赖注入
为什么要依赖注入:因为Bean对象的创建我们都交给了Spring创建,那么Bean对象种的指,也肯定是需要交给Spring来赋值的。
依赖注入的方式有两种:构造函数注入和set方法注入
a、构造函数注入
构造函数注入就是使用类的构造函数,给成员变量赋值,赋值是Spring直接进行的赋值。
package com.lcl.galaxy.spring.service.impl; import com.lcl.galaxy.spring.domain.UserDo; import com.lcl.galaxy.spring.service.UserService; public class UserServiceImpl implements UserService { private String id; private String name; @Override public UserDo getUserById() { return UserDo.builder().id(this.id).name(this.name).build(); } public UserServiceImpl(){ this.id = "initId"; this.name = "initName"; } public UserServiceImpl(String id, String name){ this.id = id; this.name = name; } }
<bean id="userService4" class="com.lcl.galaxy.spring.service.impl.UserServiceImpl"> <constructor-arg name="id" value="1"/> <constructor-arg name="name" value="lcl"/> </bean>
从上面可以看到,给Service传值,类中必须要有构造函数,同时在bean标签中还需要设置constructor-arg标签,在constructor-arg标签中,有以下几个属性:
index:指定参数在构造函数中的索引位置
name:指定参数在构造函数中的名称
value:赋值操作,可以对简单类型赋值(简单类型:8中基本类型+String)
ref:赋值操作,可以配置在spring中已经配置的bean
b、set方法注入
set方法注入有手动的装配方式(Xml方式)和自动装配方式(注解方式:会用到@Autowired、@Resource、@Inject这些注解)
手动的装配方式,需要设置bean标签的property标签,同时需要在bean对象中有setter方法。
<bean id="userService5" class="com.lcl.galaxy.spring.service.impl.UserServiceImpl"> <property name="id" value="1"/> <property name="name" value="lcl"/> </bean>
(2)依赖注入不同类型
a、简单类型
<bean id="userService4" class="com.lcl.galaxy.spring.service.impl.UserServiceImpl"> <constructor-arg name="id" value="1"/> <constructor-arg name="name" value="lcl"/> </bean>
b、引用类型
<bean id="userDao" class="com.lcl.galaxy.spring.dao.UserDao"/> <bean id="userService6" class="com.lcl.galaxy.spring.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"/> </bean>
c、集合类型-List
<bean id="collectionDto" class="com.lcl.galaxy.spring.dto.CollectionDto"> <property name="nameList"> <list> <value>lcl</value> <value>qmm</value> </list> </property> </bean>
d、集合类型-Set
<bean id="collectionDto2" class="com.lcl.galaxy.spring.dto.CollectionDto"> <property name="nameList"> <set> <value>lcl</value> <value>qmm</value> </set> </property> </bean>
e、集合类型-Map
<bean id="collectionDto3" class="com.lcl.galaxy.spring.dto.CollectionDto"> <property name="nameMap"> <map> <entry key="lcl" value="18"/> <entry key="qmm" value="15"/> </map> </property> </bean>
f、集合类型-properties
<bean id="collectionDto4" class="com.lcl.galaxy.spring.dto.CollectionDto"> <property name="properties"> <props> <prop key="lcl">21</prop> <prop key="qmm">18</prop> </props> </property> </bean>
(二)基于注解和XML的混合使用
这里使用注解和XML混合使用,主要是指的在XML文件中设置自动扫描,而在具体的bean中使用@Service等注解以便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:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd"> <context:component-scan base-package="com.lcl.galaxy.spring"/> </beans>
package com.lcl.galaxy.spring.service.impl; import com.lcl.galaxy.spring.service.UserService2; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @Service @Slf4j public class UserServiceImpl2 implements UserService2 { public UserServiceImpl2(){ log.info("无参构造被调用"); } }
那么具体有哪些注解可以被扫描到,清单如下所示:
分类 | 注解 | 作用 | 属性 |
Ioc注解 相当于: <bean id="" class=""/> |
@Component | 让spring来管理资源,相当于对xml中配置的一个bean | 指定bean的id,如果不指定,默认bean的id是当前类名,首字母小写 |
@Controller | 对于@Component的衍生注解,一般用于表现层注解 | ||
@Service | 同上,主要用于业务层注解 | ||
@Repository | 同上,主要用于持久层注解 | ||
DI注解 依赖注入 相当于: <property name="" value=""/> <property name="" ref=""/> |
@Autowired |
1、默认按照类型装配 2、是由org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor实现的 3、是spring自带的注解 4、@Autowired注解默认情况下要求依赖对象必须存在,如果允许不存在,需要设置它的required属性为false 5、如果我们想按照名称装配(byName),可以结合@Qualifier注解进行使用 |
|
@Qualifier |
1、在自动按照类型注入的同时,在按照bean的id注入 2、它在给字段注入时不能独立使用,必须和@Autowired一起使用 3、给方法参数注入时,可以单独使用 |
||
@Resource |
1、默认按照名称装配,可以通过@Resource的name属性指定名称,如果没有指定name属性,当注解写到字段上时,默认取字段名并按照字段名查找,当找不到与字段名匹配的bean时才按照类型进行装配 2、如果name属性指定,则一定会按照名称进行装配 |
||
@Inject |
1、根据类型自动装配,如果需要按照名称进行装配,则需要配合@Name注解 2、@Inject可以作用在变量、setter方法、构造函数上 |
||
@Value | 给简单类型(8种基本类型+String)来注入值 | ||
@Autowired、@Resoure、@Inject区别 |
1、@Autowired是spring自带的,@Inject是JSR330规范实现的,@Resource是JSR250规范实现的,需要导入不同的包 2、@Autowired和@Inject用发基本一致,但是@Autowired有一个request属性 3、@Autowired和@Inject默认按照类型匹配,@Resource默认按照名称匹配 4、@Autowired要是想按照名称匹配,需要@Qualifier注解配合;@Inject要是想按照名称匹配,需要@Name注解配合 |
||
@Scope |
作用域注解: 改变作用域,相当于下面的配置代码,value内容有:singletion、prototype、request、session、globalsession <bean id="" class="" scope=""/> |
||
@PostConstrust @PreDestory |
生命周期注解: 相当于以下代码 <bean id="" class="" init-method="" destroy-method=""/> |
xml配置和注解配置的对比
Xml配置 | 注解配置 | |
Bean定义 | 使用<bean id="" class=""/>来配置 | 使用@Component和其衍生注解@Controller、@Service、@Repository |
Bean名称 | 通过id或name来设置 | 通过注解内加名称使用,例如:@Service("UserService") |
Bean注入 |
使用<property>标签注入 |
使用@Autowired或@Inject或@Inject来注入 如果使用@Autowired注入,可以配合@Qualifier按名称注入等 |
生命过程和作用域 | 在bean标签中使用init-method、destory-method、scope来设置 | 使用@PostConstruct、@PreDestory、@Scope来设置 |
适用场景 |
Bean来自第三方 |
Bean是由我们自己写的 |
(三)基于纯注解的使用
1、@Configuration
@Configuration是用来配置Bean的,对应的就是原来spring的xml配置文件,同样,在主函数中也是需要加载该配置类的
package com.lcl.galaxy.spring.config; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; @Configuration @Slf4j public class SpringConfiguration { public SpringConfiguration(){ log.info("spring容器启动"); } }
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
对应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:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd"> </beans>
ApplicationContext factory2=new ClassPathXmlApplicationContext("classpath:spring-config2.xml");
2、@Bean
Bean标签用来注册bean
@Bean @Scope("singletion") public DefaultSqlSessionFactory getSqlSession(){ return new DefaultSqlSessionFactory(new org.apache.ibatis.session.Configuration()); }
对应XML编写
<bean id="userDao" class="com.lcl.galaxy.spring.dao.UserDao"/>
3、@CompenentScan
指定要扫描的包
@Configuration @Slf4j @ComponentScan(basePackages = "com.lcl.galaxy.spring") public class SpringConfiguration { public SpringConfiguration(){ log.info("spring容器启动"); } }
对应xml代码
<context:component-scan base-package="com.lcl.galaxy.spring"/>
4、@PropertySource
加载properties文件的内容
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://****** jdbc.username=****** jdbc.password=******
package com.lcl.galaxy.spring.config; import com.mchange.v2.c3p0.ComboPooledDataSource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import javax.sql.DataSource; @Configuration @PropertySource("classpath:db.properties") @Slf4j public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean(name = "dataSource") public DataSource createDataSource() throws Exception{ DataSource dataSource = new ComboPooledDataSource(); ((ComboPooledDataSource) dataSource).setDriverClass(driver); ((ComboPooledDataSource) dataSource).setJdbcUrl(url); ((ComboPooledDataSource) dataSource).setUser(username); ((ComboPooledDataSource) dataSource).setPassword(password); log.info("dabasource=============【{}】", dataSource); return dataSource; } }
对应XML代码
<context:property-placeholder location="classpath:db.properties"/> <bean id="dataSource2" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean>
5、@Import
导入其他Spring配置类
@Configuration @Slf4j @ComponentScan(basePackages = "com.lcl.galaxy.spring") @Import(JdbcConfig.class) public class SpringConfiguration { }
Xml代码
<import resource="spring-config1.xml"/>
二、核心高级
(一)AOP
1、相关术语
术语 | 说明 | |
JoinPoint | 连接点:指的是那些被拦截的点。在spring中,连接点指的是方法,因为spring只支持方法类型的连接点 | |
PointCut | 切入点:指我们要对哪些JointPoint进行拦截 | |
Advice | 通知/增强:指拦截到JointPoint后要做的事。通知分为前置通知、后置通知、异常通知、最终通知和环绕通知 | |
Introduction | 引介:是一种特殊的通知,在不修改代码的前提下,Introduction可以在运行期为类动态的添加一些方法或Field | |
Target |
目标对象:代理的目标对象 |
|
Weaving | 织入:是吧增强应用到目标对象来创建新的代理对象的过程 | |
Proxy | 代理:一个类被AOP织入增强后,就产生一个代理类 | |
Aspect | 切面:是切点和通知的结合 | |
Advisor | 通知器/顾问:和Aspect相似 |
2、织入过程
对于AOP的织入过程,可以分为动态织入和静态织入。
其中,动态织入实在运行时动态的将要增强的代码织入到目标类中,这种一般基于动态代理来完成,例如JDK的动态代理(Proxy,通过反射实现)或者CGLIB动态代理(通过继承实现),其中,Spring Aop采用的就是基于运行时增强的动态代理技术
(1)静态代理:AspectJ
静态代理是在程序进行编译的时候进行的织入,这就需要一种特殊的程序编译器,例如acj编译器,他主要是先将增强的源代码编译成字节码文件(class文件),然后在编译目标对象时,将增强的字节码文件一起织入,生成最终增强后的字节码文件。
(2)动态代理
spring AOP是通过动态代理技术实现的,而动态代理又是通过反射实现的。动态代理有两种实现方式:基于接口的JDK动态代理和基于接口的CGLIB动态代理。
无论是基于什么的动态代理,都是在运行期,针对目标对象胜场Proxy代理对象,然后在代理对象中织入增强处理。
a、JDK动态代理
public static UserService getProxyByJdk(final UserService userService){ //使用proxy类生成代理对象 UserService proxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() { //代理对象方法一执行,invoke方法就会执行一次 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("save".equals(method.getName())){ log.info("=============记录日志=============="); } //让service类的save方法正常的执行下去 return method.invoke(userService, args); } }); //返回代理对象 return proxy; }
b、CGLIB
public static UserService getProxyByCglib(){ //创建CGLIB核心类 Enhancer enhancer = new Enhancer(); //设置父类 enhancer.setSuperclass(UserServiceImpl.class); //设置回调函数 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { if("save".equals(method.getName())){ log.info("=============记录日志=============="); log.info("=============开启事务=============="); } log.info("=============提交事务志=============="); return methodProxy.invokeSuper(o, objects) } }); //生成代理对象 UserService proxy = (UserService) enhancer.create(); return proxy; }
(二)基于AspectJ的AOP使用
基于AspectJ的AOP使用,其实就是Spring对于AspectJ的整合,不过Spring已经将AspectJ整合到自身的框架种了,并且底层织入仍然采用的是动态织入的方式。
1、准备代码
(1)添加依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.0.7.RELEASE</version> </dependency> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency>
(2)编写目标类和目标方法
package com.lcl.galaxy.spring.aop; public interface UserService { public void insert(); public void insert(String id); public void insert(String id, String name); public void userInsert(); }
package com.lcl.galaxy.spring.aop.service; import com.lcl.galaxy.spring.aop.UserService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @Slf4j @Service public class UserServiceImpl implements UserService { @Override public void insert() { log.info("=========================insert======================"); } @Override public void insert(String id) { log.info("=========================insert111======================"); } @Override public void insert(String id, String name) { log.info("=========================insert2222======================"); } @Override public void userInsert() { log.info("=========================userInsert======================"); } }
2、Xml实现
(1)编写通知
package com.lcl.galaxy.spring.aop.advice; import lombok.extern.slf4j.Slf4j; @Slf4j public class MyAdvice { public void log(){ log.info("===================打印日志==================="); } }
(2)配置通知、AOP切面、自动扫描包
<?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:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="com.lcl.galaxy.spring.aop"/> <bean name="myAdvice" class="com.lcl.galaxy.spring.aop.advice.MyAdvice"/> <aop:config> <aop:aspect ref="myAdvice"> <aop:before method="log" pointcut="execution(public void com.lcl.galaxy.spring.aop.service.UserServiceImpl.insert())"/> </aop:aspect> </aop:config> </beans>
切入点表达式:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
表达式说明:
参数 | 说明 |
execution | 必须 |
修饰符 | 可省略 |
返回值类型 | 必须 |
包名 |
1、多级包之间使用.分割 2、包可以使用*代替 3、多级包名可以使用多个*代替 4、如果想省略中间的包,可以使用.. |
类名 |
1、可以使用*代替 2、也可以使用*ServiceImpl |
方法名 |
1、可以使用*代替 2、也可以写成save* |
参数 |
1、参数使用*代替 2、如果有多个参数,可以使用..代替 |
对于不同的写法如下代码所示:
<aop:config> <aop:aspect ref="myAdvice"> <!-- 详细写法 --> <!-- <aop:before method="log" pointcut="execution(public void com.lcl.galaxy.spring.aop.service.UserServiceImpl.insert())"/> --> <!-- 省略execution写法--> <!-- <aop:before method="log" pointcut="execution(void com.lcl.galaxy.spring.aop.service.UserServiceImpl.insert())"/> --> <!-- 一层包名使用*写法 --> <!--<aop:before method="log" pointcut="execution(void com.lcl.galaxy.spring.aop.*.UserServiceImpl.insert())"/>--> <!-- 多层包名省略写法--> <!-- <aop:before method="log" pointcut="execution(void com.lcl.galaxy..insert())"/>--> <!-- 类名使用*写法--> <!-- <aop:before method="log" pointcut="execution(void com.lcl.galaxy.spring.aop.service.*Impl.insert())"/>--> <!-- 方法名使用*写法--> <!-- <aop:before method="log" pointcut="execution(void com.lcl.galaxy.spring.aop.service.*Impl.insert*())"/>--> <!-- 方法名使用*写法--> <!-- <aop:before method="log" pointcut="execution(void com.lcl.galaxy.spring.aop.service.*Impl.insert*())"/>--> <!-- 方法名使用*写法--> <aop:before method="log" pointcut="execution(void com.lcl.galaxy.spring.aop.service.UserServiceImpl.insert(..))"/> </aop:aspect> </aop:config>
通知类型
通知类型 | 执行时机 | 配置文件 | 应用场景 |
前置通知 | 目标对象方法之前执行通知 |
<aop:before method="" pointcut=""/> |
方法开始时进行校验 |
后置通知 | 目标对象方法之后执行,有异常不执行 |
<aop:after-running method="" pointcut=""/> |
可以修改方法的返回值 |
最终通知 | 目标对象方法之后执行,有异常也执行 |
<aop:after method="" pointcut=""/> |
资源释放等 |
环绕通知 | 目标对象方法之前和之后都会执行 |
<aop:around method="" pointcut=""/> |
事务,统计代码执行时间等 |
异常通知 | 目标对象方法执行异常会执行 |
<aop:after-throwing method="" pointcut=""/> |
包装异常 |
3、注解 + xml 实现
(1)通知代码
package com.lcl.galaxy.spring.aop.advice; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Slf4j @Aspect @Component("myAdviceAuto") public class MyAdviceAuto { @Before(value = "execution(void *..*Impl.insert(..))") public void log(){ log.info("===================打印日志==================="); } }
(2)配置文件
目标类仍然使用上面的目标类,但是需要在配置文件添加注解扫描和开启AOP自动代理
<context:component-scan base-package="com.lcl.galaxy.spring.aop"/> <aop:aspectj-autoproxy/>
(3)对于环绕通知的写法
@Around(value = "execution(* *..*Impl.*(..))") public String trans(ProceedingJoinPoint proceedingJoinPoint){ Object[] args = proceedingJoinPoint.getArgs(); String proceed = null; try { proceed = "返回结果" + (String) proceedingJoinPoint.proceed(args) ; } catch (Throwable throwable) { return "================异常"; } return proceed; }
(4)定义通用切入点
通用切入点指的是,再通知内设置value时,不再设置具体的拦截方案,会调用一个通用的拦截方案。
在通用
@Before(value = "MyAdviceAuto.fn()" ) public void logfn(){ log.info("=========================logfn============================="); } @Before(value = "MyAdviceAuto.fn()" ) public void killfn(){ log.info("=========================killfn============================="); } @Pointcut(value = "execution(* *..*.*(..))") public void fn(){ log.info("===================fn()===================="); }
4、纯注解方式
纯注解和注解+xml的区别仅在于需要使用注解替换在xml中配置的,直接在配置类上加上@EnableAspectJAutoProxy注解即可,其余的都一致。
那么 注解+xml 的方式在xml里面主要做了两件事,注解扫描和开启AOP自动代理
package com.lcl.galaxy.spring.demo.config; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory; import org.springframework.context.annotation.*; @Configuration @Slf4j @ComponentScan(basePackages = "com.lcl.galaxy.spring") @Import(JdbcConfig.class) @EnableAspectJAutoProxy public class SpringConfiguration { public SpringConfiguration(){ log.info("spring容器启动"); } @Bean @Scope("singletion") public DefaultSqlSessionFactory getSqlSession(){ return new DefaultSqlSessionFactory(new org.apache.ibatis.session.Configuration()); } }
三、组件支撑
(一)整合Junit
在测试类中,每一个测试类都要有加载xml文件的代码
@Slf4j public class LclSpringTest { @Test public void test1(){ ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-config4.xml"); UserService userService = (UserService) context.getBean("userServiceImpl"); String name = userService.around("lcl"); log.info("=============={}", name); } }
可以看到上面的测试代码,在测试方法中需要加载spring配置文件,同时需要获取对应的Bean,但是如果有很多测试方法,那么每个方法都需要去写这两行代码。
好的一点是,Junit提供了RunWith注解用来让我们替换他的执行器,同时,Spring也提供了一个运行器,可以读取配置文件来创建容器,我们只需要告诉它配置文件的地址就OK。
那么主要就需要调整三个点:
(1)使用Junit的@RunWith注解,向注解内传入Spring的Junit执行器
(2)使用@ContextConfiguration注解加载配置文件或配置类
(3)使用@Autowired注解直接获取Bean
package com.lcl.galaxy.mybatis; import com.lcl.galaxy.spring.aop.UserService; import com.lcl.galaxy.spring.demo.config.SpringConfiguration; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @Slf4j @RunWith(SpringJUnit4ClassRunner.class) //@ContextConfiguration(locations = "classpath:spring-config4.xml") @ContextConfiguration(classes = SpringConfiguration.class) public class LclSpringTest2 { @Autowired private UserService userService; @Test public void test(){ String name = userService.around("lcl"); log.info("=============={}", name); } }
(二)事务支持
1、事务相关特性介绍
(1)事务的特性(ACID)
事务缩写 | 名称 | 说明 |
A | 原子性:Atomicty | 事务是一个原子操作,只会成功或失败,不存在中间状态 |
C | 一致性:Consistency | 在一个事务内,要么全部成功,要么全部失败 |
I | 隔离性:Isolation | 各个事务相互隔离,一个事务没有提交之前,不会被其他事务看到 |
D | 持久性:Durability | 指一个事务一旦被提交,就是永久性的。 |
(2)事务的并发问题
问题 | 描述 |
脏读 | 一个事务读取到了另一个事务未提交的数据 |
不可重复读 | 一个事务因读取到了另一个事务已提交的数据,导致对同一条记录读取两次以上的结果不一致,主要针对update |
幻读 | 一个事务因读取到了另一个事务已提交的数据,导致对同一条记录读取两次以上的结果不一致,主要针对delete和insert |
(3)事务隔离级别
四种隔离级别
级别 | 描述 |
读未提交:Read uncommitted | 最低级别,任何情况都无法保证 |
读已提交:Read committed | 可以避免脏读 |
可重复读:Repeatable Read | 可以避免脏读、不可重复读 |
串行化:Serializable | 可以避免脏读、不可重复读、幻读 |
大多数数据库的默认隔离级别是ReadCommitted,例如Oracle、DB2等
MySql的默认隔离级别是可重复读(Repeatable Read)
这里有一点需要注意:隔离级别越高,越能保证数据的完整性和一致性,但是对于并发性能影响也越大
(4)Spring事务管理接口
Spring并不是直接操作事务,而是提供事务管理接口PlatformTransactionManager,通过这个接口,Spring为各个平台(JDBC、Hibernate等)都提供了对应的事务管理器。
这里有几个接口需要介绍一下
接口名称 | 说明 | 实现类 | 常用方法 |
PlatformTransactionManager接口 | 平台事务管理器,是真正管理事务的类,该接口有具体的实现类,根据不同的持久层框架,需要选择不同的实现类。 |
JDBC\Mybatis:DataSourceTransactionManager Hibernate:HibernateTransactionManager |
获取事务状态方法: TransactionStatus getTransaction(TransactionDefinition definition) 提交事务方法:void commit(TransactionStatus status) 回滚事务方法:void rollback(TransactionStatus status) |
TransactionDefinition接口 | 事务定义信息,可以定义事务隔离级别、事务传播行为、超时时间、是否只读等信息 | ||
TransactionStatus接口 | 事务的状态:是否是新事务、是否已提交、是否有保存点、是否回滚 |
上述几个接口的关系:平台事务管理器(PlatformTransactionManager)真正管理事务对象,根据事务定义信息(TransactionDefination)进行事务管理,在管理事务中产生一些状态,将状态等信息记录在事务状态(TransactionStatus)中。
对于TransactionDefinition中对于事务隔离级别和传播行为有以下几种定义
a、事务隔离级别常量
隔离级别常量 | 隔离级别描述 |
ISOLATION_DEFAULT | 采用数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 读未提交 |
ISOLATION_READ_COMMITTED | 读已提交 |
ISOLATION_REPEATABLE_READ | 可重复读 |
ISOLATION_SERIALIZABLE | 串行化 |
b、事务传播行为(默认值为PROPAGATION_REQUIRED)
传播行为 | 描述 |
PROPAGATION_REQUIRED | A中有事务,使用A中的事务;A中没有事务,B开启一个新的事务 |
PROPAGATION_SUPPORTS | A中有事务,使用A中的事务;A中没有事务,B也不使用事务 |
PROPAGATION_MANDATORY | A中有事务,使用A中的事务;A中没有事务,抛出异常 |
PROPAGATION_REQUIRED_NEW | A中有事务,将A的事务挂起,B创建一个新的事务 |
PROPAGATION_NOT_SUPPORTED | A中有事务,将A中的事务挂起 |
PROPAGATION_NEVER | A中有事务,抛出异常 |
PROPAGATION_NESTED | 嵌套事务,当A执行后,就会在这个位置设置一个保存点,如果B没有问题,执行通过。如果B出现异常,根据需求回滚(回滚到保存点或者最初始状态) |
2、Spring框架事务管理的分类
主要分为编程式事务管理和声明式事务管理,且声明式事务管理可以使用xml编写方式、xml+注解编写方式和纯注解使用方式。
(1)编程式事务管理
编程式事务管理主要是使用了TransactionTemplate模板类,通过该模板类进行事务处理。
首先编写业务逻辑层代码,在业务逻辑层代码中,调用TransactionTemplate的execute方法,在该方法内的所有操作,都是一个事务。
package com.lcl.galaxy.spring.transaction.service.impl; import com.lcl.galaxy.spring.transaction.dao.OrderInfoDao; import com.lcl.galaxy.spring.transaction.dao.UserDao; import com.lcl.galaxy.spring.transaction.domain.OrderInfoDo; import com.lcl.galaxy.spring.transaction.service.UserService; import com.lcl.galaxy.spring.transaction.domain.UserDo; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; @Slf4j @Data public class UserServiceImpl implements UserService { @Autowired private TransactionTemplate transactionTemplate; @Autowired private UserDao userDao; @Autowired private OrderInfoDao orderInfoDao; @Override public void transactionTest(UserDo userDo, OrderInfoDo orderInfoDo) { log.info("事务处理"); transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { log.info("======================="); userDao.insert(userDo); orderInfoDao.insert(orderInfoDo); } }); log.info("事务结束"); } }
然后是对TransactionTemplate的配置
<?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:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="****"></property> <property name="user" value="******"></property> <property name="password" value="5H5eLQsp6yO4"></property> </bean> <bean id="transactionManager" 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="transactionManager"/> </bean> <bean id="userDao" class="com.lcl.galaxy.spring.transaction.dao.impl.UserDaoImpl"/> <bean id="orderInfoDao" class="com.lcl.galaxy.spring.transaction.dao.impl.OrderInfoDaoImpl"/> <bean id="userService" class="com.lcl.galaxy.spring.transaction.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"/> <property name="orderInfoDao" ref="orderInfoDao"/> <property name="transactionTemplate" ref="transactionTemplate"/> </bean> </beans>
(2)声明式事务--xml实现方式
使用xml的方式实现声明式事务,主要需要做的就是使用AOP的配置,将事务增强到切面方法上。
<aop:config> <aop:advisor advice-ref="txadvice" pointcut="execution(* *..*.save(..))"/> </aop:config>
对于事务增强,又需要事务管理器来处理
<tx:advice id="txadvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="save*"/> <tx:method name="find*" read-only="true"/> </tx:attributes> </tx:advice>
事务管理器又需要DataSource数据源
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="*****"></property> <property name="user" value="******"></property> <property name="password" value="******"></property> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
配置Service类
<bean id="userService2" class="com.lcl.galaxy.spring.transaction.service.impl.UserService2Impl"/>
最后测试代码
package com.lcl.galaxy.spring; @Slf4j @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:spring-transaction-xml-config.xml") public class LclSpringTransactionTest { @Autowired private UserService2 userService2; @Test public void test2(){ UserDo userDo = UserDo.builder().address("beijing").username("lcl111").sex("男").build(); OrderInfoDo orderInfoDo = OrderInfoDo.builder().orderId("1111").name("lcl").payMoney(new BigDecimal(String.valueOf("1.01"))).build(); log.info("测试启动"); userService2.saveInfo(userDo, orderInfoDo); log.info("测试完成"); } }
(3)声明式事务--注解 + xml实现方式
使用注解+xml实现声明式事务和纯xml实现声明式事务的主要差别就是我们不需要在xml里面配置事务增强和AOP配置,以及Bean的配置,但是仍然要配置dataSource和TransactionManager。同时需要配置 tx:annotation-driven 标签来开启事务注解
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="******"></property> <property name="user" value="******"></property> <property name="password" value="******"></property> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/> <context:component-scan base-package="com.lcl.galaxy.spring.transaction"/>
在使用事务时,使用@Transaction注解来处理,该注解可以使用在类上也可以使用在方法上
package com.lcl.galaxy.spring.transaction.service.impl; @Slf4j @Data @Service public class UserService3Impl implements UserService3 { @Transactional @Override public void saveInfo(UserDo userDo, OrderInfoDo orderInfoDo) { log.info("=================="); } }
(4)声明式事务--纯注解实现方式
纯注解的实现方式,在xml中只需要配置DataSource,然后在主类上使用@EnableTransactionManagement注解即可。
@Slf4j @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:spring-transaction-xml-auto2-config.xml") @EnableTransactionManagement public class LclSpringAutoTransactionTest2 { @Autowired private UserService3 userService3; @Test public void test(){ UserDo userDo = UserDo.builder().address("beijing").username("lcl111").sex("男").build(); OrderInfoDo orderInfoDo = OrderInfoDo.builder().orderId("1111").name("lcl").payMoney(new BigDecimal(String.valueOf("1.01"))).build(); log.info("测试启动"); userService3.saveInfo(userDo, orderInfoDo); log.info("测试完成"); } }
标签:事务,spring,Spring,import,lcl,public,注解,核心 来源: https://www.cnblogs.com/liconglong/p/14100429.html