首页 > 其他分享 >Spring — AOP

Spring — AOP

时间:2023-06-18 11:45:24浏览次数:32  
标签:Spring mi 代理 接口 AOP new 方法 public

Spring — AOP

AOP 简介

  • 面向切面的编程,是 OOP 的扩展与补充,可以对业务逻辑的各部分进行隔离,降低各部分之间的耦合度,提高程序的可重用性,提高开发效率。

  • 在不修改源码的情况下,对业务功能进行增强。AOP 适用于具有横切逻辑的场合,如日志记录、性能检测、访问控制、事务控制等

  • 常用来做增强测试:

    Date d = new Date(); // 获取当前日期
    SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss:SSSS”);
    sdf.format(d);  // 格式化过日期结构的日期显示
    // 做控制台输出在所需地方,就可以充当日志作用————横切逻辑
    
    • 就像是在进超市时,核心业务是走进超市,由 AOP 来执行开门和关门操作

AOP 相关概念

  • 连接点,即被 [ 拦截 ] 到的点,业务层接口中的所有方法都是连接点

    • 类里面的方法其实可以说都是连接点
  • 织入,悄无声息的,在影响较小的情况下加入功能

    • 侵略性不强
  • 目标对象,没有被增强的那些对象,接口 / 实现类

  • 切入点,即被 [ 拦截并增强 ] 的连接点

  • 通知,又称增强,即指拦截到切入点后所做的事情

  • 切面,即切入点和通知的结合

  • 代理,即一个类被 AOP 织入通知后产生的新类

    • 代理对象就是通过后面讲的三种方法实现了增强的接收的对象,可以是接口 / 实现类

    • 代理类和实际业务类:应该实现相同的功能,这是代理机制实现的根本

    • 代理机制的实现方式:接口方式 + 继承方式

代理

静态代理 ( StaticProxy )

  • 业务接口与业务实现类 ( 实现方法 )

  • 定义增强类并编写增强方法

  • 静态代理类,包含业务类和增强类,是手动对所有方法进行增强 ( 直接在现有方法里编写 )

    public class StaticMathProxy implements MathInter{ // 继承接口
        MathInter mathInter; //业务类
    	TimeTool timeTool;   //增强类
    	public StaticMathProxy(MathInter mi, TimeTool tt){
    		this.mathInter = mi;  // 用外面传进来的MathInter来完成方法,而不是所在类自己写,方法就return mathInter.xxxx就可(如下面的“方法里:”),即代理来做,工具timeTool也可以进行代理
    		this.timeTool = tt;				
            
    	// ... 方法里:
        timeTool.before();
    	int r = mathInter.add(i, j);
    	timeTool.after();
        //...
            
        }
    }
    
    • 在上述代理类中做方法的增强 ( timeTool 操作 ),这样源码:接口、实现类、工具类三个都没改,改的话就是改代理
  • 测试 Test

    MathInter mi = new MathInterImpl();//业务类
    TimeTool tt = new TimeTool();      //增强类
    StaticMathProxy proxy = new StaticMathProxy(mi,tt);//代理类
    

动态代理 ( Proxy )

基于 [ 接口 ] 的动态代理,即 jdk 动态代理

