二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
为什么使用代理模式:可以增强功能,可以保护代理目标,可以让两个不能直接交互的目标进行交互。

初始:
- package com.songzhishu.proxy.service;
-
- /**
- * @BelongsProject: Spring6
- * @BelongsPackage: com.songzhishu.proxy.service
- * @Author: 斗痘侠
- * @CreateTime: 2023-10-17 11:49
- * @Description: TODO
- * @Version: 1.0
- */
- public class OrderServiceImpl implements OrderService{
- @Override
- public void generate() {
- try {
- Thread.sleep(1234);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("订单已生成");
- }
-
- @Override
- public void detail() {
- try {
- Thread.sleep(2541);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("订单信息如下:******");
- }
-
- @Override
- public void modify() {
- try {
- Thread.sleep(1010);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("订单已修改");
- }
- }
-
现在想统计每一个模块的耗时,怎么办,直接在原来的代码上修改!
方法一:硬编码

这样可以是可以,但是每一个模块都要写重复的代码,而且违背OCP的开闭原则!
方法二:继承重写方法
创建一个子类,然后继承实现类后重写方法也可以实现功能的拓展。

这种解决了问题,没有违背OCP原则,但是使用继承增强了耦合度
方法三:静态代
- package com.songzhishu.proxy.service;
-
- /**
- * @BelongsProject: Spring6
- * @BelongsPackage: com.songzhishu.proxy.service
- * @Author: 斗痘侠
- * @CreateTime: 2023-10-17 12:50
- * @Description: 代理对象
- * @Version: 1.0
- */
- public class OrderServiceProxy implements OrderService {
- //要包含公共的功能 达到和目标对象一样的功能 要执行目标对象中目标方法
-
- //将目标对象作为代理对象的一个属性
- private OrderService target; //使用这种方式要比继承的耦合度低 注入公共接口要比实现类好
-
- public OrderServiceProxy(OrderService target) {
- //通过构造方法赋值
- this.target = target;
- }
-
- @Override
- public void generate() {
- //使用代理方法添加增强功能
- long begin = System.currentTimeMillis();
-
- target.generate();
- //调用目标对象目标功能
- long end = System.currentTimeMillis();
- System.out.println("耗费时长"+(end - begin)+"毫秒");
- }
-
- @Override
- public void modify() {
- //使用代理方法添加增强功能
- long begin = System.currentTimeMillis();
-
- //调用目标对象目标功能
- target.modify();
- long end = System.currentTimeMillis();
- System.out.println("耗费时长"+(end - begin)+"毫秒");
- }
-
- @Override
- public void detail() {
- //使用代理方法添加增强功能
- long begin = System.currentTimeMillis();
-
- //调用目标对象目标功能
- target.detail();
- long end = System.currentTimeMillis();
- System.out.println("耗费时长"+(end - begin)+"毫秒");
- }
- }
-
静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。
程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。
在内存当中动态生成类的技术常见的包括:
公共接口:
- package com.songzhishu.spring6.service;
-
- /**
- * @BelongsProject: Spring6
- * @BelongsPackage: com.songzhishu.proxy.service
- * @Author: 斗痘侠
- * @CreateTime: 2023-10-17 11:45
- * @Description: 公共接口
- * @Version: 1.0
- */
- public interface OrderService {
- //生成订单
- void generate();
-
- //修改订单
- void modify();
-
- //查看订单
- void detail();
-
- //获得名字
- String getName();
- }
-
实现类:
- package com.songzhishu.spring6.service;
-
- /**
- * @BelongsProject: Spring6
- * @BelongsPackage: com.songzhishu.proxy.service
- * @Author: 斗痘侠
- * @CreateTime: 2023-10-17 11:49
- * @Description: TODO
- * @Version: 1.0
- */
- public class OrderServiceImpl implements OrderService{
- @Override
- public void generate() {
- try {
- Thread.sleep(1234);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("订单已生成");
- }
-
- @Override
- public void detail() {
- try {
- Thread.sleep(2541);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("订单信息如下:******");
- }
-
- @Override
- public void modify() {
-
- try {
- Thread.sleep(1010);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("订单已修改");
- }
-
- @Override
- public String getName() {
- System.out.println("getName方法执行");
- return "张胜男";
- }
- }
-
客户端:
- package com.songzhishu.spring6.client;
-
- import com.songzhishu.spring6.service.OrderService;
- import com.songzhishu.spring6.service.OrderServiceImpl;
- import com.songzhishu.spring6.service.TimeInvocationHandler;
- import com.songzhishu.spring6.utils.ProxyUtil;
-
- import java.lang.reflect.Proxy;
-
- /**
- * @BelongsProject: Spring6
- * @BelongsPackage: com.songzhishu.spring6.client
- * @Author: 斗痘侠
- * @CreateTime: 2023-10-17 15:45
- * @Description: TODO
- * @Version: 1.0
- */
- public class ClientTest {
- public static void main(String[] args) {
- //创建目标对象
- OrderService target =new OrderServiceImpl();
-
- //创建代理对象
- /*
- * Proxy.newProxyInstance(类加载器,代理类要实现的接口,调用处理器);
- * 第一步 在内存中创建一个代理类的字节码文件
- * 第二步 通过代理类来实例化代理类对象
- *
- * 参数一 ClassLoader loader
- * 类加载器:内存中和硬盘上的class其实没有太大区别,都是class文件,都要加载到java的虚拟机中才能运行
- * 注意:目标类的类加载器和代理类的加载器要使用的是同一个
- *
- * 参数二 Class>[] interfaces
- * 代理类和目标类要实现同一个或者同一些接口
- *
- * 参数三 InvocationHandler h 调用处理器类 实现一个接口
- * 然后可以编写增强代码
- * */
-
- //使用啦util工具
- OrderService proxy = (OrderService) ProxyUtil.newProxyInstance(target);
-
- //使用代理对象调用代理方法, 如果增强的话,目标方法需要执行
- proxy.generate();
- proxy.detail();
- proxy.modify();
- String name = proxy.getName();
- System.out.println(name);
- }
- }
-
调用处理器:
- package com.songzhishu.spring6.service;
-
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
-
- /**
- * @BelongsProject: Spring6
- * @BelongsPackage: com.songzhishu.spring6.service
- * @Author: 斗痘侠
- * @CreateTime: 2023-10-17 16:24
- * @Description: 调用处理器,用来计时增强功能
- * @Version: 1.0
- */
- public class TimeInvocationHandler implements InvocationHandler {
- //目标对象
- private Object target;
- //构造方法 目标对象
- public TimeInvocationHandler(Object target) {
- //给目标对象赋值
- this.target=target;
- }
-
- /*
- * invoke方法什么时候调用 只有代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法就
- * 会被调用
- *
- * invoke方法里面的参数
- * 参数一 代理对象
- * 参数二 目标对象的目标方法
- * 参数三 目标方法上的实参
- *
- *
- * */
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- //使用代理方法添加增强功能
- long begin = System.currentTimeMillis();
-
- Object revalue = method.invoke(target, args);
- //调用目标对象目标功能
- long end = System.currentTimeMillis();
- System.out.println("耗费时长"+(end - begin)+"毫秒");
-
- //如果代理对象需要返回值的话,invoke方法必须将目标对象的目标方法的执行结果返回
- return revalue; //返回方法的返回值!!!!!
- }
- }
-
AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
Spring的AOP使用的动态代理是:JDK动态代理 + CGLIB动态代理技术。Spring在这两种动态代理中灵活切换,如果是代理接口,会默认使用JDK动态代理,如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB。当然,你也可以强制通过一些配置让Spring只使用CGLIB。
一般一个系统当中都会有一些系统服务,例如:日志、事务管理、安全等。这些系统服务被称为:交叉业务 核心业务是纵向的!这些交叉业务几乎是通用的,不管你是做银行账户转账,还是删除用户数据。日志、事务管理、安全,这些都是需要做的。
如果在每一个业务处理过程当中,都掺杂这些交叉业务代码进去的话,存在两方面问题:

用一句话总结AOP:将与核心业务无关的代码独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为AOP。
AOP的优点:
通过下图,大家可以很好的理解AOP的相关术语:
切点表达式用来定义通知(Advice)往哪些方法上切入。语法格式:
execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
访问控制权限修饰符:可选项
返回值类型:必填项
全限定类名:可选项
方法名:必填项
形式参数列表:必填项
异常:可选项
Spring对AOP的实现包括以下3种方式:
spring配置文件:
- "1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:context="http://www.springframework.org/schema/context"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
-
-
- <context:component-scan base-package="com.songzhishu.spring6.service"/>
-
-
- <aop:aspectj-autoproxy proxy-target-class="true" />
- beans>
目标类:

切面:

好啦这就是大概的流程
通知类型包括:
切面:
- package com.songzhishu.spring6.service;
-
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.*;
- import org.springframework.stereotype.Component;
- import org.springframework.stereotype.Service;
-
- /**
- * @BelongsProject: Spring6
- * @BelongsPackage: com.songzhishu.spring6.service
- * @Author: 斗痘侠
- * @CreateTime: 2023-10-17 20:14
- * @Description: TODO
- * @Version: 1.0
- */
- @Component("logAspect")
- @Aspect //这个注解表示是一个切面 ,如果没有这个注解就不是切面
- public class LogAspect { //切面
- //切面等于通知+切点
-
- /*
- 通知是以方法的形式出现 (方法可以写增强代码)
- @before(切点表达式)表示是一个前置通知,然后切点表达式就是可以表示要切入的方法
- */
- @Before("execution(* com.songzhishu.spring6.service..*(..))")
- public void before() {
- System.out.println("前置");
- }
-
- //后置
- @AfterReturning("execution(* com.songzhishu.spring6.service..*(..))")
- public void afterReturningAdvice() {
- System.out.println("后置");
- }
-
- //环绕 是最大的通知, 在前置之前 在后置之后
- @Around("execution(* com.songzhishu.spring6.service..*(..))")
- public void surround(ProceedingJoinPoint joinPoint) throws Throwable {
- //前
- System.out.println("前环绕");
- //目标方法
- joinPoint.proceed();
- //后
- System.out.println("后环绕");
- }
-
- //异常通知
- @AfterThrowing("execution(* com.songzhishu.spring6.service..*(..))")
- public void afterThrowing(){
- System.out.println("异常通知");
- }
-
-
- //最终通知 finally
- @After("execution(* com.songzhishu.spring6.service..*(..))")
- public void ultimately(){
- System.out.println("最终");
- }
- }
-
然后这个是没有出现异常的时候的顺序!

然后我手动的扔出来一个异常后的执行顺序!

相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。
优先级高的切面:外面
优先级低的切面:里面
使用@Order注解可以控制切面的优先级:
@Order(较小的数):优先级高
@Order(较大的数):优先级低

定义切点:
- //切点重用:
- @Pointcut("execution(* com.songzhishu.spring6.service..*(..))")
- public void pointCut(){}
同切面重用:
- @Before("pointCut()")
- public void before() {
- System.out.println("日志前置");
- }

不同的切面重用:
- @Before("com.songzhishu.spring6.service.LogAspect.pointCut()1")
- public void beforeAdvice(){
- System.out.println("安全前置通知");
- }

①获取连接点信息 :
获取连接点信息可以在通知方法的参数位置设置JoinPoint类型的形参
- @Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
- public void beforeMethod(JoinPoint joinPoint){
- //获取连接点的签名信息
- String methodName = joinPoint.getSignature().getName();
- //获取目标方法到的实参信息
- String args = Arrays.toString(joinPoint.getArgs());
- System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
- }
②获取目标方法的返回值
@AfterReturning中的属性returning,用来将通知方法的某个形参,接收目标方法的返回值
- @AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result")
- public void afterReturningMethod(JoinPoint joinPoint, Object result)
- { String methodName = joinPoint.getSignature().getName();
- System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);}
③获取目标方法的异常
@AfterThrowing中的属性throwing,用来将通知方法的某个形参,接收目标方法的异常
- @AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
- public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
- String methodName = joinPoint.getSignature().getName();
- System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
- }
使用配置类代替配置文件
- @Configuration//代替配置文件
- @ComponentScan({"com.songzhishu.spring6.service"})//组件扫描
- @EnableAspectJAutoProxy(proxyTargetClass = true)//启用自动代理
- public class SpringConfig {
- }
测试:
- //全注解
- @Test
- public void test2(){
- ApplicationContext applicationContext=new AnnotationConfigApplicationContext(SpringConfig.class);
- UserService bean = applicationContext.getBean("userService", UserService.class);
- bean.login();
- }
- <context:component-scan base-package="com.atguigu.aop.xml">context:component-scan>
-
- <aop:config>
-
- <aop:aspect ref="loggerAspect">
- <aop:pointcut id="pointCut"
- expression="execution(* com.atguigu.aop.xml.CalculatorImpl.*(..))"/>
- <aop:before method="beforeMethod" pointcut-ref="pointCut">aop:before>
- <aop:after method="afterMethod" pointcut-ref="pointCut">aop:after>
- <aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointCut">aop:after-returning>
- <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointCut">aop:after-throwing>
- <aop:around method="aroundMethod" pointcut-ref="pointCut">aop:around>
- aop:aspect>
- aop:config>
什么是事务:
事务的四个处理过程:
事务的四个特性:
Spring实现事务的两种方式:
编程式事务:通过编写代码的方式来实现事务的管理。
声明式事务:(1)基于注解方式(2)基于XML配置方式
配置文件:
- "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:tx="http://www.springframework.org/schema/tx"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
-
-
- <context:component-scan base-package="com.songzhishu.spring6"/>
-
-
-
- <context:property-placeholder location="jdbc.properties">context:property-placeholder>
-
- <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
- <property name="driverClassName" value="${jdbc.driver}">property>
- <property name="url" value="${jdbc.url}">property>
- <property name="password" value="${jdbc.password}">property>
- <property name="username" value="${jdbc.username}">property>
- bean>
- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
- <property name="dataSource" ref="druidDataSource">property>
- bean>
-
-
- <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
-
- <property name="dataSource" ref="druidDataSource">property>
- bean>
-
-
- <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
-
- beans>
- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource"/>
- bean>
- "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:tx="http://www.springframework.org/schema/tx"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<tx:annotation-driven transaction-manager="transactionManager"/>
在类上添加该注解,该类中所有的方法都有事务。在某个方法上添加该注解,表示只有这个方法使用事务。
在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。
事务传播行为在spring框架中被定义为枚举类型:

一共有七种传播行为:
REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】
MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【有就加入,没有就抛异常】
REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】
NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】
NEVER:以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】
NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】
隔离级别在spring中以枚举类型存在:

@Transactional(isolation = Isolation.READ_COMMITTED)
数据库中读取数据存在的三大问题:(三大读问题)
事务隔离级别包括四个级别:
读未提交:READ_UNCOMMITTED
读提交:READ_COMMITTED
可重复读:REPEATABLE_READ
序列化:SERIALIZABLE
大家可以通过一个表格来记忆:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
| 读未提交 | 有 | 有 | 有 |
| 读提交 | 无 | 有 | 有 |
| 可重复读 | 无 | 无 | 有 |
| 序列化 | 无 | 无 | 无 |
@Transactional(timeout = 10)
以上代码表示设置事务的超时时间为10秒。
表示超过10秒如果该事务中所有的DML语句还没有执行完毕的话,最终结果会选择回滚。
默认值-1,表示没有时间限制。
这里有个坑,事务的超时时间指的是哪段时间?(最后一条DML之前的时间!!!)
在当前事务当中,最后一条DML语句执行之前的时间。如果最后一条DML语句后面很有很多业务逻辑,这些业务代码执行的时间不被计入超时时间。
@Transactional(readOnly = true)
将当前事务设置为只读事务,在该事务执行过程中只允许select语句执行,delete insert update均不可执行。
该特性的作用是:启动spring的优化策略。提高select语句执行效率。
如果该事务中确实没有增删改操作,建议设置为只读事务。
默认的是只要有异常就会回滚事务,设置运行时异常以及子类都会回滚!!
@Transactional(rollbackFor = RuntimeException.class)
表示发生NullPointerException或该异常的子类异常不回滚,其他异常则回滚。
@Transactional(noRollbackFor = NullPointerException.class)
- <aop:config>
-
- <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.atguigu.spring.tx.xml.service.impl.*.*(..))">aop:advisor>
- aop:config>
-
- <tx:advice id="txAdvice" transaction-manager="transactionManager">
- <tx:attributes>
-
-
- <tx:method name="get*" read-only="true"/>
- <tx:method name="query*" read-only="true"/>
- <tx:method name="find*" read-only="true"/>
-
-
-
-
-
-
-
- <tx:method name="save*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
- <tx:method name="update*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
- <tx:method name="delete*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
- tx:attributes>
- tx:advice>