全面深度讲解spring5底层原理二(6-12讲)

3 篇文章 0 订阅

转载自:https://blog.csdn.net/qq_38505969/article/details/123739542

介绍

代码仓库地址:https://gitee.com/CandyWall/spring-source-study
跟着黑马满一航老师的spring高级49讲做的学习笔记,本笔记跟视频内容的项目名称和代码略有不同,我将49讲的代码每一讲的代码都拆成了独立的springboot项目,并且项目名称尽量做到了见名知意,都是基于我自己的考量,代码都已经过运行验证过的,仅供参考。

视频教程地址:https://www.bilibili.com/video/BV1P44y1N7QG

注:

1. 每一讲对应一个二级标题,每一个三级标题是使用子项目名称命名的,和我代码仓库的项目是一一对应的;
2. 代码里面用到了lombok插件来简化了Bean中的get()、set()方法,以及日志的记录的时候用了lombok的@Slf4j注解。

笔记中如有不正确的地方,欢迎在评论区指正,非常感谢!!!

每个子项目对应的视频链接以及一些重要内容的笔记

第六讲 Aware和InitializingBean接口以及@Autowired注解失效分析

spring_06_aware_initializingbean

p26 025-第六讲-Aware与InitializingBean接口

Aware 接口用于注入一些与容器相关信息,例如:

​ a. BeanNameAware 注入 Bean 的名字

​ b. BeanFactoryAware 注入 BeanFactory 容器

​ c. ApplicationContextAware 注入 ApplicationContext 容器

​ d. EmbeddedValueResolverAware 注入 解析器,解析${}

定义一个MyBean类,实现BeanNameAwareApplicationContextAwareInitializingBean接口并实现其方法,再定义两个方法,其中一个加@Autowired注解,注入ApplicationContext容器,另一个加@PostConstruct注解,具体代码如下:

public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean {
    @Override
    public void setBeanName(String name) {
        log.debug("当前bean:" + this + ",实现 BeanNameAware 调用的方法,名字叫:" + name);
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.debug("当前bean:" + this + ",实现 ApplicationContextAware 调用的方法,容器叫:" + applicationContext);
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        log.debug("当前bean:" + this + ",实现 InitializingBean 调用的方法,初始化");
    }
    @Autowired
    public void aaa(ApplicationContext applicationContext) {
        log.debug("当前bean:" + this +",使用 @Autowired 容器是:" + applicationContext);
    }
    @PostConstruct
    public void init() {
        log.debug("当前bean:" + this + ",使用 @PostConstruct 初始化");
    }
}

测试代码:

public class TestAwareAndInitializingBean {
    @Test
    public void testAware1() throws Exception {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("myBean", MyBean.class);
        context.refresh();
        context.close();
    }
}

运行结果:

image-20220330173209568

加了@Autowired@PostConstruct注解的方法并没有被执行,而AwareInitializingBean接口方法都被执行了。

修改测试代码,把解析@Autowired@PostConstruct注解的Bean后处理加进来,然后再运行一下

public class TestAwareAndInitializingBean {
    @Test
    public void testAware1() throws Exception {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("myBean", MyBean.class);
        // 解析 @Autowired 注解的Bean后处理器
        context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
        // 解析 @PostConstruct 注解的Bean后处理器
        context.registerBean(CommonAnnotationBeanPostProcessor.class);
        context.refresh();
        context.close();
    }
}

运行结果:

image-20220330173807848

可以看到这下都执行了