过程
  • 同静态定义业务接口与业务实现类

  • 同静态定义增强类及增强方法

  • 定义 JDK 动态代理类 ( 包含了业务类 + 增强类 ) — implements InvocationHandler

    // 注:接口导入的是java.lang.reflect,实现invoke()
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object o;
        timeTool.before();  // 在所有的方法执行前进行增强(before就是工具类的方法)
        o = method.invoke(mathInter, args);  // 这样就对所有的方法都集中进行了执行前后的增强操作,使用静态方法的话,要在每个方法前都调用增强方法(方法多了会很麻烦)
        timeTool.after();
        return o;
    }
    
    // 如果想对个别方法进行增强操作:
            If(method.getName().equals(“sub”)){   // 就只对sub方法进行增强了
                a.before();
                o = method.invoke(mathInter, args); 
                a.after();
            }
    
  • Test 测试

    MathInter mi = new MathInterImpl();//业务类
    TimeTool tt = new TimeTool();      //增强类	
    JdkMathProxy mathProxy = new JdkMathProxy(mi,tt);//代理类,作为Proxy.newProxyInstance的第3个参数
    
    MathInter mi2 = (MathInter)Proxy.newProxyInstance(  // 强转成:接口 (实现类不可以, JDK Proxy工作原理如此,即:不能用接口的实现类来转换Proxy的实现类,它们是同级,应该用共同的接口来转换 —— 可以理解为狗不能强转为猫,但都可以强转成动物——向上转型)
        mi.getClass().getClassLoader(),  // 传入三个参数
        mi.getClass().getInterfaces(),  // 前两个参数都是固定的
        mathProxy);
    
    // mi.add(1, 2);  //业务对象执行方法,方法未增强
    mi2..add(1, 2);//使用Proxy+JdkMathProxy创建的代理对象执行方法,方法已增强
    
补充
  • 使用 java 的 [ Proxy ] 类,要求被代理类至少实现一个 [ 接口 ]

  • 提供者:JDK

  • 涉及的类:Proxy

  • 要点:实现 InvocationHandler 接口

  • 创建代理对象的方法:Proxy.newProxyInstance,方法参数:

    • 参数1:ClassLoader,类加载器,和被代理对象使用相同的 [ 类加载器 ],固定写法 —— 似先前说的类模板
    • 参数2:Class[],字节码数组,和被代理对象具有相同的 [ 行为 ]、实现相同的 [ 接口 ],固定写法
    • 参数3:InvocationHandler,接口,代理的具体内容即 [ 增强代码 ],使用匿名内部类实现

基于 [ 子类 ] 的动态代理,即 cglib 动态代理

过程
  • 编写业务实现类不继承接口

  • 定义增强类及增强方

  • 代理类的继承的接口改变了:implements MethodInterceptor

    // 注:接口导入的是net.cglib.proxy,实现intercept()
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { // 接口必须实现的方法
            Object o1 = null;
            rt.before();  //前置通知
            try{
    //            环绕开始
                o1 = methodProxy.invoke(mi, objects);
    //            环绕结束
            }catch(Exception e){
    //            异常
            }
            finally {
    //            后置返回
            }
            rt.after();   //后置通知
            return o1;
    }
    
  • Test 测试

    remathimpl_2 mi = new remathimpl_2();
    reTool_3 rt = new reTool_3();
    CglibMathProxy_4 proxy = new CglibMathProxy_4(mi, rt); //代理类,作为Enhancer的setCallback()的参数
    
    //        Enhancer:cglib里一个重要的类
    Enhancer e = new Enhancer();       //定义Enhancer对象,cglib里面带的第三方的类,e是Enhancer的实例——其实感觉就是代理
    e.setSuperclass(mi.getClass());    //设置Enhancer对象的父类
    e.setCallback(proxy);              //设置Enhancer对象的回调/如何处理方法(用的proxy)
    
    //e.create返回的是一个Object(ctrl查看),所以需要强转成自己所需要的类
    remathimpl_2 mi2 = (remathimpl_2)e.create();    //e来创建代理mi2
    
    // mi.add(1,2);                 //业务对象执行方法,方法未增强
    mi2.add(1,2);                //使用Enhancer+CglibMathProxy创建的代理对象执行方法,方法已增强
    

Spring 使用 ProxyFactoryBean 创建 AOP 代理

  • ProxyFactoryBean就像是先前方法中的Enhancer、Proxy —— 创建代理
  • 需要导入一个依赖 —— aspectjweaver
    • 若缺少 aspectjweaver,则测试类 new AspectJExpressionPointcut() 编译不报错,运行报错

原始编码方式

