文章目录
Spring
1.前言 什么是框架
框架(Framework):
- 框(指其约束性)架(指其支撑性)其实就相当于一个解决实际问题的“半成品”,其本身一般不完整到可以解决特定问题,在软件设计中指为解决一个开放性问题而设计的具有一定约束性的支撑结构。
- 框架天生就是为扩展而设计的,在此结构上可以根据具体问题扩展、安插更多的组成部分,从而更迅速和方便地构建完整的解决问题的方案。
- 框架里面可以为后续扩展的组件提供很多辅助性、支撑性的方便易用的实用工具(utilities),也就是说框架时常配套了一些帮助解决某类问题的库(libraries)或工具(tools)。
What should we do? What should we learn from framwork?
- 知道框架能做什么
- 学习框架的语法,一般框架完成一个功能需要一定的步骤
- 框架的内部实现原理
- 尝试实现一个框架
2. 什么是Spring
Spring 被称为 J2EE 的春天,以interface21框架为基础,是一个是分层的 Java SE/EE full-stack 开源的轻量级的 Java 开发框架, 是最受欢迎的企业级 Java 应用程序开发框架,数以百万的来自世界各地的开发人员使用 Spring 框架来创建性能好、易于测试、可重用的代码。
Spring具有控制反转(IoC)和面向切面(AOP)两大核心。Java Spring 通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
Spring 框架不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何 Java 应用都可以从 Spring 中受益。Spring 框架还是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力。
Spring官网 https://spring.io
3. Spring的优势
- 方便解耦,简化开发
Spring 就是一个大工厂,可以将所有对象的创建和依赖关系的维护交给 Spring 管理。 - 方便集成各种优秀框架
Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如 Struts2、Hibernate、
MyBatis 等)的直接支持。 - 降低 Java EE API 的使用难度
Spring 对 Java EE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等)都提供了封装,
使这些 API 应用的难度大大降低。 - 方便程序的测试
Spring 支持 JUnit4,可以通过注解方便地测试 Spring 程序。 - AOP 编程的支持
Spring 提供面向切面编程,可以方便地实现对程序进行权限拦截和运行监控等功能。 - 声明式事务的支持
只需要通过配置就可以完成对事务的管理,而无须手动编程。
4.Spring的体系结构
Spring 为我们提供了一站式解决方案,但Spring 是模块化的,允许咱们挑选和选择适用于项目的模块,不需要把剩余部分也引入。Spring 框架提供约 20 个模块,可以根据应用程序的要求来选择。
4.1 核心容器
核心容器由 Spring-core、Spring-beans、Spring-context、Spring-context-support Spring expression(SpEL,Spring 表达式语言,Spring Expression Language)等模块组成,Spring-core 模块提供了框架的基本组成部分,包括 IoC 和依赖注入功能。
-
Spring-core 模块
提供了框架的基本组成部分,包括 IoC 和依赖注入功能。
-
Spring-beans 模块
提供 BeanFactory(核心容器的主要组件是 BeanFactory ),它是工厂模式的微妙实现,它移除了编码式单例的需要,并且可以把配置和依赖从实际编码逻辑中解耦。
-
context 模块
建立在由 core和 beans 模块的基础上,它以一种类似于 JNDI 注册的方式访问对象。Context 模块继承自 Bean 模块,并且添加了国际化(比如,使用资源束)、事件传播、资源加载和透明地创建上下文(比如,通过 Servlet 容器)等功能。Context 模块也支持 Java EE 的功能,比如 EJB、JMX 和远程调用等。ApplicationContext 接口是 Context 模块的焦点。Spring-context-support 提供了对第三方集成到 Spring 上下文的支持,比如缓存(EhCache, Guava, JCache)、邮件(JavaMail)、调度(CommonJ, Quartz)、模板引擎(FreeMarker, JasperReports, Velocity)等。
-
Spring-expression 模块
提供了强大的表达式语言,用于在运行时查询和操作对象图。它是 JSP2.1规范中定义的统一表达式语言的扩展,支持 set 和 get 属性值、属性赋值、方法调用、访问数组集合及索引的内容、逻辑算术运算、命名变量、通过名字从 Spring IoC 容器检索对象,还支持列表的投影、选择以及聚合等。
4.2 数据访问/集成
-
JDBC(Java Data Base Connectivity ) 模块
提供了 JDBC 抽象层,它消除了冗长的 JDBC 编码和对数据库供应商特定错误代码的解析。
-
ORM(Object Relational Mapping ) 模块
提供了对流行的对象关系映射 API 的集成,包括 JPA、JDO 和 Hibernate 等。通过此模块可以让这些 ORM 框架和 Spring的其它功能整合,比如前面提及的事务管理。
-
OXM(Object XML Mapping )模块
提供了对 OXM 实现的支持,比如 JAXB、Castor、XML Beans、JiBX、XStream 等。
-
JMS(Java Message Service ) 模块
包含生产(produce)和消费(consume)消息的功能。从 Spring 4.1 开始,集成了Spring-messaging 模块。
-
事务模块
为实现特殊接口类及所有的 POJO 支持编程式和声明式事务管理。
4.3 Web
-
Web 模块
提供面向 web 的基本功能和面向 web 的应用上下文,比如多部分(multipart)文件上传功能、使用 Servlet 监听器初始化 IoC 容器等。它还包括 HTTP 客户端以及 Spring 远程调用中与 web 相关的部分。
-
Web-MVC 模块
为 web 应用提供了模型视图控制(MVC)和 REST Web服务的实现。Spring 的MVC 框架可以使领域模型代码和 web 表单完全地分离,且可以与 Spring 框架的其它所有功能进行集成。
-
Web-Socket 模块
为 WebSocket-based 提供了支持,而且在 web 应用程序中提供了客户端和服务器端之间通信的两种方式。
-
Web-Portlet 模块
提供了用于 Portlet 环境的 MVC 实现,并反映了 Spring-webmvc 模块的功能。
4.4 其他
-
AOP 模块
提供了面向方面(切面)的编程实现,允许你定义方法拦截器和切入点对代码进行干净地解耦,从而使实现功能的代码彻底的解耦出来。
-
Aspects 模块
提供了与 AspectJ 的集成,这是一个功能强大且成熟的面向切面编程(AOP)框架。
-
Instrumentation 模块
在一定的应用服务器中提供了类 instrumentation 的支持和类加载器的实现。
-
Messaging 模块
为 STOMP 提供了支持作为在应用程序中 WebSocket 子协议的使用。它也支持一个注解编程模型,它是为了选路和处理来自 WebSocket 客户端的 STOMP 信息。
-
测试模块
支持对具有 JUnit 或 TestNG 框架的 Spring 组件的测试。
5.Spring IoC
5.1 什么是控制翻转
控制反转(Inversion of Control,IoC )是一种设计思想,指在程序开发中,实例的创建不再由调用者管理,而是由 Spring 容器创建。
Spring 容器会负责控制程序之间的关系,而不是由程序代码直接控制,因此,控制权由程序代码转移到了 Spring 容器中,控制权发生了反转,这就是 Spring 的 IoC 思想。
为了接下来的学习,需要导入相关依赖,也可以右键项目/模块选择框架支持。
<dependencies>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--编译插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
5.2 获取Spring容器
5.2.1 BeanFactory
BeanFactory 是基础类型的 IoC 容器,是一个管理 Bean 的工厂,它主要负责初始化各种 Bean,并调用它们的生命周期方法。
BeanFactory 接口有多个实现类,最常见的是 org.Springframework.beans.factory.xml.XmlBeanFactory,它是根据 XML 配置文件中的定义装配 Bean 的。
BeanFactory beanFactory = new XmlBeanFactory(new FileSystemResource(Spring配置文件的名称));
5.2.2 ApplicationContext
ApplicationContext 是 BeanFactory 的子接口,也被称为应用上下文。它不仅提供了 BeanFactory 的所有功能,还添加了对 i18n(国际化)、资源访问、事件传播等方面的良好支持。ApplicationContext 接口有两个常用的实现类:
-
ClassPathXmlApplicationContext
这个类极为常用,该类从类路径 ClassPath 中寻找指定的 XML 配置文件,找到并装载完成ApplicationContext 的实例化工作。ApplicationContext applicationContext=new ClassPathXmlApplicationContext(Spring配置文件的名称);
-
FileSystemXmlApplicationContext
它与 ClassPathXmlApplicationContext 的区别是:在读取 Spring 的配置文件时,FileSystemXmlApplicationContext 不再从类路径中读取配置文件,而是通过参数指定配置文件的位置,它可以获取类路径之外的资源,如“D:\application.xml”。ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);
5.3 bean标签的属性
5.4 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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
spring容器创建对象的方式:
(1)通过无参构造方法
(2)通过有参构造方法
(3)通过工厂方法
-->
<!--(1)通过无参构造方法(默认方式)
alias:起别名,可以配置多个,用逗号,分号,空格隔开
-->
<bean id="team1" alias="team" class="com.lijinghua.pojo.Team"/>
<!--(2)通过有参构造方法,根据有参构造需要的参数进行构造 构造器注入(下面会提到) -->
<bean id="team2" class="com.lijinghua.pojo.Team">
<!--name:参数名-->
<constructor-arg name="id" value="001"/>
<constructor-arg name="name" value="篮网"/>
<constructor-arg name="location" value="布鲁克林"/>
</bean>
<bean id="team3" class="com.lijinghua.pojo.Team">
<!--index:参数下标-->
<constructor-arg index="0" value="002"/>
<constructor-arg index="1" value="湖人"/>
<constructor-arg index="2" value="洛杉矶"/>
</bean>
<!--(3)通过工厂方法-->
<bean id="factory" class="com.lijinghua.Utils.MyFactory"/>
<!--实例方法: new MyFactory().instanceFunction();-->
<bean id="instanceTeam" factory-bean="factory" factory-method="instanceFunction"/>
<!--静态方法: MyFactory.staticFunction();-->
<bean id="staticTeam" class="com.lijinghua.Utils.MyFactory" factory-method="staticFunction"/>
</beans>
<!--团队合作通过import 实现将多个Spring xml文件形成一个Spring配置文件,但是需要注意重名现象-->
<import resource="{path}/beans.xml"/>
5.5 依赖注入(DI)
5.5.1 什么是依赖注入
依赖注入(Dependency Injection,DI),是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。
依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
5.5.2 IoC和DI之间的关系
IoC 是一个概念,是一种思想,其实现方式多种多样。依赖注入就是其中用的比较多的一种方式。
Ioc和DI是同一个概念的不同角度描述。=IoC是一种思想、概念,DI是实现它的手段。
我们可以通过几个问题去理解他们的关系:
- 谁依赖于谁:当然是应用程序依赖于IoC容器;
- 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
- 谁注入谁:很明显是IoC容器注入应用程序中的某个对象——应用程序依赖的对象;
- 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
“依赖注入”明确描述了“被注入对象依赖于IoC容器配置依赖对象”。
Spring框架使用依赖注入实现IoC。Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean。Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的松耦合。
5.6 基于XML的DI
5.6.1 注入的方式
bean 实例在调用无参构造器创建对象后,就要对 bean 对象的属性进行初始化。初始化是由容器自动完成的,称为注入。
5.6.1.1 通过set方法注入
set 注入也叫设值注入,是通过 setter 方法传入被调用者的实例。这种注入方式简单、直观,因而在Spring 的依赖注入中大量使用。
<bean id="addr" class="com.kuang.pojo.Address">
<property name="address" value="重庆"/>
</bean>
<bean id="student" class="com.kuang.pojo.Student">
<!--常量注入-->
<property name="name" value="小明"/>
<!--Bean注入-->
<property name="address" ref="addr"/>
<!--数组注入-->
<property name="books">
<array>
<value>西游记</value>
<value>红楼梦</value>
<value>水浒传</value>
</array>
</property>
<!--List注入-->
<property name="hobbys">
<list>
<value>听歌</value>
<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>
<value>COC</value>
</set>
</property>
<!--Null注入-->
<property name="wife"><null/></property>
<!--Properties注入-->
<property name="info">
<props>
<prop key="学号">20190604</prop>
<prop key="性别">男</prop>
<prop key="姓名">小明</prop>
</props>
</property>
</bean>
5.6.1.2 通过构造方法注入
构造注入是在构造调用者实例的同时,完成被调用者的实例化,前提是必须要有无参构造方法。使用构造器设置依赖关系。
案例见前面的全参构造方法创建对象。
5.6.1.3 通过自动注入
对于引用类型属性的注入,可以不在配置文件中显示的注入。而是通过为标签设置 autowire 属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属性)。根据自动注入判断标准的不同,可以分为两种:
-
byName 根据名称自动注入
当配置文件中被调用者 bean 的 id 值与代码中调用者 bean 类的属性名相同时,可使用byName 方式,让容器自动将被调用者 bean 注入给调用者 bean。容器是通过调用者的 bean类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。
-
byType 根据类型自动注入
使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类,要与代码中调用者 bean 类的某引用类型属性类型同源。即要么相同,要么有 is-a 关系(子类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配哪一个了。
<?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="teamDao" class="com.lijinghua.dao.TeamDao"/>
<!-- <bean id="teamDao1" class="com.lijinghua.dao.TeamDao"/>-->
<bean id="teamService" class="com.lijinghua.service.TeamService">
<!--使用set注入,spring容器先创建teamDao,再创建teamService-->
<property name="teamDao" ref="teamDao"></property>
</bean>
<bean id="teamService2" class="com.lijinghua.service.TeamService">
<!--使用有参构造方法注入,而且必须有无参构造方法,spring容器先创建teamDao,再创建teamService-->
<constructor-arg name="teamDao" ref="teamDao"></constructor-arg>
</bean>
<bean id="teamService3" class="com.lijinghua.service.TeamService" autowire="byName">
<!--使用自动注入 spring容器先创建teamDao,再创建teamService
查找spring容器中,bean的对象名id 与要注入的属性 名称相同的对象
-->
</bean>
<bean id="teamService4" class="com.lijinghua.service.TeamService" autowire="byType">
<!--使用自动注入spring容器先创建teamDao,再创建teamService
查找spring容器中,与要注入的属性 相同类型的对象,要求类型相同的对象唯一,否则不知道匹配谁了
-->
</bean>
</beans>
5.5.3.4 通过命名空间注入(扩展)
可以参考B站中Java狂神的介绍。
5.7 基于注解实现IoC
对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 bean 实例。Spring 中使用注解,需要在原有 Spring 运行环境基础上再做一些改变。
5.7.1 组件扫描
组件扫描(component scanning),即 Spring 能够从 classpath 下自动扫描,侦测和实例化具有特定注解(annotation)的Bean。
注意,如果存在多个包需要扫描的情况怎么办:
挨个扫描
当需要扫描多个包的时候,可以如图一个个扫描;
<context:component-scan basepackage="com.lijinghua.dao"/> <context:component-scan basepackage="com.lijinghua.service"/> <context:component-scan basepackage="com.lijinghua.controller"/>
一次性多个扫描
<!--base-package中直接声明要扫描的多个包 ,多个值用逗号、分号或者空格分割,但是空格不推荐--> <context:component-scan basepackage="com.lijinghua.dao,com.lijinghua.service,com.lijinghua.controller"/>
base-package指定到父包名
base-package 的值表示基本包,容器启动会扫描包及其子包中的注解,当然也会扫描到子包下级的子包。所以如果需要扫描多个包,可以直接让 base-package 指定一个父包。但不建议使用顶级的父包,因为扫描的路径比较多,导致容器启动时间变慢。
<context:component-scan base-package="com.lijinghua"></context:component-scan>
5.7.2 声明Bean的注解说明
-
@Component 注解
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Indexed public @interface Component { String value() default ""; } //方式一:@Component(value = "td")//等价于 <bean id="td" class="com.lijinghua.dao.TeamDao"/> //方式二: @Component("td")//等价于 <bean id="td" class="com.lijinghua.dao.TeamDao"/> //方式三: //@Component//等价于 <bean id="teamDao" class="com.lijinghua.dao.TeamDao"/> public class TeamDao { public void add(){ System.out.println("TeamDao----add"); } }
- 一般用于实体类或者其他类的注解。
- 在类上添加注解@Component表示该类创建对象的权限交给Spring容器。注解的value属性用于指定bean的id值,value可以省略。
- @Component 不指定 value 属性,bean 的 id 是类名的首字母小写。
-
@Repository 注解
一般用于dao实现类的的注解,创建持久层对象。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Repository { @AliasFor(//@AliasFor注解,来给注解的属性起别名 annotation = Component.class ) String value() default ""; }
-
@Service 注解
一般用于service实现类的注解,创建业务层对象,业务层对象可以加入事务功能 。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Service { @AliasFor( annotation = Component.class ) String value() default ""; }
-
@Controller 注解
一般用于controller实现类的注解,创建控制层对象,可以作为处理器接收用户的请求。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Controller { @AliasFor( annotation = Component.class ) String value() default ""; }
说明:其实@Repository、@Service、@Controller 就是对@Component 注解的细化。
5.7.3 使用注解完成DI
5.7.3.1 @Vaule属性注入
- 使用@Value注解在需要注入的属性上,可以在创建对象完毕后完成属性注入,该注解的 value 属性用于指定要注入的值。使用该注解完成属性注入时,类中无需 setter,本质是set注入。
- 若属性有 setter,则也可不加在属性上,而是将其加到 setter 上。
5.7.3.2 @Autowired自动装配
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
@Service
public class TeamService {
@Autowired
private TeamDao teamDao;
//特别地,如果允许对象为null,设置required = false,默认为true
@Autowired(required = false)
private Cat cat;
}
使用@Autowired注解在特殊的引用属性上,可以在创建对象完毕后完成引用属性注入。
- 使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。
- @Autowired注入首先默认根据byType注入,当类型大于1时再根据byName注入。只有当类型大于1时,且名字id不匹配才报错。这个时候就需要@Qualifier注解的配合使用。
5.7.3.3 @Qualifier自动装配
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
String value() default "";
}
@Service
public class TeamService {
@Autowired
@Qualifier("teamDao1")
private TeamDao teamDao;
//特别地,如果允许对象为null,设置required = false,默认为true
@Autowired(required = false)
private Cat cat;
}
需要在引用属性上联合使用注解@Autowired 与@Qualifier。@Qualifier 的 value 属性用于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到 set 方法上。
5.7.3.4 @Resource自动装配
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
String name() default "";
String lookup() default "";
Class<?> type() default java.lang.Object.class;
enum AuthenticationType {
CONTAINER,
APPLICATION
}
AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
boolean shareable() default true;
String mappedName() default "";
String description() default "";
}
Spring提供了对JDK中@Resource注解的支持。@Resource可在属性上,也可在 set 方法上。
-
@Resource的匹配方式:
- @Resource注解如有指定的name属性,则先按该属性进行byName方式查找装配;
- 其次再按默认的byName方式匹配Bean;
- 最后都不成功的话,再按byType的方式匹配 Bean;
- 以上都不成功的话报异常。
拓展:也可以同时设置使用byName方式与byType方式一起进行查找匹配,这种形式必须都匹配上才成功。
-
使用该注解,要求 JDK 必须是 1.6 及以上版本。
5.7.3.5 @Autowired与@Resource异同
- 相同点
- @Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。
- 它们的作用相同都是用注解方式注入对象。
- 不同点
- 执行顺序不同,@Autowired先byType,@Resource先byName。
- @Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用。
- @Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。 当找不到与名称匹配的bean时才按照类型进行装配。
6 代理模式
6.1 为什么要学习代理模式
- 理由一:代理模式是设计模式的一种(设计模式很重要);
- 理由二:Spring的一大特色就是 AOP ,而 AOP 的底层实现机制是动态代理。
6.2 什么是代理模式
简单来说就是托管/中间商/中介(自己不做,交由别人帮自己去完成),实现在原有功能的基础上添加新的功能。
核心思想:没有什么是加一层解决不了的, 解耦
代理模式主要分为静态代理和动态代理两种。
举例:
(1)平时我们租房子,通常不是自己去找房东寻求租房,而是去找一个中介,帮我们去找房源;
(2)明星、球星等等通常会找个经纪人……
6.3 原有方式
原先没有使用代理模式的编写方式:核心业务和服务方法都编写在一起。
弊端:业务划分不明确,重复性代码很高,一旦有改动,维护代价高昂。
6.4 静态代理
静态代理就是在编译的时候,我们的代理就已经存在了。
静态代理角色分析:
-
抽象角色
一般使用接口或者抽象类来实现。(要出租房子的人:房东)
-
真实角色
被代理的角色。(房东A、房东B……)
-
代理角色
代理真实角色 。代理真实角色后 , 一般会做一些附属的操作。(中介)
6.4.1 基于类的静态代理
将服务性代码分离交由对应的代理去做,只把核心业务保留。
优点
实现了一定程度上的解耦。
缺点
基于类的静态代理一次只能代理一个类,如果需要被代理的类很多,就会很复杂。
//真实角色:被代理对象,要租房子的人
public class TeamService {
public void add() {
System.out.println("TeamService----add----核心业务");
}
}
//代理角色:中介
public class TeamServiceProxy extends TeamService {
@Override
public void add(){
try {
System.out.println("开始事务");
super.add();//核心业务由被代理对象去完成,代理只需要完成其他的服务功能
System.out.println("事务提交");
} catch (Exception e) {
System.out.println("回滚");
} finally {
System.out.println("关闭资源");
}
}
}
6.4.2 基于接口的静态代理
在基于类的静态代理基础上,为核心业务提供了一个公共接口,通过接口暴露被代理的方法。
优点
- 相比于基于类的静态代理,实现了更高程度的解耦,代理类的数量有效减少;
- 接口的多继承形式可以充分代理我们需要被代理的方法(一个被代理的类,可以通过继承多个接口,让多个代理类帮忙做我们要做的事。比如通过多级代理的方式实现嵌套代理)。
缺点
对代理类和被代理类添加了限制条件:它们实现了同一个接口。
//抽象角色:房东
public interface MyService {
public abstract void add();
public abstract void delete();
}
//真实角色:房东A
public class MyServiceImpl1 implements MyService {
@Override
public void add() {
System.out.println("MyServiceImpl1----add----核心业务");
}
@Override
public void delete() {
System.out.println("MyServiceImpl1----delete----核心业务");
}
}
//真实角色:房东B
public class MyServiceImpl2 implements MyService {
@Override
public void add() {
System.out.println("MyServiceImpl2----add----核心业务");
}
@Override
public void delete() {
System.out.println("MyServiceImpl2----delete----核心业务");
}
}
//代理角色:中介
public class MyServiceProxy implements MyService {
private MyService myService;//被代理角色
public MyServiceProxy(MyService myService) {
this.myService = myService;
}
@Override
public void add() {
try {
System.out.println("开始事务");
myService.add();//核心业务由被代理对象去完成,代理只需要完成其他的服务功能
System.out.println("事务提交");
} catch (Exception e) {
System.out.println("回滚");
} finally {
System.out.println("关闭资源");
}
}
@Override
public void delete() {
try {
System.out.println("开始事务");
myService.delete();//核心业务由被代理对象去完成,代理只需要完成其他的服务功能
System.out.println("事务提交");
} catch (Exception e) {
System.out.println("回滚");
} finally {
System.out.println("关闭资源");
}
}
}
6.4.3 基于切面的静态代理
从基于类、接口的静态代理可以看出,其实我们的代理需要增添的服务性代码通常有几个固定的位置(切面思想),比如说:在我们的核心代码之前、核心代码之后、异常的处理、资源的释放……
这样我们就可以继续加一层有关切面的接口去定义我们需要切入的部分。
优点
- 相比于基于类、接口的静态代理,更优化重复性代码,实现了更高程度的解耦;
- 通过切面的方式更细化地让代理对象织入服务性代码,在原有基础上增强功能。
缺点
一旦我们的接口变更方法,所有的实现类(代理类和被代理类)都需要去变更维护。
因为代理类和被代理类要求实现同一个接口,所以被代理类的增加还是会让代理类的数量增加。
/**
* 切面接口: 定义切入的位置、部分
*/
public interface Aop {
public abstract void before();
public abstract void after();
public abstract void myException();
public abstract void myFinally();
}
/**
* 具体切面: 具体切入位置、部分要切入的有关事务的服务性代码
*/
public class TransAop implements Aop{
@Override
public void before() {
System.out.println("开始事务");
}
@Override
public void after() {
System.out.println("提交事务");
}
@Override
public void myException() {
System.out.println("回滚事务");
}
@Override
public void myFinally() {
System.out.println("资源释放");
}
}
/**
* 具体切面: 具体需要切入位置、部分要切入的有关日志的服务性代码
*/
public class LogAop implements Aop{
@Override
public void before() {
System.out.println("开始日志");
}
@Override
public void after() {
System.out.println("提交日志");
}
@Override
public void myException() {
System.out.println("处理日志异常");
}
@Override
public void myFinally() {
System.out.println("日志资源释放");
}
}
public class MyServiceAopProxy implements MyService {
private MyService myService;//被代理对象
private Aop aop;//要加入的切面
public MyServiceAopProxy(MyService myService, Aop aop) {
this.myService = myService;
this.aop = aop;
}
@Override
public void add() {
try {
aop.before();
myService.add();//被代理对象要做的
aop.after();
} catch (Exception e) {
aop.myException();
} finally {
aop.myFinally();
}
}
@Override
public void delete() {
try {
aop.before();
myService.delete();//被代理对象要做的
aop.after();
} catch (Exception e) {
aop.myException();
} finally {
aop.myFinally();
}
}
}
//测试类
public class StaticProxyTest {
@Test
public void test() {
MyService myService = new MyServiceImpl();//被代理对象:核心代码
Aop transAop = new TransAop();//切面:事务服务代码
Aop logAop = new LogAop();//切面:日志服务代码
MyService proxy = new MyServiceAopProxy(myService, transAop);//代理对象:一级代理
MyService proxy2 = new MyServiceAopProxy(proxy, logAop);//代理对象:二级代理
//proxy.add();
proxy2.add();//调用代理对象执行方法,相当于租客去找中介(执行看房子功能)
}
}
6.5 动态代理
6.5.1 动态代理和静态代理的区别
-
静态代理
要求代理类在编译的时候就存在了。
-
动态代理
程序运行的时候,根据需要被代理的对象动态生成代理类。
6.5.2 动态代理的种类
- 基于 JDK 的动态代理
- 基于 CGLIB 的动态代理
6.5.3 基于JDK的动态代理
JDK中提供了一个位于java.lang.reflect包下(基于反射)的Proxy类,提供了用于创建对象的静态方法,在运行期间为某些接口(指定的接口列表中的接口)创建代理实例。
注:代理类是final和非抽象的。
API文档解读:
-
loader:类加载器,因为是动态代理类,所以借助别人的类加载器,一般使用被代理对象的类加载器,可以通过如下语句进行获取:
被代理类.getClass().getClassLoader();
-
interfaces:接口类对象的集合,针对哪些接口做代理,一般使用的就是被代理对象的接口;
-
h:句柄/回调函数,代理的规则。
MyService myService = new MyServiceImpl();//被代理类的对象
//获取代理类对象
MyService proxy = (MyService) Proxy.newProxyInstance(
myService.getClass().getClassLoader(),//类加载器,因为动态代理类,借助别人的类加载器。一般使用被代理对象的类加载器
myService.getClass().getInterfaces(),//接口类对象的集合,针对接口的代理,针对哪些接口做代理,一般使用的就是被代理对象的接口。
new InvocationHandler() {//句柄,回调函数,编写代理的规则代码
/**
* @param proxy 代理对象
* @param method 被代理的方法
* @param args 被代理类的被代理方法的参数列表
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj = null;
try {
System.out.println("开始事务");
obj = method.invoke(myService,args);//需要执行的被代理方法
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("事务回滚");
}finally {
System.out.println("资源释放");
}
return obj;
}
}
);
proxy.add();//代理对象去执行方法
可以进一步对以上代码进行封装:
//句柄,回调函数,编写代理的规则代码
public class ProxyHandler<T> implements InvocationHandler {
private T proxyedObject;//被代理对象
private Aop aop;//切面对象 执行服务代码
public ProxyHandler(T proxyedObject, Aop aop) {
this.proxyedObject = proxyedObject;
this.aop = aop;
}
/**
* 句柄,回调函数,编写代理的规则代码
*
* @param proxy 代理对象
* @param method 被代理的方法
* @param args 被代理类的被代理方法的参数列表
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj = null;
try {
aop.before();
obj = method.invoke(proxyedObject, args);//需要执行的被代理方法
aop.after();
} catch (Exception e) {
aop.myException();
} finally {
aop.myFinally();
}
return obj;
}
//代理对象的工厂类
public class ProxyFactory {
/**
* 获取代理类对象
*
* @param proxyedObject 被代理类的对象
* @param aop 具体要切面的服务对象 执行服务代码
* @param <T> 被代理对象的类型
* @return
*/
public static <T> Object getProxyInstance(T proxyedObject, Aop aop){
return proxyedObject == null ? null : Proxy.newProxyInstance(
proxyedObject.getClass().getClassLoader(),//类加载器,因为动态代理类,借助别人的类加载器。一般使用被代理对象的类加载器
proxyedObject.getClass().getInterfaces(),//接口类对象的集合,针对接口的代理,针对哪些接口做代理,一般使用的就是被代理对象的接口。
new ProxyHandler(proxyedObject, aop)//句柄,回调函数,编写代理的规则代码
);
}
}
public class MyJDKProxy {
public static void main(String[] args) {
//被代理类的对象
MyService myService = new MyServiceImpl();
//切面
Aop transAop = new TransAop();
Aop logAop = new LogAop();
//获取代理类对象
MyService proxy = (MyService) ProxyFactory.getProxyInstance(myService,transAop);//一级代理
MyService proxy2 = (MyService) ProxyFactory.getProxyInstance(proxy,logAop);//二级代理
proxy2.add();
}
}
注:
- 代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用JDK动态代理。
- 一个动态代理 , 一般代理某一类业务 , 一个JDK动态代理可以代理多个类,代理的是接口。
思考:如果想要功能扩展,但目标对象没有实现接口,怎样功能扩展?
答:就需要接下来说到的基于CGLIB的动态代理
6.5.4 基于CGLIB的动态代理
Cglib代理,也叫做子类代理。在内存中构建一个子类对象从而实现对目标对象功能的扩展。
- CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它被许多AOP的框架广泛使用,例如Spring AOP和dynaop,为他们提供方法的interception。
- CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
6.5.4.1 基于CGLIB的动态代理和基于JDK的动态代理的区别
JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类,就可以使用CGLIB实现。
6.5.4.2 使用前提
导入cglib的依赖包。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
6.5.4.3 编码实现
//目标对象:没有实现接口
NBAService nbaService = new NBAService();
//创建代理对象:使用cglib动态代理
NBAService proxy = (NBAService) Enhancer.create(
nbaService.getClass(),
new MethodInterceptor() {//回调函数 编写代理规则
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable{
Object obj = null;
try {
System.out.println("开始事务");
obj = methodProxy.invokeSuper(o, objects);//需要执行的被代理方法
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("回顾事务");
} finally {
System.out.println("资源释放");
}
return obj;
}
}
);
//代理对象干活
proxy.add("雷霆",007);
同理,也可以对上述代码进行封装:
//回调函数:编写代理规则
public class MyMethodInterceptor implements MethodInterceptor {
private Aop aop;
public MyMethodInterceptor(Aop aop) {
this.aop = aop;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object obj = null;
try {
aop.before();
obj = methodProxy.invokeSuper(o, objects);//需要执行的被代理方法
aop.after();
} catch (Exception e) {
aop.myException();
} finally {
aop.myFinally();
}
return obj;
}
}
//基于CGLIB的动态代理工厂
public class CglibProxyFactory {
/**
* 获取代理类对象
*
* @param proxyedObject 被代理类的对象:没有实现接口
* @param aop 具体要切面的服务对象 执行服务代码
* @param <T> 被代理对象的类型
* @return
*/
public static <T> Object getProxyInstance(T proxyedObject, Aop aop) {
return proxyedObject == null ? null : Enhancer.create(
proxyedObject.getClass(),//被代理对象的Class对象
new MyMethodInterceptor(aop)//句柄,回调函数,编写代理的规则代码
);
}
}
//基于CGLIB的动态代理
public class MyCglibProxy {
public static void main(String[] args) {
//目标对象:没有实现接口
NBAService nbaService = new NBAService();
//切面
TransAop transAop = new TransAop();
//创建代理对象:使用cglib动态代理
NBAService proxy = (NBAService) CglibProxyFactory.getProxyInstance(nbaService,transAop);
//代理对象干活
proxy.add("雷霆",007);
}
7. Spring AOP
AOP为Aspect Oriented Programming的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
Spring的AOP实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。
7.1 AOP的优势
-
尽量避免不修改源码
通过spring工厂自动实现将服务性代码以切面的方式加入到核心业务代码中,使得程序在运行期间对方法进行功能增强。
-
减少代码的重复
避免传统的写法:核心业务和服务性代码混合在一起,减少重复代码,提高开发效率,便于维护。
-
专注核心业务的开发
在开发中实现各自做自己擅长的事情,运行的时候将服务性代码织入到核心业务中。
7.2 AOP的相关术语
-
Target(目标对象)
要被增强的对象,一般是业务逻辑类的对象。
-
Proxy(代理)
一个类被 AOP 织入增强后,就产生一个结果代理类。
-
Aspect(切面)
表示增强的功能,就是一些代码完成的某个功能,非业务功能。是切入点和通知的结合。
-
Joinpoint(连接点)
所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法(一般是类中的业务方法),因为Spring只支持方法类型的连接点。
-
Pointcut(切入点)
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。
被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
-
Advice(通知/增强)
所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。通知定义了增强代码切入到目标代码的时
间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。通知的类型:前置通知、后置通知、异常通知、最终通知、环绕通知。
切入点定义切入的位置(核心业务),通知定义切入的时间。 -
Weaving(织入)
是指把增强应用到目标对象来创建新的代理对象的过程。 Spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
切面的三个关键因素:
切面的功能
切面能干啥
切面的执行位置
使用Pointcut表示切面执行的位置
切面的执行时间
使用Advice表示时间,在目标方法之前还是之后执行
7.3 AspectJ 对 AOP 的实现
对于AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向切面编程。
AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷而且还支持注解式开发。所以,Spring 又将AspectJ 的对于 AOP 的实现也引入到了自己的框架中。在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式。
注:AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切面实现。
7.3.1 AspectJ的常用的五种通知类型
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
7.3.2 AspectJ的切入点表达式
AspectJ 定义了专门的表达式用于指定切入点。
表达式的原型如下:
execution(modifiers-pattern? ret-type-pattern
declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
说明:
modifiers-pattern] 访问权限类型
ret-type-pattern 返回值类型
declaring-type-pattern 包名类名
name-pattern(param-pattern) 方法名(参数类型和参数个数)
throws-pattern 抛出异常类型
?表示可选的部分
以上表达式共 4 个部分。
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中就是方法的签名。
表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:
符号 | 意义 |
---|---|
* | 0-多个任意字符 |
… | 用在方法参数中,表示任意个参数;用在包名后,表示当前及其子包路径 |
+ | 用在类名后,表示当前及其子类;用在接口后,表示当前接口及其实现类 |
示例:
execution(* com.lijinghua.service.*.*(..))
指定切入点为:定义在 service 包里的任意类的任意方法。
execution(* com.lijinghua.service..*.*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。
execution(* com.lijinghua.service.MyService+.*(..))
指定切入点为:MyService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。
7.4 Spring使用AOP
7.4.1 Spring中的AOP
-
开发阶段
关注核心业务和AOP代码。
-
运行阶段
spring框架会在运行的时候将核心业务和AOP代码通过动态代理的方式编织在一起。
-
代理方式的选择
取决于是否实现了接口:有接口就选择JDK动态代理;没有就选择CGLIB动态代理。
-
导入依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.13.RELEASE</version> </dependency>
-
编写spring配置文件
7.4.2 基于注解实现AOP
-
接口类
public interface Aop { public abstract void before(JoinPoint joinPoint); public abstract void afterReturn(Object result); public abstract void myException(JoinPoint joinPoint, Throwable throwable); public abstract void myFinally(); public abstract Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable; } public interface MyService { public abstract void add(String name, int id); public abstract boolean update(int id); }
-
实现类
/** * 切面类 */ @Component//IOC 切面对象的创建权限给Spring容器 @Aspect//AspectJ的注解 标注当前类是一个切面类 public class AopImpl implements Aop { /** * @Pointcut 注解表示切入点表达式,方法一般声明为私有 * 简化:其他的通知可以在注解中的value属性值中直接使用 */ @Pointcut(value = "execution(* com.lijinghua.service..*.*(..))") private void pointCut() { } @Pointcut(value = "execution(* com.lijinghua.service..*.update*(..))") private void pointCut2() { } //@Before(value = "execution(* com.lijinghua.service..*.*(..))") /** * @param joinPoint 连接点:拦截的方法 * @Before:标注前置通知的注解:并写入切入点表达式,表示需要拦截的连接点(要拦截的方法)集合 execution(所有权限 com.lijinghua.service包及其子包下的所有类的所有方法 ( 任意参数)) */ @Before("pointCut()") @Override public void before(JoinPoint joinPoint) { System.out.println("前置通知:在目标方法被执行之前执行"); String name = joinPoint.getSignature().getName(); System.out.println("拦截的方法名:" + name); Object[] args = joinPoint.getArgs(); System.out.println("拦截的方法的参数数量:" + args.length); for (Object arg : args) { System.out.println("\t" + arg); } } /** * @param result 返回的结果 * @AfterReturning: 标注后置通知的注解 * execution(所有权限 com.lijinghua.service包及其子包下的所有类的以update开头的方法 ( 任意参数)) * returning:标注返回的结果 */ @AfterReturning(value = "pointCut2()", returning = "result") //@AfterReturning(value = "execution(* com.lijinghua.service..*.update*(..))", returning = "result") @Override public void afterReturn(Object result) { System.out.println("后置通知:在目标方法被执行之后执行"); if (result != null) { boolean res = (boolean) result; System.out.println("结果" + (res == true ? ">" : "<=") + "100 "); } } /** * @param joinPoint 连接点:拦截的目标方法 * @param throwable 抛出的异常 * @AfterThrowing:标注异常通知的注解 execution(所有权限 com.lijinghua.service包及其子包下的所有类的所有方法 ( 任意参数)) * throwing: 标注抛出的异常 */ @AfterThrowing(value = "pointCut2()", throwing = "throwable") //@AfterThrowing(value = "execution(* com.lijinghua.service..*.*(..))", throwing = "throwable") @Override public void myException(JoinPoint joinPoint, Throwable throwable) { System.out.println("异常通知:在目标方法被执行出现异常时执行"); //一般会把异常发生的位置、时间,记录下来 System.out.println(new Date(System.currentTimeMillis())); System.out.println(joinPoint.getSignature() + "方法出现异常,异常信息:" + throwable.getMessage()); } /** * @After:标注最终通知的注解 execution(所有权限 com.lijinghua.service包及其子包下的所有类的所有方法 ( 任意参数)) */ @After(value = "pointCut()") //@After(value = "execution(* com.lijinghua.service..*.*(..))") @Override public void myFinally() { System.out.println("最终通知:无论是否出现异常最终都执行"); } /** * @param proceedingJoinPoint proceedingJoinPoint中的proceed方法表示目标方法被执行 * @return * @throws Throwable * @Around:标注环绕通知的注解 execution(所有权限 com.lijinghua.service包及其子包下的所有类的所有方法 ( 任意参数)) */ @Around(value = "pointCut()") //@Around(value = "execution(* com.lijinghua.service..*.*(..))") @Override public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕通知:在目标方法之前执行"); Object proceed = proceedingJoinPoint.proceed(); System.out.println("环绕通知:在目标方法之后执行"); return proceed; } } /** * 核心业务类 */ @Service public class MyServiceImpl implements MyService { @Override public void add(String name, int id) { //int num = 1/0;//测试用:出现异常 System.out.println("MyServiceImpl----add----"); } @Override public boolean update(int id) { System.out.println("MyServiceImpl----update----"); return id > 100 ? true : false; } }
-
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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd 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标签中引入AOP依赖--> <!--组件扫描:扫描包--> <context:component-scan base-package="com.lijinghua.service"/> <context:component-scan base-package="com.lijinghua.aop"/> <!--开启SpringAop的自动代理 proxy-target-class: (1)默认为false,表示使用jdk动态代理织入增强; (2)当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target- class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理 --> <aop:aspectj-autoproxy proxy-target-class="true"/> </beans>
-
测试类
public class MyServiceTest { @Test public void test01(){ ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); MyService myService = (MyService) context.getBean("myServiceImpl"); myService.add("勇士",100); myService.update(20); } }
-
运行结果
7.4.3 基于XML实现AOP
-
实现类
/** * 切面类,基于XML配置 */ @Component//IOC 切面对象的创建权限给Spring容器 public class AopImpl2 implements Aop { @Override public void before(JoinPoint joinPoint) { System.out.println("前置通知:在目标方法被执行之前执行"); } @Override public void afterReturn(Object result) { System.out.println("后置通知:在目标方法被执行之后执行"); } @Override public void myException(JoinPoint joinPoint, Throwable throwable) { System.out.println("异常通知:在目标方法被执行出现异常时执行"); //一般会把异常发生的位置、时间,记录下来 System.out.println(new Date(System.currentTimeMillis())); System.out.println(joinPoint.getSignature() + "方法出现异常,异常信息:" + throwable.getMessage()); } @Override public void myFinally() { System.out.println("最终通知:无论是否出现异常最终都执行"); } @Override public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕通知:在目标方法之前执行"); Object proceed = proceedingJoinPoint.proceed(); System.out.println("环绕通知:在目标方法之后执行"); return proceed; } }
-
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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd 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标签中引入AOP依赖--> <!--组件扫描:扫描包--> <context:component-scan base-package="com.lijinghua.service"/> <context:component-scan base-package="com.lijinghua.aop"/> <!--AOP基于XML方式--> <aop:config> <!--声明切入点表达式--> <aop:pointcut id="pointCut" expression="execution(* com.lijinghua.service..*.*(..))"/> <aop:pointcut id="pointCut1" expression="execution(* com.lijinghua.service..*.update*(..))"/> <aop:aspect ref="aopImpl2"> <aop:before method="before" pointcut="execution(* com.lijinghua.service..*.*(..))"></aop:before> <aop:after-returning method="afterReturn" pointcut-ref="pointCut1" returning="result"></aop:after-returning> <aop:after-throwing method="myException" pointcut-ref="pointCut" throwing="throwable"></aop:after-throwing> <aop:after method="myFinally" pointcut-ref="pointCut"></aop:after> <aop:around method="around" pointcut-ref="pointCut"></aop:around> </aop:aspect> </aop:config> </beans>
-
运行结果
注意:
这里的通知执行顺序(基于注解和基于XML的AOP执行顺序略有不同)
- 基于注解的AOP执行顺序是默认的:环绕前置->@Before->目标方法执行->@AfterReturning->@After->环绕返回->环绕最终,不可以改变;
- 基于XML的AOP执行顺序与配置顺序有关。
7.5 Spring事务管理
事务原本是数据库中的概念,在 Dao 层。但在实际开发中,一般将事务提升到业务层,即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务。
7.5.1 事务管理器接口
事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。
注:
- PlatformTransactionManager 接口常用的实现类是DataSourceTransactionManager
使用 JDBC 或 MyBatis 进行数据库操作时使用。- Spring的回滚方式
- 发生运行时异常和 error 时回滚(默认);
- 对于受查异常,程序员也可以手工设置其回滚方式。
7.5.2 事务定义接口
事务定义接口 TransactionDefinition 中定义了事务描述相关的三类常量及对它们的操作:
-
事务隔离级别;
-
事务隔离基本常量
这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。
-
DEFAULT
采用 DB 默认的事务隔离级别。
MySql 的默认为 REPEATABLE_READ; Oracle默认为READ_COMMITTED。
-
READ_UNCOMMITTED
读未提交。未解决任何并发问题。
-
READ_COMMITTED
读已提交。解决脏读,存在不可重复读与幻读。
-
REPEATABLE_READ
可重复读。解决脏读、不可重复读,存在幻读。
-
SERIALIZABLE
串行化。不存在并发问题。
-
-
-
事务传播行为;
-
事务传播行为常量
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。
事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。
-
Propagation.REQUIRED
比较常用的设置,当前没有事务的时候,就会创建一个新的事务;如果当前有事务,就直接加入该事务。
-
Propagation.SUPPORTS
支持当前事务,如果当前有事务,就直接加入该事务;当前没有事务的时候,就以非事务方式执行。
-
Propagation.MANDATORY
支持当前事务,如果当前有事务,就直接加入该事务;当前没有事务的时候,就抛出异常。 -
Propagation.REQUIRES_NEW
创建新事务,无论当前是否有事务都会创建新的。
-
PROPAGATION_NESTED
-
PROPAGATION_NEVER
-
PROPAGATION_NOT_SUPPORTED
-
-
-
事务默认超时时限。
常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,sql 语句的执行时长。
注意:事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可 。
7.5.3 声明式事务控制
Spring提供的对事务的管理,就叫做声明式事务管理。
如果用户需要使用spring的声明式事务管理,在配置文件中配置即可:不想使用的时候直接移除配置。这种方式实现了对事务控制的最大程度的解耦。
注意:
- 声明式事务管理,核心实现就是基于AOP;
- Spring中提供了对事务的管理。开发者只需要按照spring的方式去做就行。事务必须在service层统一控制。
- 事务的粗细粒度
- 细粒度:对方法中的某几行的代码进行开启提交回滚;
- 粗粒度:对整个方法进行开启提交回滚;
- Spring中的aop只能对方法进行拦截,所有我们也就针对方法进行事务的控制。
- 如果只有单条的查询语句,可以省略事务;如果一次执行的是多条查询语句,例如统计结果、报表查询。必须开启事务。
7.5.3.1 基于注解的事务
package com.lijinghua.service;
import com.lijinghua.dao.TeamDao;
import com.lijinghua.pojo.Team;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class TeamService {
@Autowired
private TeamDao teamDao;
/**
* @Transactional 属性 说明:
* readOnly:是否只读
*
* rollbackFor={Exception.class}: 遇到什么异常会回滚,这里用父类
*
* propagation事务的传播:
* Propagation.REQUIRED:当前没有事务的时候,就会创建一个新的事务;如果当前有事务,就直接加入该事务,比较常用的设置
* Propagation.SUPPORTS:支持当前事务,如果当前有事务,就直接加入该事务;当前没有事务的时候,就以非事务方式执行
* Propagation.MANDATORY:支持当前事务,如果当前有事务,就直接加入该事务;当前没有事务的时候,就抛出异常
* Propagation.REQUIRES_NEW:创建新事务,无论当前是否有事务都会创建新的
*
* isolation=Isolation.DEFAULT:事务的隔离级别:默认是数据库的默认隔离级别
*/
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
public int insert(Team team){
int num1=teamDao.insert(team);
System.out.println("第一条执行结果:num1="+num1);
System.out.println(10/0);
int num2=teamDao.insert(team);
System.out.println("第二条执行结果:num2="+num2);
return num2+num1;
}
}
<!--配置文件中添加context、tx约束-->
<context:component-scan base-package="com.lijinghua"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--启动事务的注解->
<tx:annotation-driven transaction-manager="transactionManager" />
@Test
public void test01(){
ApplicationContext ac=new ClassPathXmlApplicationContext("application.xml");
TeamService teamService = (TeamService) ac.getBean("teamService");
int num=teamService.insert(new Team("凯尔特人","波士顿"));
System.out.println(num);
}
7.5.3.2 基于XML的事务
-
添加依赖
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency>
-
编写配置文件
<!-- 事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="insert*" propagation="REQUIRED"/> <tx:method name="add*" propagation="REQUIRED"/> <tx:method name="find*" propagation="SUPPORTS" read-only="true"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="pt" expression="execution(* com.lijinghua.service..*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt" /> </aop:config>