Spring进阶(AOP的理解)——静态/动态代理 & 面向切面编程AOP(Aspect Oriented Programming) & 日志记录 & 增强方法

引出


1.静态代理,动态代理的概念,动态代理的spring-cglib和jav-reflect实现;
2.面向切面编程AOP的思想,以及相关术语,连接点JointPoint;
3.增强方法,AOP的应用,日志的记录;

在这里插入图片描述

静态代理和动态代理

在Java中,代理(Proxy)是一种设计模式,它允许通过代理对象来控制对真实对象的访问。代理对象充当了真实对象的中间人,可以在访问真实对象之前或之后执行一些额外的操作。

Java中的代理可以分为两种类型:静态代理和动态代理。

代理模式

1、代理模式: 代理模式就是本该我做的事,我不做,我交给代理人去完成。就比如,我生产了一些产品,我自己不卖,我委托代理商帮我卖,让代理商和顾客打交道,我自己负责主要产品的生产就可以了。 代理模式的使用,需要有本类,和代理类,本类和代理类共同实现统一的接口。然后在 main 中调用就可以了。本类中的业务逻辑一般是不会变动的,在我们需要的时候可以不断的添加代理对象,或者修改代理类来实现业务的变更。

2、代理模式可以分为: 静态代理 优点:可以做到在不修改目标对象功能的前提下,对目标功能扩展 缺点:因为本来和代理类要实现统一的接口,所以会产生很多的代理类,类太多,一旦接口增加方法,目标对象和代理对象都要维护。 动态代理(JDK 代理/接口代理)代理对象,不需要实现接口,代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象,需要我们指定代理对象/目标对象实现的接口的类型。 Cglib 代理 特点: 在内存中构建一个子类对象,从而实现对目标对象功能的扩展。

3、使用场景: 修改代码的时候。不用随便去修改别人已经写好的代码,如果需要修改的话,可以通过代理的方式来扩展该方法。 隐藏某个类的时候,可以为其提供代理类 当我们要扩展某个类功能的时候,可以使用代理类 当一个类需要对不同的调用者提供不同的调用权限的时候,可以使用代理类来实现。 减少本类代码量的时候。 需要提升处理速度的时候。就比如我们在访问某个大型系统的时候,一次生成实例会耗费大量的时间,我们可以采用代理模式,当用来需要的时候才生成实例,这样就能提高访问的速度。

静态代理

静态代理 - 指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;

静态代理: 静态代理是在编译时就已经确定代理类和真实类的关系。代理类和真实类实现相同的接口,代理类持有真实类的引用,并在方法调用前后执行一些额外的操作。静态代理的缺点是需要为每个真实类编写一个代理类,当真实类的接口发生变化时,代理类也需要相应地修改。

在这里插入图片描述

计划被代理的类Target.java

package com.tianju.book.jpa.staticProxy;

/**
 * 计划被增强的类,被代理的类
 * 真实类
 */
public class Target {
    public void add(){
        System.out.println(">>>>"+this.getClass().getName()+": 增加数据的方法");
    }

    public void delete(String id){
        System.out.println(">>>>"+this.getClass().getName()+": 删除数据的方法,传的参数="+id);
    }

}

给Target.java做代理,增强他的功能

package com.tianju.book.jpa.staticProxy;

/**
 * 静态代理:
 * 对target进行代理
 */
public class ProxyTarget {
    private Target target; // 被代理的类

    public ProxyTarget(Target target) {
        this.target = target;
    }

    public void add(){
        System.out.println("执行增加之前做一些事情。。。");
        target.add();
        System.out.println("执行增加之后做一些事情。。。");
    }

    public void delete(String id){
        System.out.println("执行删除之前做一些事情。。。。");
        target.delete(id);
        System.out.println("执行删除之后做一些事情。。。。");
    }

}

测试被代理之后的类

package com.tianju.book.jpa.staticProxy;

/**
 * 测试静态代理
 */