过程
  • 业务接口与业务实现类 ( 实现方法 )

  • 定义通知类

    // 注:接口导入的是org.aop.......实现invoke()
    //  实现环绕通知
    public class MyAdvice_3 implements MethodInterceptor { // 通知
    
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
            Object o;
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSS");
            Date d1 = new Date();
            System.out.println("at:" + sdf.format(d1) + ", 开始执行"); // 工具
    
            o = methodInvocation.proceed(); // 代理
            
            Date d2 = new Date();
            System.out.println("at:" + sdf.format(d1) + ", 执行结束");
            return 0;
        } // 就像是综合了工具类和动态代理类,做增强
    }
    
  • 定义切面 = 切入点 + 通知

    // 注意:继承了 PointcutAdvisor 接口,实现 isPerInstance()
    public class FilterAdvice_4 implements PointcutAdvisor {
    
        Pointcut pointcut;  //切入点
        Advice advice;   //通知
    
        public FilterAdvice_4(Pointcut pointcut, Advice advice){
            this.pointcut = pointcut;
            this.advice = advice;
        }
    
        public Pointcut getPointcut() {
            return this.pointcut;
        }
        public Advice getAdvice() {
            return this.advice;
        }
    
        public boolean isPerInstance() {
            return false;
        }
    }
    
  • Test 测试

    public class Test_Aop {
        public static void main(String[] args) {
    //        1.定义被代理对象
            Mathimpl_2 mi = new Mathimpl_2();
    //        2.定义通知(定义增强类)
            Advice advice = new MyAdvice_3();
    //        3.定义创建被代理对象mi 的代理的对象pfb(用pfb创建mi的代理对象m2)
            ProxyFactoryBean pfb = new ProxyFactoryBean();
    //        设置接口
            pfb.setInterfaces(mi.getClass().getInterfaces());
    //        设置要代理的对象
            pfb.setTarget(mi);
    //        因为mi 的 Mathimpl_2可能实现了接口也可能没有,所以:
    //        如果使用jdk基于接口实现动态代理则采用默认(不用写),如果使用cglib则需要设置参数为true
            pfb.setProxyTargetClass(true);
    //        4.定义一个切入点
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    //        切入点匹配一个方法:写入类方法的全包名,注意:方法后的括号里面是两个点!!
            pointcut.setExpression("execution (public int com.qut.aop_4.Mathimpl_2.add(..))");
    //        5.定义切面,关联切入点和通知
            FilterAdvice_4 advisor = new FilterAdvice_4(pointcut, advice);
    //        6.切面加入到pfb中
            pfb.addAdvisor(advisor);
    //        7.创建代理对象,强转
            Mathimpl_2 mi2 = (Mathimpl_2)pfb.getObject();
            System.out.println(mi2.add(3, 2));
        }
    }
    

Spring 式 —— 配置文件

  • 业务类和实现类

  • 增强类里定义增强方法

  • 注解方式:需要 xml 里加上 aop 的 xmlns 和 xsi

    注意:需要导入一个依赖 —— aspectjweaver

    <!--  1.定义被代理对象  -->
        <bean id="Mathimpl" class="com.qut.aop_bean_5.Mathimpl_1"></bean>
        <!--  2.定义增强(配置文件的话不能使Advice,只能用Tool)  -->
        <bean id="reTool" class="com.qut.aop_bean_5.reTool_2"></bean>
        <aop:config>
            <!--  3.定义切入点(明确指出对谁做增强)  -->
            <aop:pointcut id="addPointCut" expression="execution(public int com.qut.*.Mathimpl_1.add(..))"/>
            <!--  4.定义切面(切点 + 通知)  -->
            <aop:aspect ref="reTool">
                <!--  4.1 配置切面的before方法为切入点的前置通知  -->
                <aop:before method="before" pointcut-ref="addPointCut"></aop:before>
                <!--  4.2 配置切面的after方法为切入点的后置通知  -->
                <aop:after method="after" pointcut-ref="addPointCut"></aop:after>
            </aop:aspect>
    </aop:config>
    
  • Test 测试

    public class Test_AopBean {
        public static void main(String[] args) {
    // 读配置文件,取bean,代理类执行方法
            ApplicationContext ctx = new ClassPathXmlApplicationContext("3.xml");
            Mathimpl_1 mi = (Mathimpl_1)ctx.getBean("Mathimpl");
            System.out.println(mi.add(3, 2));
        }
    }
    
  • 切入点表达式写法

    • execution (public int com.qst.pkg4AopXML.A.sub(..))
    • 全匹配方式:public void com.qst.bean.User.add()
    • 省略访问修饰符:void com.qst.bean.User.add()
    • 星号表示任意返回类型,不能省:* com.qst.bean.User.add()
    • 星号表示任意包,有几级包写几个* :* * . * . * .User.add()*
    • 星号表示任意类,不能省:* * . * . * . *.add()
    • 星号表示任意方法,不能省:* * . * . * . * . *()
    • .. 表示参数列表中任意参数:* * . * . * . * . *(..)
    • 注意:星号不能跨包,只能是一层一个

