我的复习2-Spring
文章目录
目录
3.建立spring配置文件4.配置所需资源(Service)为spring控制的资源
5.表现层(App)通过spring获取资源(Service实例)
PlatformTransactionManager - 事务管理器
TransactionDefinition - 事务详情信息对象
TransactionStatus - 事务状态对象(了解)
5、BeanFactory和ApplicationContext有什么区别?
前言
这整合了五大框架 以及一些基本的面试问题,欢迎来一起为了前途消耗时间精力。
复习的顺序为Mybaits,Spring,SpringMvc,Springboot,SpringCloud五大框架.后续会增加一些例如Es,RocketMq等知识,以及一些前端的基本知识,具体后期会有详细的介绍。
一、Spring概念
1什么是Spring
Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring的核心是控制反转(IoC)和面向切面(AOP)。
用一句话来概述:Spring是分层的JavaSE/EE应用full-stack轻量级开源框架
2Spring的特点
1、方便解耦,简化开发
通过Spring提供的IoC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。有了Spring,用户不必再为单实例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
2、AOP编程的支持
通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
3、声明式事务的支持
在Spring中,我们可以从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
4、方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,在Spring里,测试不再是昂贵的操作,而是随手可做的事情。例如:Spring对Junit4支持,可以通过注解方便的测试Spring程序。
5、方便集成各种优秀框架
Spring不排斥各种优秀的开源框架,相反,Spring可以降低各种框架的使用难度,Spring提供了对各种优秀框架(如Struts,Hibernate、Hessian、Quartz)等的直接支持。
6、降低Java EE API的使用难度
Spring对很多难用的Java EE API(如JDBC,JavaMail,远程调用等)提供了一个薄薄的封装层,通过Spring的简易封装,这些Java EE API的使用难度大为降低。
7、Java 源码是经典学习范例
Spring的源码设计精妙、结构清晰、匠心独运,处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。Spring框架源码无疑是Java技术的最佳实践范例。如果想在短时间内迅速提高自己的Java技术水平和应用开发水平,学习和研究Spring源码将会使你收到意想不到的效果。
3Spring框架结构
1、核心容器:核心容器提供 Spring 框架的基本功能(Spring Core)。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
2、Spring 上下文:Spring 上下文是一个配置文件,向 Spring框架提供上下文信息。Spring 上下文包括企业服务,例如JNDI、EJB、电子邮件、国际化、校验和调度功能。
3、Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
4、Spring DAO:JDBCDAO抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
5、Spring ORM:Spring 框架插入了若干个ORM框架,从而提供了 ORM 的对象关系工具,其中包括JDO、Hibernate和iBatisSQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
6、Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
7、Spring MVC 框架:MVC框架是一个全功能的构建 Web应用程序的 MVC 实现。通过策略接口,MVC框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。模型由javabean构成,存放于Map;视图是一个接口,负责显示模型;控制器表示逻辑代码,是Controller的实现。Spring框架的功能可以用在任何J2EE服务器中,大多数功能也适用于不受管理的环境。Spring 的核心要点是:支持不绑定到特定 J2EE服务的可重用业务和数据访问对象。毫无疑问,这样的对象可以在不同J2EE 环境(Web 或EJB)、独立应用程序、测试环境之间重用。
4Spring框架特征
轻量——从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的。此外,Spring是非侵入式的:典型地,Spring应用中的对象不依赖于Spring的特定类。
控制反转IOC——Spring通过一种称作控制反转(IoC)的技术促进了低耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
面向切面AOP——Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
容器——Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。
框架——Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。
MVC——Spring的作用是整合,但不仅仅限于整合,Spring 框架可以被看做是一个企业解决方案级别的框架。客户端发送请求,服务器控制器(由DispatcherServlet实现的)完成请求的转发,控制器调用一个用于映射的类HandlerMapping,该类用于将请求映射到对应的处理器来处理请求。HandlerMapping 将请求映射到对应的处理器Controller(相当于Action)在Spring 当中如果写一些处理器组件,一般实现Controller 接口,在Controller 中就可以调用一些Service 或DAO 来进行数据操作 ModelAndView 用于存放从DAO 中取出的数据,还可以存放响应视图的一些数据。 如果想将处理结果返回给用户,那么在Spring 框架中还提供一个视图组件ViewResolver,该组件根据Controller 返回的标示,找到对应的视图,将响应response 返回给用户。
5Spring优点
用简单的语言,模仿他人成就,总结以下优点:
1.低侵入式设计,代码污染极低
2.独立于各种应用服务器,基于Spring框架的应用,可以真正实现Write Once,Run Anywhere的承诺
3.Spring的DI机制降低了业务对象替换的复杂性,更加满足变成的思想高内聚,低耦合。
4.Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式管理,更好的提高了复用性
5.Spring的ORM和DAO提供了与第三方持久层框架的良好整合,并简化了底层的数据库访问
6.Spring并不强制应用完全依赖于Spring,开发者可自由选用Spring框架的部分或全部
二、Spring入门案例
案例环境说明
-
模拟三层架构中表现层调用业务层功能
-
表现层:UserApp模拟UserServlet(使用main方法模拟)
-
业务层:UserService
-
IoC入门案例制作步骤
1.导入spring坐标(5.1.9.release)
2.编写业务层与表现层(模拟)接口与实现类
3.建立spring配置文件
4.配置所需资源(Service)为spring控制的资源
5.表现层(App)通过spring获取资源(Service实例)
1.导入spring坐标(5.1.9.release)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
2.编写业务层与表现层(模拟)接口与实现类
public interface UserService {
//业务方法
void save();
}
public class UserServiceImpl implements UserService {
public void save() {
System.out.println("user service running...");
}
}
3.建立spring配置文件4.配置所需资源(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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 1.创建spring控制的资源-->
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>
</beans>
5.表现层(App)通过spring获取资源(Service实例)
public class UserApp {
public static void main(String[] args) {
//2.加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//3.获取资源
UserService userService = (UserService) ctx.getBean("userService");
userService.save();
}
}
spring两大核心(重点)
* IOC:控制反转,将创建对象向的权利交由给spring进行管理,spring创建的对象在都存放spring核心容器中(IOC容器)
* AOP:面向切面编程
* 注意:spring的核心配置文件 - applicationContext.xml(不是固定)
* 耦合:程序与程序之间的依赖关系,依赖关系越强,耦合度就越高,程序性能越低
* 开发的终极思想:高内聚,低耦合,不能够完全消除程序之间的耦合性,尽可能的去降低耦合性
bean标签(重点)
* 作用:用于让spring创建对象,相当于空参构造方法创建对象 * spring创建对象的原理:通过类全类名,使用反射的方式创建对象,默认使用空参构造创建对象 属性: id - 表示spring创建出来的对象在核心容器中唯一标识,名称 class - 表示要创建对象的全类名 scope - 创建对象单例、多例 singleton 创建的对象是单例(默认) 创建时机:当核心容器被加载时,单例对象就会被创建 销毁时机:当核心容器被销毁时,单例对象就会被销毁 prototype 创建的对象是多例 创建时机:当调用getBean()方法时多例对象会被创建 销毁时机:java内部垃圾回收机制会自动销毁多例对象 name - 表示给核心容器中对象起别名 init-method - 表示对象初始化方法 destroy-method - 表示对象销毁方法
核心类与API
* 创建spring核心容器对象(加载spring的核心配置文件) ApplicationContext new ClassPathXmlApplicationContext(String fileName); 参数:核心配置文件名称 ApplicationContext new FileSystemXmlApplicationContext(String fileName); 参数:核心配置文件磁盘全路径 ApplicationContext new AnnotationConfigApplicationContext(Class class); 参数:核心配置类的class字节码对象 * 获取核心容器对象中对象 Object getBean(String id); - 根据id获取核心容器中的对象 Object getBean(Class c); - 根据类型核心容器中的对象 Object getBean(String id, Class c); - 根据id和类型获取核心容器中的对象
DI依赖注入
* 依赖注入:就是将核心容器中的对象注入给另一个对象的成员变量
set注入(重点)
* 步骤: 1.在需要注入对象的类中,提供对应对象的成员变量 2.给需要注入的成员变量提供set方法 3.在核心配置文件中完成注入操作 * property标签 - 就相当于调用对象的set方法 属性: name - 相当于类中set方法中的属性 ref - 给对象注入引用数据类型属性的 value - 给对象注入基本数据类型(包含包装类)或者字符串
构造方法注入
* 步骤: 1.在需要注入对象的类中,提供对应对象的成员变量 2.需要给对应的成员变量提供有参构造方法,由于有参构造会覆盖无参构造,spring默认是用无参构造创建对象,最好添加有构造的同时补上无参构造 3.在核心配置文件中完成注入操作 * constructor-arg标签 - 表示用构造方法完成依赖注入 属性: name - 构造方法参数名称 ref - 给对象注入引用数据类型属性的 value - 给对象注入基本数据类型(包含包装类)或者字符串
复杂数据类型注入
* 数组 <array> <value>值</value> ... </array> * list集合 <list> <value>值</value> ... </list> * set集合 <set> <value>值</value> ... </set> * map集合 <map> <entry key="键" value="值"></entry> ... </map> * properties集合 <props> <prop key="键">值</prop> ... </props>
其他配置
读取其他配置文件与EL表达式
* 读取properties配置文件 、 <context:property-placeholder location="classpath:jdbc.properties"/> location - 表示文件路径 classpath: - 类路径,maven工程编译后的target目录下的classes根目录 * spring的EL表达式 - ${} 作用:根据根据propertie配置文件中名称读取配置文件中值 * 例:创建数据源 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean>
引入其他配置文件
* 引入其他spring的配置文件 <import resource="classpath:applicationContext-dataSource.xml"/> resource: - 资源路径
报错
* org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.itheima.service.UserService' available: expected single matching bean but found 4: userService,userService2,userService11,userService12 * 查询对象并不是唯一的异常,原因是因为根据类型获取spring容器中对象,如果核心容器中有多个该类型的对象没有办法进行匹配
三、Spring整合mybaits
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
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
">
<!--读取jdbc的配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置数据源(连接池)-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--配置SqlSession工厂实例对象-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<!--给SqlSession工厂配置数据源信息-->
<property name="dataSource" ref="dataSource"/>
<!--给SqlSession工厂配置实体类的别名的包的全类名-->
<property name="typeAliasesPackage" value="com.itheima.domain"/>
</bean>
<!--配置mybatis的映射扫描组件-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--配置扫描的dao接口所在的包路径-->
<property name="basePackage" value="com.itheima.dao"/>
</bean>
</beans>
四、IOC注解
2.1)启动注解功能
启动注解扫描,加载类中配置的注解项
<context:component-scan base-package="packageName"/>
说明:
在进行包所扫描时,会对配置的包及其子包中所有文件进行扫描
扫描过程是以文件夹递归迭代的形式进行的
扫描过程仅读取合法的java文件
扫描时仅读取spring可识别的注解
扫描结束后会将可识别的有效注解转化为spring对应的资源加入IoC容器
注意:
无论是注解格式还是XML配置格式,最终都是将资源加载到IoC容器中,差别仅仅是数据读取方式不同
从加载效率上来说注解优于XML配置文件
* 用于创建对象并讲对象存入核心容器 * 用在类上的注解 @Component - 用于在非三层架构的类上,该类也想被加载进spring核心容器中使用该注解 @Controller - 用于web层的类上,将web的类对应的对象加入的spring核心容器中 @Service - 用于service层的实现类上,将service的实现类对应的对象加入的spring核心容器中 @Repository - 用于dao层的实现类上,将dao的实现类对应的对象加入的spring核心容器中 属性:value-相当于bean标签中的id,表示唯一标识(名称) * 用在方法上的注解 @Bean - 将【方法的返回值】加入到spring的核心容器 属性:value-相当于bean标签中的id,表示唯一标识(名称)
DI注解
* 将对象注入到其他对象的成员变量中
* 用在成员变量上(注入基本数据类型和字符串)
@Value
1.直接给定值即可
2.通过el表达式根据properties配置文件中的键获取值
* 用在成员变量上(注入引用数据类型)
@Autowired - 用于给成员变量注入引用数据类型数据
注意:@Autowired是根据数据类型匹配spring核心容器中的对象
@Qualifier - 辅助@Autowired使用,当容器中有多个相同类型数据,可以使用该注解标明id
属性:value-表示spring核心容器中的id
注意:@Qualifier不能单独使用,需要配合@Autowired
@Resource - 相当于@Autowired+@Qualifier的组合,可以根据名称或类型进行匹配
属性:
name-根据名称进行匹配
type-根据类型进行匹配
注意:使用@Resource多数情况下都是按照名称进行
替代spring核心配置文件注解
* @Configuration - 用在类上,标明当前类是一个spring的核心配置类 作用:能够替代核心配置文件 *<context:component-scan base-package="packageName"/> * @ComponentScan - 相当于开启IOC注解扫描的标签 作用:替代 - <context:component-scan basePackages = "com.itheima"/> * @Import - 相当于引入其他spring的配置类的标签 作用:替代 - <import resouce="xxx.xml"/> * @PropertySource - 相当于引入外部properties资源文件的标签 作用:替代 - <context:property-placeholder location="classpath:xxx.properties"/>
Spring整合Mybatis(全注解开发)
配置类
/*
* Spring核心配置类
* */
@Configuration //指定该类为核心配置类
@ComponentScan("com.itheima") //开启ioc注解扫描
@PropertySource("classpath:jdbc.properties") //读取外部properties配置文件
@Import({JdbcConfig.class, MybatisConfig.class}) //引入其他配置类
public class SpringConfig {
}
/*
* 数据源配置类
* */
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("dataSource")
public DataSource createDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
/**
* Mybatis配置类
*/
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean createSqlSessionFactoryBean(@Autowired DataSource dataSource){
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setTypeAliasesPackage("com.itheima.domain");
factoryBean.setDataSource(dataSource);
return factoryBean;
}
@Bean
public MapperScannerConfigurer createMapperScannerConfigurer(){
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
configurer.setBasePackage("com.itheima.dao");
return configurer;
}
}
service
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
}
spring单元测试
@RunWith(SpringJUnit4ClassRunner.class) //使用spring的单元测试类加载器(写法固定)
//用法1.单元测试:加载核心配置文件(常用)
@ContextConfiguration(value = "classpath:applicationContext.xml")
//用法2.单元测试:加载核心配置类(不太常用)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testFindAll(){
List<Account> all = accountService.findAll();
System.out.println(all);
}
}
spring - AOP
AOP概述
概念&核心思想
* AOP:面向切面编程,在代码运行期间,动态的将两个类中的方法结合到一起运行的思想就叫做面向切面编程
* 底层原理:动态代理
相关概念
* 连接点(JoinPoint):开发者开发的所有的业务逻辑方法,并且可以被增强的方法就叫做连接点
* 切入点(Pointcut):真正意义上被增强的方法就叫做切入点
* 通知(Advice):用于给切入点做增强的方法叫做通知
* 织入(Weaving):【动词】,将切入点方法和通知方法连接起来的动作叫做织入,织入形成的运行体系叫做切面
* 切面(Aspect):切入点方法 + 通知方法形成的运行体系叫做切面
通知类型
* 前置通知:在切入点方法运行之前运行的方法叫做前置通知
* 后置通知:在切入点方法运行之后运行的方法叫做后置通知
* 异常通知:在切入点方法运行过程中出现异常后运行的方法叫做异常通知
* 最终通知:在切入点方法运行完成之后一定要执行的方法叫做最终通知
* 环绕通知:该通知可以完成以上4个通知的功能
图解
快速入门(全XML配置文件形式)
主体思路
* 1.向spring核心容器中声明切入点方法所在的类 * 2.向spring核心容器中声明通知方法所在的类 * 3.配置AOP切面 里面需要指定切入点方法位置 - 通过切入点表达式 里面需要声明切面 - 声明通知类引用,与通知方法和切点表达式
切入点方法所在类
/**
* 切入点方法所在的类
*/
public class UserServiceImpl implements UserService {
public void save() {
System.out.println("UserService save running...");
}
}
通知方法所在类
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 通知方法所在的类
*/
public class AOPAdvice {
//前置通知方法
public void bofore(){
System.out.println("开启事务...前置通知...");
}
//后置通知方法
public void afterReturning(){
System.out.println("提交事务...后置通知...");
}
//异常通知方法
public void afterThrowing(){
System.out.println("回滚事务...异常通知...");
}
//异最终通知方法
public void after(){
System.out.println("释放资源...最终通知...");
}
/**
* 环绕通知方法
* @param pjp 切入点所在类对象
* 参数、返回值、异常
*/
public Object around(ProceedingJoinPoint pjp){
Object result = null;
try {
System.out.println("开启事务...环绕前置通知...");
//获取参数
Object[] args = pjp.getArgs();
//调用方法传递参数、并获取返回值
result = pjp.proceed(args);
System.out.println("提交事务...环绕后置通知...");
} catch (Throwable throwable) {
System.out.println("回滚事务...环绕异常通知...");
throwable.printStackTrace();
} finally {
System.out.println("释放资源...环绕最终通知...");
}
return result;
}
}
核心配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--【切入点和连接点方法】所在对象-->
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>
<!--【通知方法】所在对象-->
<bean id="advice" class="com.itheima.aop.AOPAdvice"/>
<!--AOP配置-->
<aop:config>
<!--指定切入点(位置)-->
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
<!--
配置切面
ref:引用哪个通知对象
-->
<aop:aspect ref="advice">
<!--
aop:before - 前置通知标签
前置通知
method:表示通知使用的方法
pointcut-ref:表示引用的切点表达式(切点方法)
-->
<aop:before method="beforeMethod" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
</beans>
配置详解
切入点表达式
* aop:pointcut标签 - 配置切入点 id:表示切点表达式唯一标识 expression:表示切点表达式(指定切入点所在位置的) * 例:<aop:pointcut id="pt" expression="execution(* *..*.*(..))"/> * 语法详解: 关键字(访问修饰符 返回值 包名.类名.方法名(参数) 异常名) 1.访问修饰符可以省略 2.返回值,可以使用具体类型,也可以使用*表示通配 3.包名,表示类和方法所在包,多个包之间可以使用..表示通配 4.类名和方法名都可以使用*表示通配 5.参数,可以指定参数个数和类型,使用..表示通配,任意类型任意个数的参数 6.关键字固定的就是execution
切入点表达式配置位置问题(了解)
<!--AOP配置 - 切点表达式位置--> <aop:config> <!--公共的切点表达式--> <aop:pointcut id="pt" expression="execution(* *..save(..))"/> <aop:aspect ref="advice"> <!--局部切点表达式--> <aop:pointcut id="pt" expression="execution(* *..save1(..))"/> <!--私有切点表达式:如果配置了私有的切点表达式,那么就不能引用外部的切点表达式了--> <aop:before method="beforeMethod" pointcut="execution(* *..save(..))"/> </aop:aspect> </aop:config>
通知类型配置
<!--AOP配置 - 通知类型-->
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
<aop:aspect ref="advice">
<!--
aop:before - 前置通知标签
aop:after-returning - 后置通知标签
aop:after-throwing - 异常通知标签
aop:after - 最终通知标签
aop:around - 环绕通知标签
属性:
method:表示通知使用的方法
pointcut-ref:表示引用的切点表达式(切点方法)
pointcut:表示单独指定切点表达式
-->
<aop:before method="beforeMethod" pointcut-ref="pt"/>
<aop:after-returning method="afterReturningMethod" pointcut-ref="pt"/>
<aop:after-throwing method="afterThrowingMethod" pointcut-ref="pt"/>
<aop:after method="afterMethod" pointcut-ref="pt"/>
<aop:around method="aroundMethod" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
在通知方法中获取切入点参数(了解)
* 【五种通知】中都可以获取切入点参数
* 方式1:(掌握)
步骤:
给通知方法添加一个参数JoinPoint,环绕通知直接使用ProceedingJoinPoint对象,调用getArgs()方法即可获取
* 例:
public void beforeMethod1(JoinPoint jp){
Object[] param = jp.getArgs();
System.out.println("前置通知增强..." + param[0]);
}
public Object aroundMethod(ProceedingJoinPoint pjp) {
try {
System.out.println("环绕前置通知...");
//环绕通知获取参数方法
Object param = pjp.getArgs();
//调用原有方法
Object result = pjp.proceed();
System.out.println("环绕后置通知..." + result);
return result;
} catch (Throwable throwable) {
System.out.println("环绕异常通知..." + "异常信息:" + throwable.getMessage());
throwable.printStackTrace();
} finally {
System.out.println("环绕最终通知...");
}
return null;
}
* 方式2:(了解)
步骤:
1.需要给通知中添加与切入点方法中类型相同参数
2.需要单独配置局部、私有的切点表达式
3.需要制定出参数的个数与类型,通过 && + arg(通知方法的参数名称) 传递参数
* 例:
public void beforeMethod2(int param){
System.out.println("前置通知增强..." + param);
}
<aop:before method="beforeMethod2" pointcut="execution(* *..*(int)) && args(param)"/>
在通知方法中获取切入点返回值(了解)
* 只有【后置通知】与【环绕通知】中可以获取切入点的返回值
* 方式1:在后置通知中获取返回值
步骤:
1.后置通知方法中添加参数,建议类型使用Object
2.在XML文件的切面配置处,使用returning属性指定通知中参数名称
3.名称要与通知中的参数名称一致
* 例:
public void afterReturningMethod(Object result){
System.out.println("后置通知增强..." + result);
}
<aop:after-returning
method="afterReturningMethod"
pointcut-ref="pt"
returning="result"/>
* 方式2:在环绕通知中获取返回值
步骤:
1.直接通过调用原有方法的操作,获取返回值即可
2.但是此处需要注意:环绕通知方法必须需要带返回值
* 例:
public Object aroundMethod(ProceedingJoinPoint pjp) {
try {
System.out.println("环绕前置通知...");
//调用原有方法
Object result = pjp.proceed();
System.out.println("环绕后置通知..." + result);
return result;
} catch (Throwable throwable) {
System.out.println("环绕异常通知..." + "异常信息:" + throwable.getMessage());
throwable.printStackTrace();
} finally {
System.out.println("环绕最终通知...");
}
return null;
}
<aop:around method="aroundMethod" pointcut-ref="pt"/>
在通知方法中获取切入点异常信息(了解)
* 只有【异常通知】与【环绕通知】中可以获取切入点的异常对象
* 方式1:在异常通知中获取异常对象
步骤:
1.异常通知方法中添加参数,建议类型使用Throwable
2.在切面配置处使用throwing指定通知中参数名称
3.名称要与通知中的参数名称一致
* 例:
public void afterThrowingMethod(Throwable tt){
System.out.println("异常通知增强..." + "异常信息:" + tt.getMessage());
}
<aop:after-throwing
method="afterThrowingMethod"
pointcut-ref="pt"
throwing="tt"/>
* 方式2:在环绕通知中获取异常对象
步骤:直接在环绕通知中处理异常即可
* 例:
public Object aroundMethod(ProceedingJoinPoint pjp) {
try {
System.out.println("环绕前置通知...");
//调用原有方法
Object result = pjp.proceed();
System.out.println("环绕后置通知..." + result);
return result;
} catch (Throwable throwable) {
System.out.println("环绕异常通知..." + "异常信息:" + throwable.getMessage());
throwable.printStackTrace();
} finally {
System.out.println("环绕最终通知...");
}
return null;
}
<aop:around method="aroundMethod" pointcut-ref="pt"/>
AOP注解+XML配置
主体思路
* 1.通过注解向spring核心容器中声明切入点方法所在的类 @Service
* 2.通过注解向spring核心容器中声明通知方法所在的类,使用@Component,因为通知类不属于三层架构
* 3.配置AOP
直接使用@Aspect注解在通知类上即可
配置切点表达式的方法,使用
@PointCut("execution(* *..*(..))")
public void pt(){}
声明通知方法,并引用切点表达式,使用
@Before("pt()") - 前置通知
@AfterReturning("pt()") - 后置通知
@AfterThrowing("pt()") - 异常通知
@After("pt()") - 最终通知
@Around("pt()") - 环绕通知
* 4.需要在核心配置文件中开启IOC的注解扫描,以及AOP的注解支持
<context:component-scan base-package="com.itheima"/>
<aop:aspectj-autoproxy/>
* 注意:
使用注解形式配置AOP通知方法,有一个问题,是注解原生自带的,最终通知总会在后置通知与异常通知之前执行,注解形式建议使用环绕通知来配置
切入点方法所在类
import com.itheima.service.UserService;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserServiceImpl implements UserService {
public void save() {
System.out.println("userService save running...");
//int a = 1 / 0;
}
}
通知方法所在类
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/*
* 通知类(【通知的方法】所在的类)
* */
@Component //将通知所在类的对象加入spring核心容器
@Aspect //声明切面,切面需要引用当前通知类
public class AOPAdvice {
//声明切点表达式的方法
@Pointcut("execution(* *..*(..))")
public void pt(){}
//前置通知方法
@Before("pt()")
public void beforeMethod(){
System.out.println("前置通知增强...");
}
//后置通知方法
@AfterReturning("pt()")
public void afterReturningMethod(){
System.out.println("后置通知增强...");
}
//异常通知方法
@AfterThrowing("pt()")
public void afterThrowingMethod(){
System.out.println("异常通知增强...");
}
//最终通知方法
@After("pt()")
public void afterMethod(){
System.out.println("最终通知增强...");
}
@Around("pt()")
//传递参数 是原切点表达式方法
public void aroundMethod(ProceedingJoinPoint pjp) {
try {
System.out.println("环绕前置通知...");
//调用原有方法
pjp.proceed();
System.out.println("环绕后置通知...");
} catch (Throwable throwable) {
System.out.println("环绕异常通知...");
throwable.printStackTrace();
} finally {
System.out.println("环绕最终通知...");
}
}
}
核心配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.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
">
<!--开启ioc注解扫描-->
<context:component-scan base-package="com.itheima"/>
<!--开启aop注解支持-->
<aop:aspectj-autoproxy/>
</beans>
AOP全注解配置
主体思路
* 注解+XML配置的基础之上使用核心配置类来取代核心配置文件即可
* 将@EnableAspectJAutoProxy注解添加到核心配置类上,表示开启AOP注解支持
核心配置类
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy //开启AOP注解支持
public class SpringConfig {
}
AOP底层原理
AOP动态代理方式配置
* 注意:AOP默认是用JDK动态代理
* 配置文件
aop:aspectj-autoproxy 标签
aop:config 标签
属性:
proxy-target-class
取值:
false:使用JDK动态代理,默认值
true:使用CGlib动态代理
* 注解配置
@EnableAspectJAutoProxy
属性:
proxyTargetClass
取值:
false:使用JDK动态代理,默认值
true:使用CGlib动态代理
JDK动态代理
* 基于JDK动态代理 (aop默认底层原理是 jdk动态代理)
使用前提:
基于JDK动态代理 - 代理类和被代理之间是兄弟级别关系
实现必要条件 1.有顶层接口
2.有接口实现类
接口
/**
* 顶层接口
*/
public interface Dog {
public void wang();
public void eat(String food);
public String dance();
}
实现类(切点所在类)
/**
* 接口实现类(被代理类)
*/
public class HaShiQi implements Dog {
public void wang() {
System.out.println("wang...");
}
public void eat(String food) {
System.out.println("吃了" + food + "...");
}
public String dance() {
return "跳舞...";
}
}
测试代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 测试类
*/
public class JDKProxyTest {
public static void main(String[] args) {
//哈士奇是准备被增强对象
final HaShiQi hsq = new HaShiQi();
/**
* 动态代理核心代码 *固定方法
* Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
* 参数:
//类加载器 三种方法获取( 详情参见类加载器 反射 )
* ClassLoader loader - 使用被代理对象的类加载器
* Class<?>[] interfaces - 使用被代理对象实现的接口数组
* InvocationHandler h - 执行处理器
* 返回值:
*
*/
Dog JiXieHSQ = (Dog) Proxy.newProxyInstance(hsq.getClass().getClassLoader(), hsq.getClass().getInterfaces(), new InvocationHandler() {
/**
* 只要代理对象有任何方法执行,该方法都会运行
* @param proxy 表示最终生成的代理对象
* @param method 表示当前代理对象正在执行的方法
* @param args 表示当前代理对象正在执行的方法的参数
* @return 表示当前代理对象正在执行的方法的返回值
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//针对不同方法进行增强
String methodName = method.getName();
if("wang".equals(methodName)){
//前置增强
System.out.println("机械哈士奇喝了2两...");
//想要实现原有逻辑
Object result = method.invoke(hsq, args);
//后置增强
System.out.println("喝完吐了...");
return result;
}else if("eat".equals(methodName)){
//前置增强
System.out.println("机械哈士奇喝了2两...");
//对于参数进行增强
args[0] = "香肠 + 火腿";
//想要实现原有逻辑
Object result = method.invoke(hsq, args);
//后置增强
System.out.println("打个嗝,睡了...");
return result;
}else if("dance".equals(methodName)){
Object result = method.invoke(hsq, args);
return "唱..." + result + "rap...篮球...";
}else {
//其他方法按照原有逻辑实现
return method.invoke(hsq, args);
}
}
});
//JiXieHSQ.wang();
//JiXieHSQ.eat("香肠");
String result = JiXieHSQ.dance();
System.out.println(result);
}
}
CGlib动态代理
* 基于CGlib动态代理
使用前提:
基于CGlib动态代理 - 代理类和被代理之间是父子级别关系
实现前提 有父类(继承体系中)
* CGlib动态代理实现步骤
1.需要创建一个增强者对象
2.设置要增强的父类对象
3.设置方法拦截的回调函数
4.获取代理对象,调用方法
父类(切点所在类)
/**
* 顶层父类
*/
public class Cat {
public void miao(){
System.out.println("miao...");
}
}
测试代码
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGlibProxyTest {
public static void main(String[] args) {
final Cat cat = new Cat();
//1.创建一个增强者对象
Enhancer enhancer = new Enhancer();
//2.设置增强父类对象
enhancer.setSuperclass(cat.getClass());
//3.设置增强回调函数
enhancer.setCallback(new MethodInterceptor() {
/**
* 只要代理对象有任何方法执行,该方法都会运行
* @param proxy 表示最终生成的代理对象
* @param method 表示当前代理对象正在执行的方法
* @param args 表示当前代理对象正在执行的方法的参数
* @return 表示当前代理对象正在执行的方法的返回值
*/
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
/*if("xxx".equals(method.getName())){
args[0] = "猫罐头";
return "吃吃吃吃...";
}*/
System.out.println("喝了2两...");
Object result = method.invoke(cat, args);
System.out.println("吐了...");
return result;
}
});
//4.构建增强对象
Cat pop = (Cat) enhancer.create();
pop.miao();
}
}
spring04 - 声明式事务
Spring中事务核心对象
PlatformTransactionManager - 事务管理器
TransactionDefinition - 事务详情信息对象
TransactionStatus - 事务状态对象(了解)
基于AOP的编程式事务(了解)
切点所在类
import com.itheima.dao.AccountDao;
import com.itheima.service.AccountService;
/*
* 业务逻辑实现类(切点所在类)
* */
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
//转账操作 张三给李四转500块
public void transfer(String outName, String inName, Double money) {
accountDao.outMoney(outName, money);
int a = 1/0;
accountDao.inMoney(inName, money);
}
}
通知所在类
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import javax.sql.DataSource;
/*
* 通知方法所在类
* */
public class TxAdvice {
private DataSource dataSource;//给service注入数据源对象,为了给事务管理器使用
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
//环绕通知方法
public Object transactionManager(ProceedingJoinPoint pjp) throws Throwable {
//spring编程式事务 - 出现异常自动回滚
//1.获取事务管理器 - 指定事务管理器所需要的数据源
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
//2.获取事务详情信息对象
TransactionDefinition td = new DefaultTransactionDefinition();
//3.开启事务
TransactionStatus ts = transactionManager.getTransaction(td);
//原有方法逻辑
Object result = pjp.proceed(pjp.getArgs());
//没有问题 - 提交事务
transactionManager.commit(ts);
return result;
}
}
核心配置文件
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--切入点方法所在类-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!--AOP 配置事务控制操作-->
<!--声明通知方法所在的类-->
<bean id="txAdvice" class="com.itheima.aop.TxAdvice">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--AOP配置-->
<aop:config>
<!--切点表达式-->
<aop:pointcut id="pt" expression="execution(* *..transfer(..))"/>
<!--配置切面-->
<aop:aspect ref="txAdvice">
<aop:around method="transactionManager" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
</beans>
声明式事务(XML配置文件形式)(重点)
主体思路
* 首先声明式事务无序编写通知类等任何代码,只需要完成配置即可
步骤:
1.声明事务管理器,并给事务管理器注入数据源对象
2.声明切点所在类,也就是业务逻辑层实现类对象
3.声明通知类,引用的就是事务管理器,并指定id
4.配置事务属性,比如声明式事务生效的方法名、是否只读等等...
5.配置切面,指定切点表达式,与通知id
声明式事务配置详解
* tx:advice - 声明通知方法所在的类标签
属性:
id:唯一标识
transaction-manager:引用事务管理器
* tx:attributes - 配置事务属性标签
* tx:method - 配置声明式事务所生效的方法,以及详细信息的标签
属性:
name:配置方法名称的
read-only:配置当前方法的事务是否是只读的,默认读写false,表示读写
timeout:事务超时时间,默认-1,表示不设置超时时间
isolation:表示事务的隔离级别,默认DEFAULT,表示会根据使用数据库自动选择数据的隔离级别
no-rollback-for:表示出现某个异常时不回滚,取值为异常对象全类名
rollback-for:表示出现某个异常时必须回滚,取值为异常对象全类名
propagation:事务传播行为,默认值REQUIRED,表示必须有事务支持。详见图解
* aop:advisor - 专门用于配置声明式事务切面的标签
属性:
pointcut-ref:指定切点表达式的引用
advice-ref:指定通知类的引用
配置文件
<?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" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--声明式事务,无序编写通类知代码-->
<!--业务层接口(切点所在类)-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!--声明式事务-配置事务控制操作-->
<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--
tx:advice - 声明通知方法所在的类标签
transaction-manager:引用事务管理器
-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--
tx:attributes - 配置事务属性
tx:method - 配置声明式事务所生效的方法,以及详细信息
name:配置方法名称的
read-only:配置当前方法的事务是否是只读的
timeout:事务超时时间,-1表示不设置超时时间
isolation:表示事务的隔离级别,DEFAULT会根据使用数据库自动选择数据的隔离级别
no-rollback-for:表示出现某个异常时不回滚
rollback-for:表示出现某个异常时必须回滚
propagation:事务传播行为
-->
<tx:attributes>
<!--表示查询方法的事务为只读的,其他操作方法的事务为读写的-->
<tx:method name="*" read-only="false"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="get*" read-only="true"/>
<tx:method
name="transfer"
read-only="false"
timeout="-1"
isolation="DEFAULT"
no-rollback-for="java.lang.ArithmeticExceptio"
rollback-for=""
propagation="REQUIRED"
/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--切点表达式-->
<aop:pointcut id="pt" expression="execution(* com.itheima.service.*Service.*(..))"/> <!--
aop:advisor - 专门用于配置声明式事务切面的标签
pointcut-ref:指定切点表达式的引用
advice-ref:指定通知类的引用
-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>
</beans>
事务的传播行为详解
声明式事务(XML配置文件+注解形式)(重点)
主体思路
* 使用步骤
1.在纯XML配置文件的基础上,在配置文件中【只保留事务管理器的相关配置】
2.在核心配置文件中,开始声明式事务的注解驱动
3.在业务层的接口实现类上使用@Transactional注解完成配置
4.针对特殊方法也可以在方法上使用@Transactional注解单独配置
* <tx:annotation-driven transaction-manager="transactionManager"/> - 开启声明式事务注解驱动
属性:
transaction-manager:事务管理器的引用
业务层代码
import com.itheima.dao.AccountDao;
import com.itheima.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import javax.sql.DataSource;
@Transactional //声明式事务注解 仅在这里处理 在实现类中 添加注解 如有特殊 在方法中再次定义即可
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Transactional(
readOnly = false, //是否只读
timeout = -1, //超时时间
isolation = Isolation.DEFAULT, //隔离级别
propagation = Propagation.REQUIRED //传播行为
//noRollbackFor = {java.lang.ArithmeticException.class} //出现哪个异常不回滚
)
public void transfer(String outName, String inName, Double money) {
accountDao.outMoney(outName,money);
int a = 1/0;
accountDao.inMoney(inName,money);
}
}
核心配置文件
<?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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--
声明式事务-XML配置文件+注解
1.保留事务管理器
-->
<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--
开启声明式事务的注解驱动
transaction-manager:指定事务管理器
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
声明式事务(全注解形式)
主体思路
* 将transactionManager事务管理器使用方法和@Bean注解添加到spring核心容器中
* 使用@EnableTransactionManagement注解在核心配置类上,开启声明式事务的注解驱动
核心配置类
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JDBCConfig.class, MyBatisConfig.class})
//开启声明式事务的注解驱动
@EnableTransactionManagement
public class SpringConfig {
}
事务管理器配置类
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
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("dataSource")
public DataSource getDataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
@Bean
public PlatformTransactionManager getTransactionManager(@Autowired DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
RedisTemplate模板官方API文档
https://docs.spring.io/spring-data/redis/docs/2.2.4.RELEASE/api/
常见面试题
1、Spring是什么?
Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。常见的配置方式有三种:基于XML的配置、基于注解的配置、基于Java的配置。
主要由以下几个模块组成:
Spring Core:核心类库,提供IOC服务;
Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);
Spring AOP:AOP服务;
Spring DAO:对JDBC的抽象,简化了数据访问异常的处理;
Spring ORM:对现有的ORM框架的支持;
Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传;
Spring MVC:提供面向Web应用的Model-View-Controller实现。
2、Spring 的优点?
(1)spring属于低侵入式设计,代码的污染极低;
(2)spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;
(3)Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。
(4)spring对于主流的应用框架提供了集成支持。
3、Spring的AOP理解:
OOP面向对象,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。
AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理。
AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。
(1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
①JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
②如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
(3)静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理实例; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
4、Spring的IoC理解:
(1)IOC就是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IoC容器来动态注入对象需要的外部资源。
(2)最直观的表达就是,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。
(3)Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。
IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。
5、BeanFactory和ApplicationContext有什么区别?
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
(1)BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
①继承MessageSource,因此支持国际化。
②统一的资源文件访问方式。
③提供在监听器中注册bean的事件。
④同时加载多个配置文件。
⑤载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
(2)①BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
②ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
③相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
(3)BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
(4)BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。
6、请解释Spring Bean的生命周期?
首先说一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy;
Spring上下文中的Bean生命周期也类似,如下:
(1)实例化Bean:
对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。
(2)设置对象属性(依赖注入):
实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成依赖注入。
(3)处理Aware接口:
接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:
①如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值;
②如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。
③如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;
(4)BeanPostProcessor:
如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;
(5)InitializingBean 与 init-method:
如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。
(6)如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;
以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。
(7)DisposableBean:
当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;
(8)destroy-method:
最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
7、 解释Spring支持的几种bean的作用域。
Spring容器中的bean可以分为5个范围:
(1)singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。
(2)prototype:为每一个bean请求提供一个实例。
(3)request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
(4)session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。
(5)global-session:全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。
8、Spring框架中的单例Beans是线程安全的么?
Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。但实际上,大部分的Spring bean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用域由“singleton”变更为“prototype”。
9、Spring如何处理线程并发问题?
在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。
ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
10-1、Spring基于xml注入bean的几种方式:
(1)Set方法注入;
(2)构造器注入:①通过index设置参数的位置;②通过type设置参数类型;
(3)静态工厂注入;
(4)实例工厂;
10-2、Spring的自动装配:
在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使用autowire来配置自动装载模式。
在Spring框架xml配置中共有5种自动装配:
(1)no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。
(2)byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。
(3)byType:通过参数的数据类型进行自动装配。
(4)constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。
(5)autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。
基于注解的方式:
使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,。在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:
如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
如果查询的结果不止一个,那么@Autowired会根据名称来查找;
如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。
@Autowired可用于:构造函数、成员变量、Setter方法
注:@Autowired和@Resource之间的区别
(1) @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。
(2) @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。
11、Spring 框架中都用到了哪些设计模式?
(1)工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
(2)单例模式:Bean默认为单例模式。
(3)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
(4)模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
(5)观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现--ApplicationListener。
12、Spring事务的实现方式和实现原理:
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
(1)Spring事务的种类:
spring支持编程式事务管理和声明式事务管理两种方式:
①编程式事务管理使用TransactionTemplate。
②声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中。
声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式,使业务代码不受污染,只要加上注解就可以获得完全的事务支持。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。
(2)spring的事务传播行为:
spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。
① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘
③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
(3)Spring中的隔离级别:
① ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。
② ISOLATION_READ_UNCOMMITTED:读未提交,允许另外一个事务可以看到这个事务未提交的数据。
③ ISOLATION_READ_COMMITTED:读已提交,保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新。
④ ISOLATION_REPEATABLE_READ:可重复读,保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新。
⑤ ISOLATION_SERIALIZABLE:一个事务在执行的过程中完全看不到其他事务对数据库所做的更新。
13、Spring框架中有哪些不同类型的事件?
Spring 提供了以下5种标准的事件:
(1)上下文更新事件(ContextRefreshedEvent):在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。
(2)上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。
(3)上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
(4)上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
(5)请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。
如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。
14、解释一下Spring AOP里面的几个名词:
(1)切面(Aspect):被抽取的公共模块,可能会横切多个对象。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。
(2)连接点(Join point):指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。
(3)通知(Advice):在切面的某个特定的连接点(Join point)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链。
(4)切入点(Pointcut):切入点是指 我们要对哪些Join point进行拦截的定义。通过切入点表达式,指定拦截的方法,比如指定拦截add*、search*。
(5)引入(Introduction):(也被称为内部类型声明(inter-type declaration))。声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。
(6)目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。
(7)织入(Weaving):指把增强应用到目标对象来创建新的代理对象的过程。Spring是在运行时完成织入。
切入点(pointcut)和连接点(join point)匹配的概念是AOP的关键,这使得AOP不同于其它仅仅提供拦截功能的旧技术。 切入点使得定位通知(advice)可独立于OO层次。 例如,一个提供声明式事务管理的around通知可以被应用到一组横跨多个对象中的方法上(例如服务层的所有业务操作)。
15、Spring通知有哪些类型?
(1)前置通知(Before advice):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
(2)返回后通知(After returning advice):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
(3)抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。
(4)后置通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
(5)环绕通知(Around Advice):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。 环绕通知是最常用的一种通知类型。大部分基于拦截的AOP框架,例如Nanning和JBoss4,都只提供环绕通知。
同一个aspect,不同advice的执行顺序:
①没有异常情况下的执行顺序:
around before advice
before advice
target method 执行
around after advice
after advice
afterReturning
②有异常情况下的执行顺序:
around before advice
before advice
target method 执行
around after advice
after advice
afterThrowing:异常发生
java.lang.RuntimeException: 异常发生