【小家Spring】面向切面编程Spring AOP创建代理的方式:ProxyFactoryBean、ProxyFactory、AspectJProxyFactory(JDK Proxy和CGLIB)(上)

简介: 【小家Spring】面向切面编程Spring AOP创建代理的方式:ProxyFactoryBean、ProxyFactory、AspectJProxyFactory(JDK Proxy和CGLIB)(上)

前言


Spring AOP是大家都非常熟悉的一个概念,在Spring家族体系中扮演着举足轻重的作用。


然后Spring作为一个优秀的框架,提供了多种应用层面上代理的方式:ProxyFactoryBean、ProxyFactory、AspectJProxyFactory


注意:此处这里指的是Spring提供的应用层得方式,并不是指的底层实现方式。底层实现方式现在只有业界都熟悉的两种:JDK动态代理和CGLIB代理~


ProxyFactoryBean是将我们的AOP和IOC融合起来,而ProxyFactory 则是只能通过代码硬编码进行编写 一般都是给spring自己使用。而AspectJ是目前大家最常用的 起到集成AspectJ和Spring~~~

image.png


Spring AOP进化史:


image.png


及的关键类说明


ProxyConfig:为上面三个类提供配置属性

AdvisedSupport:继承ProxyConfig,实现了Advised。封装了对通知(Advise)和通知器(Advisor)的操作

ProxyCreatorSupport:继承AdvisedSupport,其帮助子类(上面三个类)创建JDK或者cglib的代理对象

Advised:可以获取拦截器和其他 advice, Advisors和代理接口


涉及到的几个关键概念


Advice:通知,定义在连接点做什么,比如我们在方法前后进行日志打印(前置通知、后置通知、环绕通知等等)

Pointcut:切点,决定advice应该作用于那个连接点,比如根据正则等规则匹配哪些方法需要增强(Pointcut 目前有getClassFilter(类匹配),getMethodMatcher(方法匹配),Pointcut TRUE (全匹配))

JoinPoint:连接点,就是spring允许你是通知(Advice)的地方,那可就真多了,基本每个方法的前、后(两者都有也行),或抛出异常是时都可以是连接点,spring只支持方法连接点。其他如AspectJ还可以让你在构造器或属性注入时都行,不过那不是咱们关注的,只要记住,和方法有关的前前后后都是连接点(通知方法里都可以获取到这个连接点,顺便获取到相关信息)。

Advisor:把pointcut和advice连接起来(可由Spring去完成,我们都交给容器管理就行,当然,你也可以手动完成)Spring的Advisor是Pointcut和Advice的配置器,它是将Advice注入程序中Pointcut位置的代码。org.springframework.aop.support.DefaultPointcutAdvisor是最通用的Advisor类


继续阅读之前,强烈建议先阅读:【小家Spring】Spring AOP原理使用的基础类打点(ProxyProcessorSupport、AopInfrastructureBean、Advised、TargetClassAware)


ProxyFactoryBean


使用Spring提供的类org.springframework.aop.framework.ProxyFactoryBean是创建AOP的最基本的方式。

它是个工厂Bean,然后我们可以自定义我们的代理实现逻辑,最终交给Spring容器管理即可。

public class ProxyFactoryBean extends ProxyCreatorSupport
    implements FactoryBean<Object>, BeanClassLoaderAware, BeanFactoryAware {
    ...
}


写个例子如下:


// 先定义一个前置通知
@Component("logMethodBeforeAdvice")
public class LogMethodBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("this is LogMethodBeforeAdvice");
    }
}
// 注册一个代理Bean
    @Bean
    public ProxyFactoryBean proxyFactoryBean(HelloService helloService) {
        ProxyFactoryBean factoryBean = new ProxyFactoryBean();
        //代理的目标对象  效果同setTargetSource(@Nullable TargetSource targetSource)
        // 此处需要注意的是,这里如果直接new,那么该类就不能使用@Autowired之类的注入  因此建议此处还是从容器中去拿
        // 因此可以写在入参上(这也是标准的写法~~)
        //factoryBean.setTarget(new HelloServiceImpl());
        factoryBean.setTarget(helloService);
        // setInterfaces和setProxyInterfaces的效果是相同的。设置需要被代理的接口,
        // 若没有实现接口,那就会采用cglib去代理
        // 需要说明的一点是:这里不设置也能正常被代理(若你没指定,Spring内部会去帮你找到所有的接口,然后全部代理上~~~~~~~~~~~~)  设置的好处是只代理指定的接口
        factoryBean.setInterfaces(HelloService.class);
        //factoryBean.setProxyInterfaces(new Class[]{HelloService.class});
        // 需要植入进目标对象的bean列表 此处需要注意:这些bean必须实现类 org.aopalliance.intercept.MethodInterceptor或 org.springframework.aop.Advisor的bean ,配置中的顺序对应调用的顺序
        factoryBean.setInterceptorNames("logMethodBeforeAdvice");
        // 若设置为true,强制使用cglib,默认是false的
        //factoryBean.setProxyTargetClass(true);
        return factoryBean;
    }