Spring 式 —— Aspect 注解

  • 业务接口和业务类 / 实现类

  • 注解业务类 @Component + 配置文件扫描 + 导入依赖 aspectjweaver

    // 实现类前
    @Component(value = "xxx")
    
    // xml中
    <!-- 1、配置包自动扫描 -->
    <context:component-scan base-package="com.qst"></context:component-scan>
    <!-- 2、配置自动生成aop代理 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
  • 定义增强类 / 通知类

  • 注解增强类 @Component + 切面 @Aspect

    • 对增强类的方法做注解
    @Component
    @Aspect
    public class TimeTool {	
    	@Before(value = "execution(public int com.qst.*.MathInterImpl.sub(..))") 
    	public void before(){  // 只要@注解对,方法名都随意
    		System.out.println("前置通知");
    	}
    	@After(value = "execution(public int com.qst.*.MathInterImpl.sub(..))") 
    	public void after(){
    		System.out.println("后置(最终)通知");
    	}
    	@AfterReturning(value = "execution(public int com.qst.*.MathInterImpl.sub(..))") 
    	public void afterReturning() { 
    		System.out.println("返回通知"); 
    	} 
    	@AfterThrowing(value = "execution(public int com.qst.*.MathInterImpl.sub(..))")  
    	public void afterThrowing() { 
    		System.out.println("异常通知"); 
    	} 
    	@Around(value = "execution(public int com.qst.*.MathInterImpl.sub(..))") 
    	public Object arounds(ProceedingJoinPoint jp) throws Throwable {
            System.out.println("环绕之前");
            Object obj=jp.proceed();
            System.out.println("环绕之后");
            return obj;
        }	
    } // ———— 即把xml中的一大堆改成了此样式
    
  • Test 测试 ( 1 读配置文件,2 取 bean,3 代理类执行方法 )

    ApplicationContext ctx = new ClassPathXmlApplicationContext("4.xml");
    //        remath_1 mi = (remath_1) ctx.getBean("xxx");
    remath_1 mi = ctx.getBean("xxx", Mathimpl_2.class);
    System.out.println(mi.add(2,3));
    
  • 采用此方式,就可以在其余地方编写增强 / 通知类(带上 @ 的),然后直接拷贝到项目中,项目就会自行扫描并执行 ——— 典型的 AOP 应用 —— 在外部写增强并添加,不加就不增强

    • 感觉就像是:传统方式 — 改动源码 < 配置文件 — 差点灵活性 < 注解方式 — 更便利
  • 由于上述方法的切入点表达式稍复杂,所以做了优化 ( 了解 ):

    // 类中:
        @Pointcut(“execution 切入点表达式”) // 切入点表达式就只写一次就够了
        Public void 空方法名subPointCut() { 空 }
    
        @Before(value = “subPointCut()”)
        F1方法。。。。。
        @After(value = “subPointCut()”)
        F2方法。。。。。
    

代码

  • 重:最后的 Spring 使用 ProxyFactoryBean 创建 AOP 代理中的 Aspect 注解方式
  • 动态代理 ( Proxy ) —— 基于 [ 接口 ] 的动态代理,即 jdk 动态代理

