spring (Aop) day 1024

ops/2024/10/25 8:53:24/

ok了家人们,继续学习spring ,这段知识点有点绕,建议搭配b站的视频去学,passion!!!

.AOP-面向切面编程

8.1 动态代理

8.1.1 概述
什么是代理?在现实生活中,代理很常见。比如,我们需要去
某个地方,需要自己开车过去,但是公交车师傅的出现替代了
我们自己开车的这个步骤,我们只需要乘坐公交车就可以到达
我们需要到达的目的地。这就是一种代理方式。
用自己的话总结一下:代理就是原本需要自己去完成的工作可
以由一个新的替代者来完成,这就是代理的一种表现。 在
Java 中就是在不侵入原来代码的情况下完成功能的增强。
Java 中常见代理分类:静态代理,基于 JDK 的动态代理,基于
CGlib 的动态代理
8.1.2 静态代理
举个例子:现实中明星都是有经纪人的,经纪人的作用就是帮
明星来处理业务,节目组要找明星来表演,那么肯定不是和明
星直接联系的,而是明星的经纪人出面和节目组洽谈,而且只
有当业务条件满足时,明星才会出面表演,那么这个经纪人就
是明星的代理人,他可以帮助明星来筛选演出业务,条件达不
到要求的表演就过滤了,只有达到要求了,才会被经纪人接收
然后明星就会出场表演,也就是拦截了外界对明星的直接访
} 问。这也是 JAVA 中代理对象的作用,产生一个代理对象用来
拦截外界对真实业务的访问。
可以看出,经纪人会将外界给明星的信息进行拦截、处理,这
就是我们常说的代理模式。
静态代理的实现方式
  • 经纪人和明星在一些行为上有共同点,共同的目标,所以
    定义一个共有接口: start 接口 ( 参加节目 )
  • 明星实现接口的行为,因为真正出力的是明星。
  • 经纪人要代表明星,就需要和明星有同样的行为,同时持
    有明星的引用 ( 经纪人能找到明星 )
  • 调用方要找明星之前,先找到经纪人。由经纪人出面进行
    谈判,比如参加节目前片酬,比如参加节目后发的微博。
     

静态代理的代码实现

  • 接口 
public interface FindWife {public void findWife(String message);
}
  •  代理类