public class staticProxyTest {
    public static void main(String[] args) {
        // 获得的是被增强之后的方法
        ProxyTarget proxyTarget = new ProxyTarget(new Target());

        System.out.println("##########执行被增强之后的add方法############");
        proxyTarget.add();

        System.out.println("##############执行被增强之前的delete方法##############");
        proxyTarget.delete("1314");
    }
}

在这里插入图片描述

动态代理

动态代理: 动态代理是在运行时动态生成代理类,无需为每个真实类编写一个代理类。

动态代理 - 在运行时在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。

Spring的cglib实现

在这里插入图片描述

cglib包

在这里插入图片描述

使用Spring的cglib实现动态代理

package com.tianju.book.jpa.staticProxy;


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 DynamicProxyFactory {
    Target target = new Target();

    Target targetProxy = (Target) Enhancer.create(
            target.getClass(),
            new MethodInterceptor() {
                @Override
                public Object intercept(
                        Object o, Method method, Object[] objects,
                        MethodProxy methodProxy) throws Throwable {
                    System.out.println(o.getClass().getName());
                    System.out.println(method+"执行之前");
                    Object invoke = method.invoke(target, objects);
                    System.out.println(method+"执行之后");
                    return invoke;
                }
            });
}

进行测试

package com.tianju.book.jpa.staticProxy;

/**
 * 动态代理的测试
 */
public class DynamicProxyTest {
    public static void main(String[] args) {
        DynamicProxyFactory dynamicProxyFactory = new DynamicProxyFactory();
        Target targetProxy = dynamicProxyFactory.targetProxy;
        System.out.println("################动态代理之前执行add方法############");
        targetProxy.add();

        System.out.println("################动态代理之后执行delete方法############");
        targetProxy.delete("5678");

    }
}

在这里插入图片描述

java.lang.reflect包实现

Java提供了java.lang.reflect包来支持动态代理。动态代理需要借助于接口和InvocationHandler接口实现。

动态代理通过InvocationHandler接口的invoke方法来拦截对真实对象方法的调用,并在调用前后执行一些额外的操作。动态代理可以在运行时动态地创建代理类,无需为每个真实类编写一个代理类,更加灵活和方便。

在这里插入图片描述

接口

package com.tianju.book.jpa.jdkProxy;

/**
 * 接口
 */
public interface USMode {
    void provoke();

    void wastewater(String water);
}

接口的实现,要被代理的方法

package com.tianju.book.jpa.jdkProxy;

/**
 * 真实类
 */
public class American implements USMode{
    public void provoke() {
        System.out.println("美国provoke china");
    }
    public void wastewater(String water) {
        System.out.println("正在排放:"+water);
    }
}

java的reflect进行代理并测试

package com.tianju.book.jpa.jdkProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class Client {
    public static void main(String[] args) {
        American american = new American();
        System.out.println("双亲委派 getClassLoader: "+
                american.getClass().getClassLoader());

        USMode japan = (USMode) Proxy.newProxyInstance(
                american.getClass().getClassLoader(),
                american.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(
                            Object proxy, Method method, Object[] args
                    ) throws Throwable {
                        System.out.println("被代理的方法:"+method);
                        System.out.println("传的参数="+ Arrays.toString(args));
                        System.out.println("耗子尾汁,好好反思");
                        return method.invoke(american, args);
                    }
                }
        );

        System.out.println("############动态代理1################");
        japan.provoke();

        System.out.println("############动态代理2################");
        japan.wastewater("太平洋");
    }
}

在这里插入图片描述

面向切面编程AOP(Aspect Oriented Programming)

AOP思想

在软件开发中,分布于应用中多处的功能被称为横切关注点(cross-cutting con-cerns)。通常,这些横切关注点从概念上是与应用的业务逻辑相分离的(但是往往直接嵌入到应用的业务逻辑之中)。将这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所要解决的

AOP为Aspect Oriented Programming,日志是应用切面的常见范例,但是它并不是切面适用的唯一场景。通览本书,我们还会看到切面所适用的多个场景,包括声明式事务、安全和缓存

切面实现了横切关注点(跨越多个应用对象的逻辑)的模块化

图4.1展现了一个被划分为模块的典型应用。每个模块的核心功能都是为特定业务领域提供服务,但是这些模块都需要类似的辅助功能,例如安全和事务管理。