标签:Spring,mi,代理,接口,AOP,new,方法,public
From: https://www.cnblogs.com/zhu-ya-zhu/p/17488883.html

相关文章

  • 关于Spring Security
    工作原理SpringSecurity所解决的问题就是安全访问控制,而安全访问控制功能其实就是对所有进入系统的请求进行拦截,校验每个请求是否能够访问它所期望的资源。根据前边知识的学习,可以通过Filter或AOP等技术来实现,SpringSecurity对Web资源的保护是靠Filter实现的,所以从这个Filter来......
  • Spring — JdbcTemplate
    Spring—JdbcTemplateJdbcTemplate做持久层的操作导入包aop、ccbe(四核心)、spring-jdbc、c3p0、mysql-connector-java配置数据源编写数据库配置文件db.properties(driver、url、user、pwd)mysql5:Class.forName("com.mysql.jdbc.Driver");Stringurl="......
  • Spring Boot单体应用引入sleuth链路追踪
    文章目录前言一、问题模拟二、引入sleuth链路跟踪1、引入sleuth的maven依赖2、添加属性配置3、logback配置4、日志信息5、通过@NewSpan注解声明新的Span三、引入Sleuth链路跟踪的好处四、Sleuth概念说明五、Logback的MDC特性前言最近排查生产环境的异常时发现一个问题,虽然找到了......
  • Spring —— IOC
    Spring—IOC传统方式:先前service层调用dao实现类:常用new方式,高耦合(即依赖——模块与模块之间的联系)而好的程序应该是:高内聚(模块内部功能的联系)低耦合New的方式就是写死了,是硬编码(一般来说应该是要避免的)要改变的话就是改源代码将紧耦合变......
  • SpringBoot中跨域问题的处理
    跨越问题产生原因:产生跨域问题的原因是浏览器的同源策略,所谓同源是指:域名,协议,端口相同。如果不同,将会出现跨域问题。一、创建项目我们创建两个项目,一个命名为provider提供服务,一个命名为consumer消费服务,第一个项目端口配置为8080,第二个项目端口配置为8081,然后在provider中提供一个......
  • SpringBoot整合RocketMQ
    前提是必须已经安装了RocketMQ并配置好相关的环境变量(自行百度) 第一步: 第二步: 第三步:  第四步: ......
  • 手写SpringBoot启动器主要步骤
    这里写目录标题背景过程2.1自启动实现原理2.2手动实现SpringBoot自启动2.2.1宏观2.2.1微观2.2.1.1三个服务之间调用2.2.1.2自定义注解2.2.1.1业务组装2.2.1.3启动类升华自定义注解:手动装配组件:简化启动过程:自动化注入依赖:简化启动类:背景更好的理解框架:通过手写SpringBoot启动类,......
  • springboot中自定义注解在service方法中,aop失效
    问题描述写了个自定义注解,但是该注解只会出现在serviece层中的方法中。启动发现aop未拦截到问题原因:调用service中的xx()方法时,Spring的动态代理帮我们动态生成了一个代理的对象,暂且叫他$XxxxService。所以调用xx()方法实际上是代理对象$XxxxService调用的。但是在xx()方法内调用同......
  • springboot注册过滤器
    springboot注册过滤器需要使用过滤器的话,优先选择拦截器。因为拦截器符合aop思想。在springboot中使用过滤器有三种方式。分别如下方式一:传统web在传统javaweb、ssm中使用过滤器差不多类似,这里以java配置为例,实现Filter接口@WebFilter("/*")publicclassMyFilter01i......
  • SpringBatch从入门到实战(一):简介和环境搭建
    一:简介SpringBatch是一个轻量级的批处理框架,适合处理大批量的数据(如百万级别)。功能就是从一个地方读数据写到另一个地方去。一般都是系统之间不能直接访问同一个数据库,需要通过文件来交换数据。二:从文件中读然后写到数据库这代码谁都会写,那么为什么还要使用框架?try(BufferedReader......