有人可能会问:bcd的功能用 @Autowired注解就能实现啊,为啥还要用 Aware 接口呢?
InititalizingBean 接口可以用 @PostConstruct注解实现,为啥还要用InititalizingBean呢?
简单地说:

  • @Autowired@PostConstruct注解的解析需要用到 Bean 后处理器,属于扩展功能,而 Aware 接口属于内置功能,不加任何扩展,Spring就能识别;

  • 某些情况下,扩展功能会失效,而内置功能不会失效

    p27 026-第六讲-@Autowired失效分析

    • 例1:比如没有把解析@Autowired@PostStruct注解的Bean的后处理器加到Bean工厂中,你会发现用 Aware 注入 ApplicationContext 成功, 而 @Autowired 注入 ApplicationContext 失败

    • 例2:定义两个Java Config类(类上加@Configuration注解),名字分别叫MyConfig1MyConfig2,都实现注入ApplicationContext容器和初始化功能,MyConfig1@Autowired@PostConstruct注解实现,MyConfig2用实现AwareInitializingBean接口的方式实现,另外,两个Config类中都通过@Bean注解的方式注入一个BeanFactoryPostProcessor,代码如下:

      MyConfig1:

      @Slf4j
      public class MyConfig1 {
          @Autowired
          public void setApplicationContext(ApplicationContext applicationContext) {
              log.debug("注入 ApplicationContext");
          }
          @PostConstruct
          public void init() {
              log.debug("初始化");
          }
          @Bean
          public BeanFactoryPostProcessor processor1() {
              return beanFactory -> {
                  log.debug("执行 processor1");
              };
          }
      }
      

      测试代码:

      @Slf4j
      public class TestAwareAndInitializingBean {
          @Test
          public void testAware_MyConfig1() {
              GenericApplicationContext context = new GenericApplicationContext();
              // MyConfig1没有加上@
              context.registerBean("myConfig1", MyConfig1.class);
              // 解析 @Autowired 注解的Bean后处理器
              context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
              // 解析 @PostConstruct 注解的Bean后处理器
              context.registerBean(CommonAnnotationBeanPostProcessor.class);
              // 解析@ComponentScan、@Bean、@Import、@ImportResource注解的后处理器
              // 这个后处理器不加出不来效果
              context.registerBean(ConfigurationClassPostProcessor.class);
              // 1. 添加beanfactory后处理器;2. 添加bean后处理器;3. 初始化单例。
              context.refresh();
              context.close();
      }
      

      运行结果:

      image-20220331090139965

      MyConfig2:

      @Slf4j
      public class MyConfig2 implements ApplicationContextAware, InitializingBean {
          @Override
          public void setApplicationContext(ApplicationContext applicationContext) {
              log.debug("注入 ApplicationContext");
          }
          @Override
          public void afterPropertiesSet() throws Exception {
              log.debug("初始化");
          }
          @Bean
          public BeanFactoryPostProcessor processor1() {
              return beanFactory -> {
                  log.debug("执行 processor1");
              };
          }
      }
      

      测试代码:

      @Slf4j
      public class TestAwareAndInitializingBean {
          @Test
          public void testAutowiredAndInitializingBean_MyConfig2() {
              GenericApplicationContext context = new GenericApplicationContext();
              context.registerBean("myConfig2", MyConfig2.class);
              // 1. 添加beanfactory后处理器;2. 添加bean后处理器;3. 初始化单例。
              context.refresh();
              context.close();
          }
      }
      

      运行结果:

      image-20220331090537999

      Java配置类在添加了 bean 工厂后处理器后,你会发现用传统接口方式的注入和初始化依然成功,而 @Autowired@PostConstruct 的注入和初始化失败。

      那是什么原因导致的呢?

      配置类 @Autowired 注解失效分析

      • Java 配置类不包含 BeanFactoryPostProcessor 的情况

        ApplicationContext BeanFactoryPostProcessor BeanPostProcessor Java配置类 3. 创建和初始化 3.1 执行 Aware 及 InitializingBean 3.2 创建成功 1. 执行 BeanFactoryPostProcessor 2. 注册 BeanPostProcessor ApplicationContext BeanFactoryPostProcessor BeanPostProcessor Java配置类

总结:

  • Aware 接口提供了一种【内置】 的注入手段,可以注入 BeanFactoryApplicationContext
  • InitializingBean 接口提供了一种 【内置】 的初始化手段;
  • 内置的注入和初始化不收扩展功能的影响,总会被执行,因此 spring 框架内部的类常用它们。

第七讲 Bean的初始化与销毁

spring_07_init_destroy

p28 027-第七讲-初始化与销毁

定义Bean1类,实现InitializingBean接口和对应的接口方法afterPropertiesSet(),再定义init1()方法,在方法上加@PostConstruct注解,最后定义init3()

@Slf4j
public class Bean1 implements InitializingBean {
    @PostConstruct
    public void init1() {
        log.debug("初始化1,@PostConstruct");
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        log.debug("初始化2,InitializingBean接口");
    }
    public void init3() {
        log.debug("初始化3,@Bean的initMethod");
    }
}

定义Bean2类,实现DisposableBean接口和对应的接口方法destroy(),再定义destroy1()方法,在方法上加@PreDestroy注解,最后定义init3()

@Slf4j
public class Bean2 implements DisposableBean {
    @PreDestroy
    public void destroy1() {
        log.debug("销毁1,@PreDestory");
    }
    @Override
    public void destroy() throws Exception {
        log.debug("销毁2,DisposableBean接口");
    }
    public void destroy3() {
        log.debug("销毁3,@Bean的destroyMethod");
    }
}

定义Config类,类上加@Configuration注解,类中通过@Bean注解把Bean1Bean2加到Bean工厂中,分别在@Bean注解中指定initMethod = "init3",destroyMethod = "destroy"

@Configuration
public class Config {
    @Bean(initMethod = "init3")
    public Bean1 bean1() {
        return new Bean1();
    }
    @Bean(destroyMethod = "destroy3")
    public Bean2 bean2() {
        return new Bean2();
    }
}

编写测试代码,观察三个初始化方法和三个销毁方法的执行顺序

@Slf4j
public class TestInitAndDestroy {
    @Test
    public void testInitAndDestroy() throws Exception {
        // ⬇️GenericApplicationContext 是一个【干净】的容器,这里只是为了看初始化步骤,就不用springboot启动类进行演示了
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("config", Config.class);
        // 解析@PostConstruct注解的bean后处理器
        context.registerBean(CommonAnnotationBeanPostProcessor.class);
        // 解析@Configuration、@Component、@Bean注解的bean工厂后处理器
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.refresh();
        context.close();
    }
}

运行结果:

image-20220401013108042

可以看到,spring提供了多种初始化和销毁手段

  • 对于init,三个初始化方法的执行顺序是

    @PostConstruct -> InitializingBean接口 -> @BeaninitMethod

  • 对于destory, 三个销毁方法的执行顺序是

    @PreDestroy -> DisposableBean接口 -> @Beandestroy

第八讲 Scope类型、注意事项、销毁和失效分析

spring_08_scope

p29 028-第八讲-Scope

springscope类型:

  • singleton:单例
  • prototype:多例
  • requestweb请求
  • sessionweb的会话
  • applicationwebServletContext

测试scope类型中的requestsessionapplication

定义**BeanForRequest类,加上@Component@Scope注解,指定Scope类型为request**,在类型中定义destroy()方法,方法上加@PreDestory注解,代码如下:

@Slf4j
@Scope("request")
@Component
public class BeanForRequest {
    @PreDestroy
    public void destory() {
        log.debug("destroy");
    }
}

定义**BeanForSession类,加上@Component@Scope注解,指定Scope类型为session**,在类型中定义destroy()方法,方法上加@PreDestory注解,代码如下:

@Slf4j
@Scope("request")
@Component
public class BeanForRequest {
    @PreDestroy
    public void destory() {
        log.debug("destroy");
    }
}

定义**BeanForApplication类,加上@Component@Scope注解,指定Scope类型为application**,在类型中定义destroy()方法,方法上加@PreDestory注解,代码如下:

@Slf4j
@Scope("request")
@Component
public class BeanForRequest {
    @PreDestroy
    public void destory() {
        log.debug("destroy");
    }
}

编写一个MyController类,加上@RestController注解,在该类中通过@Autowired注解注入BeanForRequestBeanForSessionBeanForApplication的实例,需要注意,这里还需要加@Lazy注解(至于原因后面会解释),否则会导致@Scope域失效,再定义一个方法tes(),加上@GetMapping注解,用于响应一个http请求,在test()方法中,打印beanForRequestbeanForSessionbeanForApplication,代码如下:

@RestController
public class MyController {
    @Lazy
    @Autowired
    private BeanForRequest beanForRequest;
    @Lazy
    @Autowired
    private BeanForSession beanForSession;
    @Lazy
    @Autowired
    private BeanForApplication beanForApplication;
    @GetMapping(value = "/test", produces = "text/html")
    public String test(HttpServletRequest request, HttpSession session) {
        // ServletContext sc = request.getServletContext();
        String result = "<ul>" +
                        "<li>request scope: " +  beanForRequest + "</li>" +
                        "<li>session scope: " +  beanForSession + "</li>" +
                        "<li>application scope: " +  beanForApplication + "</li>" +
                        "</ul>";
        return result;
    }
}

springboot启动类

@Slf4j
@SpringBootApplication
public class ScopeApplicationContext {
    public static void main(String[] args) throws InterruptedException {
        testRequest_Session_Application_Scope();
    }
    // 演示 request, session, application作用域
    private static void testRequest_Session_Application_Scope() throws InterruptedException {
        ConfigurableApplicationContext context = SpringApplication.run(ScopeApplicationContext.class);
    }
}

启动后,用谷歌浏览器访问 http://localhost:8080/test

浏览器运行结果如下:

image-20220401222141258

再刷新一下当前页,查看运行结果:

image-20220401222154488

控制台运行结果如下:

image-20220401214139467

可以看到两次刷新只有BeanForRequest对象发生了改变,这是由于scoperequest类型的对象,会在请求结束后销毁,再来一次请求就会重新创建,请求结束后又会销毁。

接下来我们换个Edge浏览器访问 http://localhost:8080/test,对比两个浏览器的显示结果:

image-20220401222256653

可以看到这回除了BeanForRequest对象不同,BeanForSession对象也不同了,这是因为开一个新的浏览器会创建一个新的会话,所以BeanForSession对象也不同了。

继续进行测试,在application.properties配置一个属性server.servlet.session.timeout=10s,这个属性的默认值为30分钟,这样10s没有操作浏览器的话就会销毁对应session,不过经过测试这个这个属性最少为1分钟,低于1分钟一律按照1分钟算。具体原理看这篇博客:https://www.jianshu.com/p/9d91cca74082,里面进行了源码级别的分析。

设置好之后,重启项目, 然后去浏览器访问,1分钟后控制台会打印session被销毁,如下图所示:

image-20220402144856449

那什么时候scopeapplication的对象BeanForApplication会销毁呢?按理说应该是在SpringBoot程序结束,也即内置的Tomcat服务器停止的时候调用,但是经过测试:无论是在控制台停止SpringBoot项目,还是调用ApplicationContextclose()方法,都没有调用BeanForApplication的销毁方法,有知道什么方法可以让它调用的,请评论区告知,谢谢!!!

p30 029-第八讲-Scope失效解决1,2

p31 030-第八讲-Scope失效解决3,4

定义1个单例类SingletonBean,指定它的Scopesingleton,定义5个多例类,PrototypeBeanPrototypeBean1PrototypeBean2PrototypeBean3PrototypeBean4,将这5个多例类的对象注入到SingletonBean5个属性中,其中PrototypeBean用来演示多例对象注入到单例对象中Scope失效的情况,其他四个类的对象用来演示四种解决Scope失效的方法。相关类的定义如下:

SingletonBean

@Scope("singleton")
@Component
public class SingletonBean {
    @Autowired
    private PrototypeBean prototypeBean;
    @Lazy
    @Autowired
    private PrototypeBean1 prototypeBean1;
    @Autowired
    private PrototypeBean2 prototypeBean2;
    @Autowired
    private ObjectFactory<PrototypeBean3> factory;
    @Autowired
    private ApplicationContext context;
    public PrototypeBean getPrototypeBean() {
        return prototypeBean;
    }
    public PrototypeBean1 getPrototypeBean1() {
        return prototypeBean1;
    }
    public PrototypeBean2 getPrototypeBean2() {
        return prototypeBean2;
    }
    public PrototypeBean3 getPrototypeBean3() {
        return factory.getObject();
    }
    public PrototypeBean4 getPrototypeBean4() {
        return context.getBean(PrototypeBean4.class);
    }
}

PrototypeBean

@Scope("prototype")
@Component
public class PrototypeBean {
}

PrototypeBean1

@Scope("prototype")
@Component
public class PrototypeBean1 {
}

PrototypeBean2

@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class PrototypeBean2 {
}

PrototypeBean3

@Scope("prototype")
@Component
public class PrototypeBean3 {
}

PrototypeBean4

@Scope("prototype")
@Component
public class PrototypeBean4 {
}

测试代码:

@Slf4j
@SpringBootApplication
public class ScopeApplicationContext {
    public static void main(String[] args) throws Exception {
        testSingletonPrototypeInvalidAndSolve();
    }
    // 演示单例中注入多例失效的情况,以及解决失效问题的方法
    private static void testSingletonPrototypeInvalidAndSolve() {
        ConfigurableApplicationContext context = SpringApplication.run(ScopeApplicationContext.class);
        SingletonBean singletonBean = context.getBean(SingletonBean.class);
        // 单例中注入多例失效的情况
        log.debug("{}", singletonBean.getPrototypeBean().getClass());
        log.debug("{}", singletonBean.getPrototypeBean());
        log.debug("{}", singletonBean.getPrototypeBean());
        log.debug("{}", singletonBean.getPrototypeBean());
        System.out.println("------------------------------------");
        // 解决方法1:在SingletonBean的PrototypeBean1属性上加@Lazy注解
        log.debug("{}", singletonBean.getPrototypeBean1().getClass());
        log.debug("{}", singletonBean.getPrototypeBean1());
        log.debug("{}", singletonBean.getPrototypeBean1());
        log.debug("{}", singletonBean.getPrototypeBean1());
        System.out.println("------------------------------------");
        // 解决方法2:在PrototypeBean2的类上的@Scope注解多配置一个属性,如,@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
        log.debug("{}", singletonBean.getPrototypeBean2().getClass());
        log.debug("{}", singletonBean.getPrototypeBean2());
        log.debug("{}", singletonBean.getPrototypeBean2());
        log.debug("{}", singletonBean.getPrototypeBean2());
        System.out.println("------------------------------------");
        // 解决方法3:使用ObjectFactory<PrototypeBean3>工厂类,在每次调用getProtypeBean3()方法中返回factory.getObject()
        log.debug("{}", singletonBean.getPrototypeBean3().getClass());
        log.debug("{}", singletonBean.getPrototypeBean3());
        log.debug("{}", singletonBean.getPrototypeBean3());
        log.debug("{}", singletonBean.getPrototypeBean3());
        System.out.println("------------------------------------");
        // 解决方法4:在SingletonBean中注入一个ApplicationContext,使用context.getBean(PrototypeBean4.class)获取对应的多例
        log.debug("{}", singletonBean.getPrototypeBean4().getClass());
        log.debug("{}", singletonBean.getPrototypeBean4());
        log.debug("{}", singletonBean.getPrototypeBean4());
        log.debug("{}", singletonBean.getPrototypeBean4());
        System.out.println("------------------------------------");
    }
}

运行结果如下:

image-20220406141812580

第九讲 aop之ajc增强

aopspring框架中非常重要的功能,其主要实现通常情况下是动态代理,但是这个说法并不全面,还有另外两种实现:

  • ajc编译器
  • agent类加载

spring_09_aop_ajc

p32 031-第九讲-aop之ajc增强

先看aop的第一种实现ajc编译器代码增强,这是一种编译时的代码增强。

新建一个普通的maven项目

  • 添加依赖

    使用ajc编译器进行代码增强,首先需要在pom.xml文件中加入ajc编译器插件依赖

    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>aspectj-maven-plugin</artifactId>
                <version>1.11</version>
                <configuration>
                    <complianceLevel>1.8</complianceLevel>
                    <source>8</source>
                    <target>8</target>
                    <showWeaveInfo>true</showWeaveInfo>
                    <verbose>true</verbose>
                    <Xlint>ignore</Xlint>
                    <encoding>UTF-8</encoding>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <!-- use this goal to weave all your main classes -->
                            <goal>compile</goal>
                            <!-- use this goal to weave all your test classes -->
                            <goal>test-compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    

    加入aspectjweaver的依赖

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.7</version>
    </dependency>
    

    加入日志和单元测试的依赖

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.10</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
    </dependency>
    
  • 需要增强的类MyService

    public class MyService {
        private static final Logger log = LoggerFactory.getLogger(MyAspect.class);
        public void foo() {
            log.debug("foo()");
        }
    }
    
  • 切面类MyAspect,编写execution表达式,对MyService类的foo()方法进行增强

    @Aspect // ⬅️注意此切面并未被 Spring 管理,本项目pom文件中根本没有引入spring的相关类
    public class MyAspect {
        private static final Logger log = LoggerFactory.getLogger(MyAspect.class);
        @Before("execution(* top.jacktgq.service.MyService.foo())")
        public void before() {
            log.debug("before()");
        }
    }
    
  • 测试代码

    public class Aop_Aspectj_Test {
        @Test
        public void testAopAjc() {
            new MyService().foo();
        }
    }
    
  • 编译项目,这里需要使用maven来编译,打开idea中的maven面板,点击compile

    image-20220410183011468

    然后再运行测试代码,可以看到创建MyService对象并调用foo()方法会先执行切面类中的before()方法

    image-20220410183108780

    注:

    • 有些小伙伴可能会遇到问题:明明按照一样的步骤来操作,可是运行以后代码并没有增强。这是由于idea中在执行代码之前会默认编译一遍代码,这本来是正常的,可是,如果使用maven来编译代码,会在执行代码前将maven编译的代码覆盖,这就会导致mavenajc编译器增强的代码被覆盖,所以会看不到最终的运行效果。

    • 解决办法:在设置中将自动构建项目的选项勾上,就不会出现多次编译覆盖的问题了。

      image-20220410183955137

总结:

  • 可以看到没有引入任何跟spring框架相关的包,MyService类是通过直接new()的方式获得的,所以也就不存在使用了动态代理的说法了

  • 打开编译后的MyService.class文件,双击以后idea会反编译该字节码文件,可以看到foo()方法体的开头加了一行代码,这就是增强的代码,这是ajc编译器在编译MyService类的时候为我们添加的代码,这是一种编译时的增强。

    image-20220410184651224

第十讲 aop之agent增强

spring_10_aop_agent

p33 032-第十讲-aop之agent增强

现在来看aop的另外一种实现agent增强,这是一种类加载时的代码增强。

  • 新建一个普通的maven项目

    • 加入aspectjweaver的依赖

      <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjweaver</artifactId>
          <version>1.9.7</version>
      </dependency>
      

      加入日志和单元测试的依赖

      <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
          <version>1.7.36</version>
      </dependency>
      <dependency>
          <groupId>ch.qos.logback</groupId>
          <artifactId>logback-classic</artifactId>
          <version>1.2.10</version>
      </dependency>
      <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.13.2</version>
      </dependency>
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>1.18.22</version>
      </dependency>
      
    • 需要增强的类MyService

      @Slf4j
      public class MyService {
          public void foo() {
              log.debug("foo()");
              bar();
          }
          public void bar() {
              log.debug("bar()");
          }
      }
      
    • 切面类MyAspect,编写execution表达式,对MyService类的foo()方法进行增强

      @Aspect // ⬅️注意此切面并未被 Spring 管理,本项目pom文件中根本没有引入spring的相关类
      @Slf4j
      public class MyAspect {
          @Before("execution(* top.jacktgq.service.MyService.*())")
          public void before() {
              log.debug("before()");
          }
      }
      
    • 测试代码

      public class Aop_agent_Test {
          @Test
          public void testAopAgent() throws Exception {
              MyService myService = new MyService();
              myService.foo();
              System.in.read();
          }
      }
      

      运行时需要在 VM options 里加入 -javaagent:D:\同步空间\repository\org\aspectj\aspectjweaver\1.9.7\aspectjweaver-1.9.7.jar把其中 D:\同步空间\repository 改为你自己 maven 仓库起始地址

      image-20220410210057854

      注:还需要在resources/META-INF目录下建一个aop.xml配置文件,内容如下,aspectj会自动扫描到这个配置文件,不加这个配置文件不会出效果。

      image-20220410210354881

      <aspectj>
          <aspects>
              <aspect name="top.jacktgq.aop.MyAspect"/>
              <weaver options="-verbose -showWeaveInfo">
                  <include within="top.jacktgq.service.MyService"/>
                  <include within="top.jacktgq.aop.MyAspect"/>
              </weaver>
          </aspects>
      </aspectj>
      

      运行测试代码,可以看到创建MyService对象并调用foo()方法会先执行切面类中的before()方法

      image-20220410210458098

      注:

      • 有些小伙伴可能会遇到问题:明明按照一样的步骤来操作,可是运行以后代码并没有增强。这是由于idea中在执行代码之前会默认编译一遍代码,这本来是正常的,可是,如果使用maven来编译代码,会在执行代码前将maven编译的代码覆盖,这就会导致mavenajc编译器增强的代码被覆盖,所以会看不到最终的运行效果。

      • 解决办法:在设置中将自动构建项目的选项勾上,就不会出现多次编译覆盖的问题了。

        image-20220410183955137

    总结:

    • 可以看到没有引入任何跟spring框架相关的包,MyService类是通过直接new()的方式获得的,所以也就不存在使用了动态代理的说法了

    • 打开编译后的MyService.class文件,双击以后idea会反编译该字节码文件,可以看到foo()方法体中并没有添加多余的代码,所以就不是编译时增强了,而是类加载的时候增强的,这里可以借助阿里巴巴的Arthas工具,下载地址:https://arthas.aliyun.com/doc/en/download.html,解压以后进入到arthas的bin目录下,启动黑窗口,输入java -jar .\arthas-boot.jar,在输出的java进程列表里面找到我们要连接的进程,输入对应进程的序号,我这里是4,连接上以后会打印ARTHASlogo

      image-20220410235629087

      再输入jad top.jacktgq.service.MyService反编译内存中的MyService

      image-20220410235616607

      可以看到foo()bar()方法体的第一行都加了一行代码,这就说明通过添加虚拟机参数-javaagent的方式可以在类加载的时候对代码进行增强。

第十一讲 aop之proxy增强-jdk和cglib

spring_11_aop_proxy_jdk_cglib

p34 033-第十一讲-aop之proxy增强-jdk

测试代码

public class AopJdkProxyTest {
    @Test
    public void testJdkProxy() {
        // jdk的动态代理,只能针对接口代理
        // 目标对象
        Target target = new Target();
        // 用来加载在运行期间动态生成的字节码
        ClassLoader loader = AopJdkProxyTest.class.getClassLoader();
        Foo fooProxy = (Foo) Proxy.newProxyInstance(loader, new Class[]{Foo.class}, (proxy, method, args) -> {
            System.out.println("before...");
            Object result = method.invoke(target, args);
            System.out.println("after...");
            return result;  // 让代理也返回目标方法执行的结果
        });
        fooProxy.foo();
    }
}
interface Foo {
    void foo();
}
@Slf4j
final class Target implements Foo {
    public void foo() {
        log.debug("target foo");
    }
}

运行结果

image-20220411083443041

jdk动态代理总结:

  1. 代理对象和目标对象是兄弟关系,都实现了Foo接口,代理对象类型不能强转成目标对象类型;
  2. 目标类定义的时候可以加final修饰。

p35 034-第十一讲-aop之proxy增强-cglib

public class AopCglibProxyTest {
    @Test
    public void testCglibProxy1() {
        // 目标对象
        Target target = new Target();
        Foo fooProxy = (Foo) Enhancer.create(Target.class, (MethodInterceptor) (obj, method, args, proxy) -> {
            System.out.println("before...");
            Object result = method.invoke(target, args); // 用方法反射调用目标
            System.out.println("after...");
            return result;
        });
        System.out.println(fooProxy.getClass());
        fooProxy.foo();
    }
    @Test
    public void testCglibProxy2() {
        // 目标对象
        Target target = new Target();
        Foo fooProxy = (Foo) Enhancer.create(Target.class, (MethodInterceptor) (obj, method, args, proxy) -> {
            System.out.println("before...");
            // proxy 它可以避免反射调用
            Object result = proxy.invoke(target, args); // 需要传目标类
            System.out.println("after...");
            return result;
        });
        System.out.println(fooProxy.getClass());
        fooProxy.foo();
    }
    @Test
    public void testCglibProxy3() {
        // 目标对象
        Foo fooProxy = (Foo) Enhancer.create(Target.class, (MethodInterceptor) (obj, method, args, proxy) -> {
            System.out.println("before...");
            // proxy 它可以避免反射调用
            Object result = proxy.invokeSuper(obj, args); // 不需要目标类,需要代理自己
            System.out.println("after...");
            return result;
        });
        System.out.println(fooProxy.getClass());
        fooProxy.foo();
    }
}
interface Foo {
    void foo();
}
@Slf4j
class Target implements Foo {
    public void foo() {
        log.debug("target foo");
    }
}

运行结果

image-20220411085629883

cglib动态代理总结:

  1. MethodInterceptorintercept()方法的第2个参数是method,可以通过反射对目标方法进行调用

    Object result = method.invoke(target, args); // 用方法反射调用目标
    
  2. 第4个参数proxy,可以不用反射就能对目标方法进行调用;

    Object result = proxy.invoke(target, args); // 需要传目标类 (spring用的是这种)
    // 或者
    Object result = proxy.invokeSuper(obj, args); // 不需要目标类,需要代理自己
    
  3. 代理类不需要实现接口;

  4. 代理对象和目标对象是父子关系,代理类继承于目标类;

  5. 目标类定义的时候不能加final修饰,否则代理类就无法继承目标类了,会报java.lang.IllegalArgumentException: Cannot subclass final class top.jacktgq.proxy.cglib.Target异常;

  6. 目标类方法定义的时候不能加final修饰,否则代理类继承目标类以后就不能重写目标类的方法了。

第十二讲 jdk代理原理

spring_12_aop_proxy_jdk_cglib_principle

p36 035-第十二讲-jdk代理原理

p37 036-第十二讲-jdk代理原理

p38 037-第十二讲-jdk代理源码

为了更好地探究jdk动态代理原理,先用代码显式地模拟一下这个过程。

先定义一个Foo接口,里面有一个foo()方法,再定义一个Target类来实现这个接口,代码如下所示:

public interface Foo {
    void foo();
}
@Slf4j
public final class Target implements Foo {
    public void foo() {
        log.debug("target foo");
    }
}
public class $Proxy0 implements Foo {
    @Override
    public void foo() {
        // 1. 功能增强
        System.out.println("before...");
        // 2. 调用目标
        new Target().foo();
    }
}
public class Main {
    public static void main(String[] args) {
        Foo proxy = new $Proxy0();
        proxy.foo();
    }
}

接下来对Target类中的foo()方法进行增强

  1. 首先想到的是,再定义一个类也同样地实现一下Foo接口,然后在foo()方法中编写增强代码,接着再new一个Target对象,调用它的foo()方法,代码如下所示:

    public class $Proxy0 implements Foo {
        @Override
        public void foo() {
            // 1. 功能增强
            System.out.println("before...");
            // 2. 调用目标
            new Target().foo();
        }
    }
    // 测试运行
    public class Main {
        public static void main(String[] args) {
            Foo proxy = new $Proxy0();
            proxy.foo();
        }
    }
    
  2. 上面的代码把功能增强的代码和调用目标的代码都固定在了代理类的内部,不太灵活。因此可以通过定义一个InvocationHandler接口的方式来将这部分代码解耦出来,代码如下:

    public interface InvocationHandler {
        void invoke();
    }
    public interface Foo {
        void foo();
    }
    @Slf4j
    public final class Target implements Foo {
        public void foo() {
            System.out.println("target foo");
        }
    }
    public class $Proxy0 implements Foo {
        private InvocationHandler h;
        public $Proxy0(InvocationHandler h) {
            this.h = h;
        }
        @Override
        public void foo() {
            h.invoke();
        }
    }
    public class Main {
        public static void main(String[] args) {
            Foo proxy = new $Proxy0(new InvocationHandler() {
                @Override
                public void invoke() {
                    // 1. 功能增强
                    System.out.println("before...");
                    // 2. 调用目标
                    new Target().foo();
                }
            });
            proxy.foo();
        }
    }
    
  3. 第2个版本的代码虽然将功能增强的代码和调用目标的代码通过接口的方式独立出来了,但还是有问题,如果此时接口中新增了一个方法bar()Target类和$Proxy0类中都要实现bar()方法,那么调用proxyfoo()bar()方法都将间接调用目标对象的foo()方法,因为在InvocationHandlerinvoke()方法中调用的是target.foo()方法,代码如下:

    public interface InvocationHandler {
        void invoke();
    }
    public interface Foo {
        void foo();
        void bar();
    }
    @Slf4j
    public final class Target implements Foo {
        public void foo() {
            System.out.println("target foo");
        }
        @Override
        public void bar() {
            log.debug("target bar");
        }
    }
    public class $Proxy0 implements Foo {
        private InvocationHandler h;
        public $Proxy0(InvocationHandler h) {
            this.h = h;
        }
        @Override
        public void foo() {
            h.invoke();
        }
        @Override
        public void bar() {
            h.invoke();
        }
    }
    public class Main {
        public static void main(String[] args) {
            Foo proxy = new $Proxy0(new InvocationHandler() {
                @Override
                public void invoke() {
                    // 1. 功能增强
                    System.out.println("before...");
                    // 2. 调用目标
                    new Target().foo();
                }
            });
            proxy.foo();
            proxy.bar();
        }
    }
    

    改进方法是,代理类中调用方法的时候,通过反射把接口中对应的方法Method对象作为参数传给InvocationHandler,代码如下:

    public interface InvocationHandler {
        void invoke(Method method, Object[] args) throws Throwable;
    }
    public interface Foo {
        void foo();
        void bar();
    }
    public final class Target implements Foo {
        public void foo() {
            System.out.println("target foo");
        }
        @Override
        public void bar() {
            System.out.println("target bar");
        }
    }
    public class $Proxy0 implements Foo {
        private InvocationHandler h;
        public $Proxy0(InvocationHandler h) {
            this.h = h;
        }
        @Override
        public void foo() {
            try {
                Method foo = Foo.class.getDeclaredMethod("foo");
                h.invoke(foo, new Object[0]);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
        @Override
        public void bar() {
            try {
                Method bar = Foo.class.getDeclaredMethod("bar");
                h.invoke(bar, new Object[0]);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    }
    public class Main {
        public static void main(String[] args) {
            Foo proxy = new $Proxy0(new InvocationHandler() {
                @Override
                public void invoke(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
                    // 1. 功能增强
                    System.out.println("before...");
                    // 2. 调用目标
                    method.invoke(new Target(), args);
                }
            });
            proxy.foo();
            proxy.bar();
        }
    }
    
  4. 第3个版本的代码其实已经离jdk动态代理生成的代码很相近了,为了更好地学习底层,更近一步,修改Foo接口的中bar()方法,使其具有int类型的返回值,因此InvocationHandlerinvoke()方法也得有返回值,同时将代理对象本身作为第一个参数,具体代码如下:

    public interface InvocationHandler {
        Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
    }
    public interface Foo {
        void foo();
        int bar();
    }
    public final class Target implements Foo {
        public void foo() {
            System.out.println("target foo");
        }
        @Override
        public int bar() {
            System.out.println("target bar");
            return 1;
        }
    }
    public class $Proxy0 implements Foo {
        static Method foo;
        static Method bar;
        static {
            try {
                foo = Foo.class.getDeclaredMethod("foo");
                bar = Foo.class.getDeclaredMethod("bar");
            } catch (NoSuchMethodException e) {
                throw new NoSuchMethodError(e.getMessage());
            }
        }
        private InvocationHandler h;
        public $Proxy0(InvocationHandler h) {
            this.h = h;
        }
        @Override
        public void foo() {
            try {
                h.invoke(this, foo, new Object[0]);
            } catch (RuntimeException | Error e) {
                throw e;
            } catch (Throwable e) {
                throw new UndeclaredThrowableException(e);
            }
        }
        @Override
        public int bar() {
            try {
                return (int) h.invoke(this, bar, new Object[0]);
            } catch (RuntimeException | Error e) {
                throw e;
            } catch (Throwable e) {
                throw new UndeclaredThrowableException(e);
            }
        }
    }
    public class Main {
        public static void main(String[] args) {
            Foo proxy = new $Proxy0(new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
                    // 1. 功能增强
                    System.out.println("before...");
                    // 2. 调用目标
                    return method.invoke(new Target(), args);
                }
            });
            proxy.foo();
            System.out.println("bar()方法返回值:" + proxy.bar());
        }
    }
    
  5. 到这里跟jdk的动态代理只有些微差距了,jdk的动态代码会让代理类再继承一个Proxy类,里面定义了一个InvocationHandler接口的对象,代理类中会通过super(h)调用父类Proxy的构造,这里建议结合视频教程理解。

p39 038-第十二讲-jdk代理字节码生成

p40 039-第十二讲-jdk反射优化

p41 040-第十三讲-cglib代理原理

p42 041-第十三讲-cglib代理原理-MethodProxy

p43 042-第十四讲-MethodProxy原理

p44 043-第十四讲-MethodProxy原理

p45 044-第十五讲-Spring选择代理

p46 045-第十五讲-Spring选择代理

p47 046-第十五讲-Spring选择代理

p48 047-第十六讲-切点匹配

p49 048-第十六讲-切点匹配

p50 049-第十七讲-Advisor与@Aspect

p51 050-第十七讲-findEligibleAdvisors

p52 051-第十七讲-wrapIfNecessary

p53 052-第十七讲-代理创建时机

p54 053-第十七讲-吐槽@Order

p55 054-第十七讲-高级切面转低级切面

p56 055-第十八讲-统一转换为环绕通知

p57 056-第十八讲-统一转换为环绕通知

p58 057-第十八讲-适配器模式

p59 058-第十八讲-调用链执行

p60 059-第十八讲-模拟实现调用链

p61 060-第十八讲-模拟实现调用链-责任链模式

p62 061-第十九讲-动态通知调用

p63 062-第十九讲-动态通知调用

p64 063-第廿讲-DispatcherServlet初始化时机

p65 064-第廿讲-DispatcherServlet初始化时机

p66 065-第廿讲-DispatcherServlet初始化执行的操作

p67 066-第廿讲-RequestMappingHandlerMapping

p68 067-第廿讲-RequestMappingHandlerAdapter

p69 068-第廿讲-RequestMappingHandlerAdapter-参数和返回值解析器

p70 069-第廿讲-RequestMappingHandlerAdapter-自定义参数解析器

p71 070-第廿讲-RequestMappingHandlerAdapter-自定义返回值解析器

p72 071-第廿一讲-参数解析器-准备

p73 072-第廿一讲-参数解析器-准备

p74 073-第廿一讲-参数解析器-@RequestParam 0-4

p75 074-第廿一讲-参数解析器-组合模式

p76 075-第廿一讲-参数解析器 5-9

p77 076-第廿一讲-参数解析器 10-12

p78 077-第廿二讲-获取参数名

p79 078-第廿二讲-获取参数名

p80 079-第廿三讲-两套底层转换接口

p81 080-第廿三讲-一套高层转换接口

p82 081-第廿三讲-类型转换与数据绑定演示

p83 082-第廿三讲-web环境下数据绑定演示

p84 083-第廿三讲-绑定器工厂

p85 084-第廿三讲-绑定器工厂-@InitBinder扩展

p86 085-第廿三讲-绑定器工厂-ConversionService扩展

p87 086-第廿三讲-绑定器工厂-默认ConversionService

p88 087-第廿三讲-加餐-如何获取泛型参数

p89 088-第廿四讲-@ControllerAdvice-@InitBinder

p90 089-第廿四讲-@ControllerAdvice-@InitBinder

p91 090-第廿五讲-控制器方法执行流程

p92 091-第廿五讲-控制器方法执行流程

p93 092-第廿五讲-控制器方法执行流程-代码

p94 093-第廿六讲-@ControllerAdvice-@ModelAttribute

p95 094-第廿七讲-返回值处理器

p96 095-第廿七讲-返回值处理器-1

p97 096-第廿七讲-返回值处理器-2-4

p98 097-第廿七讲-返回值处理器-5-7

p99 098-第廿八讲-MessageConverter

p100 099-第廿八讲-MessageConverter

p101 100-第廿九讲-@ControllerAdvice-ResponseBodyAdvice

p102 101-第廿九讲-@ControllerAdvice-ResponseBodyAdvice

p103 102-第卅讲-异常处理

p104 103-第卅讲-异常处理

p105 104-第卅一讲-@ControllerAdvice-@ExceptionHandler

p106 105-第卅二讲-tomcat异常处理

p107 106-第卅二讲-tomcat异常处理-自定义错误地址

p108 107-第卅二讲-tomcat异常处理-BasicErrorController

p109 108-第卅二讲-tomcat异常处理-BasicErrorController

p110 109-第卅三讲-HandlerMapping与HandlerAdapter-1

p111 110-第卅三讲-HandlerMapping与HandlerAdapter-自定义

p112 111-第卅四讲-HandlerMapping与HandlerAdapter-2

p113 112-第卅五讲-HandlerMapping与HandlerAdapter-3

p114 113-第卅五讲-HandlerMapping与HandlerAdapter-3-优化

p115 114-第卅五讲-HandlerMapping与HandlerAdapter-3-优化

p116 115-第卅五讲-HandlerMapping与HandlerAdapter-4-欢迎页

p117 116-第卅五讲-HandlerMapping与HandlerAdapter-总结

p118 117-第卅六讲-MVC执行流程

p119 118-第卅六讲-MVC执行流程

p120 119-第卅七讲-构建boot骨架项目

p121 120-第卅八讲-构建boot war项目

p122 121-第卅八讲-构建boot war项目-用外置tomcat测试

p123 122-第卅八讲-构建boot war项目-用内嵌tomcat测试

p124 123-第卅九讲-boot执行流程-构造

p125 124-第卅九讲-boot执行流程-构造-1

p126 125-第卅九讲-boot执行流程-构造-2

p127 126-第卅九讲-boot执行流程-构造-3

p128 127-第卅九讲-boot执行流程-构造-4-5

p129 128-第卅九讲-boot执行流程-run-1

p130 129-第卅九讲-boot执行流程-run-1

p131 130-第卅九讲-boot执行流程-run-8-11

p132 131-第卅九讲-boot执行流程-run-2,12

p133 132-第卅九讲-boot执行流程-run-3

p134 133-第卅九讲-boot执行流程-run-4

p135 134-第卅九讲-boot执行流程-run-5

p136 135-第卅九讲-boot执行流程-run-5

p137 136-第卅九讲-boot执行流程-run-6

p138 137-第卅九讲-boot执行流程-run-7

p139 138-第卅九讲-boot执行流程-小结

p140 139-第卌讲-Tomcat重要组件

p141 140-第卌讲-内嵌Tomcat

p142 141-第卌讲-内嵌Tomcat与Spring整合

p143 142-第卌一讲-自动配置类原理

p144 143-第卌一讲-自动配置类原理

p145 144-第卌一讲-AopAutoConfiguration

p146 145-第卌一讲-AopAutoConfiguration

p147 146-第卌一讲-自动配置类2-4概述

p148 147-第卌一讲-自动配置类2-DataSource

p149 148-第卌一讲-自动配置类3-MyBatis

p150 149-第卌一讲-自动配置类3-mapper扫描

p151 150-第卌一讲-自动配置类4-事务

p152 151-第卌一讲-自动配置类5-MVC

p153 152-第卌一讲-自定义自动配置类

p154 153-第卌二讲-条件装配底层1

p155 154-第卌二讲-条件装配底层2

p156 155-第卌三讲-FactoryBean

p157 156-第卌四讲-@Indexed

p158 157-第卌五讲-Spring代理的特点

p159 158-第卌五讲-Spring代理的特点

p160 159-第卌六讲-@Value注入底层1

p161 160-第卌六讲-@Value注入底层2

p162 161-第卌七讲-@Autowired注入底层-doResolveDependency外1

p163 162-第卌七讲-@Autowired注入底层-doResolveDependency外2

p164 163-第卌七讲-@Autowired注入底层-doResolveDependency内1

p165 164-第卌七讲-@Autowired注入底层-doResolveDependency内2

p166 165-第卌七讲-@Autowired注入底层-doResolveDependency内3

p167 166-第卌七讲-@Autowired注入底层-doResolveDependency内4

p168 167-第卌八讲-事件监听器1

p169 168-第卌八讲-事件监听器2

p170 169-第卌八讲-事件监听器3

p171 170-第卌八讲-事件监听器4

p172 171-第卌八讲-事件监听器5

p173 172-第卌九讲-事件发布器1

p174 173-第卌九讲-事件发布器2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值