// main方法测试:
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(RootConfig.class);
        //expected single matching bean but found 2: helloServiceImpl,proxyFactoryBean
        // 如果通过类型获取,会找到两个Bean:一个我们自己的实现类、一个ProxyFactoryBean所生产的代理类 而此处我们显然是希望要生成的代理类的  因此我们只能通过名称来(或者加上@Primary)
        //HelloService bean = applicationContext.getBean(HelloService.class);
        HelloService bean = (HelloService) applicationContext.getBean("proxyFactoryBean");
        bean.hello();
        System.out.println(bean); //com.fsx.service.HelloServiceImpl@4e50c791
        System.out.println(bean.getClass()); //class com.sun.proxy.$Proxy22 用得JDK的动态代理
        // 顺便说一句:这样也是没错得。因为Spring AOP代理出来的每个代理对象,都默认实现了这个接口(它是个标记接口)
        // 它这个也就类似于所有的JDK代理出来的,都是Proxy的子类是一样的思想~
        SpringProxy springProxy = (SpringProxy) bean;
    }
输出:
this is LogMethodBeforeAdvice
this is my method~~


由此课件,我们的HelloServiceImpl就被成功代理了。下面我们稍微看看它是怎么创建这个代理的,直接来到getObject()方法:


  @Override
  @Nullable
  public Object getObject() throws BeansException {
    //就是根据我们配置的interceptorNames来获取对应的bean,并却转化成Advisor。
    //this.advisorChainInitialized:标示是否已进行过初始化,若以初始化则不再进行初始化。
    initializeAdvisorChain();
    //生成代理对象时,因为Spring中有singleton类型和prototype类型这两种不同的Bean,所以要对代理对象的生成做一个区分
    if (isSingleton()) {
      //生成singleton的代理对象,这个方法是ProxyFactoryBean生成AOPProxy代理对象的调用入口
      return getSingletonInstance();
    }
    else {
      if (this.targetName == null) {
        logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
            "Enable prototype proxies by setting the 'targetName' property.");
      }
      // 生成原型的代理对象
      return newPrototypeInstance();
    }
  }
  //通过读取interceptorNames将BeanFactory工厂内对应的Advice,Advisor,MethodInterceptor
  //通过AdvisorAdapterRegistry.wrap(Advice需要有对应的AdvisorAdapter的支持才可以转换)转换为Advisor然后加载到执行链条中
  private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException {
    if (this.advisorChainInitialized) {
      return;
    }
    if (!ObjectUtils.isEmpty(this.interceptorNames)) {
      // Globals can't be last unless we specified a targetSource using the property...
      // 最后一个不能是全局的suffix *,除非我们指定了targetSource之类的
      if (this.interceptorNames[this.interceptorNames.length - 1].endsWith(GLOBAL_SUFFIX) &&
          this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) {
        throw new AopConfigException("Target required after globals");
      }
      // Materialize interceptor chain from bean names.
      for (String name : this.interceptorNames) {
        // 如国拦截器的名称是以*结尾的,说明它要去全局里面都搜索出来
        // 全局:去自己容器以及父容器中找,类型为Advisor.class以及Interceptor.class所有的,名称是以这个名称为开头的prefix的Bean.
        // 最终也一样交给addAdvisorOnChainCreation(bean, name);   相当于一个批量处理吧  在特殊场景还是很有用处的
        if (name.endsWith(GLOBAL_SUFFIX)) {
          addGlobalAdvisor((ListableBeanFactory) this.beanFactory,
              name.substring(0, name.length() - GLOBAL_SUFFIX.length()));
        }
        // 绝大部分情况肯定都走这里:精确匹配
        else {
          Object advice;
          if (this.singleton || this.beanFactory.isSingleton(name)) {
            // 从容器里把这个Bean拿出来~~~~~~~~~~~~~
            advice = this.beanFactory.getBean(name);
          }
          // 多例的  这里每次都是new一个新的
          else {
            // It's a prototype Advice or Advisor: replace with a prototype.
            // Avoid unnecessary creation of prototype bean just for advisor chain initialization.
            advice = new PrototypePlaceholderAdvisor(name);
          }
          // 这个方法的作用还挺大的:将advice对象添加到通知器链中
          //方法中首先会调用namedBeanToAdvisor(next)方法,将从ioc容器获取的普通对象转换成通知器Advisor对象。  详细如下:
          addAdvisorOnChainCreation(advice, name);
        }
      }
    }
    this.advisorChainInitialized = true;
  }
