1. Spring简介
1.1 什么是Spring
Spring是分层的 Java SE/EE应用 full-stack(全栈式) 轻量级开源框架。
提供了表现层 SpringMVC和持久层 Spring JDBC Template以及 业务层 事务管理等众多的企业级应用 技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE 企业应用开源框 架。
两大核心:以 **IOC(Inverse Of Control:控制反转)**和 **AOP(Aspect Oriented Programming:面向 切面编程)**为内核。
1.2 spring优势
-
方便解耦,简化开发
Spring就是一个容器,可以将所有对象创建和关系维护交给Spring管理
-
AOP编程的支持
Spring提供面向切面编程,方便实现程序进行权限拦截,运行监控等功能。
-
声明式事务的支持
通过配置完成事务的管理,无需手动编程
-
方便测试,降低JavaEE API的使用
Spring对Junit4支持,可以使用注解测试
-
方便集成各种优秀框架
不排斥各种优秀的开源框架,内部提供了对各种优秀框架的直接支持
1.3 spring结构体系
2. IOC简介
控制反转(Inverse Of Control)不是什么技术,而是一种设计思想。它的目的是指导我们设计出更加松耦合的程序。 控制:在java中指的是对象的控制权限(创建、销毁) 反转:指的是对象控制权由原来 由开发者在类中手动控制 反转到 由Spring容器控制
3. 入门案例
3.1 导包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
3.2 核心配置文件
3.2.1 基本配置
<?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="userDao" class="mc.study.dao.impl.UserDaoimpl" scope="singleton"
init-method="init" destroy-method="destroy" />
</beans>
id:
是容器中实例的唯一标识,
class:
Bean的全类名
scope:
指定对象的作用范围
取值范围 | 说明 |
---|---|
singleton | 默认值,单例的 |
prototype | 多例的 |
request | WEB项目中,Spring创建一个Bean的对象,将对象存入到request域中 |
session | WEB项目中,Spring创建一个Bean的对象,将对象存入到session域中 |
global session | WEB项目中,应用在Portlet环境,如果没有Portlet环境那么globalSession 相当 于 session |
-
当scope的取值为singleton时
Bean的实例化个数:1个
Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例
Bean的生命周期:
对象创建:当应用加载,创建容器时,对象就被创建了
对象运行:只要容器在,对象一直活着
对象销毁:当应用卸载,销毁容器时,对象就被销毁了
-
当scope的取值为prototype时
Bean的实例化个数:多个
Bean的实例化时机:当调用getBean()方法时实例化Bean
Bean的生命周期:
对象创建:当使用对象时,创建新的对象实例
对象运行:只要对象在使用中,就一直活着
对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了
init-method:
指定类中的初始化方法名称,在创建bean实例后调用,注意方法在bean的类中。
destory-method:
指定的方法在对象销毁前被执行。
3.2. 三种实例化方式:
1. 无参构造实例化: 默认方式,调用类的无参构造创建实例,要求bean类必须具有无参构造方法
2. 静态工厂方法:
使用场景:在一些框架或工具类中,有的实例创建需要使用静态工厂方法,若要将这种对象的创建交由spring容器,就需要用到这种配置。
<bean id="userDao" class="com.lagou.factory.StaticFactoryBean" factory-method="createUserDao" />
class
中填写工厂类,
factory-method
中填写工厂类中创建实例的 静态 方法。
3. 普通工厂方法
使用场景:与静态工厂类似,有些实例的创建使用的确实工厂对象的普通方法,如Mybatis中的SQLSession。
<bean id="dynamicFactoryBean" class="com.lagou.factory.DynamicFactoryBean"/>
<bean id="userDao" factory-bean="dynamicFactoryBean" factorymethod="createUserDao"/>
既然是通过工厂实例的普通方法构建对象,那么首先肯定要有工厂的实例。
factory-bean
指向工厂实例的 bean Id
factory-method
同样指定构建对象调用的方法。
3.3 基本API
@Test
public void test16() throws Exception{
// 加载核心配置文件,创建容器
ApplicationContext cxt = new ClassPathXmlApplicationContext("ApplicationContext.xml");
// 从容器中获取bean实例
UserDao ud = cxt.getBean("userDao", UserDao.class);
ud.findUser();
// 关闭容器,验证 destroy-method 是否执行
((ClassPathXmlApplicationContext)cxt).close();
}
3.3.1. 加载核心配置文件
三种方式
方式一:
ApplicationContext cxt = new ClassPathXmlApplicationContext("ApplicationContext.xml");
使用资源路径载入配置文件,根路径可以加 / 也可以不加。当有多个配置文件时,可以作为多个参数同时传入。
方式二:
ApplicationContext cxt = new FileSystemXmlApplicationContext("E:\\ApplicationContext.xml");
使用文件路径载入配置文件。配置文件可以在磁盘的任意位置。
方式三:
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("ApplicationContext.xml"));
使用BeanFactory加载配置文件,但注意,XmlBeanFactory已被弃用。
接口与类的关系
BeanFactory
是 IOC 容器的核心接口,它定义了IOC的基本功能。ApplicationContext
代表应用上下文对象,可以获得spring中IOC容器的Bean对象。两者都可以加载配置文件,获取容器中bean实例。
**区别在于:**前者在在第一次调用getBean是才会创建bean实例,后者则是容器启动时就创建了所有的bean实例。
常用方法
1.getBean
Object getBean(String name);
<T> T getBean(Class<T> requiredType);
<T> T getBean(String name,Class<T> requiredType);
只用类型获取,若容器中有多个同类型【或同类型的子类】的实例,则会报错。所以一般用第三个。
2. close
((ClassPathXmlApplicationContext)cxt).close();
==注意:==close方法接口中并未定义,需要使用其实现类调用,但此方法一般也没用。
4. 依赖注入
依赖注入 DI(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现。
DI的作用在于为类中的属性赋值,Spring要实现IOC特性,单单接手new操作是远远不够的,很多类功能的实现依赖于其他的类,在类的内部保存了相关类的引用,在构建类的实例时,必须为这些相关类赋值,这样构建出来的实例才是可用的。DI就是用来实现这一功能的,在构造Bean实例时,将其所依赖的实例也注入到bean实例中,这样spring构造出来的实例才是可用的。
4.1 两种注入方式
方式一:通过构造方法注入
<bean id="xxx" class="com.xxx.xxx">
<constructor-arg index="0" type="com.lagou.dao.UserDao" ref="userDao"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
直接在bean
标签内部编写constructor-arg
标签,相当于为构造方法传参,可以通过name指定为那个形参传参;也可以通过index和type联合指明为那个形参赋值,显然后者更麻烦,但使用框架时,我们可能知不道参数名是什么。
ref
指向将哪个bean实例作为实参。
方式二:通过set方法注入
<bean id="xxx" class="com.xxx.xxx">
<property name="userDao" ref="userDao"/>
</bean>
在bean
标签内部直接编写property
,表示调用对应字段的set方法。name
指定哪个字段。ref
指定将那个bean实例传入。
P命名空间
引入命名空间
xmlns:p="http://www.springframework.org/schema/p"
使用:
<!-- 基本数据类型 -->
<bean id="tv" class="mc.study.TV" p:code="1" p:info="新闻联播。。。"/>
<!-- 引用数据类型 -->
<bean id="runner" class="mc.study.Runner" p:person-ref="xiaoming"/>
说明:p命名空间同 与 P标签都是调用set方法。
4.2 三种数据类型
基本数据类型
通过value属性或value标签赋值。
<property name="name" value="小明"/>
<property name="age">
<value>18</value>
</property>
引用数据类型
通过ref属性,或ref标签赋值。
<property name="tv" ref="tv"></property>
<property name="tv">
<ref bean="tv"></ref>
</property>
通过局部bean赋值
<property name="tv">
<bean class="mc.study.TV" p:code="2" p:info="财经频道" />
</property>
局部bean指定了id好像也没用,以后再研究吧!
集合数据类型
List集合注入
<property name="msgs">
<list>
<value>事件1,11111111111</value>
<value>事件2,22222222222</value>
<ref bean="xxx"></ref>
</list>
</property>
在property
中使用list标签
为List数组赋值,
Set集合注入
<property name="msgs">
<set>
<value>事件1,11111111111</value>
<value>事件3,,233333333</value>
<value>事件3,,233333333</value>
</set>
</property>
与list类似,只不过标签是set标签
Array注入
<property name="msgs">
<array>
<value>事件1,11111111111</value>
<value>事件2,22222222222</value>
<value>事件3,,233333333</value>
<value>事件3,,233333333</value>
<value>事件3,,233333333</value>
</array>
</property>
使用array标签
Map集合注入
<map>
<entry key="k1" value="ddd"/>
<entry key="k2" value-ref="user"></entry>
</map>
map中要使用entry
标签。使用key
和value
标签指定键值对,当值为引用类型时要用value-ref
。
properties注入
<property name="properties">
<props>
<prop key="k1">v1</prop>
<prop key="k2">v2</prop>
<prop key="k3">v3</prop>
</props>
</property>
为Properties注入,使用props
标签与prop
子标签,注意:prop
标签中没有value属性,值写在标签体中。
5. 配置文件模块化
当项目复杂时,配置文件就会变的非常庞大,不便于维护。配置文件模块化就是将ApplicationContext配置文件拆成几个,以便于管理
实现方式
1. 载入配置文件时传多个值
ApplicationContext cxt = new ClassPathXmlApplicationContext("ApplicationContext.xml","xxx.xml","***.xml");
2. import标签
<import resource="classpath:ApplicationContext.xml"/>
将其中一个配置文件视为主配置文件,在其中通过import
导入其他配置文件,注意:路径以classpath开头。
拆分原则
按照功能模块或者层结构拆分,如成分为用户模块,商品模块。。。或者拆分为服务模块,持久层模块。。。
拆分后每个模块中都是以beans为跟标签:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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">
. . .
</beans>
spring配置文件中引入properties文件
// 引入context命名空间
xmlns:context="http://www.springframework.org/schema/context"
// 引入约束文件
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
// 使用 ${ } 引入数据
<prop key="driverClassName">${jdbc.driver}</prop>
6. 注解开发
常用注解
注解 | 说明 |
---|---|
@Component | 使用在类上用于实例化Bean |
@Controller | 使用在web层类上用于实例化Bean |
@Service | 使用在service层类上用于实例化Bean |
@Repository | 使用在dao层类上用于实例化Bean |
用于代替bean标签
作用:创建类的实例并将之存入spring容器中。
这四个注解功能是一样的,只是根据语义用于不同的场景,如@Service用于为服务层的类创建实例放入容器。
这四个注解都只有一个value属性,相当于bean标签中的id属性。
注解 | 说明 |
---|---|
@Autowired | 使用在字段上用于根据类型依赖注入 |
@Qualifier | 结合@Autowired一起使用,根据名称进行依赖注入 |
@Resource | 相当于@Autowired+@Qualifier,按照名称进行注入 |
@Value | 注入普通属性 |
@Autowired
当容器中有多个该类型【或子类】的实例时,会根据字段名与容器中实例的id进行匹配,若仍然无法确定唯一的实例则会报错。【字段名与实例id不是必须一致】
这时候可以通过@Qualifier
指定id名称获取唯一的实例,该注解中只有一个字段value。
@Resource(name = "idxxx")
可单独使用根据id获取实例,但Resource注解不属于Spring,需要导入jar包:
<!-- java11以前不需要 -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
@Value
注入基本数据类型,可以通过 ${ }
获取,配置文件中导入的properties的值。
注解 | 说明 |
---|---|
@Scope | 标注Bean的作用范围 |
@PostConstruct | 使用在方法上标注该方法是Bean的初始化方法 |
@PreDestroy | 使用在方法上标注该方法是Bean的销毁方法 |
注意:
使用注解后,需要在配置文件中指定组件扫描路径,使注解生效。
<context:component-scan base-package="com.lagou"></context:component-scan>
扫描指定的包及其子包中的所有类
Spring新注解
注解 | 说明 |
---|---|
@Configuration | 用于指定当前类是一个Spring 配置类,当创建容器时会从该类上加载注解 |
@PropertySource | 用于加载 properties 文件中的配置 |
@ComponentScan | 用于指定 Spring 在初始化容器时要扫描的包 |
@Import | 用于导入其他配置类 |
@Bean | 使用在方法上,标注将该方法的返回值存储到 Spring 容器中 |
前四个标签用于类上,Bean标签用于方法上。
示例:
@Configuration
@ComponentScan("mc.study")
@Import(DataSourceConfig.class)
public class SpringConfig {
@Bean("qr")
public QueryRunner getQueryRunner(@Autowired DataSource ds){
return new QueryRunner(ds);
}
}
可以在形式参数上使用@Autowired
注解。@Import
注解只包含字段value类型为Class<?>[]
@Configuration
@PropertySource("jdbc.properties")
public class DataSourceConfig {
@Value("${jdbc.driver}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean("ds")
public DataSource getDataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driverClassName);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
1. 整合Junit
Spring对单元测试进行了支持,若使用传统的单元测试,需要首先加载spring配置,构建上下文对象,然后通过getBean从容器中取值。
使用Spring单元测试,可以直接将测试对象直接注入到测试类中用于测试。
实现步骤:
1. 导入jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
使用Spring5及以上版本,需要要搭配junit 4.12 及以上版本
2. 注解测试类
为测试类添加注解,@RunWith
和 @ContextConfiguration
。
@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(value = {"classpath:applicationContext.xml"}) 加载spring核心配置文件
@ContextConfiguration(classes = {SpringConfig.class}) // 加载spring核心配置类
public class SpringJunitTest {
}
@ContextConfiguration的value属性可以配置xml配置文件,classes可以配置spring配置类。
3. 在测试类中使用Autowired注入测试对象
2. 整合Druid
<bean id="ds" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///lianxi01?characterEncoding=utf-8&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
通过set方法注入,重点记住这几个字段名:driverClassName、url、username、password不同的连接池这些字段名也不一样。
<bean id="ds2" class="com.alibaba.druid.pool.DruidDataSourceFactory" factory-method="createDataSource">
<constructor-arg name="properties">
<props>
<prop key="driverClassName">com.mysql.cj.jdbc.Driver</prop>
<prop key="url">jdbc:mysql:///lianxi01?characterEncoding=utf-8&serverTimezone=UTC</prop>
<prop key="username">root</prop>
<prop key="password">123456</prop>
</props>
</constructor-arg>
</bean>
这么做也可以,但显然是更麻烦了。值得注意的是,createDataSource的参数可以通过constructor-agr
标签传入。
1. AOP基本概念
1.1 什么是AOP
AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程
AOP 是 OOP(面向对象编程) 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内 容,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高 程序的可重用性,同时提高了开发的效率。
AOP与IOC、OOP一样,都是一种编程理念,其要实现的效果如上所述,其实现方式为动态代理。
这样做的好处是:
1、在程序运行期间,在不修改源码的情况下对方法进行功能增强
2、逻辑清晰,开发核心业务的时候,不必关注增强业务的代码
3、减少重复代码,提高开发效率,便于后期维护
1.2 AOP底层实现
实际上,AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代 理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,再去调用目标对象的方法,从而完成功能的增强。
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对 象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑 运行。
框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。 当bean实现接口时,会用JDK代理模式;当bean没有实现接口,用cglib实现( 可以强制使用cglib;在spring配置中加入<aop:aspectjautoproxy proxyt-target-class=”true”/>)
1.3 相关概念
-
Target(目标对象):代理的目标对象
-
Joinpoint(连接点):所谓连接点是指那些可以被拦截到的点。在spring中,这些点指的是方法,因为 spring只支持方法类型的连接点
-
Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义
-
Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知
分类:前置通知、后置通知、异常通知、最终通知、环绕通知
-
Aspect(切面):是切入点和通知(引介)的结合
-
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织 入,而AspectJ采用编译期织入和类装载期织入
-
Proxy (代理):一个类被 AOP 织入增强后,就产生一个结果代理类
2. 基于Xml配置开发
2.1 导入依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
2.2 编写通知类
@Component
public class MyAdvice {
public void before(){
System.out.println("## AOP前置增强。。。");
}
public void afterReturning(){
System.out.println("## AOP后置增强。。。");
}
public void afterThrowing(){
System.out.println("## AOP 异常增强。。。");
}
public void after(){
System.out.println("## AOP 最终通知。。。");
}
public Object around(ProceedingJoinPoint pjp ){
Object res = null;
try {
System.out.println("环绕增强,前置增强。。。");
res = pjp.proceed();
System.out.println("环绕增强,后置增强。。。");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕增强,异常增强。。。");
}finally {
System.out.println("环绕增强,最终增强。。。");
}
return res;
}
}
通知类就是一个普通的类,其中的方法为通知,通过切面与切入点绑定后,就可以对切入点进行增强了。
2.3 编写切面配置
<?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:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--配置切面-->
<aop:config>
<aop:pointcut id="pc" expression="execution(public * mc..MyServiceImpl.*(..))"/>
<aop:aspect ref="myAdvice">
<aop:before method="before"
pointcut="execution(public String mc.study.service.impl.impl.MyServiceImpl.find())"/>
<aop:after-returning method="afterReturning" pointcut-ref="pc"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pc"/>
<aop:after method="after" pointcut-ref="pc" />
<!-- <aop:around method="around" pointcut-ref="pc"/>-->
</aop:aspect>
</aop:config>
</beans>
<aop:config>
是配置切面的根标签。
<aop:aspect ref="myAdvice">
为一个通知类定义切面,ref指向容器中通知类的实例。
<aop:before>
定义切面,因通知类型不同,分为五种。其属性method指向通知类中的通知【方法】,pointcut指向切点,要是用切点表达式,也可以使用pointcut-ref指向抽取出来的公共的切点。
<aop:pointcut>
抽取公共切点,id为切点标识,expression指定切点表达式。
五种通知类型
<aop:before>
指定前置通知,该通知在切点执行前执行。
<aop:after-returning>
指定后置通知,在切点成功执行后执行。
<aop:after-throwing>
指定异常通知,若切点执行时有异常抛出,则执行。其与后置通知只能执行一个。
<aop:after>
指定最终通知,不论是否有异常抛出,最后都会执行
<aop:around>
指定环绕通知,允许我们在通知中自行控制增强位置,与前四种不共存。
环绕通知的方法比较特殊【2.2 around方法】:
- 必须有参数
ProceedingJoinPoint pjp
用于执行切点方法 - 若切点有返回值,则环绕通知也应该有,并且由于pjp.proceed()的返回值为Object类型,所以环绕通知的返回值也为Object类型。
切点表达式
语法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用星号 * 代替,代表任意
- 包名与类名之间一个点 . 代表当前包下的类,两个点 … 表示当前包及其子包下的类
- 参数列表可以使用两个点 … 表示任意个数,任意类型的参数列表
常用写法:
execution(* mc.study.service.impl.*.*(..))
表示为 mc.study.service.impl 包下,所有类的 任意返回值类型 任意参数的 任意方法 进行增强。
3. 基于注解的AOP
3.1 开启自动道理
XML配置
<!--开启自动代理-->
<aop:aspectj-autoproxy proxy-target-class="true" />
proxy-target-class="true"
开启强制使用 Cglib,一般不用。
开启自动代理后 @Aspect 等注解才能生效。
Spring配置类
Spring纯注解开发 开启自动代理:在配置类上添加@EnableAspectJAutoProxy
即可。
3.2 使用注解标注通知类
@Component
@Aspect
public class MyAdvice {
// 抽取公共 切点表达式
@Pointcut("execution(* mc.study.service.impl.impl.MyServiceImpl.*(..))")
public void myPointCut(){}
@Before("execution(* mc.study.service.impl.impl.MyServiceImpl.*(..))")
public void before(){
System.out.println("## AOP前置增强。。。");
}
@AfterReturning("MyAdvice.myPointCut()")
public void afterReturning(){
System.out.println("## AOP后置增强。。。");
}
@AfterThrowing("MyAdvice.myPointCut()")
public void afterThrowing(){
System.out.println("## AOP 异常增强。。。");
}
@After("MyAdvice.myPointCut()")
public void after(){
System.out.println("## AOP 最终通知。。。");
}
// @Around("MyAdvice.myPointCut()")
public Object around(ProceedingJoinPoint pjp ){
Object res = null;
try {
System.out.println("环绕增强,前置增强。。。");
res = pjp.proceed();
System.out.println("环绕增强,后置增强。。。");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕增强,异常增强。。。");
}finally {
System.out.println("环绕增强,最终增强。。。");
}
return res;
}
}
@Aspect
注解标注在通知类上,相当于<aop:aspect ref="myAdvice">
标签
@Before
等代替原来的<aop:before>
标签,用于指定切点,形成切面,参数为切点表达式。
@Pointcut
代替``aop:pointcut`用于提取公共切点表达式,其参数为切点表达式。他可以随便加在一个方法上,在Before等注解上可以引用这个方法,如上所示。
注意事项
若@Before
、@AfterReturning
、@AfterThrowing
、@After
四个注解同时扩展同一个切点。这四个通知被调用的顺序是@Before
=
>
=>
=> @After
=
>
=>
=> @AfterReturning
∣
|
∣ @AfterThrowing
可见 After 先于 AfterReturning 执行,若After中执行了释放资源,之后执行AfterReturning 时可能出现问题。
所以使用注解方式,最好使用 Round通知。或者单一的使用上述四个注解。
1. JDBCTemplate
JDBCTemplate是spring提供的持久层解决方案,其类似于DbUtils工具类,是对jdbc代码的封装。
1、核心对象
JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSource dataSource);
2、核心方法
// 查询单个对象
<T> queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args);
// 查询多条数据
List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object... args);
// 执行 增删改
int update(String sql, @Nullable Object... args)
当然还有很多重载方法和一些其他功能,但对于JDBCTemplate没必要掌握那么多。
3、使用
<!-- 导包 -->
<!--jdbcTemplate-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
@Autowired
private JdbcTemplate jdbc;
public Account findById(int id){
String sql = "select * from account where id=?";
Account account = jdbc.queryForObject(sql, new BeanPropertyRowMapper<Account>(Account.class), id);
return account;
}
public List<Account> findAll(){
String sql = "select * from account";
List<Account> list = jdbc.query(sql, new BeanPropertyRowMapper<Account>(Account.class));
return list;
}
public int save(Account account){
String sql = "insert into account values(null,?,?)";
int row = jdbc.update(sql, account.getName(), account.getMoney());
return row;
}
总结:
JDBCTemplate使用上跟DbUtils很像,而且还不用处理异常,更重要的是JDBCTemplate支持Spring事务管理。然而Spring多与Mybatis等更加优秀的持久层框架结合使用,单独使用时DBUtils有比较简单明了。
2. Spring事务控制
2.1 Spring事务简介
以前,进行事务事务控制都是要自定义一个工具类,然后在服务层开启事务,提交等代码,就算使用AOP也需要编写事务相关的通知。事务控制是一个经常用到的功能,Spring为我们提供了解决方案,通过即可简单的配置就可以使用Spring提供的事务管理机制,而不必自己编写工具类 自己管理事务了。
Spring的事务控制可以分为编程式事务控制和声明式事务控制。
编程式
开发者直接把事务的代码和业务代码耦合到一起,在实际开发中不用。
声明式
开发者采用配置的方式来实现的事务控制,业务代码与事务代码实现解耦合,使用的AOP思想。
2.2 编程式事务控制
导包:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.5.RELEASE</version>
<scope>compile</scope>
</dependency>
使用JDBCTemplate时不用导包,因为spring-jdbc依赖于spring-tx。
@Autowired
private AccountDao ad;
@Autowired
private PlatformTransactionManager txmanager;
@Override
public void transfer(String nameout, String namein, double money) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
def.setReadOnly(false);
def.setTimeout(-1);
TransactionStatus status = txmanager.getTransaction(def);
try {
ad.out(nameout,money);
int i = 1/0;
ad.in(namein,money);
txmanager.commit(status);
} catch (Exception e) {
e.printStackTrace();
txmanager.rollback(status);
}
}
PlatformTransactionManager接口
Spring事务管理主要通过PlatformTransactionManager接口,相关方法如下:
方法 | 描述 |
---|---|
TransactionStatus getTransaction(TransactionDefinition definition); | 获取事务的状态信息 |
void commit(TransactionStatus status); | 提交事务 |
void rollback(TransactionStatus status); | 回滚事务 |
PlatformTransactionManager只是接口,其实现类根据所用的持久层框架而定:
实现类 | 持久层框架 |
---|---|
DataSourceTransactionManager | jdbcTemplate 或 mybatis |
HibernateTransactionManager | Hibernate |
JpaTransactionManager | Jpa |
<bean id="txmanager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="ds"/>
</bean>
传入数据源,也可以通过构造方法传入。
TransactionDefinition 接口
调动getTransaction时需要TransactionDefinition 类型的参数,该参数中定义事务相关信息,如事务隔离级别等。TransactionDefinition 也是接口,其实现类为:DefaultTransactionDefinition。相关信息设置的设置方法也是在实现类中定义,如下:
方法 | 说明 |
---|---|
int setIsolationLevel(TransactionDefinition.XXXX) | 设置事务隔离s别 |
int setPropogationBehavior() | 获得事务的传播行为 |
int setTimeout() | 获得超时时间 |
boolean setReadOnly() | 是否只读 |
事务隔离级别:
* ISOLATION_DEFAULT 使用数据库默认级别
* ISOLATION_READ_UNCOMMITTED 读未提交
* ISOLATION_READ_COMMITTED 读已提交
* ISOLATION_REPEATABLE_READ 可重复读
* ISOLATION_SERIALIZABLE 串行化
这些变量已在TransactionDefinition中定义。
事务的传播行为:
* REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
* SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
* MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常
* REQUERS_NEW 新建事务,如果当前在事务中,把当前事务挂起
* NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
* NEVER 以非事务方式运行,如果当前存在事务,抛出异常
* NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作
是否只读
引用自 https://blog.csdn.net/stz344184987/article/details/5836490 |
---|
“只读事务”并不是一个强制选项,它只是一个“暗示”,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。 但是你非要在“只读事务”里面修改数据,也并非不可以,只不过对于数据一致性的保护不像“读写事务”那样保险而已。 因此,“只读事务”仅仅是一个性能优化的推荐配置而已,并非强制你要这样做不可 |
超时时间
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。其单位是秒,-1表示用不超时。
TransactionStatus接口
通过TransactionStatus 可以获取事务的状态信息:
方法 | 说明 |
---|---|
boolean isNewTransaction() | 是否是新事务 |
boolean hasSavepoint() | 是否是回滚点 |
boolean isRollbackOnly() | 事务是否回滚 |
boolean isCompleted() | 事务是否完成 |
2.3 声明式事务控制
2.3.1 xml配置方式
只需要在xml进行配置就可以启用Spring事务:
<!-- 1、生成DataSourceTransactionManager实例,放入容器-->
<bean id="txmanager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="ds"/>
</bean>
<!-- 2、配置通知-->
<tx:advice id="myTx" transaction-manager="txmanager">
<tx:attributes>
<tx:method name="transfer" isolation="REPEATABLE_READ"
propagation="REQUIRED" read-only="false" timeout="-1"/>
</tx:attributes>
</tx:advice>
<!--3、配置切面-->
<aop:config>
<aop:advisor advice-ref="myTx" pointcut="execution(* mc.study.service.impl.AccountServiceImpl.*(..))"/>
</aop:config>
事务通知的配置
一般的通知就是一个bean,而事务通知则是通过<tx:advice>
标签配置,
transaction-manager
指向容器中的DataSourceTransactionManager实例,
<tx:attributes>
每一个<tx:method>
代表一种事务控制,其属性与TransactionDefinition设置一样。
name
属性用于匹配要添加事务的方法,可以用 * 进行通配。
事务切面配置
也不同于一般切面配置使用<aop:aspect>
,而是使用<aop:advisor>
标签。
advice-ref
属性指向配置的事务通知。
2.3.2 注解配置
在需要开启事务的方法上使用@Transactional
注解即可开启事务,若是放在类上,则表示为所有的方法开启事务。
注解中可以通过参数来控制事务信息:
@Transactional(value = "txx",isolation=Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,readOnly = false,timeout = -1)
==注意:==使用value属性指定PlatformTransactionManager的实例id。
在xml中开启事务注解支持
<tx:annotation-driven/>
在纯注解开发中开启事务注解支持:
@EnableTransactionManagement
Spring集成Web开发环境
目前使用spring还需要通过 ClasspathXmlApplicationContext 类加载xml配置文件并构建容器。而后通过ApplicationContext接口获取实例。在web开发中如果在servlet中重复编写此段代码会导致容器多次构建。
为此可以使用spring提供的集成web环境:
第一步,导包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
第二步,配置监听器
<!--全局参数-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>•
</context-param>
<!--Spring的监听器-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
ContextLoaderListener是Spring提供的监听器其中会加载配置文件,配置文件的路径需要通过全局参数contextConfigLocation
指定。
第三步,servlet中使用
ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
Object obj = applicationContext.getBean("id");
注意:需要传递ServletContext参数。
目前只知道此用途,但此用途似乎使用自定义工具类更简洁。