在这里插入图片描述

继承与委托是最常见的实现重用通用功能的面向对象技术。但是,如果在整个应用中使用切面提供了取代继承和委托的另一种选择,而且在很多场景下更清晰简洁。在使用面向切面编程时,我们仍然在一个地方定义通用功能,但是我们可以通过声明的方式定义这个功能以何种方式在何处应用,而无需修改受影响的类。横切关注点可以被模块化为特殊的类,这些类被称为切面。

这样做有两个好处:

首先,每个关注点现在都只集中于一处,而不是分散到多处代码中;

其次,服务模块更简洁,因为它们只包含主要关注点(或核心功能)的代码,而次要关注点的代码被转移到切面中了。

在一个或多个连接点上,可以将切面的功能(通知)织入到程序的执行过程中

在这里插入图片描述

AOP相关术语

通知(Advice)

当抄表员出现在我们家门口时,他们要登记用电量并回去向电力公司报告。显然,他们必须有一份需要抄表的住户清单,他们所汇报的信息也很重要。但是记录用电量才是抄表员的主要工作。

类似地,切面也有目标一它必须要完成的工作。在AOP术语中,切面的工作被称为通知。

通知定义了切面是什么以及何时使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。它应该应用于某个方法被调用之前?之后?之前和之后?还是只在方法抛出异常时?

在这里插入图片描述

连接点(Joinpoint)

电力公司为多个住户提供服务,甚至可能是整个城市。每家都有一个电表,因此每家都是抄表员的潜在目标。抄表员也许能够读取各种类型的设备,但是为了完成他的工作,他需要针对房屋内所安装的电表。

同样,我们的应用可能也需要对数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插人到应用的正常流程之中,并添加新的行为。

切点(Poincut)

让每一位抄表员都去访问电力公司所服务的所有房屋,这是不现实的。实际上,电力公司为每一位抄表员都分别指定某一块区域的房屋。类似地,一个切面并不需要通知应用的所有连接点。切点有助于缩小切面所通知连接点的范围。

如果通知定义了切面的“什么”和“何时”,那么切点就定义了“何处”。切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称来指定这些切点,或是利用正则表达式定义匹配的类和方法名称模式来指定这些切点。有些AOP框架允许我们创建动态的切点,可以根据运行时的决策(比如方法的参数值)来决定是否应用通知。

切面(Aspect)

当抄表员开始一天的工作时,他知道自己要做的事情(报告用电量)和从那些房屋收集信息。因此,他知道要完成工作所需要的一切东西。

切面是通知和切点的结合。通知和切点共同定义了关于切面的全部内容一它是什么,在何时和何处完成其功能。

引入(Introduction)

引人允许我们向现有的类添加新方法或属性。例如,我们可以创建一个Auditable通知类,该类记录了对象最后一次修改时的状态。这很简单,只需一种方法,setLastModified(Date),和一个实例变量来保存这个状态。然后,这个新方法
和实例变量就可以被引人到现有的类中。从而可以在无需修改这些现有的类的情况下,让它们具有新的行为和状态。

织入(Veaving)

织入是将切面应用到目标对象来创建新的代理对象的过程。切面在指定的连接点被织人到目标对象中。在目标对象的生命周期里有多个点可以进行织人。

  • 编译期一切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
  • 类加载期一切面在目标类加栽到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的LTW(load-time weaving)就支持以这种方式织入切面。
  • 运行期一切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP客器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的。

在这里插入图片描述

AOP的应用

日志是应用切面的常见范例,但是它并不是切面适用的唯一场景。通览本书,我们还会看到切面所适用的多个场景,包括声明式事务、安全和缓存

spring核心容器

Spring基础(核心容器)——从配置文件到注解开发 & 创建对象+成员变量赋值IOC & 增强方法AOP

在这里插入图片描述

切入点表达式

切入点使用切入点表达式指定哪些方法是目标方法。切入点表达式有多种类型,最常用的是execution,此外还有@target、@annotation等.

****execution****用来描述目标方法的签名,由6个部分组成:

在这里插入图片描述

访问修饰符 返回值类型 类.方法名(参数列表) 异常声明

除了返回值类型、方法名、参数列表这三部分不可省略外,其余部分都可省略

各部分常用写法如下:

访问修饰符:一般省略,表示任意,但受代理方式的限制(CGLIB对private方法无效)

返回值类型:* 表示任意

类的包:包全名、前缀* 、…(某包和其子孙包)

类的类名: 类名、前缀*、*

方法名: 方法名、前缀*、*

参数列表: … 表示任意

异常声明:一般省略,表示任意

示例:

execution(* com.tianju.aop.MyTarget.show(..))   MyTarget的所有show方法,show方法有重载

execution(* com.tianju.aop.*.*(..))   aop包下所有类的所有方法

execution(* com.tianju.aop..*.*(..))   aop包和其子孙包下所有类的所有方法

execution(* com.tianju.aop.*.find*(..))   aop包下所有类的以find开头的方法

通知方法不会被自己或其他通知增强,同一个目标对象中的方法相互调用时,被调用的方法这次调用不会被增强(被其他对象的方法调用时会被增强)

@target描述了标注了某注解的类的所有方法

比如@target(com.tianju.aop.MyAnnotation)表示标注了MyAnnotation注解的类的所有方法

@annotation描述了标注了某注解的方法

比如@annotation(com.tianju.aop.MyAnnotation2)表示标注了MyAnnotation2注解的所有方法

特别的,切入点表达式还可使用 && || ! 进行逻辑运算。比如@target(com.tianju.aop.MyAnnotation) && execution(* com.tianju.aop..(…)) 表示标注了MyAnnotation注解的类的所有方法和aop包下所有类的方法的交集

(注意&在xml中需要写成&)

记录日志

记录日志,用增强方法,要点:

  • 0.是增强类 @Aspect,在容器中 @Component
  • 1.给谁做增强,
  • 2.怎么增强,@before @after @afterReturning @afterThrowing

在这里插入图片描述

LoggingAsp.java文件,给controller层做增强

package com.tianju.aop;

import com.tianju.entity.User;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.aspectj.lang.annotation.Aspect;

import javax.servlet.http.HttpSession;
import java.util.Arrays;
import java.util.Date;
/**
 * 记录日志,用增强方法,
 * 要点:
 * 0.是增强类 @Aspect,在容器中 @Component
 * 1.给谁做增强,
 * 2.怎么增强,@before @after @afterReturning @afterThrowing
 */

/**
 * @Component 在容器中
 * @Aspect 是增强方法
 * @Before("@within(org.springframework.stereotype.Controller)") 给controller层增强
 */
@Component
@Aspect
@Slf4j // 用lombok.extern.slf4j.Slf4j;
public class LoggingAsp {
    @Autowired // session也在容器里,所以直接可以注入
    private HttpSession session;

    // 给所有标注了@Controller注解的方法做增强
    @Before("@within(org.springframework.stereotype.Controller)")
    public void log(JoinPoint joinPoint){
        String className = joinPoint.getTarget().getClass().getSimpleName(); // 获取类名
        String methodName = joinPoint.getSignature().getName(); // 获取方法名
        Object[] args = joinPoint.getArgs(); // 获取传的参数
        // 获取当前登陆的人,从session中获取
        User user = (User) session.getAttribute("user");
        String username = (user==null)?"未登录人员":user.getUsername();
        log.info("{}访问了{}类的{}方法,传的参数为{}",
                new Date() + username,className,methodName, Arrays.toString(args));
    }
}

进行日志的配置

在这里插入图片描述

# 日志的相关配置
logging:
  file:
    name: D:\\620\\log\\community.log
  level:
    org.springframework.web: debug
    com.tianju: debug
    org.springframework.jdbc.support.JdbcTransactionManager: debug

总结

1.静态代理,动态代理的概念,动态代理的spring-cglib和jav-reflect实现;
2.面向切面编程AOP的思想,以及相关术语,连接点JointPoint;
3.增强方法,AOP的应用,日志的记录;

在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Arya's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值