//addAdvisorOnChainCreation
  private void addAdvisorOnChainCreation(Object next, String name) {
    // We need to convert to an Advisor if necessary so that our source reference
    // matches what we find from superclass interceptors.
    // 这里调用namedBeanToAdvisor做了一下适配:成统一的Advisor 
    Advisor advisor = namedBeanToAdvisor(next);
    addAdvisor(advisor);
  }
//namedBeanToAdvisor
  private Advisor namedBeanToAdvisor(Object next) {
    try {
      return this.advisorAdapterRegistry.wrap(next);
    }
  }




相关文章
|
5天前
|
Java 开发者 Spring
Spring AOP的切点是通过使用AspectJ的切点表达式语言来定义的。
【5月更文挑战第1天】Spring AOP的切点是通过使用AspectJ的切点表达式语言来定义的。
16 5
|
5天前
|
XML Java 数据格式
Spring AOP
【5月更文挑战第1天】Spring AOP
21 5
|
6天前
|
Java 编译器 开发者
Spring的AOP理解
Spring的AOP理解
|
6天前
|
XML Java 数据格式
如何在Spring AOP中定义和应用通知?
【4月更文挑战第30天】如何在Spring AOP中定义和应用通知?
12 0
|
6天前
|
安全 Java 开发者
在Spring框架中,IoC和AOP是如何实现的?
【4月更文挑战第30天】在Spring框架中,IoC和AOP是如何实现的?
17 0
|
6天前
|
Java 测试技术 开发者
【亮剑】如何通过自定义注解来实现 Spring AOP,以便更加灵活地控制方法的拦截和增强?
【4月更文挑战第30天】通过自定义注解实现Spring AOP,可以更灵活地控制方法拦截和增强。首先定义自定义注解,如`@MyCustomAnnotation`,然后创建切面类`MyCustomAspect`,使用`@Pointcut`和`@Before/@After`定义切点及通知。配置AOP代理,添加`@EnableAspectJAutoProxy`到配置类。最后,在需拦截的方法上应用自定义注解。遵循保持注解职责单一、选择合适保留策略等最佳实践,提高代码可重用性和可维护性。记得测试AOP逻辑。
|
6天前
|
缓存 监控 Java
【Spring系列笔记】AOP
面向切面编程就是面向特定方法编程。通过将横切关注点(cross-cutting concerns)从主要业务逻辑中分离出来,提供一种更好的代码模块化和可维护性。 横切关注点指的是在应用程序中横跨多个模块或层的功能,例如日志记录、事务管理、安全性、缓存、异常处理等。
18 0
|
2天前
|
Oracle Java 关系型数据库
windows 下 win11 JDK17安装与环境变量的配置(配置简单详细,包含IJ中java文件如何使用命令运行)
本文介绍了Windows 11中安装JDK 17的步骤,包括从官方网站下载JDK、配置环境变量以及验证安装是否成功。首先,下载JDK 17的安装文件,如果没有Oracle账户,可以直接解压缩文件到指定目录。接着,配置系统环境变量,新建`JAVA_HOME`变量指向JDK安装路径,并在`Path`变量中添加。然后,通过命令行(cmd)验证安装,分别输入`java -version`和`javac -version`检查版本信息。最后,作者分享了如何在任意位置运行Java代码,包括在IntelliJ IDEA(IJ)中创建的Java文件,只需去掉包声明,就可以通过命令行直接运行。
18 0
|
6天前
|
弹性计算 运维 Java
一键安装二进制JDK
【4月更文挑战第30天】
8 0
|
6天前
|
关系型数据库 MySQL 应用服务中间件
centos7在线安装jdk1.8+tomcat+mysql8+nginx+docker
现在,你已经成功在CentOS 7上安装了JDK 1.8、Tomcat、MySQL 8、Nginx和Docker。你可以根据需要配置和使用这些服务。请注意,安装和配置这些服务的详细设置取决于你的具体需求。
25 2