public class BuyHouseProxy implements BuyHouse{
//定义一个 有买房需求的
private BuyHouse buyHouse;
//在创建代理对象的时候 得有委托人
public BuyHouseProxy(BuyHouse buyHouse) {this.buyHouse = buyHouse;
}@Overridepublic void buyHouse() {//代理人 帮助 委托人System.out.println("我是中介,帮您买房,先付款...");buyHouse.buyHouse();//最终 委托人买方法System.out.println("将信息偷偷滴卖给装修的人...");}
}
  • 代理类
package com.cjx.service;
//买房的中介 代理
public class BuyHouseProxy implements BuyHouse{//定义一个 有买房需求的private BuyHouse buyHouse;public BuyHouseProxy(BuyHouse buyHouse) {this.buyHouse = buyHouse;}@Overridepublic void buyHouse() {//代理人 帮助 委托人System.out.println("我是中介,帮您买房,先付款...");buyHouse.buyHouse();//最终 委托人买方法System.out.println("将信息偷偷滴卖给装修的人...");}
}
package com.cjx.service;
//婚介中心
public class FindWifeProxy implements FindWife{//定义一个 有找媳妇需求的人private FindWife findWife;public FindWifeProxy(FindWife findWife) {this.findWife = findWife;}@Overridepublic void findWife(String message) {System.out.println("我们这里是百合网,交会员费,提供优质服务,按照您的条件找");findWife.findWife(message);System.out.println("祝你幸福...");System.out.println("又把信息卖给了婚纱摄影");}
}
  • 被代理类
package com.cjx.service;
//消费者 委托人
public class Customer implements BuyHouse,FindWife{@Overridepublic void buyHouse() {System.out.println("看房源...");System.out.println("谈价格...");System.out.println("过户...");}@Overridepublic void findWife(String message) {System.out.println("要求是:"+message);System.out.println("主要是暖被窝...");}
}
  • 测试
package com.cjx.test;import com.cjx.service.*;
import org.junit.Test;public class DemoTest {@Testpublic void test01(){//没有代理的时候 自己买方房Customer customer = new Customer();customer.buyHouse();customer.findWife("xx");System.out.println("-------------------");//找中介 代理人BuyHouseProxy buyHouseProxy = new BuyHouseProxy(customer);buyHouseProxy.buyHouse();System.out.println("-------------------");//找媳妇 代理人FindWifeProxy findWifeProxy = new FindWifeProxy(customer);findWifeProxy.findWife("迪丽热巴..。");System.out.println("-------------------");//      动态代理BuyHouse proxy = (BuyHouse) JDKProxy.getProxy(customer);proxy.buyHouse();}
}
从实现上可以看到 代理 的主要作用是 方法增强,它可以在不
惊动 被代理类的情况下修改被代理类的行为。这有助于系统
解耦。我们这里代理类和被代理类都是自己亲自敲好的,即在
程序运行前代理类的 .class 文件就已经存在了的形式叫做静态
代理。值得注意的是,代理类和被代理类应该共同实现一个接
口,或者是共同继承某个类。
静态代理的缺点
  • 我们需要在运行前手动创建代理类,这意味着如果有很多
    代理的话会很繁琐,一个被代理就需要一个代理;
  • 其次代理类 和 被代理类 必须实现同样的接口,万一接口
    有变动,代理、被代理类都得修改,容易出问题;
8.1.3 动态代理
动态代理 与 静态代理 最大的区别就是不用我们创建那么多
类,敲那么多代码。在程序运行时,运用反射机制动态创建而
成。
JDK 中为我们提供了 Proxy 类来实现动态代理,其中最重要的
方法是 Proxy.newProxyInstance :只需要我们传入相关信
息,它就可以返回我们需要的代理对象。
Object obj = Proxy.newProxyInstance(ClassLoader
loader,Class<?>[] interfaces, InvocationHandler h)
上面的方法可以实现动态的拦截被代理类的方法,也就是说我
们无需定义代理类,由该方法自动产生可以实现代理的对象。
package com.cjx.service;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class JDKProxy {//获取代理对象的方法 参数:被代理对象 委托人
//传入被代理人 返回一个代理对象public static Object getProxy(Object obj) {Object proxy = null;proxy = Proxy.newProxyInstance(obj.getClass().getClassLoader(),
//类加载器,将生成的代理类加载到内存中obj.getClass().getInterfaces(),
// 生成的代理类需要实现的接口的字节码
//invoke:此invoke方法并非是反射的invoke方法,此方法属于动态代理,
//调用代理类的哪个方法,invoke就表示那个方法new InvocationHandler() {/*proxy:代理对象method:委托人需要增强的方法args:要实现功能的参数Object:被增强方法的返回值*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throwsThrowable {Object result = null;
//这个方法来自哪个接口定义的方法Class<?> clazz =method.getDeclaringClass();if (clazz ==BuyHouse.class) {
//我们应该根据委托人不同的需求,执行不同的功能增强String methodName =method.getName();if(methodName.equalsIgnoreCase("buyHouse")) {
//对买房的操作进行增强System.out.println("我是中介,帮您买房,先付款...");result =method.invoke(obj, args);System.out.println("将信息偷偷滴卖给装修的人...");}}if (clazz == FindWife.class) {//我们应该根据委托人不同的需求,执行不同的功能增强String methodName = method.getName();if (methodName.equalsIgnoreCase("findWife")) {System.out.println("我们这里是百合网,交会员费,提供优质服务,按照您的条件找");result = method.invoke(obj, args);System.out.println("又把信息卖给了婚纱摄影");}}return result;}});return proxy;}}
明星与经纪人的关系介绍了静态代理,不好之处在于一个经纪
人只能代理一个明星,一旦明星有变动,或者想要代理其他明
星时,需要修改、创建经纪人,大量使用这种静态代理,会使
我们系统内的类的规模增大,并且不易维护;
而动态代理模式,大大减少类的创建、修改成本。此外动态代
理还符合 AOP ( 面向切面编程 ) 思想,在很多场合都有使用。
通过讲解可以得出结论 : 代理模式主要用于扩展原功能又不侵
入(修改)源代码。 ( 增强功能 )
8.1.4 cglib代理
JDK 的动态代理已经很好的帮我们完成了代理工作,那么,
CGlib 动态代理又有什么好用的特征呢?
我们可以看出,静态代理和基于 JDK 的动态代理的被代理类都
需要实现接口,那么,如果我的类没有实现接口,但是,我想
给它配置代理,那么,应该怎么实现呢?
这时候,就轮到 CGlib 出场了, CGlib 可以代理没有接口的类。
我们需要在 pom 文件中引入 CGlib jar 包。
<!--https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.9</version>
</dependency>
cglib 动态代理其实就是把原有对象传进去进行方法拦截,拦
截到之后进行逻辑增强。
8.1.5 代理总结
使用代理技术就是为了帮我们在不入侵原有代码的情况下增强
业务逻辑!
你完全可以使用静态代理一个一个去定义代理类,但是这样的
话太过于繁琐,而且有些情况下你不知道未来会有什么接口
(比如咱们的 Mybatis ,你现在有个 UserMapper.java ,以后
还可能有更多其他的 Mapper 接口,这些都是不确定的),所
以就是用动态代理去给他们生成代理对象吧
有接口就用 JDK 动态代理,没有接口就用 CGLIB 动态代理

8.2 AOP概述

OP Aspect Oriented Programming 的缩写,意为:面向切
面编程,通过 运行期动态 实现程序功能的统一维护的一种技
术。 AOP OOP 的延续,是软件开发中的一个热点,也是
Spring 框架中的一个重要内容。
AOP 弥补了 OOP 的不足,基于 OOP 基础上进行横向开发;
OOP 程序开发以类为主题模型,一切围绕对象进行,通过构
建模型来完成任务; AOP 程序的开发主要关注 OOP 开发中的
共性功能,一切围绕共性功能进行。
AOP 通过采取横向抽取机制,已经完全取代了传统纵向继承体
系重复性代码的编写方式
AOP 的底层封装了动态代理,我们可以理解为 AOP 是动态代理
的一种简单写法,进行功能的增强!
简单理解: AOP 就是实现业务代码和逻辑代码的解耦,但在运
行时又可以织入在一起。
场景 名称
场景描述
事务控制
批量的为业务对象,增加事务处理的功能,不用
每个对象都去做事务处理
记录跟踪
批量的为业务对象,增加日志记录的功能,不用
在每个方法执行进行日志记录
权限控制
批量的为业务对象,增加权限检查,不用每个业
务对象都做安全检查
异常处理
批量的为业务对象,增加异常情况的处理功能,
不用在每个方法执行进行异常处理
参数校验
批量的为业务对象,增加各种入参的检查,不用
在方法内部进行检
等等
.....

8.3 AOP 相关概念

  • Joinpoint( 连接点 ) : 所谓连接点是指那些被拦截到的点。
    spring , 这些点指的是方法 , 因为 spring 只支持方法类型
    的连接点。
  • Pointcut( 切入点 ) : 所谓切入点是指我们要对哪些 Joinpoint
    进行拦截的定义。例如:我们配置 save 方法就是切入点
  • Advice( 通知 / 增强 ) : 所谓通知是指拦截到 Joinpoint 之后所
    要做的事情就是通知。 通知的类型:前置通知 , 后置通知 ,
    常通知 , 最终通知 , 环绕通知。 例如: @Before 前置通知 :
    切入点运行前执行。
  • Target( 目标对象 ) : 代理的目标对象。举例:
    UserServiceImpl ,他就是目标对象,因为它里面的功能需
    要被增强。
     
  • Weaving( 织入 ) : 就是代码运行的时候 业务代码和逻辑代码
    织入到一起执行。例如:把 before 方法在 save 方法执行之
    前执行了这就是织入。
  • Aspect( 切面 ) : 是切入点和通知的结合。切面 = 切入点 + 方位
    信息 + 增强逻辑,例如: AopAdvice 类 是一个切面类
     
  •  Proxy(代理): 一个类被AOP织入增强后,就产生一个结果
    代理类。例如: AccountServiceImpl 它的功能在增强的时
    候,实际是生成了代理对象增强的。

8.4 AOP案例

8.4.1 需求分析
需求:任意业务层接口执行前后获取当前系统时间
分析:
  • 原始程序中将共性功能抽取出来独立制作成方法放在特定类中
  • 定义带有共性功能的方法名
  • 将抽取出来的共性功能与对应的方法名之间绑定关系
8.4.2 环境准备
  • 实体类
package com.cjx.pojo;import org.springframework.stereotype.Component;@Component
public class User {private int id;private String name;//省略get set方法
}
  • 配置类
package com.cjx.config;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;//Spring配置文件
@Configuration
@ComponentScan("com.cjx")
@EnableAspectJAutoProxy
public class SpringConfig {
}
  • 通知类
public class AopAdvice {
public void before(){
System.out.println("当前方法执行前初始时间毫
秒值是:"+System.currentTimeMillis());
}
public void after(){
System.out.println("当前方法执行后初始时间毫
秒值是:"+System.currentTimeMillis());
}
}
  • 业务层接口和实现类
package com.cjx.service;import com.cjx.pojo.User;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class UserServiceImpl implements UserService{@Overridepublic void save(User user) {//System.out.println("当前方法执行前初始时间毫秒值是:"+System.currentTimeMillis());System.out.println("保存了用户数据~");//System.out.println("当前方法执行后初始时间毫秒值是:"+System.currentTimeMillis());}@Overridepublic void update(User user) {//System.out.println("当前方法执行前初始时间毫秒值是:"+System.currentTimeMillis());System.out.println("更新了用户数据~");//System.out.println("当前方法执行后初始时间毫秒值是:"+System.currentTimeMillis());}@Overridepublic void delete(String id) {System.out.println("删除了用户数据~");}@Overridepublic List<User> findAll() {System.out.println("查询了用户数据~");return null;}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes =
{SpringConfig.class})
public class DemoTest {
@Autowired
private UserService userService;
@Test
public void test01(){
userService.save(new User());
}
}
8.4.3 AOP实现
  • 导入aspect坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springaspects</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
  • 将共性功能抽取出来制作成独立的通知方法写入通知类
    中。创建一个新的类,将共性功能提取出来制作成独立的
    方法
/*
通知类
*/
public class AopAdvice {public void before(){System.out.println("当前方法执行前初始时间毫秒值是:"+System.currentTimeMillis());}public void after(){System.out.println("当前方法执行后初始时间毫秒值是:"+System.currentTimeMillis());}
}
  • 将共性功能抽取的位置定义成切入点,写入通知类中
/*
通知类
切入点:@Pointcut
你想增强哪个方法,那个方法就当做切入点,切入点在通知
类中这样配置
void
com.lzw.service.UserServiceImpl.save(com.lzw.poj
o.User)
*/
public class AopAdvice {
//配置切入点 是用来找到需要被增强的方法的@Pointcut("execution(voidcom.lzw.service.UserServiceImpl.save(com.lzw.pojo.User))")public void pt(){}
}
  • 绑定原始操作与被抽取的功能之间的关系
package com.cjx.aop;import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;import java.util.Date;/*
通知类切入点:@Pointcut你想增强哪个方法,那个方法就当做切入点,切入点在通知类中这样配置
void
com.lzw.service.UserServiceImpl.
save(com.lzw.pojo.User)
@Component 我这个通知类交给spring管理
@Aspect 当前类为切面类 里面包含 切入点+方位信息
+增强逻辑 目的是spring底层自动完成织入
*/
@Component
@Aspect
public class AopAdvice {/*before after 使我们抽出来的 逻辑代码在aop的术语中 这两个方法 叫做通知方法在 pt 切入点 方法执行之前 执行 before功能*///配置切入点 是用来找到需要被增强的方法的@Pointcut("execution(void com.cjx.service.UserServiceImpl.save(com.cjx.pojo.User))")public void pt(){}@Pointcut("execution(void com.cjx.service.UserServiceImpl.update(com.cjx.pojo.User))")public void update(){}@Before("pt()")public void before(){System.out.println("当前方法执行前初始时间毫秒值是:"+System.currentTimeMillis());}@After("pt()")public void after(){System.out.println("当前方法执行后初始时间毫秒值是:"+System.currentTimeMillis());}@Before("update()")public void before01(){System.out.println("当前方法执行的时间是:"+new Date());}@After("update()")public void after01(){System.out.println("当前方法执行的时间是:"+new Date());}
}
  • Spring配置类上声明开启AOP功能
package com.cjx.config;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;//Spring配置文件
@Configuration
@ComponentScan("com.cjx")
@EnableAspectJAutoProxy
public class SpringConfig {
}
8.4.4 工作流程分析
  • Spring容器启动,加载bean
  • 启动AOP,加载所有的切入点
  • bean 工作的过程中, AOP 内部监听机制一直监听配置的切
    入点是否运行
  • 当切入点对应的对象(目标对象)执行对应的切入点方法
    时,创建目标对象的代理对象
  • 动态将通知内容织入到代理对象对应的方法中
  • 最终由代理对象完成最终工作
package com.cjx.test;import com.cjx.config.SpringConfig;
import com.cjx.pojo.User;
import com.cjx.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import java.sql.PreparedStatement;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class UserTest {@Autowiredprivate User user;@Autowiredprivate UserService userService;@Testpublic void test01(){userService.save(user);userService.update(user);userService.delete("1");userService.findAll();}
}

ok了家人们,see you later


http://www.ppmy.cn/ops/128280.html

相关文章

.NET 9 - 尝试一下Open Api 的一些变化

1.简单介绍 .NET 9 中 OpenAPI 也有一些变化&#xff0c;这边也简单体验一下.NET 9 中的OpenAPI的变化&#xff0c;具体的话&#xff0c;可以参考如下文章&#xff0c;谢谢 .NET 9 OpenAPI 2. .NET 8的OpenAPI 这边以Visual Studio 2022中的ASP.NET Core Minimal API模板来…

十七、行为型(命令模式)

命令模式&#xff08;Command Pattern&#xff09; 概念 命令模式是一种行为型设计模式&#xff0c;它将请求封装成一个对象&#xff0c;从而使您可以使用不同的请求对客户进行参数化&#xff0c;排队请求&#xff0c;以及支持可撤销操作。通过这种模式&#xff0c;调用操作的…

Kubernetes:(二)K8Sv1.20二进制部署

文章目录 一、k8s项目架构二、二进制搭建 Kubernetes v1.20 &#xff08;单master节点&#xff09;1.操作系统初始化配置2.部署 docker引擎3. etcd的概念4. 证书认证5. node01 节点操作&#xff08;192.168.44.10&#xff09;6. node02 节点操作&#xff08;192.168.44.40&…

Maven学习笔记

目录 一、什么是Maven 二、maven下载和安装目录 1、安装目录解析 2、maven仓库 二、maven项目创建&#xff08;Hello&#xff09; 四、maven项目操作 五、创建HelloFriend 六、maven项目中pom.xml标签解释 1、坐标 2、依赖 &#xff08;1&#xff09;依赖的范围 &a…

Java最全面试题->Java主流框架->Zuukeeper面试题

文章目录 ZuukeeperZooKeeper是什么?ZooKeeper和dubbo的区别?Zookeeper的java客户端都有哪些?ZooKeeper提供了什么?说说ZooKeeper文件系统说说ZAB协议?Znode有哪些类型?Zookeeper节点宕机如何处理?Zookeeper有哪几种几种部署模式?Zookeeper的典型应用场景?说一下Zooke…

开启RefCell debug_refcell feature查看借用冲突位置

文章目录 背景分析解决方法 本文解决两个问题&#xff1a; 开启rust源码库中的feature开启debug_refcell feature的方法查看 borrow 借用冲突的位置 背景 使用 RefCell 来实现内部可变性是在 Rust 开发中常用的方式&#xff0c;但是当逻辑复杂起来&#xff0c;常常会有可变借…

C++(面向对象、封装性、构造函数)

面向对象 三大特征&#xff1a;封装、继承、多态 C中的class是从C的struct扩展来的&#xff0c;两者的区别是默认访问权限不同。 struct的默认访问权限是公有的public class默认访问权限是私有的private 访问权限 类的内部派生类&#xff08;子类&#xff09…

RK3568开发板(debain系统)与Ubuntu使用nfs共享文件

VMware虚拟机 Ubuntu18.04 【网络配置陈桥接模式】 RK3568开发板【我是用讯为的RK3568】 网线连接路由器或者和电脑直连&#xff08;J13网口&#xff09; 1、Ubuntu上nfs服务器安装 1.1、命令如下&#xff1a; sudo apt-get update sudo apt-get install nfs-kernel-server1…