Spring
官方文档 https://spring.io/projects/spring-framework#learn
IOC
控制反转是一种通过描述并通过第三方去生产或获取特定对象的方式
IOC创建对象方式
通过无参构造方法来创建
<bean id="user" class="com.pojo.User">
<property name="name" value="张三"/>
</bean>
通过有参构造方法来创建
<!-- 第一种根据index参数下标设置 -->
<bean id="user1" class="com.pojo.User">
<!-- index指构造方法 , 下标从0开始 -->
<constructor-arg index="0" value="张三"/>
</bean>
<!-- 第二种根据参数名字设置 -->
<bean id="user2" class="com.pojo.User">
<!-- name指参数名 -->
<constructor-arg name="name" value="李四"/>
</bean>
<!-- 第三种根据参数类型设置 -->
<bean id="user3" class="com.pojo.User">
<constructor-arg type="java.lang.String" value="王五"/>
配置
别名
<!--设置别名:在获取Bean的时候可以使用别名获取--> <alias name="user" alias="userNew"/>
Bean的配置
<!--bean就是java对象,由Spring创建和管理-->
<!--
id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
如果配置id,又配置了name,那么name是别名
name可以设置多个别名,可以用逗号,分号,空格隔开
如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;
class是bean的全限定名=包名+类名
-->
<bean id="hello" name="hello2 h2,h3;h4" class="com.pojo.Hello">
<property name="name" value="Spring"/>
</bean>
import
<import resource="{path}/beans.xml"/>
DI
构造器注入同上
setter注入
要求被注入的属性,必须有set方法,set方法的方法名由set + 属性首字母大写,如果属性是boolean类型,没有set方法,是 is
常量注入
<bean id="student" class="com.pojo.Student"> <property name="name" value="小明"/> </bean>Bean注入
<bean id="addr" class="com.pojo.Address"> <property name="address" value="重庆"/> </bean> <bean id="student" class="com.pojo.Student"> <property name="address" ref="addr"/> </bean>数组注入
<bean id="student" class="com.pojo.Student"> <property name="books"> <array> <value>西游记</value> <value>红楼梦</value> </array> </property> </bean>List注入
<property name="hobbys"> <list> <value>听歌</value> <value>看电影</value> </list> </property>Map注入
<property name="card"> <map> <entry key="中国邮政" value="456456456465456"/> <entry key="建设" value="1456682255511"/> </map> </property>set注入
<property name="games"> <set> <value>LOL</value> <value>BOB</value> </set> </property>Null注入
<property name="wife"><null/></property>
Properties注入
<property name="info"> <props> <prop key="性别">男</prop> <prop key="姓名">小明</prop> </props> </property>
拓展注入方式
p命名空间注入
导入约束:xmlns:p="http://www.springframework.org/schema/p"
<!--P(属性: properties)命名空间 , 属性依然要设置set方法--> <bean id="user" class="com.pojo.User" p:name="小明" p:age="18"/>
c命名空间注入
导入约束:xmlns:c="http://www.springframework.org/schema/c"
<!--C(构造: Constructor)命名空间 , 属性依然要设置set方法--> <bean id="user" class="com.pojo.User" c:name="小明" c:age="18"/>
Bean的作用域
- singleton 单例
- Prototype 多例
- Request
- Session
自动装配
按名称自动装配
<bean id="user" class="com.pojo.User" autowire="byName">
<property name="str" value="小明"/>
</bean>
通过set方法查找
按类型自动装配
<bean id="user" class="com.pojo.User" autowire="byType">
<property name="str" value="小明"/>
</bean>
需要同一类型的对象在Spring容器中唯一
@Autowired
按类型自动装配
public class User {
@Autowired
private Cat cat;
public Cat getCat() {
return cat;
}
}
此时配置文件内容
<!-- 开启注解支持 --> <context:annotation-config/> <bean id="cat" class="com.pojo.Cat"/> <bean id="user" class="com.pojo.User"/>
required
默认为true,@Autowired(required=false)说明对象可以为null
@Qualifier
@Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
@Qualifier不能单独使用
@Autowired @Qualifier(value = "cat2") private Cat cat;
@Resource
- @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
- 其次再进行默认的byName方式进行装配;
- 如果以上都不成功,则按byType的方式自动装配。
- 都不成功,则报异常。
@Resource(name = "cat2") private Cat cat;
@Autowired与@Resource异同:
- @Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。
- @Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用
- @Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。 当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。
注解开发
配置文件中引入context约束
<?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"
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">
</beans>
Bean的实现
配置扫描哪些包
<!--指定注解扫描包--> <context:component-scan base-package="com.pojo"/>
扫描过滤
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>>//包含 <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>>//排除
过滤方式
annotation:注解 assignable:指定class或interface的全名 aspectj:AspectJ语法 regex:正则
如果配置了 <context:component-scan> 那么 <context:annotation-config/> 标签就可以不用再xml中配置了,因为前者包含了后者
给类加上注解
@Component
@Controller:web层
@Service:service层
@Repository:dao层
属性注入
@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
@Value("小明")
// 相当于配置文件中 <property name="name" value="小明"/>
public String name;
}
或者在set方法上添加@value("值")
作用域
@Controller("user")
@Scope("prototype")
public class User {
@Value("小明")
public String name;
}
基于Java类的纯注解配置
@Configuration//代表这是一个配置类
@Import(MyConfig2.class)//导入合并其他配置类,类似于配置文件中的 inculde 标签
@ComponentScan("com.pojo")//指定扫描的包
public class MyConfig {
@Bean(name="dog2")//通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!也可以手动指定名称
public Dog dog(){
return new Dog();
}
}
AOP
官方文档https://docs.spring.io/spring/docs/5.2.1.RELEASE/spring-framework-reference/core.html#aop
相关术语
- Joinpoint(连接点):
所谓连接点是指那些被拦截到的点。在 Spring 中,这些点指的是方法,因为 Spring 只支持方法类型的连接点。 - Pointcut(切入点):
所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。 - Advice(通知/增强):
所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。 - Introduction(引介):
引介是一种特殊的通知在不修改类代码的前提下,Introduction 可以在运行期为类动态地添加一些方法或 Field。 - Target(目标对象):
代理的目标对象。 - Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程。
Spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。 - Proxy(代理) :
一个类被 AOP 织入增强后,就产生一个结果代理类。 - Aspect(切面):
是切入点和通知(引介)的结合。
通知类型
| 通知类型 | 名称 | 说明 |
|---|---|---|
| 前置通知 | Before advice | 连接点前执行,除非抛出异常,否则不能阻止方法的继续执行 |
| 后置通知(最终通知) | After (finally) advice | 连接点执行完成后执行,总会执行 |
| 正常返回通知 | After returning advice | 在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行 |
| 异常返回通知 | After throwing advice | 在连接点抛出异常后执行 |
| 环绕通知 | Around advice | 连接点前后执行 |
切入点表达式语法
execution([修饰符] 返回值类型 包名.类名.方法名(参数)) * 表示任意一个 .. 表示多个 xml中连接子表达式用 and, or, not 注解中用 &&, ||, !
XML配置AOP
<!-- 声明开始aop的配置 -->
<aop:config>
<!-- 配置切面。
id:唯一标识,
ref:引用配置好的通知类bean的id
-->
<aop:aspect id="txAdvice" ref="txManager">
<!-- 配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
expression:用于定义切入点表达式,
id:切入点唯一标识
-->
<aop:pointcut expression="execution(表达式)" id="pt1"/>
<!-- 前置通知。
method:指定通知类中的增强方法名称;
pointcut:指定切入点表达式
pointcut-ref:指定切入点的表达式的引用
-->
<aop:before method="beginTransaction" pointcut-ref="pt1"/>
<!-- 正常返回通知
returning:获取返回值传递到对应的参数上,method的方法必须声明一个名为retVal的参数
-->
<aop:after-returning method="commit" returning="retVal" pointcut-ref="pt1"/>
<!-- 异常返回通知
throwing:获取异常传递到对应的参数上,method的方法必须声明一个名为dataAccessEx的参数
-->
<aop:after-throwing method="rollback" throwing="dataAccessEx" pointcut-ref="pt1"/>
<!-- 最终通知 -->
<aop:after method="release" pointcut-ref="pt1"/>
<!-- 环绕通知
通常情况独立使用
-->
<aop:advisor method="release" pointcut-ref="pt1"/>
</aop:aspect>
</aop:config>
注解配置AOP
@Component("txManager")
@Aspect
public class AnnotationPointcut {
@Before("execution(* com.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("---------方法执行前---------");
}
@After("execution(* com.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("---------方法执行后---------");
}
@Around("execution(* com.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println("签名:"+jp.getSignature());
//执行目标方法proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
开启注解AOP的支持
在 spring 配置文件中开启 spring 对注解 AOP 的支持
<!-- 开启 spring 对注解 AOP 的支持 --> <aop:aspectj-autoproxy/>
不使用 XML 的配置方式
@Configuration
@ComponentScan(basePackages="...")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}
aop:aspectj-autoproxy:说明
通过aop命名空间的
声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被 隐藏起来了 有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为 时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
整合MyBatis(MyBatis-Spring)
官方文档:http://mybatis.org/spring/zh/index.html
Maven依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
版本对应
| MyBatis-Spring | MyBatis | Spring 框架 | Spring Batch | Java |
|---|---|---|---|---|
| 2.0 | 3.5+ | 5.0+ | 4.0+ | Java 8+ |
| 1.3 | 3.4+ | 3.2.2+ | 2.1+ | Java 6+ |
方式一
引入Spring配置文件的beans.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">配置数据源替换mybatis的数据源
<!--配置数据源:数据源有非常多,可以使用第三方的,也可使使用Spring的--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean>配置SqlSessionFactory,关联MyBatis
<!--配置SqlSessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!--关联Mybatis--> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:com/dao/*.xml"/> </bean>注册SqlSessionTemplate,关联SqlSessionFactory
<!--注册sqlSessionTemplate , 关联sqlSessionFactory--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!--利用构造器注入--> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean>增加Dao接口的实现类;私有化SqlSessionTemplate
public class UserDaoImpl implements UserMapper { //sqlSession不用我们自己创建了,Spring来管理 private SqlSessionTemplate sqlSession; public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } public List<User> selectUser() { UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.selectUser(); } }注册bean实现
<bean id="userDao" class="com.kuang.dao.UserDaoImpl"> <property name="sqlSession" ref="sqlSession"/> </bean>
方式二:SqlSessionDaoSupport
官方文档http://mybatis.org/spring/zh/sqlsession.html#SqlSessionDaoSupport
声明式事务
事务
事务四个属性ACID
原子性(atomicity)
事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
一致性(consistency)
一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
隔离性(isolation)
可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
持久性(durability)
事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中
事务传播行为
Spring支持7种传播行为
- propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
- propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
- propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
- propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
- propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
- propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作
事务隔离级别
spring中事务的隔离级别可以通过隔离属性指定
| DEFAULT | 使用底层数据库的默认隔离级别,大部分数据库,默认隔离级别都是READ_COMMITED |
| READ_COMMITED | 只允许事务读取已经被其他事务提交的更改,可以避免脏读,但不可重复读和幻读问题仍然可能出现 |
| READ_UNCOMMITED | 允许事务读取未被其他事务提交的更改。脏读,不可重复读,幻读都可能会出现 |
| REPEATABLE_READ | 确保事务可以多次从一个字段中读取相同的值。在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,但是幻读的问题依然存在 |
| SERIALIZABLE | 确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入,更新,删除。所有的并发问题都能避免,但是性能比较低。 |
注意:事务的隔离级别需要底层数据库引擎的支持,而不是应用程序或者框架的支持
- Oracle支持2种事务隔离级别:READ_COMMITED,SERIALIZABLE
- MySQL支持4种事务隔离级别
基于XML
导入 aop 和 tx 两个名称空间
步骤:
配置事务管理器
<!-- 配置一个事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入 DataSource --> <property name="dataSource" ref="dataSource"></property> </bean>配置事务的通知引用事务管理器
<!-- 事务的配置 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"></tx:advice>
配置事务的属性
<!--在 tx:advice 标签内部 配置事务的属性 --> <tx:attributes> <!-- 指定方法名称:是业务核心方法 read-only:是否是只读事务。默认 false,不只读。 isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。 propagation:指定事务的传播行为。 timeout:指定超时时间。默认值为: -1。永不超时。 rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。没有默认值,任何异常都回滚。 no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,任何异常都回滚。 --> <tx:method name="*" read-only="false" propagation="REQUIRED"/> <tx:method name="find*" read-only="true" propagation="SUPPORTS"/> </tx:attributes>配置 AOP 切入点表达式
<!-- 配置 aop --> <aop:config> <!-- 配置切入点表达式 --> <aop:pointcut expression="execution(* com.service.impl.*.*(..))" id="pt1"/> </aop:config>配置切入点表达式和事务通知的对应关系
<!-- 在 aop:config 标签内部: 建立事务的通知和切入点表达式的关系 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
基于注解
配置事务管理器并注入数据源
<!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>在业务层使用 @Transactional 注解
@Service("accountService") @Transactional(readOnly=true,propagation=Propagation.SUPPORTS) public class AccountServiceImpl implements IAccountService { ... }- 该注解的属性和 xml 中的属性含义一致。该注解可- 以出现在接口上,类上和方法上。
- 出现接口上,表示该接口的所有实现类都有事务支持。
- 出现在类上,表示类中所有方法有事务支持
- 出现在方法上,表示方法有事务支持。
- 以上三个位置的优先级:方法>类>接口
在配置文件中开启 spring 对注解事务的支持
<!-- 开启 spring 对注解事务的支持 --> <tx:annotation-driven transaction-manager="transactionManager"/>
使用java配置类替换xml实现全注解
@Configuration
@EnableTransactionManagement
public class SpringTxConfiguration {
//里面配置数据源,配置 JdbcTemplate,配置事务管理器
}