深度学习Spring5底层原理(黑马学习随笔)

学习随笔简介

跟随着黑马满老师的《黑马程序员Spring视频教程,全面深度讲解spring5底层原理》学习,视频教程地址:黑马程序员Spring视频教程,全面深度讲解spring5底层原理_哔哩哔哩_bilibili

学到哪笔记写到哪 ,笔记如有错误,请大佬指教!(已完结,共252504字)

目录

学习随笔简介

一、BeanFactory与ApplicationContext的区别与联系

1.到底什么是BeanFactory

2.BeanFactory能干点啥

3.ApplicationContext比BeanFactory多点啥 

二、容器实现

1.BeanFactory实现的特点

2.ApplicationContext的常见实现和用法

三、Bean的生命周期 

1.Spring bean 生命周期各个阶段

2.模板设计模式=>对于不能确定的信息,可以把它抽象成接口,通过回调的方式对它的功能进行衔接和扩展

四、常见后处理器以及@Autowired注解 后处理器执行分析

 1.常见的后处理器

2.@Autowired注解 后处理器执行分析

五、常见BeanFactory后处理器以及后处理器模拟实现 

 1.常见的BeanFactory后处理器

 2.后处理器模拟实现 

六、Aware 接口和InitializingBean 接口以及@Autowired 失效分析

1.Aware接口和InitializingBean接口

2.配置类 @Autowired 失效分析

 七、Bean的初始化与销毁

八、Scope的类型、注意事项、销毁、失效分析及解决办法

1.Scope的类型

 2.Scope的销毁 

3.Scope的失效分析以及四种解决办法

九、Aop之ajc增强

十、Aop之agent增强

十一、Aop之proxy增强

1.aop之proxy增强-jdk

2.aop之proxy增强-cglib

3.jdk和cglib的区别 

十二、jdk代理原理

1.jdk代理原理(具体实现)

2.jdk代理字节码生成

3.jdk反射优化

十三、cglib代理原理

1.cglib代理原理演示 

2.cglib代理原理--MethodProxy

十四、MethodProxy原理 

1.cglib避免反射调用(如何避免)

2.与jdk对比

十五、Spring选择代理

1.jdk和cglib在Spring中的统一

  2.Spring选择代理(演示)

十六、切点匹配

十七、从 @Aspect 到 Advisor

1.创建代理器

2.代理创建时机

3.高级切面转低级切面

十八、静态通知调用

1.不同通知统一转换为环绕通知(适配器模式)

2.无参数绑定通知链执行过程(责任链模式)

3.模拟实现MethodInvocation

4.代理对象调用流程(以 JDK 动态代理实现为例)

十九、动态通知调用

廿、RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter

1.DispatcherServlet 初始化

2.RequestMappingHandlerMapping

3.RequestMappingHandlerAdapter

4.自定义参数解析器

5.自定义返回值处理器

廿一、参数解析器

1.常见解析器的作用

2.组合模式在Spring中的体现

廿二、获取参数名

廿三、对象绑定与类型转换

1.底层接口与实现 

2.演示类型转换和对象绑定 

3.演示web环境下数据绑定

4.绑定器工厂

5.获取泛型参数 

廿四、@ControllerAdvice 之 @InitBinder

廿五、控制器方法执行流程 

1.图例演示执行流程

 2.代码演示执行流程

廿六、@ControllerAdvice 之 @ModelAttribute

廿七、返回值处理器

廿八、MessageConverter

廿九、@ControllerAdvice 之 ResponseBodyAdvice

卅、异常处理

卅一、@ControllerAdvice 之 @ExceptionHandler

卅二、Tomcat异常处理 

1.Tomcat异常处理

 2.自定义错误地址

3.BasicErrorController

卅三、BeanNameUrlHandlerMapping 与 SimpleControllerHandlerAdapter

1.BeanNameUrlHandlerMapping 与 SimpleControllerHandlerAdapter

 2.自定义HandlerMapping和HandlerAdapter

卅四、RouterFunctionMapping 与 HandlerFunctionAdapter

卅五、SimpleUrlHandlerMapping 与 HttpRequestHandlerAdapter

1.SimpleUrlHandlerMapping 与 HttpRequestHandlerAdapter

 2.静态资源解析优化

 3.欢迎页处理

 4.映射器与适配器小结

卅六、MVC处理流程

卅七、Boot骨架项目

卅八、 Boot War项目

 1.构建Boot War项目

2.用外置Tomcat测试

3.用内置Tomcat测试

卅九、boot执行流程 

1.SpringApplication 构造  

2.执行 run 方法

卌、Tomcat 内嵌容器

1.Tomcat基本结构 

2.Tomcat内嵌容器 

3.内嵌Tomcat与Spring整合

卌一、自动配置类

1.自动配置类原理 

2.AopAutoConfiguration

3.DataSourceAutoConfiguration

4.MybatisAutoConfiguration

5.TransactionAutoConfiguration

6.自动配置类——MVC

7.自定义自动配置类

 卌二、条件装配底层

 卌三、FactoryBean 

 卌四、@Indexed 

 卌五、Spring代理的特点

1.探究 spring 代理的设计特点

2.探究static方法、final方法、private方法可否得到增强


一、BeanFactory与ApplicationContext的区别与联系

1.到底什么是BeanFactory

  • 它是ApplicationContext的父接口 -- 打开类图的快捷键(Ctrl+Alt+U)

  • 它是Spring的核心容器,主要的ApplicationContext实现都【组合】了它的功能
  • BeanFactory实际上是ApplicationContext的成员变量

2.BeanFactory能干点啥

表面上只有getBean,实际上控制反转、基本的依赖注入、直至Bean的生命周期的各种功能,都由它的实现类提供

查看类方法(Ctrl+F12)

 查看ConfigurableApplicationContext类中BeanFactory的实际类型:

ConfigurableListableBeanFactory beanFactory1 = run.getBeanFactory();
System.out.println(beanFactory1.getClass());

打印结果为:

       class org.springframework.beans.factory.support.DefaultListableBeanFactory

查看DefaultListableBeanFactory的类图:

3.ApplicationContext比BeanFactory多点啥 

 

多啥?多实现了四个接口,分别是:

  •  MessageSource: 支持信息的国际化和包含参数的信息的替换
  • ResourcePatternResolver: 用于解析资源文件的策略接口
  • EnvironmentCapable: 环境信息,系统环境变量,*.properties、*.application.yml等配置文件中的值
  • ApplicationEventPublisher: 发布事件对象(用于代码解耦)

 ApplicationEventPublisher详解:

         1.定义一个注册事件类,继承ApplicationEvent

         2.定义一个监听类,用于监听事件,监听类上加@Component注解,将该类交给Spring管理,然后定义一个处理事件的方法,参数类型为注册事件类的对象(类型需要保持一致),方法头上需要加上@EventListener注解

二、容器实现

1.BeanFactory实现的特点

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        //bean的定义(class,scope,初始化,销毁)
        AbstractBeanDefinition beanDefinition =
                BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();
        beanFactory.registerBeanDefinition("config", beanDefinition);

        //给BeanFactory添加一些常用的后处理器,将后处理器加到BeanFactory中
        AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

        //获取内置后处理器
        //beanFactory后处理器主要功能,补充了一些bean定义
        beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().stream().forEach(beanFactoryPostProcessor -> {
            beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
        });
        //Bean 后处理器 ,针对bean的生命周期的各个阶段提供扩展,例如 @Autowired @Resource   这里是建立BeanFactory和后处理器的联系
        beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);

        for (String name : beanFactory.getBeanDefinitionNames()) {
            System.out.println(name);
        }

        //提前准备好所有单例对象
        beanFactory.preInstantiateSingletons();
        System.out.println(beanFactory.getBean(Bean1.class).getBean2());
    }

学到了什么:

  • beanFactory 不会做的事
  1. 不会主动调用BeanFactory后处理器
  2. 不会主动添加Bean后处理器
  3. 不会主动初始化单例
  4. 不会解析BeanFactory 还不会解析${}与#{}
  • bean后处理器会有排序的逻辑

2.ApplicationContext的常见实现和用法

 四个ApplicationContext接口的实现类:

  • ClassPathXmlApplicationContext
  • FileSystemXmlApplicationContext
  • AnnotationConfigApplicationContext
  • AnnotationConfigServletWebServerApplicationContext

四个接口的测试代码如下:

//较为经典的容器,基于classpath下xml格式的配置文件来创建
    private static void testClassPathXmlApplicationContext(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("b01.xml");
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
        System.out.println(context.getBean(Bean2.class).getBean1());
    }

    //基于磁盘路径下xml格式的配置文件来创建
    private static void testFileSystemXmlApplicationContext(){
        FileSystemXmlApplicationContext context
                = new FileSystemXmlApplicationContext("src\\main\\resources\\b01.xml");

        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }

        System.out.println(context.getBean(Bean2.class).getBean1());
    }

    //较为经典的容器,基于java配置类来实现
    private static void testAnnotationConfigApplicationContext(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }

        System.out.println(context.getBean(Bean2.class).getBean1());

    }

    //较为经典的容器,基于 java 配置类来创建,用于web环境
    private static void testAnnotationConfigServletWebServerApplicationContext(){
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);

    }

    @Configuration
    static class WebConfig{
        @Bean
        public ServletWebServerFactory servletWebServerFactory(){
            return new TomcatServletWebServerFactory();
        }
        @Bean
        public DispatcherServlet dispatcherServlet(){
            return new DispatcherServlet();
        }
        @Bean
        public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet){
            return new DispatcherServletRegistrationBean(dispatcherServlet,"/");
        }
        @Bean("/hello")
        public Controller controller1(){
            return (request,response) -> {
                response.getWriter().print("hello");
                return null;
            };
        }
    }

    @Configuration
    static class Config{
        @Bean
        public Bean1 bean1(){
            return new Bean1();
        }
        @Bean
        public Bean2 bean2(Bean1 bean1){
            Bean2 bean2 = new Bean2();
            bean2.setBean1(bean1);
            return bean2;
        }
    }

    static class Bean1{}

    static class Bean2{
        private Bean1 bean1;

        public void setBean1(Bean1 bean1) {
            this.bean1 = bean1;
        }

        public Bean1 getBean1() {
            return bean1;
        }
    }

ClassPathXmlApplicationContext的内部实现:

//内部实现
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        System.out.println("读取之前...");
        for (String name : beanFactory.getBeanDefinitionNames()) {
            System.out.println(name);
        }
        System.out.println("读取之后...");
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions(new ClassPathResource("b01.xml"));
        for (String name : beanFactory.getBeanDefinitionNames()) {
            System.out.println(name);
        }

(FileSystemXmlApplicationContext和ClassPathXmlApplicationContext的内部实现基本类似,导入xml文件方式不同)

学到了什么:

  • 常见的ApplicationContext容器实现
  • 内嵌容器、DispatcherServlet的创建方法、作用

三、Bean的生命周期 

1.Spring bean 生命周期各个阶段

项目启动类代码:

@SpringBootApplication
public class A03Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(A03Application.class, args);
        context.close();
    }
}

 定义一个LifeCycleBean类:

@Component
@Slf4j
public class LifeCycleBean {

    public LifeCycleBean(){
        log.info("构造");
    }

    @Autowired
    public void autowire(@Value("${CATALINA_HOME}") String home){
        log.info("依赖注入:{}",home);
    }

    @PostConstruct
    public void init(){
        log.info("初始化");
    }

    @PreDestroy
    public void destroy(){
        log.info("销毁");
    }
}

编写自定义Bean的后处理器,对lifeCycleBean的生命周期过程进项扩展:

@Component
@Slf4j
public class MyBeanPostProcessor implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor {
    @Override
    public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean")){
            log.info("<<<<<< 销毁之前执行, 如@PreDestory");
        }
    }

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean")){
            log.info("<<<<<< 实例化之前执行,这里返回的对象会替换原本的Bean");
        }
        //返回null保持原有对象不变
        return null;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean")){
            log.info("<<<<<< 实例化之后执行,这里如果返回false会跳过依赖注入阶段");
        }
        return true;
    }

    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean")){
            log.info("<<<<<< 依赖注入阶段,如@Autowired、@Value、@Resource");
        }
        return pvs;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean")){
            log.info("<<<<<< 初始化之前执行,这里返回的对象会替换掉原本的bean,如@PostConstruct、@ConfigurationProperties");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean")){
            log.info("<<<<<< 初始化之后执行,这里返回的对象会替换掉原本的bean,如代理增强");
        }
        return bean;
    }
}

 代码执行结果:

这样 Bean的生命周期就很清晰明了!

2.模板设计模式=>对于不能确定的信息,可以把它抽象成接口,通过回调的方式对它的功能进行衔接和扩展

public class TestMethodTemplate {
    public static void main(String[] args) {
        MyBeanFactory beanFactory = new MyBeanFactory();
        beanFactory.addBeanPostProcessors(bean -> System.out.println("解析 @Autowired"));
        beanFactory.addBeanPostProcessors(bean -> System.out.println("解析 @Resource"));
        beanFactory.getBean();
    }

    //模板方法 Template Method Pattern
    static class MyBeanFactory{
        public Object getBean(){
            Object bean = new Object();
            System.out.println("构造" +bean);
            System.out.println("依赖注入"+bean);
            for (BeanPostProcessor processor : processors) {
                processor.inject(bean);
            }
            System.out.println("初始化"+bean);
            return bean;
        }
        private List<BeanPostProcessor> processors = new ArrayList<>();

        public void addBeanPostProcessors(BeanPostProcessor processor){
            processors.add(processor);
        }
    }

    static interface BeanPostProcessor{
        public void inject(Object bean);//对依赖注入阶段的扩展
    }
}

四、常见后处理器以及@Autowired注解 后处理器执行分析

Bean后处理器的作用:为Bean生命周期各个阶段提供扩展

 1.常见的后处理器

 自定义4个bean:

@Slf4j
public class Bean1 {

    private Bean2 bean2;

    @Autowired
    public void setBean2(Bean2 bean2){
        log.debug("@Autowired生效:{}",bean2);
        this.bean2 = bean2;
    }

    @Autowired
    private Bean3 bean3;

    @Resource
    public void setBean3(Bean3 bean3){
        log.debug("@Resource 生效:{}",bean3);
        this.bean3 = bean3;
    }

    private String home;

    @Autowired
    public void setHome(@Value("${CATALINA_HOME}") String home){
        log.debug("@Value生效:{}",home);
        this.home = home;
    }

    @PostConstruct
    public void init(){
        log.debug("@PostConstruct生效");
    }

    @PreDestroy
    public void Destroy(){
        log.debug("@PreDestroy生效 ");
    }

    @Override
    public String toString() {
        return "Bean1{" +
                "bean2=" + bean2 +
                ", bean3=" + bean3 +
                ", home='" + home + '\'' +
                '}';
    }
}


public class Bean2 {
}

public class Bean3 {
}

@ConfigurationProperties(prefix = "catalina")
public class Bean4 {

    private String home;
    private String base;

    public String getHome() {
        return home;
    }

    public void setHome(String home) {
        this.home = home;
    }

    public String getBase() {
        return base;
    }

    public void setBase(String base) {
        this.base = base;
    }

    @Override
    public String toString() {
        return "Bean4{" +
                "home='" + home + '\'' +
                ", base='" + base + '\'' +
                '}';
    }
}

定义好四个bean,接下来用GenericApplicationContext容器来分别探究@Autowired、@Value、@Resource、@PostConstruct、@PreDestroy和@ConfigurationProperties这六个注解具体由哪个后处理器解析 【注:】这里用GenericApplicationContext容器 来探究是因为其本身是一个【干净】的容器,默认不会添加任何后处理器,排除其他后处理器对测试结果的干扰

测试代码如下:

@SpringBootApplication
public class A04Application {
    public static void main(String[] args) {
        //GenericApplicationContext是一个【干净的容器】,默认不会添加任何后处理器
        GenericApplicationContext context = new GenericApplicationContext();

        //用原始的方法注册三个bean
        context.registerBean("bean1", Bean1.class);
        context.registerBean("bean2", Bean2.class);
        context.registerBean("bean3", Bean3.class);
        context.registerBean("bean4", Bean4.class);

        //添加解析@Value的解析器
        context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
        //添加解析Autowired @Value 后处理器
        context.registerBean(AutowiredAnnotationBeanPostProcessor.class );
        //添加解析@Resource @PostConstruct @PreDestroy 的后处理器
        context.registerBean(CommonAnnotationBeanPostProcessor.class);
        //添加解析@ConfigurationProperties的后处理器
        ConfigurationPropertiesBindingPostProcessor.register(context.getDefaultListableBeanFactory());

        //初始化容器
        context.refresh();//执行BeanFactory后处理器,添加bean后处理器,初始化所有单例

        System.out.println(context.getBean(Bean4.class));
        //销毁容器
        context.close();
    }
}

测试结果如下:

Bean4{home='D:\Tomcat\apache-tomcat-9.0.37', base='D:\Tomcat\apache-tomcat-9.0.37'}

 经过测试,我们可以得出结论:

  1. 解析@Autowired注解的后处理器是 AutowiredAnnotationBeanPostProcessor
  2. 解析@Value注解的后处理器也是AutowiredAnnotationBeanPostProcessor同时还需要ContextAnnotationAutowireCandidateResolver处理器,否则无法读取@Value中的内容,继而抛出异常
  3. 解析@Resource、@PostConstruct、@PreDestroy注解的后处理器是CommonAnnotationBeanPostProcessor
  4. 解析@ConfigurationProperties 注解的处理器是ConfigurationPropertiesBindingPostProcessor

2.@Autowired注解 后处理器执行分析

 测试代码如下:

//AutowiredAnnotationBeanPostProcessor 运行分析
public class DigInAutowired {
    public static void main(String[] args) throws Throwable {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        beanFactory.registerSingleton("bean2", new Bean2());//不会走创建过程、依赖注入、初始化
        beanFactory.registerSingleton("bean3", new Bean3());
        beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
        //设置内嵌值得解析器,即${}
        beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);

        //1.查找哪些属性、方法加了@Autowired,这称之为InjectionMetadata
        AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
        processor.setBeanFactory(beanFactory);

        Bean1 bean1 = new Bean1();
        //System.out.println(bean1);
        //processor.postProcessProperties(null, bean1, "bean1");
        //System.out.println(bean1);

        Method findAutowiringMetadata = AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class);
        //设置私有属性可以被访问
        findAutowiringMetadata.setAccessible(true);
        //获取bean1上加了@Value @Autowired注解的成员变量和方法参数信息
        InjectionMetadata metadata = (InjectionMetadata) findAutowiringMetadata.invoke(processor, "bean1", Bean1.class, null);
        System.out.println(metadata);

        //2.调用InjectionMetadata来进行依赖注入,注入时按类型查找值
        metadata.inject(bean1, "bean1", null);
        System.out.println(bean1);
        //3.如何按类型查找值
        Field bean3 = Bean1.class.getDeclaredField("bean3");
        DependencyDescriptor dd1 = new DependencyDescriptor(bean3, false);
        Object o = beanFactory.doResolveDependency(dd1, null, null, null);
        System.out.println(o);

        Method setBean2 = Bean1.class.getDeclaredMethod("setBean2", Bean2.class);
        DependencyDescriptor dd2 = new DependencyDescriptor(new MethodParameter(setBean2,0),false);
        Object o1 = beanFactory.doResolveDependency(dd2, null, null, null);
        System.out.println(o1);

        Method setHome = Bean1.class.getDeclaredMethod("setHome", String.class);
        DependencyDescriptor dd3 = new DependencyDescriptor(new MethodParameter(setHome,0), false);
        Object o2 = beanFactory.doResolveDependency(dd3, null, null, null);
        System.out.println(o2);
    }
}

测试结果如下:

19:27:43.907 [main] DEBUG com.boot.a04.Bean1 - @Value生效:D:\Tomcat\apache-tomcat-9.0.37
19:27:43.909 [main] DEBUG com.boot.a04.Bean1 - @Autowired生效:com.atguigu.boot.a04.Bean2@20322d26
Bean1{bean2=com.atguigu.boot.a04.Bean2@20322d26, bean3=com.atguigu.boot.a04.Bean3@192b07fd, home='D:\Tomcat\apache-tomcat-9.0.37'}
com.boot.a04.Bean3@192b07fd
com.boot.a04.Bean2@20322d26
19:27:43.909 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key 'CATALINA_HOME' in PropertySource 'systemEnvironment' with value of type String
D:\Tomcat\apache-tomcat-9.0.37

总结:

  • AutowiredAnnotationBeanPostProcessor 通过调用postProcessProperties(PropertyValues pvs, Object bean, String beanName) 方法来完成对@Autowired注解的解析和注入
  • postProcessProperties(PropertyValues pvs, Object bean, String beanName)  中又调用私有方法findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs)
  • 调用InjectionMetadata来进行依赖注入,注入时按类型查找值
  • 注入时按照类型分为三种:成员变量注入、方法参数注入、方法参数注入(加@Value注解) 三种方法实现代码如上,简述其过程:InjectionMetadata 会把加@Autow注解的属性(方法)BeanName(MethodName)拿到,再通过反射拿到属性(方法),然后将其封装成一个DependencyDescriptor对象,然后调用beanFactory.doResolveDependency方法     

五、常见BeanFactory后处理器以及后处理器模拟实现 

BeanFactory后处理器的作用为BeanFactory提供扩展

 1.常见的BeanFactory后处理器

探究@Component@ComponentScan、@Bean、@MapperScan这四个注解分别是由哪个后处理器解析的定义五个类,具体实现如下:

public class Bean1 {
}


@Component
@Slf4j
public class Bean2 {

    public Bean2(){
        log.debug("我被Spring管理了...");
    }
}


@Mapper
public interface Mapper1 {
}

@Mapper
public interface Mapper2 {
}

@Configuration
@ComponentScan("com.atguigu.boot.a05.component")
public class Config {

    @Bean
    public Bean1 bean1(){
        return new Bean1();
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }

    @Bean(initMethod = "init")
    public DruidDataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/db1");
        dataSource.setUsername("root");
        dataSource.setPassword("password");
        return dataSource;
    }
}

 测试代码如下:

public class A05Application {
    public static void main(String[] args) throws IOException {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("config", Config.class);
        //@ComponentScan @Bean @Import @ImportResource
        context.registerBean(ConfigurationClassPostProcessor.class);
        //@MapperScan
        context.registerBean(MapperScannerConfigurer.class,bd -> {
            bd.getPropertyValues().add("basePackage", "com.atguigu.boot.a05.mapper");
        });

      

        //初始化容器
        context.refresh();

        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
        //销毁容器
        context.close();
    }
}

测试结果如下:

  • 解析@Component、@ComponentScan注解的BeanFactory后处理器是ConfigurationClassPostProcessor
  • 解析@MapperScan注解的BeanFactory后处理器是MapperScannerConfigurer

 2.后处理器模拟实现 

1.内部实现,自定义ComponentScanPostProcessor类来解析@ComponentScan注解,代码如下:

public class ComponentScanPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        try {
            ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
            if (componentScan != null) {
                for (String basePackage : componentScan.basePackages()) {
                    System.out.println(basePackage);
                    //com.atguigu.boot.a05.component --> classpath*:com/atguigu/boot/a05/component/**/*.class
                    String path = "classpath*:" + basePackage.replace(".", "/") + "/**/*.class";
                    System.out.println(path);
                    CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
                    Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path);
                    AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
                    for (Resource resource : resources) {
                        //System.out.println(resource);
                        MetadataReader metadataReader = factory.getMetadataReader(resource);
                        System.out.println("类名:" + metadataReader.getClassMetadata().getClassName());
                        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
                        System.out.println("是否加@Component:" + annotationMetadata.hasAnnotation(Component.class.getName()));
                        System.out.println("是否加@Component 派生注解:" + annotationMetadata.hasMetaAnnotation(Component.class.getName()));

                        //抽取方法快捷键 Ctrl+Alt+V  如果直接加@Component或者间接加@Component的派生注解,就创建BeanDefinitionBuilder
                        if (annotationMetadata.hasAnnotation(Component.class.getName()) ||
                                annotationMetadata.hasMetaAnnotation(Component.class.getName())) {

                            AbstractBeanDefinition bd = BeanDefinitionBuilder
                                    .genericBeanDefinition(metadataReader.getClassMetadata().getClassName())
                                    .getBeanDefinition();

                            //查看接口的实现:Ctrl+Alt+B
                            //生成Bean名字
                            //if (configurableListableBeanFactory instanceof DefaultListableBeanFactory beanFactory) {
                                DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
                                String name = generator.generateBeanName(bd, beanFactory);
                                //加入BeanFactory
                                beanFactory.registerBeanDefinition(name, bd);
                           // }
                        }

                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 测试代码如下;

public class A05Application {
    public static void main(String[] args) throws IOException {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("config", Config.class);
       
        context.registerBean(ComponentScanPostProcessor.class);
        //初始化容器
        context.refresh();

        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
        //销毁容器
        context.close();
    }
}

测试结果如下:

com.atguigu.boot.a05.component
classpath*:com/atguigu/boot/a05/component/**/*.class
类名:com.atguigu.boot.a05.component.Bean2
是否加@Component:true
是否加@Component 派生注解:false
类名:com.atguigu.boot.a05.component.Bean3
是否加@Component:false
是否加@Component 派生注解:true
类名:com.atguigu.boot.a05.component.Bean4
是否加@Component:false
是否加@Component 派生注解:false
21:46:59.470 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'config'
21:46:59.470 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean2'
21:46:59.471 [main] DEBUG com.atguigu.boot.a05.component.Bean2 - 我被Spring管理了...
21:46:59.471 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean3'
21:46:59.471 [main] DEBUG com.atguigu.boot.a05.component.Bean3 - 我被Spring管理了...
config
com.atguigu.boot.a05.ComponentScanPostProcessor
bean2
bean3
21:46:59.510 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@6ed3ef1, started on Sat May 21 21:46:59 CST 2022

进程已结束,退出代码为 0

2.内部实现,自定义AtBeanPostProcessor类来解析@Bean注解,代码如下:

public class AtBeanPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        try {
            //读取Config元数据信息
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
            //不需要将类加载到JVM内存,效率比反射高
            MetadataReader reader = factory.getMetadataReader(new ClassPathResource("com/atguigu/boot/a05/Config.class"));

            //获得被@Bean标注的方法信息
            Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
            for (MethodMetadata method : methods) {
                System.out.println(method);
                String initMethod = method.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString();

                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
                builder.setFactoryMethodOnBean(method.getMethodName(), "config");
                //设置自动装配模式
                builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
                if (initMethod.length() > 0){
                    builder.setInitMethodName(initMethod);
                }
                AbstractBeanDefinition bd = builder.getBeanDefinition();
                DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
                beanFactory.registerBeanDefinition(method.getMethodName(), bd);

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

测试代码如下:

public class A05Application {
    public static void main(String[] args) throws IOException {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("config", Config.class);
       
        context.registerBean(AtBeanPostProcessor.class);
     
        //初始化容器
        context.refresh();

        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
        //销毁容器
        context.close();
    }
}

 测试结果如下;

config
com.atguigu.boot.a05.AtBeanPostProcessor
bean1
sqlSessionFactoryBean
dataSource
21:50:40.606 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@6ed3ef1, started on Sat May 21 21:50:40 CST 2022
21:50:40.608 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} closed

3.内部实现,自定义MapperPostProcessor类来解析@Mapper注解,代码如下:

public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
        try {
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            Resource[] resources = resolver.getResources("classpath:com/atguigu/boot/a05/mapper/**/*.class");
            AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
            for (Resource resource : resources) {
                MetadataReader reader = factory.getMetadataReader(resource);
                ClassMetadata classMetadata = reader.getClassMetadata();
                if (classMetadata.isInterface()){
                    AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class)
                            .addConstructorArgValue(classMetadata.getClassName())
                            .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
                            .getBeanDefinition();
                    AbstractBeanDefinition bd2 = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName()).getBeanDefinition();
                    String name = generator.generateBeanName(bd2, beanFactory);
                    beanFactory.registerBeanDefinition(name, bd);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }
}

测试代码如下:

public class A05Application {
    public static void main(String[] args) throws IOException {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("config", Config.class);
        context.registerBean(AtBeanPostProcessor.class);
        context.registerBean(MapperPostProcessor.class);

        //初始化容器
        context.refresh();

        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
        //销毁容器
        context.close();
    }
}

测试结果如下:

config
com.atguigu.boot.a05.AtBeanPostProcessor
com.atguigu.boot.a05.MapperPostProcessor
mapper1
mapper2
bean1
sqlSessionFactoryBean
dataSource
21:57:49.453 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@6ed3ef1, started on Sat May 21 21:57:48 CST 2022
21:57:49.453 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} closed

六、Aware 接口和InitializingBean 接口以及@Autowired 失效分析

1.Aware接口和InitializingBean接口

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

  • BeanNameAware 注入bean的名字
  • BeanFactoryAware 注入BeanFactory容器
  • ApplicationContextAware 注入ApplicationContext容器
  • EmbeddedValueResolverAware ${}

定义一个MyBean类,来演示:

@Slf4j
public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean {

    @Override
    public void setBeanName(String name) {
        log.debug("名字加"+name);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.debug("容器是"+applicationContext);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.debug("初始化...");
    }

    @Autowired
    public void aaa(ApplicationContext applicationContext){
        log.debug("使用@Autowired注入,容器是"+applicationContext);
    }

    @PostConstruct
    public void init(){
        log.debug("使用@PostConstruct初始化");
    }
}

测试代码如下:

/**
 * Aware接口及InitializingBean接口
 */
@Slf4j
public class A06Application {
    public static void main(String[] args) {
       
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("myBean", MyBean.class);
        context.refresh();//1.beanFactory后处理器 2.添加Bean后处理器 3.初始化单例
        context.close();
    }
}

测试结果如下:

09:20:01.960 [main] DEBUG com.atguigu.boot.a06.MyBean - 名字加myBean
09:20:01.963 [main] DEBUG com.atguigu.boot.a06.MyBean - 容器是org.springframework.context.support.GenericApplicationContext@6ed3ef1, started on Mon May 23 09:20:01 CST 2022
09:20:01.963 [main] DEBUG com.atguigu.boot.a06.MyBean - 初始化...

由此可以看出:

MyBean类中加了@Autowired和@PostConstruct注解的方法没有生效,然而MyBean中其他三个方法都执行了,说明测试代码中没有解析@Autowired和@PostConstruct的后处理器,在测试代码中加入解析它们的后处理器

更新测试代码:

@Slf4j
public class A06Application {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("myBean", MyBean.class);
        
        context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
        context.registerBean(CommonAnnotationBeanPostProcessor.class);
        context.refresh();//1.beanFactory后处理器 2.添加Bean后处理器 3.初始化单例
        context.close();
 }
}

再看测试结果:

09:56:46.050 [main] DEBUG com.atguigu.boot.a06.MyBean - 使用@Autowired注入,容器是org.springframework.context.support.GenericApplicationContext@6ed3ef1, started on Mon May 23 09:56:45 CST 2022
09:56:46.050 [main] DEBUG com.atguigu.boot.a06.MyBean - 名字加myBean
09:56:46.050 [main] DEBUG com.atguigu.boot.a06.MyBean - 容器是org.springframework.context.support.GenericApplicationContext@6ed3ef1, started on Mon May 23 09:56:45 CST 2022
09:56:46.050 [main] DEBUG com.atguigu.boot.a06.MyBean - 使用@PostConstruct初始化
09:56:46.050 [main] DEBUG com.atguigu.boot.a06.MyBean - 初始化...

 @Autowired能实现 b,c,d的功能,为啥要用Aware接口 简单的说:

        a.@Autowired的解析需要用到bean后处理器,属于扩展功能

        b.Aware接口属于内置功能,不加任何扩展,Spring就能识别

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

例1:用Aware注入ApplicationContext成功,而@Autowired注入ApplicationContext失败

定义一个MyConfig1的配置类:

@Configuration
@Slf4j
public class MyConfig1 {

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext){
        log.debug("注入 ApplicationContext");
    }

    @PostConstruct
    public void init(){
        log.debug("初始化");
    }

    @Bean //添加beanFactory后处理器
    public BeanFactoryPostProcessor processor1(){
        return beanFactory -> {
            log.debug("执行processor1");
        };
    }
}

 测试代码如下:

@Slf4j
public class A06Application {
    public static void main(String[] args) {
     
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("myConfig1", MyConfig1.class);
        context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
        context.registerBean(CommonAnnotationBeanPostProcessor.class);
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.refresh();//1.beanFactory后处理器 2.添加Bean后处理器 3.初始化单例
        context.close();
 }
}
测试结果如下:
10:09:17.150 [main] DEBUG com.atguigu.boot.a06.MyConfig1 - 执行processor1

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

定义一个MyConfig2的配置类:

package com.atguigu.boot.a06;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@Slf4j
public class MyConfig2 implements InitializingBean, ApplicationContextAware {

    @Override
    public void afterPropertiesSet() throws Exception {
        log.debug("初始化");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.debug("注入ApplicationContext");
    }

    @Bean //添加beanFactory后处理器
    public BeanFactoryPostProcessor processor2(){
        return beanFactory -> {
            log.debug("执行processor2");
        };
    }
}

  更改测试代码:

@Slf4j
public class A06Application {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("myConfig2", MyConfig2.class);
        context.refresh();//1.beanFactory后处理器 2.添加Bean后处理器 3.初始化单例
        context.close();
 }
}

测试结果如下: 

10:18:18.464 [main] DEBUG com.atguigu.boot.a06.MyConfig2 - 注入ApplicationContext
10:18:18.464 [main] DEBUG com.atguigu.boot.a06.MyConfig2 - 初始化

2.配置类 @Autowired 失效分析

 Java 配置类不包含 BeanFactoryPostProcessor 的情况

Java 配置类包含 BeanFactoryPostProcessor 的情况,因此要创建其中的BeanFactoryPostProcessor 必须提前创建 Java 配置类,而此时的 BeanPostProcessor 还未准备好,导致 @Autowired 等注解失效  

 

解决方法:

  • 用内置依赖注入和初始化取代扩展依赖注入和初始化

  • 用静态工厂方法代替实例工厂方法,避免工厂对象提前被创建

总结:

  • Aware接口提供了一种[内置]的注入手段,可以注入BeanFactory,ApplicationContext
  • InitializingBean接口提供了一种[内置]的初始化手段
  • 内置的注入和初始化不受扩展功能的影响,总会被执行,因此Spring类常用他们

 七、Bean的初始化与销毁

定义Bean1和Bean2两个类:

@Slf4j
public class Bean1 implements InitializingBean {

    @PostConstruct
    public void init1(){
        log.debug("初始化1");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.debug("初始化2");
    }

    public void init3(){
        log.debug("初始化3");
    }
}
@Slf4j
public class Bean2 implements DisposableBean {

    @PreDestroy
    public void destroy1(){
        log.debug("销毁1");
    }

    @Override
    public void destroy() throws Exception {
      log.debug("销毁2");
    }

    public void destroy3(){
        log.debug("销毁3");
    }
}

测试代码如下:

/**
 * 初始化和销毁的执行顺序
 */
@SpringBootApplication
public class A07Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(A07Application.class, args);
        context.close();

    }

    @Bean(initMethod = "init3")
    public Bean1 bean1(){
        return new Bean1();
    }

    @Bean(destroyMethod = "destroy3")
    public Bean2 bean2(){
        return new Bean2();
    }

}

测试结果:

11:02:51.703 [main] DEBUG com.atguigu.boot.a07.Bean1 - 初始化1
11:02:51.703 [main] DEBUG com.atguigu.boot.a07.Bean1 - 初始化2
11:02:51.703 [main] DEBUG com.atguigu.boot.a07.Bean1 - 初始化3

11:02:51.718 [main] DEBUG com.atguigu.boot.a07.Bean2 - 销毁1
11:02:51.718 [main] DEBUG com.atguigu.boot.a07.Bean2 - 销毁2
11:02:51.703 [main] DEBUG com.atguigu.boot.a07.Bean2 - 销毁3

总结:

   如果同一个 bean 用了以上手段声明了 3 个初始化方法,那么它们的执行顺序是

  1. @PostConstruct 标注的初始化方法

  2. InitializingBean 接口的初始化方法

  3. @Bean(initMethod) 指定的初始化方法

  如果同一个 bean 用了以上手段声明了 3 个销毁方法,那么它们的执行顺序是

  1. @PreDestroy 标注的销毁方法

  2. DisposableBean 接口的销毁方法

  3. @Bean(destroyMethod) 指定的销毁方法

八、Scope的类型、注意事项、销毁、失效分析及解决办法

1.Scope的类型

Scope的类型分成五种:

  1. singleton
  2. prototype
  3. request
  4. session
  5. application

 2.Scope的销毁 

演示Scope中的request、session、application 

定义三个类,分别对应request、session、application,代码如下:

@Scope("request")
@Slf4j
@Component
public class BeanForRequest {

    @PreDestroy
    public void destroy(){
        log.info("destroy");
    }

}



@Slf4j
@Component
@Scope("session")
public class BeanForSession {

    @PreDestroy
    public void destroy(){
        log.info("destroy");
    }

}


@Slf4j
@Component
@Scope("application")
public class BeanForApplication {

    @PreDestroy
    public void destroy(){
        log.info("destroy");
    }
    
}

在编写一个MyController类 和SpringBoot的启动类:

@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 sb = "<ul>" +
                "<li>"+"request scope:"+beanForRequest+"</li>"+
                "<li>"+"session scope"+beanForSession+"</li>"+
                "<li>"+"application scope"+beanForApplication+"</li>"+
                "</ul>";
        return sb;
    }
}
/**
 * singleton prototype request session application
 * 演示 request session application
 */
@SpringBootApplication
public class A08Application {
    public static void main(String[] args) {
        SpringApplication.run(A08Application.class, args);
    }
}

测试结果如下:

 重新刷新页面,结果如下:

 此时request对象发生了变化,更换浏览器重新打开该地址,结果如下:

对比发现,request对象和session对象都发生了变化 ,在配置文件中配置一个属性server.servlet.session.timeout=10s,从来来修改session的过期时间,等到过了设置对应的时间,session对象就会自动销毁,然后观察控制台输出

2022-05-23 17:57:47.577  INFO 4352 --- [nio-8080-exec-3] com.atguigu.boot.a08.BeanForRequest      : destroy
2022-05-23 17:59:43.404  INFO 4352 --- [alina-utility-2] com.atguigu.boot.a08.BeanForSession      : destroy

可以发现request对象和session对象被销毁了 

3.Scope的失效分析以及四种解决办法

定义一个类E(默认就是单例类),再定义四个多例类F1、F2、F3、F4(分别对应解决Scope失效的四种方法),代码如下:

@Component
public class E {

    //@Lazy
    @Autowired
    private F1 f1;

    public F1 getF1() {
        return f1;
    }
}

@Scope("prototype")
@Component
public class F1 {

}

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

}

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

}

@Scope("prototype")
@Component
public class F4 {

}

测试代码如下:

@ComponentScan("com.atguigu.boot.a09")
@Slf4j
public class A09Application {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context
                = new AnnotationConfigApplicationContext(A09Application.class);


        E e = context.getBean(E.class);
        log.debug("{}",e.getF1().getClass());
        log.debug("{}",e.getF1());
        log.debug("{}",e.getF1());
        log.debug("{}",e.getF1());
       

        context.close();
 }
}

测试结果如下:

19:08:29.282 [main] DEBUG com.atguigu.boot.a09.A09Application - class com.atguigu.boot.a09.F1
19:08:29.282 [main] DEBUG com.atguigu.boot.a09.A09Application - com.atguigu.boot.a09.F1@6c64cb25
19:08:29.282 [main] DEBUG com.atguigu.boot.a09.A09Application - com.atguigu.boot.a09.F1@6c64cb25
19:08:29.282 [main] DEBUG com.atguigu.boot.a09.A09Application - com.atguigu.boot.a09.F1@6c64cb25

根据测试结果可以看出,要注入的对象 F1期望是多例 , 发现它们是同一个对象,而不是期望的多例对象,导致这个结果的产生的原因是什么?

原因:对于单例对象来讲,依赖注入仅发生了一次,后续再没有用到多例的 F,因此 E 用的始终是第一次依赖注入的 F

 解决:

  • 仍然使用 @Lazy 生成代理

  • 代理对象虽然还是同一个,但当每次使用代理对象的任意方法时,由代理创建新的 f 对象

 加上@Lazy注解后,再次观察结果:

19:18:44.862 [main] DEBUG com.atguigu.boot.a09.A09Application - class com.atguigu.boot.a09.F1$$EnhancerBySpringCGLIB$$6b622d63
19:18:44.867 [main] DEBUG com.atguigu.boot.a09.A09Application - com.atguigu.boot.a09.F1@57a3af25
19:18:44.872 [main] DEBUG com.atguigu.boot.a09.A09Application - com.atguigu.boot.a09.F1@7f0eb4b4
19:18:44.872 [main] DEBUG com.atguigu.boot.a09.A09Application - com.atguigu.boot.a09.F1@1623b78d

发现注入的F1对象变成多例

注意

  • @Lazy 加在也可以加在成员变量上,但加在 set 方法上的目的是可以观察输出,加在成员变量上就不行了

  • @Autowired 加在 set 方法的目的类似

修改E类代码和测试代码:

@Component
public class E {

    @Lazy
    @Autowired
    private F1 f1;

    @Autowired
    private F2 f2;

    @Autowired
    private ObjectFactory<F3> f3;

    @Autowired
    private ApplicationContext context;

    public F1 getF1() {
        return f1;
    }

    public F2 getF2() {
        return f2;
    }

    public F3 getF3() {
        return f3.getObject();
    }

    public  F4 getF4(){
       return context.getBean(F4.class);
    }
}
@ComponentScan("com.atguigu.boot.a09")
@Slf4j
public class A09Application {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context
                = new AnnotationConfigApplicationContext(A09Application.class);


        E e = context.getBean(E.class);
        log.debug("{}",e.getF1().getClass());
        log.debug("{}",e.getF1());
        log.debug("{}",e.getF1());
        log.debug("{}",e.getF1());

        log.debug("{}",e.getF2().getClass());
        log.debug("{}",e.getF2());
        log.debug("{}",e.getF2());
        log.debug("{}",e.getF2());

        log.debug("{}",e.getF3());
        log.debug("{}",e.getF3());

        log.debug("{}",e.getF4());
        log.debug("{}",e.getF4());

        context.close();
    }
}

 测试结果如下:

18:15:06.244 [main] DEBUG com.atguigu.boot.a09.A09Application - class com.atguigu.boot.a09.F1$$EnhancerBySpringCGLIB$$6b622d63
18:15:06.244 [main] DEBUG com.atguigu.boot.a09.A09Application - com.atguigu.boot.a09.F1@2f9f7dcf
18:15:06.254 [main] DEBUG com.atguigu.boot.a09.A09Application - com.atguigu.boot.a09.F1@35e2d654
18:15:06.254 [main] DEBUG com.atguigu.boot.a09.A09Application - com.atguigu.boot.a09.F1@55183b20
18:15:06.254 [main] DEBUG com.atguigu.boot.a09.A09Application - class com.atguigu.boot.a09.F2$$EnhancerBySpringCGLIB$$66861975
18:15:06.254 [main] DEBUG com.atguigu.boot.a09.A09Application - com.atguigu.boot.a09.F2@49e53c76
18:15:06.254 [main] DEBUG com.atguigu.boot.a09.A09Application - com.atguigu.boot.a09.F2@351d00c0
18:15:06.254 [main] DEBUG com.atguigu.boot.a09.A09Application - com.atguigu.boot.a09.F2@2a3b5b47
18:15:06.254 [main] DEBUG com.atguigu.boot.a09.A09Application - com.atguigu.boot.a09.F3@35d019a3
18:15:06.254 [main] DEBUG com.atguigu.boot.a09.A09Application - com.atguigu.boot.a09.F3@18078bef
18:15:06.254 [main] DEBUG com.atguigu.boot.a09.A09Application - com.atguigu.boot.a09.F4@799f10e1
18:15:06.254 [main] DEBUG com.atguigu.boot.a09.A09Application - com.atguigu.boot.a09.F4@4c371370

总结:

  1. 单例注入其它 scope 的四种解决方法

    • @Lazy

    • @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)

    • ObjectFactory

    • ApplicationContext

  2. 解决方法虽然不同,但理念上殊途同归: 都是推迟其它 scope bean 的获取

九、Aop之ajc增强

使用ajc编译器进行增强,首先需要在pom文件导入相关依赖

<dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjrt</artifactId>
   <version>1.8.13</version>
</dependency>

加入ajc插件依赖:

<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>

定义需要被增强的类MyService:

@Service

public class MyService {

    private static final Logger log = LoggerFactory.getLogger(MyService.class);

    public void foo(){
        log.debug("foo");
    }

}

 定义切面类MyAspect:

@Aspect//注意此切面并未被Spring管理
public class MyAspect {

    private static final Logger log = LoggerFactory.getLogger(MyAspect.class);

    @Before("execution(* com.itheima.service.MyService.foo())")
    public void before(){
        log.debug("before()");
    }
}

测试代码:

@SpringBootApplication
public class A10Application {
    private static final Logger log = LoggerFactory.getLogger(A10Application.class);
    public static void main(String[] args) {

        new MyService().foo();
    }
}

测试之前需要执行clean、compile

注意:不要加@Slf4j注解,要不会编译不通过 (天坑,血的教训)

测试结果:

20:31:54.264 [main] DEBUG com.itheima.aop.MyAspect - before()
20:31:54.267 [main] DEBUG com.itheima.service.MyService - foo

 打开经过idea反编译的MyService.class文件

@Service
public class MyService {
    private static final Logger log = LoggerFactory.getLogger(MyService.class);

    public MyService() {
    }

    public void foo() {
        MyAspect.aspectOf().before();
        log.debug("foo");
    }
}

可以发现ajc编译器在编译的时候,已经添加了增强的代码

总结:

  1. 编译器也能修改 class 实现增强

  2. 编译器增强能突破代理仅能通过方法重写增强的限制:可以对构造方法、静态方法等实现增强

十、Aop之agent增强

定义需要被增强的类MyService1

@Service
public class MyService1 {

    private static final Logger log = LoggerFactory.getLogger(MyService1.class);

    final public void foo(){
        log.info("foo()");
        bar();
    }

    public void bar(){
        log.info("bar()");
    }

}

定义切面类MyAspect1

@Aspect//注意此切面并未被Spring管理
public class MyAspect1 {

    private static final Logger log = LoggerFactory.getLogger(MyAspect1.class);

    @Before("execution(* com.itheima.service.MyService1.*())")
    public void before(){
        log.info("before()");
    }
}

测试代码:

@SpringBootApplication
public class A11Application {
    private static final Logger log = LoggerFactory.getLogger(A11Application.class);

    public static void main(String[] args) {
       ConfigurableApplicationContext context =  SpringApplication.run(A11Application.class, args);
       MyService1 service1 = context.getBean(MyService1.class);

       //MyService并非代理,但foo方法增强了,做增强的java agent,在加载类时,修改了class字节码
        log.info("service1 class: {}",service1.getClass());
        service1.foo();
    }
}

运行之前需要在VM options中加入 -javaagent:D:\Maven\apache-maven-3.8.4-bin\maven-lib\repository_boot\org\aspectj\aspectjweaver\1.9.7\aspectjweaver-1.9.7.jar,其中紫色部分为自己本地Maven仓库地址

 注意:此时运行是不会有效果的,需要在resources下创建一个META-INF文件夹,写入一个aop.xml文件,文件内容如下:

<aspectj>
    <aspects>
        <aspect name="com.itheima.aop.MyAspect1"/>
        <weaver options="-verbose -showWeaveInfo">
            <include within="com.itheima.service.MyService1"/>
            <include within="com.itheima.aop.MyAspect1"/>
        </weaver>
    </aspects>
</aspectj>

 测试结果如下:


2022-05-24 10:04:03.512  INFO 4316 --- [  restartedMain] com.itheima.A11Application               : service1 class: class com.itheima.service.MyService1
2022-05-24 10:04:03.517  INFO 4316 --- [  restartedMain] com.itheima.aop.MyAspect1                : before()
2022-05-24 10:04:03.517  INFO 4316 --- [  restartedMain] com.itheima.service.MyService1           : foo()
2022-05-24 10:04:03.517  INFO 4316 --- [  restartedMain] com.itheima.aop.MyAspect1                : before()
2022-05-24 10:04:03.517  INFO 4316 --- [  restartedMain] com.itheima.service.MyService1           : bar()
[MethodUtil@7f2b838c] info AspectJ Weaver Version 1.9.7 built on Thursday Jun 24, 2021 at 16:14:45 PDT
[MethodUtil@7f2b838c] info register classloader sun.reflect.misc.MethodUtil@7f2b838c
[MethodUtil@7f2b838c] info using configuration /D:/Workspace/Html/Spring5/aspectj_01/target/classes/META-INF/aop.xml
[MethodUtil@7f2b838c] info register aspect com.itheima.aop.MyAspect1

总结:

        类加载时可以通过 agent 修改 class 实现增强

十一、Aop之proxy增强

1.aop之proxy增强-jdk

测试代码:

public class JdkProxyDemo {

    interface Foo{
        void foo();
    }

    static class Target implements Foo{
        @Override
        public void foo() {
            System.out.println("target foo");
        }
    }
    //jdk 只能针对接口进行代理
    public static void main(String[] param) {
        //目标对象
        Target target = new Target();
        ClassLoader loader = JdkProxyDemo.class.getClassLoader();//用来加载在运行期间动态生成的字节码
        // new Class[]{Foo.class}:代理类要实现的接口  InvocationHandler:代理类调用方法时要执行的行为
       Foo foo = (Foo) Proxy.newProxyInstance(loader, new Class[]{Foo.class}, (proxy, method, args) -> {
           System.out.println("before...");
           //方法.invoke(目标,参数)
           Object result = method.invoke(target, args);
           System.out.println("after...");
           return result;//让代理也返回目标方法执行的结果
        });

       foo.foo();
    }
}

测试结果:

before...
target foo
after...

总结:

  • jdk 动态代理要求目标必须实现接口,生成的代理类实现相同接口,因此代理与目标之间是平级兄弟关系

  • 目标类可以加final来修饰

2.aop之proxy增强-cglib

测试代码:

public class CglibProxyDemo {

    static class Target{
        public void foo(){
            System.out.println("target foo");
        }
    }
    //代理类是子类型,目标是父类型
    public static void main(String[] param) {
        Target target = new Target();

        Target proxy = (Target) Enhancer.create(Target.class, (MethodInterceptor) (p, method, args, methodProxy) -> {
            System.out.println("before...");
            //Object result = method.invoke(target, args);//用方法反射调用目标
            //methodProxy可以避免反射调用
            //Object result = methodProxy.invoke(target, args);//内部没有反射,需要目标
            Object result = methodProxy.invokeSuper(p, args);//内部没有反射,需要代理
            System.out.println("after...");
            return result;
        });

        proxy.foo();
    }
}

 测试结果:

before...
target foo
after...

cglib动态代理实现有三种方式:      

  • Object result = method.invoke(target, args)  用方法反射调用目标(这种方式同jdk动态代理一样)
  • Object result = methodProxy.invoke(target, args)  使用methodProxy可以避免反射调用
  • Object result = methodProxy.invokeSuper(p, args);

invoke和invokeSuper的区别:

  • invoke需要目标
  • invokeSuper需要代理

总结:

  • cglib 不要求目标实现接口,它生成的代理类是目标的子类,因此代理与目标之间是子父关系

  • 限制⛔:1.根据上述分析 final 类无法被 cglib 增强  一旦目标类加上final来修饰,代理类就无法继承目标类  2.目标类方法定义的时候加上final来修饰,就会无法实现增强目标类

3.jdk和cglib的区别 

  • jdk动态代理要求必须实现接口,代理与目标之间是兄弟关系;cglib动态代理不要求实现接口,代理与目标之间是父子关系
  • jdk动态代理中目标类可以加final来修饰,而cglib动态代理中目标类不可以加final来修饰(会抛异常) 

十二、jdk代理原理

1.jdk代理原理(具体实现)

为了探究jdk代理原理,模拟其过程,逐步来分析这个过程

  • 定义一个接口Foo和一个Target类(实现Foo接口),定义$Proxy代理类(同样实现Foo接口),在其中实现对目标类Target中的foo方法进行增强,代码如下:
public class A13 {

    interface Foo{
        void foo();
    }
    static class Target implements Foo{
        @Override
        public void foo() {
            System.out.println("target foo");
        }
    }

     public static void main(String[] param) {
        Foo proxy = new $Proxy0();
        proxy.foo();
    }
}
public class $Proxy0 implements Foo {

    @Override
    public void foo() {
       //1.功能增强
       System.out.println("before...");
       //2.调用目标
       new Target().foo();

    }
}

测试结果:

before...
target foo

实现了对目标类Target中foo方法的增强

  • 可以看出,对目标类Target中foo方法的增强和调用目标的代码都写在了代理类$Proxy0中,这样会导致代码耦合度太高,所以我们可以定义一个接口InvocationHandler来抽取这段代码,从而实现代码的解耦,具体实现如下:
public class $Proxy0 implements Foo {

    private InvocationHandler handler;

    public $Proxy0(InvocationHandler handler) {
        this.handler = handler;
    }

    @Override
    public void foo() {
        handler.invoke();

    }
}

       

public class A13 {

    interface Foo{
        void foo();
    }

    static class Target implements Foo{
        @Override
        public void foo() {
            System.out.println("target foo");
        }
    }

    interface InvocationHandler{
        void invoke();
    }

    public static void main(String[] param) {
        Foo proxy = new $Proxy0(new InvocationHandler() {
            @Override
            public void invoke() {
                //1.功能增强
                System.out.println("before...");
                //2.调用目标
                new Target().foo();
            }
        });
        proxy.foo();
    }
}

测试结果同上,依然实现了对目标类Target中foo方法的增强

  • 假设在Foo接口中新增一个方法bar(),同样要实现对它的增强,然而结果却是都调用的目标对象的foo方法,所以就需要在代理类中获得方法名,再通过反射将Method对象回传给InvocationHandler,具体实现如下:
public class $Proxy0 implements Foo {

    private InvocationHandler handler;

    public $Proxy0(InvocationHandler handler) {
        this.handler = handler;
    }

    @Override
    public void foo() {
        try {
            Method foo = Foo.class.getMethod("foo");
            handler.invoke(foo, new Object[0]);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

    }

    @Override
    public void bar() {
        try {
            Method bar = Foo.class.getMethod("bar");
            handler.invoke(bar, new Object[0]);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
}
public class A13 {

    interface Foo{
        void foo();
        void bar();
    }

    static class Target implements Foo{
        @Override
        public void foo() {
            System.out.println("target foo");
        }

        @Override
        public void bar() {
            System.out.println("target bar");
        }
    }

    interface InvocationHandler{
        void invoke(Method method,Object[] args) throws Throwable;
    }

    public static void main(String[] param) {
        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();
    }
}

测试的结果如下:

before...
target foo
before...
target bar

这样就实现了对Foo接口中两个方法的增强

  • 接下来修改Foo接口中bar方法的返回值,修改为int类型,也就是说InvocationHandler接口的invoke方法也需要有返回值,同时还需要把代理类本身作为参数传过去。对于代理类中重写的方法,需要对其异常进行分开处理,运行期异常直接抛出,检查期异常把它转换为运行期异常再抛出,具体实现如下:
public class A13 {

    interface Foo{
        void foo();
        int bar();
    }

    static class Target implements Foo{
        @Override
        public void foo() {
            System.out.println("target foo");
        }

        @Override
        public int bar() {
            System.out.println("target bar");
            return 100;
        }
    }

    interface InvocationHandler{
        Object invoke(Object proxy,Method method,Object[] args) throws Throwable;
    }

    public static void main(String[] param) {
        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();
        proxy.bar();
    }
}
public class $Proxy0 implements Foo {

    private InvocationHandler handler;

    public $Proxy0(InvocationHandler handler) {
        this.handler = handler;
    }

    @Override
    public void foo() {
        try {
            handler.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)handler.invoke(this,bar, new Object[0]);
        } catch (RuntimeException | Error e) {
            throw e;
        }catch (Throwable e){
            throw new UndeclaredThrowableException(e);
        }
    }

    static Method foo;
    static Method bar;

    static {
        try {
            foo = Foo.class.getMethod("foo");
            bar = Foo.class.getMethod("bar");
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }
}

测试结果同上,实现了对Foo接口中两个方法的增强

  • 接下来使用jdk的InvocationHandler来替换我们自己写的InvocationHandler,同时让代理类继承Proxy类,因为Proxy类中有对应的InvocationHandler成员变量和构造方法,所以这里删除自己写的InvocationHandler成员变量,然后调用父类的构造方法,具体实现如下:
public class A13 {

    interface Foo{
        void foo();
        int bar();
    }

    static class Target implements Foo{
        @Override
        public void foo() {
            System.out.println("target foo");
        }

        @Override
        public int bar() {
            System.out.println("target bar");
            return 100;
        }
    }

    //interface InvocationHandler{
    //    Object invoke(Object proxy,Method method,Object[] args) throws Throwable;
    //}

    public static void main(String[] param) {
        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();
        proxy.bar();
    }
}
public class $Proxy0 extends Proxy implements Foo {

    public $Proxy0(InvocationHandler h) {
        super(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);
        }
    }

    static Method foo;
    static Method bar;

    static {
        try {
            foo = Foo.class.getMethod("foo");
            bar = Foo.class.getMethod("bar");
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }
}

测试结果依然实现了对Foo接口中两个方法的增强,自此对代码的优化到此结束,总结一下:

  1. 方法重写可以增强逻辑,只不过这【增强逻辑】千变万化,不能写死在代理内部

  2. 通过接口回调将【增强逻辑】置于代理类之外

  3. 配合接口方法反射(是多态调用),就可以再联动调用目标方法

  4. 会用 arthas 的 jad 工具反编译代理类

  5. 限制⛔:代理增强是借助多态来实现,因此成员变量、静态方法、final 方法均不能通过代理实现

2.jdk代理字节码生成

在idea中装ASM Bytecode Outline插件,有这个插件才能动态生成字节码,装好插件之后,编写Foo接口以及代理类$Proxy(继承Proxy并且实现Foo接口),具体实现如下:

public interface Foo {
    void foo();
}

public class $Proxy0 extends Proxy implements Foo {

    public $Proxy0(InvocationHandler h) {
        super(h);
    }

    @Override
    public void foo() {
        try {
            this.h.invoke(this, foo, null);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    static Method foo;

    static {
        try {
            foo = Foo.class.getMethod("foo");
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }
}

按Ctrl+Shift+F9 重新编译这两个文件,然后点击鼠标右键,选择Show Bytecode outline,右边弹出的窗口选择ASMified,将这个拉到项目中,会生成$Proxy0Dump文件,文件内容如下:

public class $Proxy0Dump implements Opcodes {

    public static byte[] dump() throws Exception {

        ClassWriter cw = new ClassWriter(0);
        FieldVisitor fv;
        MethodVisitor mv;
        AnnotationVisitor av0;

        cw.visit(52, ACC_PUBLIC + ACC_SUPER, "com/itheima/asm/$Proxy0", null, "java/lang/reflect/Proxy", new String[]{"com/itheima/asm/Foo"});

        cw.visitSource("$Proxy0.java", null);

        {
            fv = cw.visitField(ACC_STATIC, "foo", "Ljava/lang/reflect/Method;", null, null);
            fv.visitEnd();
        }
        {
            mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", null, null);
            mv.visitParameter("h", 0);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(12, l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", false);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLineNumber(13, l1);
            mv.visitInsn(RETURN);
            Label l2 = new Label();
            mv.visitLabel(l2);
            mv.visitLocalVariable("this", "Lcom/itheima/asm/$Proxy0;", null, l0, l2, 0);
            mv.visitLocalVariable("h", "Ljava/lang/reflect/InvocationHandler;", null, l0, l2, 1);
            mv.visitMaxs(2, 2);
            mv.visitEnd();
        }
        {
            mv = cw.visitMethod(ACC_PUBLIC, "foo", "()V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            Label l1 = new Label();
            Label l2 = new Label();
            mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Throwable");
            mv.visitLabel(l0);
            mv.visitLineNumber(18, l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, "com/itheima/asm/$Proxy0", "h", "Ljava/lang/reflect/InvocationHandler;");
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETSTATIC, "com/itheima/asm/$Proxy0", "foo", "Ljava/lang/reflect/Method;");
            mv.visitInsn(ACONST_NULL);
            mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true);
            mv.visitInsn(POP);
            mv.visitLabel(l1);
            mv.visitLineNumber(21, l1);
            Label l3 = new Label();
            mv.visitJumpInsn(GOTO, l3);
            mv.visitLabel(l2);
            mv.visitLineNumber(19, l2);
            mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"});
            mv.visitVarInsn(ASTORE, 1);
            Label l4 = new Label();
            mv.visitLabel(l4);
            mv.visitLineNumber(20, l4);
            mv.visitTypeInsn(NEW, "org/springframework/cglib/proxy/UndeclaredThrowableException");
            mv.visitInsn(DUP);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitMethodInsn(INVOKESPECIAL, "org/springframework/cglib/proxy/UndeclaredThrowableException", "<init>", "(Ljava/lang/Throwable;)V", false);
            mv.visitInsn(ATHROW);
            mv.visitLabel(l3);
            mv.visitLineNumber(22, l3);
            mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
            mv.visitInsn(RETURN);
            Label l5 = new Label();
            mv.visitLabel(l5);
            mv.visitLocalVariable("e", "Ljava/lang/Throwable;", null, l4, l3, 1);
            mv.visitLocalVariable("this", "Lcom/itheima/asm/$Proxy0;", null, l0, l5, 0);
            mv.visitMaxs(4, 2);
            mv.visitEnd();
        }
        {
            mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            Label l1 = new Label();
            Label l2 = new Label();
            mv.visitTryCatchBlock(l0, l1, l2, "java/lang/NoSuchMethodException");
            mv.visitLabel(l0);
            mv.visitLineNumber(28, l0);
            mv.visitLdcInsn(Type.getType("Lcom/itheima/asm/Foo;"));
            mv.visitLdcInsn("foo");
            mv.visitInsn(ICONST_0);
            mv.visitTypeInsn(ANEWARRAY, "java/lang/Class");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
            mv.visitFieldInsn(PUTSTATIC, "com/itheima/asm/$Proxy0", "foo", "Ljava/lang/reflect/Method;");
            mv.visitLabel(l1);
            mv.visitLineNumber(31, l1);
            Label l3 = new Label();
            mv.visitJumpInsn(GOTO, l3);
            mv.visitLabel(l2);
            mv.visitLineNumber(29, l2);
            mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/NoSuchMethodException"});
            mv.visitVarInsn(ASTORE, 0);
            Label l4 = new Label();
            mv.visitLabel(l4);
            mv.visitLineNumber(30, l4);
            mv.visitTypeInsn(NEW, "java/lang/NoSuchMethodError");
            mv.visitInsn(DUP);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/NoSuchMethodException", "getMessage", "()Ljava/lang/String;", false);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/NoSuchMethodError", "<init>", "(Ljava/lang/String;)V", false);
            mv.visitInsn(ATHROW);
            mv.visitLabel(l3);
            mv.visitLineNumber(32, l3);
            mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
            mv.visitInsn(RETURN);
            mv.visitLocalVariable("e", "Ljava/lang/NoSuchMethodException;", null, l4, l3, 0);
            mv.visitMaxs(3, 1);
            mv.visitEnd();
        }
        cw.visitEnd();

        return cw.toByteArray();
    }
}

编写一个TestProxy类,内容是通过类加载使这个字节码生成一个类对象,然后通过类对象的反射拿到它的构造创建类的代理实例,具体实现如下:

public class TestProxy {
    public static void main(String[] args) throws Exception {
        byte[] dump = $Proxy0Dump.dump();

        ClassLoader loader = new ClassLoader() {
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                return super.defineClass(dump,0,dump.length);
            }
        };

        Class<?> proxyClass = loader.loadClass("com.itheima.asm.$Proxy1");
        Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
        Foo proxy = (Foo) constructor.newInstance(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("before...");
                System.out.println("调用目标");
                return null;
            }
        });
        proxy.foo();
    }

}

3.jdk反射优化

定义一个TestMethodInvoke类,具体如下:

public class TestMethodInvoke {

    public static void main(String[] args) throws Exception {
        Method foo = TestMethodInvoke.class.getDeclaredMethod("foo", int.class);
        for (int i = 0; i <= 17; i++) {
            show(i, foo);
            foo.invoke(null, i);
        }
        System.in.read();
    }

    //方法反射调用,底层MethodAccessor的实现类
    private static void show(int i,Method foo)throws Exception{
        Method getMethodAccessor = Method.class.getDeclaredMethod("getMethodAccessor");
        getMethodAccessor.setAccessible(true);
        Object invoke = getMethodAccessor.invoke(foo);
        if (invoke == null) {
            System.out.println(i + ":" + null);
            return;
        }
        Field delegate = Class.forName("jdk.internal.reflect.DelegatingMethodAccessorImpl").getDeclaredField("delegate");
        delegate.setAccessible(true);
        System.out.println(i + ":" + delegate.get(invoke));
    }

    public static void foo(int i){
        System.out.println(i+":"+"foo");
    }
}

运行时在VM options配置中添加 --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED

 此处有个坑,详情请参考:异常记录之使用方法反射调用出错_Dwight Howard_12的博客-CSDN博客

运行结果:

0:null
0:foo
1:jdk.internal.reflect.NativeMethodAccessorImpl@246b179d
1:foo
2:jdk.internal.reflect.NativeMethodAccessorImpl@246b179d
2:foo
3:jdk.internal.reflect.NativeMethodAccessorImpl@246b179d
3:foo
4:jdk.internal.reflect.NativeMethodAccessorImpl@246b179d
4:foo
5:jdk.internal.reflect.NativeMethodAccessorImpl@246b179d
5:foo
6:jdk.internal.reflect.NativeMethodAccessorImpl@246b179d
6:foo
7:jdk.internal.reflect.NativeMethodAccessorImpl@246b179d
7:foo
8:jdk.internal.reflect.NativeMethodAccessorImpl@246b179d
8:foo
9:jdk.internal.reflect.NativeMethodAccessorImpl@246b179d
9:foo
10:jdk.internal.reflect.NativeMethodAccessorImpl@246b179d
10:foo
11:jdk.internal.reflect.NativeMethodAccessorImpl@246b179d
11:foo
12:jdk.internal.reflect.NativeMethodAccessorImpl@246b179d
12:foo
13:jdk.internal.reflect.NativeMethodAccessorImpl@246b179d
13:foo
14:jdk.internal.reflect.NativeMethodAccessorImpl@246b179d
14:foo
15:jdk.internal.reflect.NativeMethodAccessorImpl@246b179d
15:foo
16:jdk.internal.reflect.GeneratedMethodAccessor2@69d0a921
16:foo
17:jdk.internal.reflect.GeneratedMethodAccessor2@69d0a921
17:foo

根据测试结果可以看出:

  1. 前 16 次反射性能较低

  2. 第 17 次调用会生成代理类,优化为非反射调用

十三、cglib代理原理

1.cglib代理原理演示 

定义一个目标类Target和一个代理类Proxy,让代理类Proxy继承目标类Target (体现父子关系),具体实现如下:

public class Target {

    public void save(){
        System.out.println("save()");
    }

    public void save(int i){
        System.out.println("save(i)");
    }

    public void save(long j){
        System.out.println("save(j)");
    }
}
public class Proxy extends Target{

    private MethodInterceptor methodInterceptor;

    public void setMethodInterceptor(MethodInterceptor methodInterceptor) {
        this.methodInterceptor = methodInterceptor;
    }

    static Method save0;
    static Method save1;
    static Method save2;

    static {
        try {
            save0 = Target.class.getMethod("save");
            save1 = Target.class.getMethod("save",int.class);
            save2 = Target.class.getMethod("save",long.class);
            
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }

    //>>>>>>>>>>>>>>>>>带增强功能的方法
    @Override
    public void save() {
        try {
            methodInterceptor.intercept(this, save0, new Object[0],null);
        } catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    @Override
    public void save(int i) {
        try {
            methodInterceptor.intercept(this, save1, new Object[]{i},null);
        } catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    @Override
    public void save(long j) {
        try {
            methodInterceptor.intercept(this, save2, new Object[]{j},null);
        } catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
}

测试代码如下:

public class A14 {
    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        Target target = new Target();
        proxy.setMethodInterceptor(new MethodInterceptor() {
            @Override
            public Object intercept(Object p, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("before...");
                return method.invoke(target, args);//反射调用
            }
        });
        proxy.save();
        proxy.save(1);
        proxy.save(2L);
    }
}

测试结果如下:

before...
save()
before...
save(i)
before...
save(j)

2.cglib代理原理--MethodProxy

在代理类Proxy中创建MethodProxy,需要通过调用MethodProxy的静态方法create()来完成创建,在这个方法中需要传入五个参数,分别是目标类对象,代理类对象,方法参数返回值的描述,增强功能的方法名和原始功能的方法名,其中方法参数返回值的描述()表示无参,有参数的需要在括号中传入相对应的表示(I表示整形,J表示长整形),()后面跟方法返回值=>V表示没有返回值

public class Proxy extends Target{

    private MethodInterceptor methodInterceptor;

    public void setMethodInterceptor(MethodInterceptor methodInterceptor) {
        this.methodInterceptor = methodInterceptor;
    }

    static Method save0;
    static Method save1;
    static Method save2;

    static MethodProxy save0Proxy;
    static MethodProxy save1Proxy;
    static MethodProxy save2Proxy;

    static {
        try {
            save0 = Target.class.getMethod("save");
            save1 = Target.class.getMethod("save",int.class);
            save2 = Target.class.getMethod("save",long.class);
            save0Proxy = MethodProxy.create(Target.class, Proxy.class, "()V", "save", "saveSuper");
            save1Proxy = MethodProxy.create(Target.class, Proxy.class, "(I)V", "save", "saveSuper");
            save2Proxy = MethodProxy.create(Target.class, Proxy.class, "(J)V", "save", "saveSuper");
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }

    //>>>>>>>>>>>>>>>>>带原始功能的方法
    public void saveSuper(){super.save();}
    public void saveSuper(int i){super.save(i);}
    public void saveSuper(long j){super.save(j);}

    //>>>>>>>>>>>>>>>>>带增强功能的方法
    @Override
    public void save() {
        try {
            methodInterceptor.intercept(this, save0, new Object[0],save0Proxy);
        } catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    @Override
    public void save(int i) {
        try {
            methodInterceptor.intercept(this, save1, new Object[]{i},save1Proxy);
        } catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    @Override
    public void save(long j) {
        try {
            methodInterceptor.intercept(this, save2, new Object[]{j},save2Proxy);
        } catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
}

 接下来就可以使用MethodProxy来调用目标,测试代码如下:

public class A14 {
    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        Target target = new Target();
        proxy.setMethodInterceptor(new MethodInterceptor() {
            @Override
            public Object intercept(Object p, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("before...");
                //return method.invoke(target, args);//反射调用
                //return methodProxy.invoke(target, args);//内部无反射,结合目标用
                return methodProxy.invokeSuper(p, args);//内部无反射,结合代理用
            }
        });
        proxy.save();
        proxy.save(1);
        proxy.save(2L);
    }
}

测试结果同上

总结:

        cglib动态代理和jdk动态代理原理差不多(重新归结一下,之前也有提过)

两者之间的区别:

  1. 回调的接口换了一下,InvocationHandler 改成了 MethodInterceptor

  2. 调用目标时有所改进

    1. method.invoke 是反射调用,必须调用到足够次数才会进行优化

    2. methodProxy.invoke 是不反射调用,它会正常(间接)调用目标对象的方法(Spring 采用)

    3. methodProxy.invokeSuper 也是不反射调用,它会正常(间接)调用代理对象的方法,可以省略目标对象

十四、MethodProxy原理 

1.cglib避免反射调用(如何避免)

当调用 MethodProxy 的 invoke 或 invokeSuper 方法时, 会动态生成两个类

  • ProxyFastClass(记录了 Proxy 中方法与编号的对应关系 ) 配合代理对象一起使用, 避免反射

  • TargetFastClass(记录了 Target 中方法与编号的对应关系 ) 配合目标对象一起使用, 避免反射 (Spring 用的这种)

1.自定义TargetFastClass,根据方法名和参数个数、类型找到这些方法编号,然后再根据编号去调用目标方法(直接调用),避免了反射,具体实现如下:

public class TargetFastClass {//避免方法的反射调用

    static Signature s0 = new Signature("save", "()V");
    static Signature s1 = new Signature("save", "(I)V");
    static Signature s2 = new Signature("save", "(J)V");
    //获取目标方法的编号
    /*
        Target
            save()   0
            save(int) 1
            save(long)  2
        signature 包括方法名字、参数返回值
     */
    public int getIndex(Signature signature){
        if (s0.equals(signature)) {
            return 0;
        }else if (s1.equals(signature)){
            return 1;
        }else if (s2.equals(signature)){
            return 2;
        }
        return -1;
    }
    //根据方法编号,正常调用目标对象方法
    public Object invoke(int index,Object target,Object[] args){
        if (index == 0) {
            ((Target) target).save();
            return null;
        }else if (index == 1){
            ((Target) target).save((int)args[0]);
            return null;
        }else if (index == 2){
            ((Target) target).save((Long) args[0]);
            return null;
        }else {
            throw new RuntimeException("无此方法");
        }
    }

    public static void main(String[] args) {
        TargetFastClass fastClass = new TargetFastClass();
        int index = fastClass.getIndex(new Signature("save", "()V"));
        System.out.println(index);
        fastClass.invoke(index, new Target(), new Object[0]);
    }
}

测试结果如下:

0
save()

2.自定义ProxyFastClass类,调用Proxy代理类中带原始功能的方法,不能去调用带增强功能的方法(会死循环调用intercept),查找方式与上边的TargetFastClass相同,具体实现如下:

public class ProxyFastClass {
    static Signature s0 = new Signature("saveSuper", "()V");
    static Signature s1 = new Signature("saveSuper", "(I)V");
    static Signature s2 = new Signature("saveSuper", "(J)V");
    //获取代理方法的编号
    /*
        Proxy
            saveSuper()   0
            saveSuper(int) 1
            saveSuper(long)  2
        signature 包括方法名字、参数返回值
     */
    public int getIndex(Signature signature){
        if (s0.equals(signature)) {
            return 0;
        }else if (s1.equals(signature)){
            return 1;
        }else if (s2.equals(signature)){
            return 2;
        }
        return -1;
    }
    //根据方法编号,正常调用目标对象方法
    public Object invoke(int index,Object proxy,Object[] args){
        if (index == 0) {
            ((Proxy) proxy).saveSuper();
            return null;
        }else if (index == 1){
            ((Proxy) proxy).saveSuper((int)args[0]);
            return null;
        }else if (index == 2){
            ((Proxy) proxy).saveSuper((Long) args[0]);
            return null;
        }else {
            throw new RuntimeException("无此方法");
        }
    }

    public static void main(String[] args) {
        ProxyFastClass fastClass = new ProxyFastClass();
        int index = fastClass.getIndex(new Signature("saveSuper", "()V"));
        System.out.println(index);
        fastClass.invoke(index, new Proxy(), new Object[0]);
    }

}

测试结果同上

总结:

  • 一个代理类配两个 FastClass 类, 代理类中还得增加仅调用 super 的一堆方法 ,这个过程较为麻烦,但是避免反射,提高性能
  • 用编号处理方法对应关系比较省内存, 另外, 最初获得方法顺序是不确定的, 这个过程没法固定死

2.与jdk对比

  •  jdk先需要调16次,第17次的时候会针对一个方法产生一个代理类,这个代理类会使调用变成无需反射,而cglib在调用MethodProxy时候就会产生代理,避免反射调用,提高性能
  • 相较jdk,最终产生的代理类数目较少

十五、Spring选择代理

1.jdk和cglib在Spring中的统一

Spring 中对切点、通知、切面的抽象如下

  • 切点:接口 Pointcut,典型实现 AspectJExpressionPointcut

  • 通知:典型接口为 MethodInterceptor 代表环绕通知

  • 切面:Advisor,包含一个 Advice 通知,PointcutAdvisor 包含一个 Advice 通知和一个 Pointcut

代理相关类图

  • AopProxyFactory 根据 proxyTargetClass 等设置选择 AopProxy 实现

  • AopProxy 通过 getProxy 创建代理对象

  • 图中 Proxy 都实现了 Advised 接口,能够获得关联的切面集合与目标(其实是从 ProxyFactory 取得)

  • 调用代理方法时,会借助 ProxyFactory 将通知统一转为环绕通知:MethodInterceptor

  2.Spring选择代理(演示)

定义一个类A15,在其中定义一个I1接口,一个Target1类实现I1接口,一个Target2类不实现任何接口,ProxyFactory 是用来创建代理的核心实现

具体实现如下:

public class A15 {

    public static void main(String[] args) {
        //1.备好切点
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* foo())");
        //2.备好通知
        MethodInterceptor advice = invocation -> {
            System.out.println("before...");
            Object result = invocation.proceed();//调用目标
            System.out.println("after...");
            return result;
        };
        //3.备好切面
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut,advice);
        /*
            4.创建代理
               
         */
        Target1 target1 = new Target1();
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(target1);
        factory.addAdvisor(advisor);
        I1 proxy = (I1) factory.getProxy();
        System.out.println(proxy.getClass());
        proxy.foo();
        proxy.bar();
       
    }

    interface I1{
        void foo();
        void bar();
    }

    static class Target1 implements I1{
        @Override
        public void foo() {
            System.out.println("target1 foo");
        }
        @Override
        public void bar() {
            System.out.println("target1 bar");
        }
    }

    static class Target2{

        public void foo() {
            System.out.println("target2 foo");
        }
        public void bar() {
            System.out.println("target2 bar");
        }
    }

}

测试结果:

class com.itheima.a15.A15$Target1$$EnhancerBySpringCGLIB$$212b29b6
before...
target1 foo
after...
target1 bar

可以看出,创建代理是由cglib实现的

Proxyfactory的父类ProxyConfig中定义了一个属性proxyTargetClass,这个属性的默认值为false,选择代理时需要用到这一属性

a.proxyTargetClass = false,如果目标实现了接口,用jdk实现

b.proxyTargetClass = false,如果目标没有实现接口,用cglib实现

c.proxyTargetClass = true,总是使用cglib实现

上面的测试结果属于第二种 ,这里需要注意Target1虽然实现了I1接口,但是ProxyFactory中没有设置对应的接口,所以还是用到cglib实现,接下来给ProxyFactory中设置接口,更改部分代码:

        Target1 target1 = new Target1();
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(target1);
        factory.addAdvisor(advisor);
        factory.setInterfaces(target1.getClass().getInterfaces());
        //factory.setProxyTargetClass(true);
        I1 proxy = (I1) factory.getProxy();
        System.out.println(proxy.getClass());
        proxy.foo();
        proxy.bar();
       
        

 结果如下:

class com.itheima.a15.$Proxy0
before...
target1 foo
after...
target1 bar

可以看出,这个时候已经变成jdk实现了

添加factory.setProxyTargetClass(true); ,结果又会变成由cglib实现

十六、切点匹配

定义一个类T1,其中定义两个方法foo和bar(foo方法上加@Transactional注解),定义一个类T2,类上加@Transactional注解,在其中定义foo方法,具体实现如下:

static class T1{
        @Transactional
        public void foo(){
        }
        public void bar(){
        }
    }

    @Transactional
    static class T2{
        public void foo(){}
    }

在测试代码中定义AspectJExpressionPointcut切点,分别测试根据方法名字和方法上的注解分别进行匹配,具体实现如下:

public class A16 {
    public static void main(String[] args) throws NoSuchMethodException {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* bar())");
        //根据名字进行方法匹配
        System.out.println(pointcut.matches(T1.class.getMethod("foo"), T1.class));
        System.out.println(pointcut.matches(T1.class.getMethod("bar"), T1.class));
        //根据方法上注解进行匹配
        AspectJExpressionPointcut pointcut1 = new AspectJExpressionPointcut();
        pointcut1.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");
        System.out.println(pointcut1.matches(T1.class.getMethod("foo"), T1.class));
        System.out.println(pointcut1.matches(T1.class.getMethod("bar"), T1.class));
}

测试结果如下:

false
true
true
false

可以看出,根据方法名字匹配测试中,只匹配到了定义好的bar方法,而没有匹配到foo方法;

根据方法上注解匹配测试中,只匹配到了加了@Transactional注解的foo方法

定义一个接口I3(接口上加@Transactional),定义一个类T3(实现I3接口),具体实现如下:

@Transactional
    interface I3{
        void foo();
    }
    static class T3 implements I3{
        @Override
        public void foo() {
        }
    }

在测试代码中定义StaticMethodMatcherPointcut切点,实现其中的抽象方法matches,然后可以在其中实现分别检查方法和类上是否加了@Transactional注解。具体实现如下:

MergedAnnotations.SearchStrategy.TYPE_HIERARCHY:更改搜索策略为从继承树上搜索

StaticMethodMatcherPointcut pointcut2 = new StaticMethodMatcherPointcut() {
            @Override
            public boolean matches(Method method, Class<?> targetClass) {
                //检查方法上是否加了Transactional注解
                MergedAnnotations annotations = MergedAnnotations.from(method);
                if (annotations.isPresent(Transactional.class)) {
                    return true;
                }
                //检查类上是否加了Transactional注解
                annotations = MergedAnnotations.from(targetClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
                if (annotations.isPresent(Transactional.class)) {
                    return true;
                }
                return false;
            }
        };

        System.out.println(pointcut2.matches(T1.class.getMethod("foo"), T1.class));
        System.out.println(pointcut2.matches(T1.class.getMethod("bar"), T1.class));
        System.out.println(pointcut2.matches(T2.class.getMethod("foo"), T2.class));
        System.out.println(pointcut2.matches(T3.class.getMethod("foo"), T3.class));
    }

测试结果如下:

true
false
true
true

可以看出,只有没加@Transactional注解的方法bar没有被搜索到

总结:

  • 底层切点实现如何匹配的:调用了aspectj的匹配方法
  • 比较关键得是它实现了MethodMatcher接口,用来执行方法的匹配

十七、从 @Aspect 到 Advisor

1.创建代理器

准备工作:定义高级切面类Aspect1和配置类Config(配置类中定义低级切面类) ,创建一个【干净】的容器GenericApplicationContext,在容器中将Aspect1和Config注册进去,同时加入一些后处理器,具体实现如下:

public class A17 {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("aspect1", Aspect1.class);
        context.registerBean("config", Config.class);
        context.registerBean(ConfigurationClassPostProcessor.class);
        //BeanPostProcessor
        //创建 -> (*)依赖注入 -> 初始化(*)
        context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }

    @Aspect //高级切面类
    static class Aspect1{
        @Before("execution(* foo())")
        public void before(){
            System.out.println("aspect1 before...");
        }

        @After("execution(* foo())")
        public void after(){
            System.out.println("aspect1 after...");
        }
    }

    @Configuration
    static class Config{
        @Bean //低级切面
        public Advisor advisor3(MethodInterceptor advice3){
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression("execution(* foo())");
            return new DefaultPointcutAdvisor(pointcut,advice3);
        }
        @Bean
        public MethodInterceptor advice3(){
            return invocation -> {
                System.out.println("advice3 before...");
                Object result = invocation.proceed();//调用目标
                System.out.println("advice3 after...");
                return result;
            };
        }
    }
}

测试结果:

aspect1
config
org.springframework.context.annotation.ConfigurationClassPostProcessor
advisor3
advice3

由测试结果可以看出,容器中有加进去的切面类和配置类,以及Bean工厂的后处理器,还有进一步解析Config类中@Bean的低级切面类Advisor3和通知advice3

接下来就是需要探究如何将高级切面转换为低级切面,这个时候就需要引入AnnotationAwareAspectJAutoProxyCreator,它继承了AbstractAdvisorAutoProxyCreator这个类,现在需要调用AbstractAdvisorAutoProxyCreator这个类中的findEligibleAdvisorswrapIfNecessary 方法,然而这两个方法都是protected,所以无法直接调用,此时我们可以走个小捷径(定义和AbstractAdvisorAutoProxyCreator相同的包,把测试类放到对应包下),就可以直接调用那两个方法了

 在原有基础上,定义两个目标类Target1和Target2,然后在main方法中演示这两个方法的作用,具体实现如下:

 static class Target1{
        public void foo(){
            System.out.println("target1 foo");
        }
    }

    static class Target2{
        public void bar(){
            System.out.println("target2 bar");
        }
    }
AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
List<Advisor> advisors = creator.findEligibleAdvisors(Target1.class, "target1");
for (Advisor advisor : advisors) {
    System.out.println(advisor);
 }

Object o1 = creator.wrapIfNecessary(new Target1(), "target1", "target1");
System.out.println(o1.getClass());
Object o2 = creator.wrapIfNecessary(new Target2(), "target2", "target2");
System.out.println(o2.getClass());

((Target1) o1).foo();

测试结果:

org.springframework.aop.interceptor.ExposeInvocationInterceptor.ADVISOR
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.framework.autoproxy.A17$Config$$Lambda$56/275266973@158a8276]
InstantiationModelAwarePointcutAdvisor: expression [execution(* foo())]; advice method [public void org.springframework.aop.framework.autoproxy.A17$Aspect1.before()]; perClauseKind=SINGLETON
InstantiationModelAwarePointcutAdvisor: expression [execution(* foo())]; advice method [public void org.springframework.aop.framework.autoproxy.A17$Aspect1.after()]; perClauseKind=SINGLETON
class org.springframework.aop.framework.autoproxy.A17$Target1$$EnhancerBySpringCGLIB$$ca2177bd
class org.springframework.aop.framework.autoproxy.A17$Target2
advice3 before...
aspect1 before...
target1 foo
aspect1 after...
advice3 after...

总结:

  1. AnnotationAwareAspectJAutoProxyCreator 的作用

    • 将高级 @Aspect 切面统一为低级 Advisor 切面

    • 在合适的时机创建代理

  2. findEligibleAdvisors 找到有【资格】的 Advisors

    • 有【资格】的 Advisor 一部分是低级的, 可以由自己编写, 如本例 A17 中的 advisor3

    • 有【资格】的 Advisor 另一部分是高级的, 由解析 @Aspect 后获得

  3. wrapIfNecessary

    • 它内部调用 findEligibleAdvisors, 只要返回集合不空, 则表示需要创建代理

    • 它的调用时机通常在原始对象初始化后执行, 但碰到循环依赖会提前至依赖注入之前执行

2.代理创建时机

定义 一个配置类Config和Bean1、Bean2两个类(在其中定义其构造方法和初始化方法),观察代理的创建时机,具体实现如下:

public class A17_1 {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.registerBean(Config.class);
        //BeanPostProcessor
        //创建 -> (*)依赖注入 -> 初始化(*)
        context.refresh();
        context.close();
    }


    @Configuration
    static class Config{
        @Bean//解析@Aspect、产生代理
        public AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator(){
            return new AnnotationAwareAspectJAutoProxyCreator();
        }

        @Bean//解析@Autowired
        public AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor(){
            return new AutowiredAnnotationBeanPostProcessor();
        }

        @Bean//解析@PostConstruct
        public CommonAnnotationBeanPostProcessor commonAnnotationBeanPostProcessor(){
            return new CommonAnnotationBeanPostProcessor();
        }

        @Bean
        public Advisor advisor(MethodInterceptor advice){
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression("execution(* foo())");
            return new DefaultPointcutAdvisor(pointcut,advice);
        }

        @Bean
        public MethodInterceptor advice(){
            return  invocation -> {
                System.out.println("before...");
                return invocation.proceed();
            };
        }

        @Bean
        public Bean1 bean1(){
            return new Bean1();
        }

        @Bean
        public Bean2 bean2(){
            return new Bean2();
        }

    }

    //循环依赖
    static class Bean1{
        public void foo(){}
        public Bean1(){
            System.out.println("Bean1()");
        }
        //@Autowired
        //public void setBean2(Bean2 bean2){
        //    System.out.println("Bean1 setBean2(bean2) class is:"+bean2.getClass());
        //}
        @PostConstruct
        public void init(){
            System.out.println("Bean1 init()");
        }
    }

    static class Bean2{
        public Bean2(){
            System.out.println("Bean2()");
        }
        @Autowired
        public void setBean1(Bean1 bean1){
            System.out.println("Bean2 setBean1(bean1) class is:"+bean1.getClass());
        }
        @PostConstruct
        public void init(){
            System.out.println("Bean2 init()");
        }
    }

}

 测试结果如下:

Bean1()
Bean1 init()
Bean2()
Bean2 setBean1(bean1) class is:class org.springframework.aop.framework.autoproxy.A17_1$Bean1$$EnhancerBySpringCGLIB$$e1c9fbc5
Bean2 init()

可以看出,代理创建是在Bean1的初始化之后实现的,而且在Bean2中注入的是Bean1的代理对象

接下来,在Bean1中注入Bean2,构成循环依赖,具体实现如下:

 static class Bean1{
        public void foo(){}
        public Bean1(){
            System.out.println("Bean1()");
        }
        @Autowired
        public void setBean2(Bean2 bean2){
            System.out.println("Bean1 setBean2(bean2) class is:"+bean2.getClass());
        }
        @PostConstruct
        public void init(){
            System.out.println("Bean1 init()");
        }
    }

再看测试结果:

Bean1()
Bean2()
Bean2 setBean1(bean1) class is:class org.springframework.aop.framework.autoproxy.A17_1$Bean1$$EnhancerBySpringCGLIB$$fd0227a
Bean2 init()
Bean1 setBean2(bean2) class is:class org.springframework.aop.framework.autoproxy.A17_1$Bean2
Bean1 init()

可以看出,代理创建是在注入Bean2之前实现的

总结:

  1. 代理的创建时机

    • 初始化之后 (无循环依赖时)

    • 实例创建后, 依赖注入前 (有循环依赖时), 并暂存于二级缓存

  2. 依赖注入与初始化不应该被增强, 仍应被施加于原始对象

3.高级切面转低级切面

 定义一个切面类Aspect和目标类Target,这里主要演示@Before前置通知转换对应的低级通知,具体实现如下:

public class A17_2 {

    static class Aspect{
        @Before("execution(* foo())")
        public void before1(){
            System.out.println("before1");
        }
        @Before("execution(* foo())")
        public void before2(){
            System.out.println("before2");
        }
        public void after(){
            System.out.println("after");
        }
        public void afterReturning(){
            System.out.println("afterReturning");
        }
        public void afterThrowing(){
            System.out.println("afterThrowing");
        }
        public Object around(ProceedingJoinPoint pjp)throws Throwable{
            try {
                System.out.println("around...before");
                return pjp.proceed();
            } finally {
                System.out.println("around...after");
            }
        }
    }

    static class Target{
        public void foo(){
            System.out.println("target foo");
        }
    }

    public static void main(String[] args) {

        AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
        //高级切面类转低级切面类
        List<Advisor> list = new ArrayList<>();
        for (Method method : Aspect.class.getDeclaredMethods()) {
            if (method.isAnnotationPresent(Before.class)) {
                //解析切点
                String expression = method.getAnnotation(Before.class).value();
                AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
                pointcut.setExpression(expression);
                //通知类  第三个参数:切面的实例对象
                AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);
                //切面
                Advisor advisor = new DefaultPointcutAdvisor(pointcut,advice);
                list.add(advisor);
            }
        }

        for (Advisor advisor : list) {
            System.out.println(advisor);
        }

    }
}

测试结果:

org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void org.springframework.aop.framework.autoproxy.A17_2$Aspect.before2()]; aspect name '']
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void org.springframework.aop.framework.autoproxy.A17_2$Aspect.before1()]; aspect name '']

可以看出,@Before前置通知转换对应的低级通知

总结:

  1. @Before 前置通知会被转换为原始的 AspectJMethodBeforeAdvice 形式, 该对象包含了如下信息

    1. 通知代码从哪儿来

    2. 切点是什么(这里为啥要切点, 后面解释)

    3. 通知对象如何创建, 本例共用同一个 Aspect 对象

  2. 类似的还有

    1. AspectJAroundAdvice (环绕通知)

    2. AspectJAfterReturningAdvice

    3. AspectJAfterThrowingAdvice (环绕通知)

    4. AspectJAfterAdvice (环绕通知)

十八、静态通知调用

1.不同通知统一转换为环绕通知(适配器模式)

定义一个切面类Aspect和一个目标类Target:

 static class Aspect{
        @Before("execution(* foo())")
        public void before1(){
            System.out.println("before1");
        }
        @Before("execution(* foo())")
        public void before2(){
            System.out.println("before2");
        }
        public void after(){
            System.out.println("after");
        }
        @AfterReturning("execution(* foo())")
        public void afterReturning(){
            System.out.println("afterReturning");
        }
        @AfterThrowing("execution(* foo())")
        public void afterThrowing(Exception e){
            System.out.println("afterThrowing"+e.getMessage());
        }
        @Around("execution(* foo())")
        public Object around(ProceedingJoinPoint pjp)throws Throwable{
            try {
                System.out.println("around...before");
                return pjp.proceed();
            } finally {
                System.out.println("around...after");
            }
        }
    }

    static class Target{
        public void foo(){
            System.out.println("target foo");
        }
    }

演示转换过程:

        1.高级切面类转换为低级切面类:

 AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new A18.Aspect());
        //1.高级切面类转低级切面类
        List<Advisor> list = new ArrayList<>();
        for (Method method : A18.Aspect.class.getDeclaredMethods()) {
            if (method.isAnnotationPresent(Before.class)) {
                //解析切点
                String expression = method.getAnnotation(Before.class).value();
                AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
                pointcut.setExpression(expression);
                //通知类  第三个参数:切面的实例对象
                AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);
                //切面
                Advisor advisor = new DefaultPointcutAdvisor(pointcut,advice);
                list.add(advisor);
            } else if (method.isAnnotationPresent(AfterReturning.class)) {
                //解析切点
                String expression = method.getAnnotation(AfterReturning.class).value();
                AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
                pointcut.setExpression(expression);
                //通知类  第三个参数:切面的实例对象
                AspectJAfterReturningAdvice advice = new AspectJAfterReturningAdvice(method, pointcut, factory);
                //切面
                Advisor advisor = new DefaultPointcutAdvisor(pointcut,advice);
                list.add(advisor);
            } else if (method.isAnnotationPresent(Around.class)) {
                //解析切点
                String expression = method.getAnnotation(Around.class).value();
                AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
                pointcut.setExpression(expression);
                //通知类  第三个参数:切面的实例对象
                AspectJAroundAdvice advice = new AspectJAroundAdvice(method, pointcut, factory);
                //切面
                Advisor advisor = new DefaultPointcutAdvisor(pointcut,advice);
                list.add(advisor);
            }
        }

        for (Advisor advisor : list) {
            System.out.println(advisor);
        }

        转换结果:

org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJAfterReturningAdvice: advice method [public void org.springframework.aop.framework.A18$Aspect.afterReturning()]; aspect name '']
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void org.springframework.aop.framework.A18$Aspect.before1()]; aspect name '']
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void org.springframework.aop.framework.A18$Aspect.before2()]; aspect name '']
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJAroundAdvice: advice method [public java.lang.Object org.springframework.aop.framework.A18$Aspect.around(org.aspectj.lang.ProceedingJoinPoint) throws java.lang.Throwable]; aspect name '']

        2.通知统一转换为环绕通知 MethodInterceptor

/*
         * 无论ProxyFactory基于哪种方式创建代理,最后干活(调用advice)的是一个MethodInvocation对象
         *      a.因为advisor有多个,且一个套一个调用,因此需要一个调用链对象,即MethodInvocation
         *      b.MethodInvocation要知道advice有哪些,还要知道目标,调用次序如下:
         *
         *          将MethodInvocation 放入当前线程
         *          | -> before1-------------------------------------- 从当前线程获取MethodInvocation
         *          |     | -> before2-----------------------         |从当前线程获取MethodInvocation
         *          |     |                                 |         |
         *          |     |    | -> target ------- 目标  advice2    advice1
         *          |     |                                 |         |
         *          |     | -> after2------------------------         |
         *          |                                                 |
         *          | -> after1----------------------------------------
         *
         *       c.从上图看出,环绕通知才适合作为advice,因此其他before、afterReturning 都会被转换为环绕通知
         *       d.统一转换为环绕通知,体现的是设计模式中的适配器模式
         *          - 对外是为了方便使用要区分 before、afterReturning
         *          - 对内统一都是环绕通知,统一用MethodInterceptor
         *     此步获取所有执行时需要的advice(静态)
         *       a.即统一转换为MethodInterceptor 环绕通知,这体现在方法名中的Interceptors上
         *       b.适配如下:
         *          -   MethodBeforeAdviceAdapter 将@Before解析后的AspectJMethodBeforeAdvice适配为MethodBeforeAdviceInterceptor
         *          -   AfterReturningAdviceAdapter 将@AfterReturning解析后的AspectJAfterReturningAdvice适配为AfterReturningAdviceInterceptor
         */
        Target target = new Target();
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target);
        proxyFactory.addAdvice(ExposeInvocationInterceptor.INSTANCE);//准备把 MethodInvocation放入当前线程  如果不把它放入当前线程,则会报IllegalStateException异常,这是因为一些advice内部需要用到调用链对象
        proxyFactory.addAdvisors(list);

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        //获得环绕通知集合
        List<Object> methodInterceptorList = proxyFactory.getInterceptorsAndDynamicInterceptionAdvice(Target.class.getMethod("foo"), Target.class);
        for (Object o : methodInterceptorList) {
            System.out.println(o);
        }

        转换为环绕通知的结果:

org.springframework.aop.interceptor.ExposeInvocationInterceptor@1bce4f0a
org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor@5e3a8624
org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@5c3bd550
org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@91161c7
org.springframework.aop.aspectj.AspectJAroundAdvice: advice method [public java.lang.Object org.springframework.aop.framework.A18$Aspect.around(org.aspectj.lang.ProceedingJoinPoint) throws java.lang.Throwable]; aspect name ''

2.无参数绑定通知链执行过程(责任链模式)

接着上面的过程,实现第三步:

        3.创建并执行调用链(环绕通知s + 目标) 选择实现的快捷键(Ctrl+Alt+B)

 //参数:代理对象,目标对象,目标对象中的方法,方法中对应的参数,目标类型,转换好的环绕通知
        MethodInvocation methodInvocation = new ReflectiveMethodInvocation(
                null, target,Target.class.getMethod("foo"), new Object[0], Target.class, methodInterceptorList);
        methodInvocation.proceed();//一层一层调用目标

         结果如下:

before1
before2
around...before
target foo
around...after
afterReturning

总结:

  1. 通过 proxyFactory 的 getInterceptorsAndDynamicInterceptionAdvice() 将其他通知统一转换为 MethodInterceptor 环绕通知

    • MethodBeforeAdviceAdapter 将 @Before AspectJMethodBeforeAdvice 适配为 MethodBeforeAdviceInterceptor

    • AfterReturningAdviceAdapter 将 @AfterReturning AspectJAfterReturningAdvice 适配为 AfterReturningAdviceInterceptor

    • 这体现的是适配器设计模式

  2. 所谓静态通知,体现在上面方法的 Interceptors 部分,这些通知调用时无需再次检查切点,直接调用即可

  3. 结合目标与环绕通知链,创建 MethodInvocation 对象,通过它完成整个调用  

3.模拟实现MethodInvocation

定义目标类Target,两个通知类Advice1和Advice2,具体实现如下:

static class Target{
        public void foo(){
            System.out.println("Target.foo()");
        }
    }

    static class Advice1 implements MethodInterceptor{
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("Advice1.before()");
            Object result = invocation.proceed();//调用下一个通知或者目标
            System.out.println("Advice1.after()");
            return result;
        }
    }

    static class Advice2 implements MethodInterceptor{
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("Advice2.before()");
            Object result = invocation.proceed();//调用下一个通知或者目标
            System.out.println("Advice2.after()");
            return result;
        }
    }

 定义MyInvocation类,具体实现如下:

 static class MyInvocation implements MethodInvocation{

        private Object target;
        private Method method;
        private Object[] args;
        //环绕通知集合
        List<MethodInterceptor> methodInterceptorList;
        private int count = 1;

        public MyInvocation(Object target, Method method, Object[] args, List<MethodInterceptor> methodInterceptorList) {
            this.target = target;
            this.method = method;
            this.args = args;
            this.methodInterceptorList = methodInterceptorList;
        }

        @Override
        public Method getMethod() {
            return method;
        }

        @Override
        public Object[] getArguments() {
            return args;
        }

        @Override
        public Object proceed() throws Throwable {//调用每一个环绕通知,调用目标
            if (count > methodInterceptorList.size()) {
                //调用目标,返回并结束递归
                return method.invoke(target, args);
            }
            //逐一调用通知,count + 1
            MethodInterceptor methodInterceptor = methodInterceptorList.get(count++ - 1);
            return methodInterceptor.invoke(this);
        }

        @Override
        public Object getThis() {
            return target;
        }

        @Override
        public AccessibleObject getStaticPart() {
            return method;
        }
    }

proceed() 方法调用链中下一个环绕通知,每个环绕通知内部继续调用 proceed(),调用到没有更多通知了, 就调用目标方法

4.代理对象调用流程(以 JDK 动态代理实现为例)

  • 从 ProxyFactory 获得 Target 和环绕通知链,根据他俩创建 MethodInvocation,简称 mi

  • 首次执行 mi.proceed() 发现有下一个环绕通知,调用它的 invoke(mi)

  • 进入环绕通知1,执行前增强,再次调用 mi.proceed() 发现有下一个环绕通知,调用它的 invoke(mi)

  • 进入环绕通知2,执行前增强,调用 mi.proceed() 发现没有环绕通知,调用 mi.invokeJoinPoint() 执行目标方法

  • 目标方法执行结束,将结果返回给环绕通知2,执行环绕通知2 的后增强

  • 环绕通知2继续将结果返回给环绕通知1,执行环绕通知1 的后增强

  • 环绕通知1返回最终的结果

图中不同颜色对应一次环绕通知或目标的调用起始至终结

十九、动态通知调用

定义一个通知类MyAspect(定义一个无参数绑定的通知和一个有参数绑定的通知)、一个目标类Target以及一个配置类MyConfig,具体实现如下:

 @Aspect
    static class MyAspect{
        @Before("execution(* foo(..))")//静态通知调用,不带参数绑定,执行时不需要切点对象
        public void before1(){
            System.out.println("before1");
        }
        @Before("execution(* foo(..)) && args(x)")//动态通知调用,需要参数绑定,执行时还需要切点对象
        public void before2(int x){
            System.out.printf("before2(%d)%n",x);
        }
    }

    static class Target{
        public void foo(int x){
            System.out.printf("target foo(%d)%n",x);
        }
    }

    @Configuration
    static class MyConfig{
        @Bean
        AnnotationAwareAspectJAutoProxyCreator proxyCreator(){//1.把高级切面转换为低级切面 2.创建代理对象
            return new AnnotationAwareAspectJAutoProxyCreator();
        }

        @Bean
        public MyAspect myAspect(){
            return new MyAspect();
        }
    }

测试代码:

public static void main(String[] args) throws Throwable {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.registerBean(MyConfig.class);
        context.refresh();

        AnnotationAwareAspectJAutoProxyCreator proxyCreator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
        List<Advisor> list = proxyCreator.findEligibleAdvisors(Target.class, "target");

        Target target = new Target();
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(target);
        factory.addAdvisors(list);
        Object proxy = factory.getProxy();//获取代理

        //获得环绕通知集合
        List<Object> interceptorList = factory.getInterceptorsAndDynamicInterceptionAdvice(Target.class.getMethod("foo", int.class), Target.class);
        for (Object o : interceptorList) {
            showDetail(o);
        }

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        ReflectiveMethodInvocation invocation = new ReflectiveMethodInvocation(
                proxy,target,Target.class.getMethod("foo", int.class),new Object[]{100},Target.class,interceptorList
        ){};

        invocation.proceed();
    }

    public static void showDetail(Object o) {
        try {
            Class<?> clazz = Class.forName("org.springframework.aop.framework.InterceptorAndDynamicMethodMatcher");
            if (clazz.isInstance(o)) {
                Field methodMatcher = clazz.getDeclaredField("methodMatcher");
                methodMatcher.setAccessible(true);
                Field methodInterceptor = clazz.getDeclaredField("interceptor");
                methodInterceptor.setAccessible(true);
                System.out.println("环绕通知和切点:" + o);
                System.out.println("\t切点为:" + methodMatcher.get(o));
                System.out.println("\t通知为:" + methodInterceptor.get(o));
            } else {
                System.out.println("普通环绕通知:" + o);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  
}

测试结果如下:

普通环绕通知:org.springframework.aop.interceptor.ExposeInvocationInterceptor@10d68fcd
普通环绕通知:org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@117e949d
环绕通知和切点:org.springframework.aop.framework.InterceptorAndDynamicMethodMatcher@5f8edcc5
	切点为:AspectJExpressionPointcut: (int x) execution(* foo(..)) && args(x)
	通知为:org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@7b02881e
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
before1
before2(100)
target foo(100)

静态通知调用和动态通知调用的对比

  • 静态通知调用不需要参数绑定,而动态通知调用需要参数绑定
  • 静态通知调用执行时不需要切点对象,动态通知调用执行时需要切点对象
  • 静态通知调用复杂程度低,性能较高;动态同通知调用复杂度高,性能较低

廿、RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter

1.DispatcherServlet 初始化

定义一个配置类WebConfig,具体实现如下:

@Configuration
@ComponentScan
@PropertySource("classpath:application.properties")
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})
public class WebConfig {
    //内嵌web容器工厂
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }

    //创建DispatcherServlet
    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    //注册DispatcherServlet,Spring MVC的入口
    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet,
                                                                               WebMvcProperties webMvcProperties) {
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
        return registrationBean;
    }
}

测试代码如下:

public class A20 {

    private static final Logger log = LoggerFactory.getLogger(A20.class);

    public static void main(String[] args) throws Exception {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
 }
}

总结:

  1. DispatcherServlet 是在第一次被访问时执行初始化, 也可以通过配置修改为 Tomcat 启动后就初始化

  2. 在初始化时会从 Spring 容器中找一些 Web 需要的组件, 如 HandlerMapping、HandlerAdapter 等,并逐一调用它们的初始化

2.RequestMappingHandlerMapping

定义一个Controller1控制器类,其中定义四个控制器方法 ,定义一个实体类User,具体实现如下:

@Controller
public class Controller1 {

    private static final Logger log = LoggerFactory.getLogger(Controller1.class);

    @GetMapping("/test1")
    public ModelAndView test1() throws Exception{
        log.debug("test1()");
        return null;
    }

    @PostMapping("/test2")
    public ModelAndView test2(@RequestParam("name") String name){
        log.debug("test2({})",name);
        return null;
    }

    @PutMapping("/test3")
    public ModelAndView test3(@Token String token){
        log.debug("test3({})",token);
        return null;
    }

    @RequestMapping("/test4")
    @Yml
    public User test4(){
        log.debug("test4");
        return new User("张三", 18);
    }

    public static class User{
        private String name;
        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }

    public static void main(String[] args) {
        String str = new Yaml().dump(new User("张三", 18));
        System.out.println(str);
    }
    
}

如果使用DispatcherServlet 初始化时默认添加的组件,并不会作为bean,给测试带来困扰,所以此时需要在WebConfig配置类中自己加入RequestMappingHandlerMapping,即如下代码:

 //1.加入RequestMappingHandlerMapping
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping(){
        return new RequestMappingHandlerMapping();
    }

 在测试代码中,获取容器中自己加入的RequestMappingHandlerMapping,用来解析@RequestMapping注解 以及其派生注解,生成路径与控制器方法的映射关系 在初始化时生成,模拟请求来了,获取控制器方法 返回处理器执行链对象,具体实现如下:

   //作用: 解析@RequestMapping注解 以及其派生注解,生成路径与控制器方法的映射关系  在初始化时生成
        RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);

        //获得映射结果
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
        handlerMethods.forEach((k,v) -> {
            System.out.println(k + "=" + v);
        });

        //请求来了,获取控制器方法 返回处理器执行链对象
        MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/test3");
        HandlerExecutionChain chain = handlerMapping.getHandler(request);
        System.out.println(chain);

测试结果如下:

{GET [/test1]}=com.itheima.a20.Controller1#test1()
{ [/test4]}=com.itheima.a20.Controller1#test4()
{POST [/test2]}=com.itheima.a20.Controller1#test2(String)
{PUT [/test3]}=com.itheima.a20.Controller1#test3(String)
09:47:58.156 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped to com.itheima.a20.Controller1#test3(String)
HandlerExecutionChain with [com.itheima.a20.Controller1#test3(String)] and 0 interceptors

总结:

RequestMappingHandlerMapping 初始化时,会收集所有 @RequestMapping 映射信息,封装为 Map,其中

  • key 是 RequestMappingInfo 类型,包括请求路径、请求方法等信息

  • value 是 HandlerMethod 类型,包括控制器方法对象、控制器对象

  • 有了这个 Map,就可以在请求到达时,快速完成映射,找到 HandlerMethod 并与匹配的拦截器一起返回给 DispatcherServlet

3.RequestMappingHandlerAdapter

调用RequestMappingHandlerAdapter类中的invokeHandlerMethod方法,发现这个方法是受保护的,无法直接调用,所以这里自己创建一个RequestMappingHandlerAdapter的子类MyRequestMappingHandlerAdapter,在其中重写invokeHandlerMethod方法,修改修饰符为public,具体实现如下:

public class MyRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {

    @Override
    public ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        return super.invokeHandlerMethod(request, response, handlerMethod);
    }
}

在WebConfig配置类中继续加入MyRequestMappingHandlerAdapter,具体实现如下:

@Bean
    public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter(){
       return new MyRequestMappingHandlerAdapter;
    }

 在测试代码中,获取容器中自己加入的MyRequestMappingHandlerAdapter,然后调用invokeHandlerMethod方法,具体实现如下:

MockHttpServletRequest request = new MockHttpServletRequest("POST", "/test2");
request.setParameter("name", "张三");
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
//HandlerAdapter 作用: 调用控制器方法
MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
handlerAdapter.invokeHandlerMethod(request,response, (HandlerMethod) chain.getHandler());

测试结果如下:

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
10:17:17.141 [main] DEBUG com.itheima.a20.Controller1 - test2(张三)

很明显,@RequestParam注解被“识别”到,之所以会被识别到,是因为RequestMappingHandlerAdapter中有很多参数解析器和返回值解析器,在测试代码中输出这些解析器,具体实现如下:

System.out.println(">>>>>>>>>>>>> 所有的参数解析器:");
for (HandlerMethodArgumentResolver resolver : handlerAdapter.getArgumentResolvers()) {
            System.out.println(resolver);
        }
System.out.println(">>>>>>>>>>>>> 所有的返回值解析器:");
for (HandlerMethodReturnValueHandler handler : handlerAdapter.getReturnValueHandlers()) {
            System.out.println(handler);
        }

输出结果如下:

>>>>>>>>>>>>> 所有的参数解析器:
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@5990e6c5
org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@56e07a08
org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@35d6ca49
org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@1950e8a6
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@47289387
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@12cd9150
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@114a85c2
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@f415a95
org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@cf65451
org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@724f138e
org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@37eeec90
org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@32fe9d0a
org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@c9413d8
org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@64da2a7
org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@46074492
org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@d78795
org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@2c715e84
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@47428937
org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@3b9d6699
org.springframework.web.method.annotation.ModelMethodProcessor@7caa550
org.springframework.web.method.annotation.MapMethodProcessor@21694e53
org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@72b16078
org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@22c86919
org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@70fab835
com.itheima.a20.TokenArgumentResolver@1b0a7baf
org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@62417a16
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@32057e6
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@26be6ca7
>>>>>>>>>>>>> 所有的返回值解析器:
org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler@6ea1bcdc
org.springframework.web.method.annotation.ModelMethodProcessor@759fad4
org.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler@64712be
org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler@53499d85
org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler@30ed9c6c
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@782a4fff
org.springframework.web.servlet.mvc.method.annotation.HttpHeadersReturnValueHandler@46c670a6
org.springframework.web.servlet.mvc.method.annotation.CallableMethodReturnValueHandler@59fc684e
org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler@5ae81e1
org.springframework.web.servlet.mvc.method.annotation.AsyncTaskMethodReturnValueHandler@2fd1731c
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@5ae76500
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@6063d80a
org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler@1133ec6e
org.springframework.web.method.annotation.MapMethodProcessor@355e34c7
com.itheima.a20.YmlReturnValueHandler@54709809
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@2a2da905

总结:

 RequestMappingHandlerAdapter 初始化时,会准备 HandlerMethod 调用时需要的各个组件,如:

  • HandlerMethodArgumentResolver 解析控制器方法参数

  • HandlerMethodReturnValueHandler 处理控制器方法返回值

4.自定义参数解析器

 自定义一个@Token注解,具体实现如下:

//自定义token注解
//经常需要用到请求头中的token信息,用下面注解来标注由那个参数来获取他
//token=令牌
@Target(ElementType.PARAMETER) //加在方法的参数上
@Retention(RetentionPolicy.RUNTIME) //注解在运行期都有效
public @interface Token {
}

接下来就是自定义Token参数解析器TokenArgumentResolver,实现HandlerMethodArgumentResolver接口,具体实现如下:

public class TokenArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    //是否支持某个参数 true表示支持某个参数,可以解析  parameter:封装方法中的参数信息
    public boolean supportsParameter(MethodParameter parameter) {
        Token token = parameter.getParameterAnnotation(Token.class);
        return token != null;
    }

    @Override
    //解析参数
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        return webRequest.getHeader("token");
    }
}

在WebConfig配置类的MyRequestMappingHandlerAdapter中设置自定义参数解析器 ,具体实现如下:

@Bean
    public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter(){
        TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver();
        MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
        //设置自定义参数解析器
        List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();
        argumentResolvers.add(tokenArgumentResolver);
        handlerAdapter.setCustomArgumentResolvers(argumentResolvers);
        return handlerAdapter;
    }

修改测试代码:

MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/test3");
request.addHeader("token", "某个令牌");

测试结果如下:

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
10:55:02.407 [main] DEBUG com.itheima.a20.Controller1 - test3(某个令牌)

成功解析自定义Token注解

5.自定义返回值处理器

自定义一个@Yml 注解,具体实现如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Yml {
}

接下来就是自定义加了@Yml注解的返回值处理器YmlReturnValueHandler,实现HandlerMethodReturnValueHandler接口,具体实现如下:

public class YmlReturnValueHandler implements HandlerMethodReturnValueHandler {
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        Yml yml = returnType.getMethodAnnotation(Yml.class);
        return yml != null;
    }

    @Override //returnValue:返回值   webRequest:既有原始的请求,也有原始的响应
    public void handleReturnValue(Object returnValue, MethodParameter returnType,
                                  ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        //1.转换返回结果为 yaml 字符串
        String str = new Yaml().dump(returnValue);

        //2.将yaml字符串写入响应
        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
        response.setContentType("text/plain;charset=utf-8");
        response.getWriter().print(str);

        //3.设置请求已经处理完毕
        mavContainer.setRequestHandled(true);
    }
}

在WebConfig配置类的MyRequestMappingHandlerAdapter中设置自定义返回值解析器 ,具体实现如下:

@Bean
    public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter(){
        TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver();
        YmlReturnValueHandler ymlReturnValueHandler = new YmlReturnValueHandler();
        MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
        //设置自定义参数解析器
        List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();
        argumentResolvers.add(tokenArgumentResolver);
        handlerAdapter.setCustomArgumentResolvers(argumentResolvers);
        //设置自定义返回值处理器
        List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<>();
        returnValueHandlers.add(ymlReturnValueHandler);
        handlerAdapter.setCustomReturnValueHandlers(returnValueHandlers);
        return handlerAdapter;
    }

在测试代码中修改测试请求并添加检查响应的代码:

MockHttpServletRequest request = new MockHttpServletRequest("GET","/test4");
//检查响应
byte[] content = response.getContentAsByteArray();
System.out.println(new String(content, StandardCharsets.UTF_8));

测试结果如下:

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
11:13:10.486 [main] DEBUG com.itheima.a20.Controller1 - test4
!!com.itheima.a20.Controller1$User {age: 18, name: 张三}

成功解析返回结果

廿一、参数解析器

1.常见解析器的作用

 准备工作:

        定义一个控制器类ControllerUser,在其中测试常见参数的解析,同时定义测试需要的实体类User,具体实现如下:

static class Controller {
        public void test(@RequestParam("name1") String name1,
                         String name2,
                         @RequestParam("age") int age,
                         @RequestParam(name = "home", defaultValue = "${CATALINA_HOME}") String home1,
                         @RequestParam("file") MultipartFile file,
                         @PathVariable("id") int id,
                         @RequestHeader("Content-Type") String header,
                         @CookieValue("token") String token,
                         @Value("${CATALINA_HOME}") String home2,
                         HttpServletRequest request,
                         @ModelAttribute User user1,
                         User user2,
                         @RequestBody User user3
        ) {
        }
    } 
static class User {
        private String name;
        private int age;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

定义一个模拟浏览器发送请求的方法mockRequest(),方便进行测试,具体实现如下:

private static HttpServletRequest mockRequest() {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("name1", "zhangsan");
        request.setParameter("name2", "lisi");
        request.addPart(new MockPart("file", "abc", "hello".getBytes(StandardCharsets.UTF_8)));
        Map<String, String> map = new AntPathMatcher()
                .extractUriTemplateVariables("/test/{id}", "/test/123");
        request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, map);
        request.setContentType("application/json");
        request.setCookies(new Cookie("token", "123456"));
        request.setParameter("name", "zhangsan");
        request.setParameter("age", "18");
        request.setContent("""
                    {
                        "name":"李四",
                        "age":20
                    }"""
                .getBytes(StandardCharsets.UTF_8));
        return new StandardServletMultipartResolver().resolveMultipart(request);
    }

测试@RequestParam注解,具体实现如下:

public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();

        //准备测试request
        HttpServletRequest request = mockRequest();

        //要点1. 控制器方法被封装为HandlerMethod
        HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getMethod("test", String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class, String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));

        //要点2. 准备对象绑定与类型转换
        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null,null);

        //要点3. 准备ModelAndViewController用来存储中间 Model 结果
        ModelAndViewContainer container = new ModelAndViewContainer();

        //要点4. 解析每个参数值
        for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
            //true表示不是必须有@RequestParam注解,false表示必须有@RequestParam注解
            RequestParamMethodArgumentResolver resolver = new RequestParamMethodArgumentResolver(beanFactory, false);
            //获得参数上的注解
            String annotations = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());
            String str = annotations.length() > 0 ? " @" + annotations + " " : " ";
            //设置参数名解析器
            parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());

            if (resolver.supportsParameter(parameter)) {
                //支持此参数
                Object v = resolver.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
                System.out.println("[" + parameter.getParameterIndex() + "]" + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() + "->" + v);
                System.out.println("模型数据为:"+container.getModel());
            } else {
                System.out.println("[" + parameter.getParameterIndex() + "]" + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());
            }
        }
    }

测试结果如下:

[0] @RequestParam String name1->zhangsan
模型数据为:{}
[1] String name2
[2] @RequestParam int age->18
模型数据为:{}
09:47:47.789 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key 'CATALINA_HOME' in PropertySource 'systemEnvironment' with value of type String
[3] @RequestParam String home1->D:\Tomcat\apache-tomcat-9.0.37
模型数据为:{}
[4] @RequestParam MultipartFile file->org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@525575
模型数据为:{}
[5] @PathVariable int id
[6] @RequestHeader String header
[7] @CookieValue String token
[8] @Value String home2
[9] HttpServletRequest request
[10] @ModelAttribute User user1
[11] User user2
[12] @RequestBody User user3

进程已结束,退出代码为 0

可以看出加了@RequestParam注解的参数都已经被解析,没有加@RequestParam注解的参数没有被解析

总结:

  1. 初步了解 RequestMappingHandlerAdapter 的调用过程

    1. 控制器方法被封装为 HandlerMethod

    2. 准备对象绑定与类型转换

    3. 准备 ModelAndViewContainer 用来存储中间 Model 结果

    4. 解析每个参数值

  2. 解析参数依赖的就是各种参数解析器,它们都有两个重要方法

    • supportsParameter 判断是否支持方法参数

    • resolveArgument 解析方法参数

2.组合模式在Spring中的体现

添加多个解析器组合HandlerMethodArgumentResolverComposite,在其中添加解析各个注解所需的解析器,同时修改部分测试代码,具体实现如下:

//多个解析器组合
            HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
         composite.addResolvers(
                    new RequestParamMethodArgumentResolver(beanFactory,false),//必须有@RequestParam
                    new PathVariableMethodArgumentResolver(),
                    new RequestHeaderMethodArgumentResolver(beanFactory),
                    new ServletCookieValueMethodArgumentResolver(beanFactory),
                    new ExpressionValueMethodArgumentResolver(beanFactory),
                    new ServletRequestMethodArgumentResolver(),
                    new ServletModelAttributeMethodProcessor(false),//必须有@ModelAttribute
                    new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())),
                    new ServletModelAttributeMethodProcessor(true),//省略@ModelAttribute
                    new RequestParamMethodArgumentResolver(beanFactory,true)//省略@RequestParam
            );

             if (composite.supportsParameter(parameter)) {
                //支持此参数
                Object v = composite.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
                System.out.println("[" + parameter.getParameterIndex() + "]" + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() + "->" + v);
                System.out.println("模型数据为:"+container.getModel());
            } else {
                System.out.println("[" + parameter.getParameterIndex() + "]" + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());
            }
        }

修改之后的测试结果如下:

[0] @RequestParam String name1->zhangsan
模型数据为:{}
[1] String name2->lisi
模型数据为:{}
[2] @RequestParam int age->18
模型数据为:{}
09:55:50.711 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key 'CATALINA_HOME' in PropertySource 'systemEnvironment' with value of type String
[3] @RequestParam String home1->D:\Tomcat\apache-tomcat-9.0.37
模型数据为:{}
[4] @RequestParam MultipartFile file->org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@3568f9d2
模型数据为:{}
[5] @PathVariable int id->123
模型数据为:{}
[6] @RequestHeader String header->application/json
模型数据为:{}
[7] @CookieValue String token->123456
模型数据为:{}
09:55:50.731 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key 'CATALINA_HOME' in PropertySource 'systemEnvironment' with value of type String
[8] @Value String home2->D:\Tomcat\apache-tomcat-9.0.37
模型数据为:{}
[9] HttpServletRequest request->org.springframework.web.multipart.support.StandardMultipartHttpServletRequest@34a875b3
模型数据为:{}
[10] @ModelAttribute User user1->User{name='zhangsan', age=18}
模型数据为:{user=User{name='zhangsan', age=18}, org.springframework.validation.BindingResult.user=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
[11] User user2->User{name='zhangsan', age=18}
模型数据为:{user=User{name='zhangsan', age=18}, org.springframework.validation.BindingResult.user=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
09:55:50.831 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Read "application/json" to [User{name='李四', age=20}]
[12] @RequestBody User user3->User{name='李四', age=20}
模型数据为:{user=User{name='zhangsan', age=18}, org.springframework.validation.BindingResult.user=org.springframework.validation.BeanPropertyBindingResult: 0 errors}

常见的参数解析:

  • @RequestParam ==> RequestParamMethodArgumentResolver(beanFactory,false)

  • 省略 @RequestParam ==> RequestParamMethodArgumentResolver(beanFactory,true)

  • @RequestParam(defaultValue)  

  • @PathVariable ==> PathVariableMethodArgumentResolver()

  • @RequestHeader ==> RequestHeaderMethodArgumentResolver(beanFactory)

  • @CookieValue ==> ServletCookieValueMethodArgumentResolver(beanFactory)

  • @Value ==> ExpressionValueMethodArgumentResolver(beanFactory)

  • HttpServletRequest 等 ==> ServletRequestMethodArgumentResolver()

  • @ModelAttribute ==> ServletModelAttributeMethodProcessor(false)

  • 省略 @ModelAttribute ==> ServletModelAttributeMethodProcessor(true)

  • @RequestBody ==> RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter()))

廿二、获取参数名

在自己的工程下面创建新的文件夹,不要放在src目录下(为了避免idea自动编译它,可以减少idea在自动编译时添加的一些选项),创建Bean2.Java文件 ,文件内容如下:

package com.itheima.a22;

public class Bean2 {
    public void foo(String name,int age){

    }
}

在终端中通过javac命令编译Bean2,编译后生成的class文件,通过idea反编译查看

package com.itheima.a22;

public class Bean2 {
    public Bean2() {
    }

    public void foo(String var1, int var2) {
    }
}

可以发现无法获取参数名

1.此时通过 javac -parameters .\Bean2.java 命令重新进行编译,查看编译后生成的class文件

package com.itheima.a22;

public class Bean2 {
    public Bean2() {
    }

    public void foo(String name, int age) {
    }
}

发现可以获取到参数名

2.测试通过 javac -g .\Bean2.java 命令重新进行编译,查看编译后生成的class文件,发现此时也是可以获取到参数名

通过 javap -c -v .\Bean2.class 命令分别反编译上述两个命令生成的class文件,可以看到通过javac -parameters .\Bean2.java 编译生成的class文件中会生成一个MethodParameters,在其中会包含参数信息;通过 javac -g .\Bean2.java 编译生成的class文件中会生成一LocalVariableTable本地变量表,其中也会包含参数信息

写测试类来区别这两种方式的差异,具体实现如下:

 public static void main(String[] args) throws NoSuchMethodException {
        //1.反射获取参数名
        Method foo = Bean2.class.getMethod("foo", String.class, int.class);
        for (Parameter parameter : foo.getParameters()) {
            System.out.println(parameter.getName());
        }
}

分别使用对两种命令编译后的文件,然后测试能否获得参数名字

经测试,可以发现编译时添加了 -parameters生成的参数表,可以通过反射拿到参数名,添加-g生成的本地变量表无法通过反射拿到参数名

更改测试代码:

LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(foo);
System.out.println(Arrays.toString(parameterNames));

此时发现编译时添加了 -parameters生成的参数表,通过这种方式拿到的是null,而添加-g生成的本地变量表可以通过这种方式拿到参数名

总结:

  1. 如果编译时添加了 -parameters 可以生成参数表, 反射时就可以拿到参数名

  2. 如果编译时添加了 -g 可以生成调试信息, 但分为两种情况

                普通类, 会包含局部变量表, 用 asm 可以拿到参数名

                接口, 不会包含局部变量表, 无法获得参数名

  • 这也是 MyBatis 在实现 Mapper 接口时为何要提供 @Param 注解来辅助获得参数名

Spring提供了DefaultParameterNameDiscoverer类,可以同时支持这两种方式

廿三、对象绑定与类型转换

1.底层接口与实现 

底层第一套转换接口与实现

  • Printer 把其它类型转为 String

  • Parser 把 String 转为其它类型

  • Formatter 综合 Printer 与 Parser 功能

  • Converter 把类型 S 转为类型 T

  • Printer、Parser、Converter 经过适配转换成 GenericConverter 放入 Converters 集合

  • FormattingConversionService 利用其它们实现转换

底层第二套转换接口

 

  • PropertyEditor 把 String 与其它类型相互转换

  • PropertyEditorRegistry 可以注册多个 PropertyEditor 对象

  • 与第一套接口直接可以通过 FormatterPropertyEditorAdapter 来进行适配

高层接口与实现

 

  • 它们都实现了 TypeConverter 这个高层转换接口,在转换时,会用到 TypeConverter Delegate 委派ConversionService 与 PropertyEditorRegistry 真正执行转换(Facade 门面模式)

    • 首先看是否有自定义转换器, @InitBinder 添加的即属于这种 (用了适配器模式把 Formatter 转为需要的 PropertyEditor)

    • 再看有没有 ConversionService 转换

    • 再利用默认的 PropertyEditor 转换

    • 最后有一些特殊处理

  • SimpleTypeConverter 仅做类型转换

  • BeanWrapperImpl 为 bean 的属性赋值,当需要时做类型转换,走 Property(getter和setter方法)

  • DirectFieldAccessor 为 bean 的属性赋值,当需要时做类型转换,走 Field(成员变量)

  • ServletRequestDataBinder 为 bean 的属性执行绑定,当需要时做类型转换,根据 directFieldAccess 选择走 Property 还是 Field,具备校验与获取校验结果功能

2.演示类型转换和对象绑定 

分别以高级接口中的四个实现作为演示 :

1.演示SimpleTypeConverter ,具体实现如下:

public class TestSimpleConverter {
    public static void main(String[] args) {

        //仅有类型转换功能
        SimpleTypeConverter converter = new SimpleTypeConverter();
        Integer number = converter.convertIfNecessary("13", int.class);
        Date date = converter.convertIfNecessary("1999/03/04", Date.class);
        System.out.println(number);
        System.out.println(date);
    }
}

测试结果:

13
Thu Mar 04 00:00:00 CST 1999

2.演示BeanWrapperImpl,具体实现如下:

public class TestBeanWrapper {
    public static void main(String[] args) {
        //利用反射原理,为bean的属性赋值
        MyBean target = new MyBean();
        BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
        wrapper.setPropertyValue("a","10");
        wrapper.setPropertyValue("b","hello");
        wrapper.setPropertyValue("c","1999/03/04");
        System.out.println(target);
    }

    static class MyBean{
        private int a;
        private String b;
        private Date c;

        public int getA() {
            return a;
        }

        public void setA(int a) {
            this.a = a;
        }

        public String getB() {
            return b;
        }

        public void setB(String b) {
            this.b = b;
        }

        public Date getC() {
            return c;
        }

        public void setC(Date c) {
            this.c = c;
        }

        @Override
        public String toString() {
            return "MyBean{" +
                    "a=" + a +
                    ", b='" + b + '\'' +
                    ", c=" + c +
                    '}';
        }
    }
}

测试结果:

MyBean{a=10, b='hello', c=Thu Mar 04 00:00:00 CST 1999}

3.测试DirectFieldAccessor,具体实现如下:

public class TestFieldAccessor {
    public static void main(String[] args) {
        //利用反射原理,为bean的属性赋值
        MyBean target = new MyBean();
        DirectFieldAccessor accessor = new DirectFieldAccessor(target);
        accessor.setPropertyValue("a","10");
        accessor.setPropertyValue("b","hello");
        accessor.setPropertyValue("c","1999/03/04");
        System.out.println(target);
    }

    static class MyBean{
        private int a;
        private String b;
        private Date c;

        @Override
        public String toString() {
            return "MyBean{" +
                    "a=" + a +
                    ", b='" + b + '\'' +
                    ", c=" + c +
                    '}';
        }
    }
}

测试结果:

MyBean{a=10, b='hello', c=Thu Mar 04 00:00:00 CST 1999}

4.测试ServletRequestDataBinder,具体实现如下:

public class TestDataBinder {

    public static void main(String[] args) {
        //执行数据绑定
        MyBean target = new MyBean();
        DataBinder dataBinder = new DataBinder(target);
        dataBinder.initDirectFieldAccess();
        MutablePropertyValues pvs = new MutablePropertyValues();
        pvs.add("a","10");
        pvs.add("b","hello");
        pvs.add("c","1999/03/04");
        dataBinder.bind(pvs);
        System.out.println(target);
    }

    static class MyBean{
        private int a;
        private String b;
        private Date c;

        public int getA() {
            return a;
        }

        public void setA(int a) {
            this.a = a;
        }

        public String getB() {
            return b;
        }

        public void setB(String b) {
            this.b = b;
        }

        public Date getC() {
            return c;
        }

        public void setC(Date c) {
            this.c = c;
        }

        @Override
        public String toString() {
            return "MyBean{" +
                    "a=" + a +
                    ", b='" + b + '\'' +
                    ", c=" + c +
                    '}';
        }
    }
}

测试结果如下:

MyBean{a=10, b='hello', c=Thu Mar 04 00:00:00 CST 1999}

3.演示web环境下数据绑定

 演示代码如下:

public class TestServletDataBinder {

    public static void main(String[] args) {
        //web环境下数据绑定
        MyBean target = new MyBean();
        ServletRequestDataBinder dataBinder = new ServletRequestDataBinder(target);
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("a", "10");
        request.setParameter("b", "hello");
        request.setParameter("c", "1993/03/04");
        dataBinder.bind(request);
        System.out.println(target);
    }

    static class MyBean{
        private int a;
        private String b;
        private Date c;

        public int getA() {
            return a;
        }

        public void setA(int a) {
            this.a = a;
        }

        public String getB() {
            return b;
        }

        public void setB(String b) {
            this.b = b;
        }

        public Date getC() {
            return c;
        }

        public void setC(Date c) {
            this.c = c;
        }

        @Override
        public String toString() {
            return "MyBean{" +
                    "a=" + a +
                    ", b='" + b + '\'' +
                    ", c=" + c +
                    '}';
        }
    }
}

演示结果如下:

MyBean{a=10, b='hello', c=Thu Mar 04 00:00:00 CST 1993}

4.绑定器工厂

目的:将这个格式的1999|01|02日期转换并赋值给User对象的birthday属性

1.用工厂绑定器 

 public static void main(String[] args) throws Exception{
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("birthday", "1999|01|02");
        request.setParameter("address.name", "西安");

        User target = new User();
        //1.用工厂,无转换功能
        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
        WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user");
        dataBinder.bind(new ServletRequestParameterPropertyValues(request));
        System.out.println(target);
    }
 public static class User{
        //@DateTimeFormat(pattern = "yyyy|MM|dd")
        private Date birthday;
        private Address address;

        public Date getBirthday() {
            return birthday;
        }

        public void setBirthday(Date birthday) {
            this.birthday = birthday;
        }

        public Address getAddress() {
            return address;
        }

        public void setAddress(Address address) {
            this.address = address;
        }

        @Override
        public String toString() {
            return "User{" +
                    "birthday=" + birthday +
                    ", address=" + address +
                    '}';
        }
    }

    public static class Address{
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "Address{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }

 测试结果:

User{birthday=null, address=Address{name='西安'}}

发现没有给birthday属性赋值成功,即工厂绑定器没有转换功能

2.用 @InitBinder 转换 (底层为PropertyEditorRegistry PropertyEditor)

因为@InitBinder注解需要加在控制器方法上,所以提供一个控制器MyController类,代码如下:

static class MyController{
        @InitBinder
        public void aaa(WebDataBinder dataBinder){
            //扩展 dataBinder 的转换器
            dataBinder.addCustomFormatter(new MyDateFormatter("用 @InitBinder 方式扩展的"));
        }
    }

同时定义MyDateFormatter日期转换器类,代码如下:

public class MyDateFormatter implements Formatter<Date> {
    private static final Logger log = LoggerFactory.getLogger(MyDateFormatter.class);
    private final String desc;

    public MyDateFormatter(String desc) {
        this.desc = desc;
    }

    @Override
    public Date parse(String text, Locale locale) throws ParseException {
        log.debug(">>>> 进入了:{}"+desc);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
        return sdf.parse(text);
    }

    @Override
    public String print(Date date, Locale locale) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
        return sdf.format(date);
    }

并修改部分代码:

InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class));
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(List.of(method), null);

测试结果为:

20:03:28.110 [main] DEBUG com.itheima.a23.MyDateFormatter - >>>> 进入了:{}用 @InitBinder 方式扩展的
User{birthday=Sat Jan 02 00:00:00 CST 1999, address=Address{name='西安'}}

成功转换日期并赋值给User的birthday属性

3.用 ConversionService 转换 (底层为ConversionService Formatter)

修改部分代码:

FormattingConversionService service = new FormattingConversionService();
service.addFormatter(new MyDateFormatter("用 ConversionService 方式进行转换功能"));
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(service);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);

测试结果为:

20:04:59.998 [main] DEBUG com.itheima.a23.MyDateFormatter - >>>> 进入了:{}用 ConversionService 方式进行转换功能
User{birthday=Sat Jan 02 00:00:00 CST 1999, address=Address{name='西安'}}

成功转换日期并赋值给User的birthday属性

4.同时加了 @InitBinder 和 ConversionService

修改部分代码:

InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class));
FormattingConversionService service = new FormattingConversionService();
service.addFormatter(new MyDateFormatter("用 ConversionService 方式进行转换功能"));
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(service);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(List.of(method), initializer);

测试结果为:

20:06:22.025 [main] DEBUG com.itheima.a23.MyDateFormatter - >>>> 进入了:{}用 @InitBinder 方式扩展的
User{birthday=Sat Jan 02 00:00:00 CST 1999, address=Address{name='西安'}}

成功转换日期并赋值给User的birthday属性,可以看出@InitBinder 的转换器的优先级高于ConversionService 的转换器

5.使用默认 ConversionService 转换

修改部分代码:

DefaultFormattingConversionService service = new DefaultFormattingConversionService();//boot程序使用ApplicationConversionService
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(service);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);

  同时需要在User实体类上加上@DateTimeFormat(pattern = "yyyy|MM|dd")注解

测试结果为:

User{birthday=Sat Jan 02 00:00:00 CST 1999, address=Address{name='西安'}}

 成功转换日期并赋值给User的birthday属性

总结:

ServletRequestDataBinderFactory 的用法和扩展点

  1. 可以解析控制器的 @InitBinder 标注方法作为扩展点,添加自定义转换器

    • 控制器私有范围

  2. 可以通过 ConfigurableWebBindingInitializer 配置 ConversionService 作为扩展点,添加自定义转换器

    • 公共范围

  3. 同时加了 @InitBinder 和 ConversionService 的转换优先级

    1. 优先采用 @InitBinder 的转换器

    2. 其次使用 ConversionService 的转换器

    3. 使用默认转换器

    4. 特殊处理(例如有参构造)

5.获取泛型参数 

public class TestGenericType {
    public static void main(String[] args) {
        // 小技巧
        // 1. java api
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");
        Type type = TeacherDao.class.getGenericSuperclass();
        System.out.println(type);

        if (type instanceof ParameterizedType parameterizedType) {
            System.out.println(parameterizedType.getActualTypeArguments()[0]);
        }

        // 2. spring api 1
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");
        Class<?> t = GenericTypeResolver.resolveTypeArgument(TeacherDao.class, BaseDao.class);
        System.out.println(t);

        // 3. spring api 2
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");
        System.out.println(ResolvableType.forClass(TeacherDao.class).getSuperType().getGeneric().resolve());
    }

廿四、@ControllerAdvice 之 @InitBinder

 准备 @InitBinder 在整个 HandlerAdapter 调用过程中所处的位置

  • RequestMappingHandlerAdapter 在图中缩写为 HandlerAdapter

  • HandlerMethodArgumentResolverComposite 在图中缩写为 ArgumentResolvers

  • HandlerMethodReturnValueHandlerComposite 在图中缩写为 ReturnValueHandlers

 准备一个配置类WebConfig,具体实现如下:

@Configuration
public class WebConfig {

    @ControllerAdvice
    static class MyControllerAdvice{
        @InitBinder
        public void binder3(WebDataBinder dataBinder){
            dataBinder.addCustomFormatter(new MyDateFormatter("binder3 转换器"));
        }
    }

    @Controller
    static class Controller1{
        @InitBinder
        public void binder3(WebDataBinder dataBinder){
            dataBinder.addCustomFormatter(new MyDateFormatter("binder1 转换器"));
        }
        public void foo(){

        }
    }

    @Controller
    static class Controller2{
        @InitBinder
        public void binder21(WebDataBinder dataBinder){
            dataBinder.addCustomFormatter(new MyDateFormatter("binder21 转换器"));
        }
        @InitBinder
        public void binder22(WebDataBinder dataBinder){
            dataBinder.addCustomFormatter(new MyDateFormatter("binder22 转换器"));
        }
        public void bar(){}
    }
}

测试代码:

public class A24 {
    private static final Logger log = LoggerFactory.getLogger(A24.class);

    public static void main(String[] args) throws Exception{

        /*
         * @InitBinder 的来源有两个:
         *  1. @ControllerAdvice 中 @InitBinder标注的方法,由RequestMappingHandlerAdapter在初始化时解析并记录
         *  2. @Controller 中 @InitBinder 标注的方法,由RequestMappingHandlerAdapter会在控制器方法首次执行时解析并记录
         */
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(WebConfig.class);
        RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
        handlerAdapter.setApplicationContext(context);
        handlerAdapter.afterPropertiesSet();

        log.debug("1.刚开始...");
        showBindMethods(handlerAdapter);

        Method getDataBinderFactory = RequestMappingHandlerAdapter.class.getDeclaredMethod("getDataBinderFactory", HandlerMethod.class);
        getDataBinderFactory.setAccessible(true);

        log.debug("2.模拟调用 Controller1 的foo 方法时...");
        getDataBinderFactory.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller1(), WebConfig.Controller1.class.getMethod("foo")));
        showBindMethods(handlerAdapter);

        log.debug("2.模拟调用 Controller2 的bar 方法时...");
        getDataBinderFactory.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller2(), WebConfig.Controller2.class.getMethod("bar")));
        showBindMethods(handlerAdapter);

        context.close();
    }
    /*
     *  学到了什么:
     *      a. Method 对象的获取利用了缓存进行加速
     *      b. 绑定器工厂的扩展点(advice之一),通过 @InitBinder扩展类型转换器
     */

    //通过反射查看两个成员变量的内容
    @SuppressWarnings("all")
    private static void showBindMethods(RequestMappingHandlerAdapter handlerAdapter) throws NoSuchFieldException, IllegalAccessException {
        Field initBinderAdviceCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderAdviceCache");
        initBinderAdviceCache.setAccessible(true);
        Map<ControllerAdviceBean, Set<Method>> globalMap = (Map<ControllerAdviceBean, Set<Method>>) initBinderAdviceCache.get(handlerAdapter);
        log.debug("全局的 @InitBinder 方法 {}",
                globalMap.values().stream()
                        .flatMap(ms -> ms.stream().map(m -> m.getName()))
                        .collect(Collectors.toList())
        );

        Field initBinderCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderCache");
        initBinderCache.setAccessible(true);
        Map<Class<?>, Set<Method>> controllerMap = (Map<Class<?>, Set<Method>>) initBinderCache.get(handlerAdapter);
        log.debug("控制器的 @InitBinder 方法 {}",
                controllerMap.entrySet().stream()
                        .flatMap(e -> e.getValue().stream().map(v -> e.getKey().getSimpleName() + "." + v.getName()))
                        .collect(Collectors.toList())
        );
    }
}

测试结果:

09:42:10.901 [main] DEBUG com.itheima.a24.A24 - 1.刚开始...
09:42:10.910 [main] DEBUG com.itheima.a24.A24 - 全局的 @InitBinder 方法 [binder3]
09:42:10.915 [main] DEBUG com.itheima.a24.A24 - 控制器的 @InitBinder 方法 []
09:42:10.917 [main] DEBUG com.itheima.a24.A24 - 2.模拟调用 Controller1 的foo 方法时...
09:42:10.926 [main] DEBUG com.itheima.a24.A24 - 全局的 @InitBinder 方法 [binder3]
09:42:10.931 [main] DEBUG com.itheima.a24.A24 - 控制器的 @InitBinder 方法 [Controller1.binder3]
09:42:10.931 [main] DEBUG com.itheima.a24.A24 - 2.模拟调用 Controller2 的bar 方法时...
09:42:10.931 [main] DEBUG com.itheima.a24.A24 - 全局的 @InitBinder 方法 [binder3]
09:42:10.932 [main] DEBUG com.itheima.a24.A24 - 控制器的 @InitBinder 方法 [Controller1.binder3, Controller2.binder21, Controller2.binder22]

总结:

  1. RequestMappingHandlerAdapter 初始化时会解析 @ControllerAdvice 中的 @InitBinder 方法

  2. RequestMappingHandlerAdapter 会以类为单位,在该类首次使用时,解析此类的 @InitBinder 方法

  3. 以上两种 @InitBinder 的解析结果都会缓存来避免重复解析

  4. 控制器方法调用时,会综合利用本类的 @InitBinder 方法和 @ControllerAdvice 中的 @InitBinder 方法创建绑定工厂

廿五、控制器方法执行流程 

1.图例演示执行流程

 

 HandlerMethod 需要

  • bean 即是哪个 Controller

  • method 即是 Controller 中的哪个方法

ServletInvocableHandlerMethod 需要

  • WebDataBinderFactory 负责对象绑定、类型转换

  • ParameterNameDiscoverer 负责参数名解析

  • HandlerMethodArgumentResolverComposite 负责解析参数

  • HandlerMethodReturnValueHandlerComposite 负责处理返回值

 2.代码演示执行流程

准备一个配置类WebConfig,具体实现如下:

@Configuration
public class WebConfig {

    @ControllerAdvice
    static class MyControllerAdvice{
        @ModelAttribute("a")
        public String aa(){
            return "aa";
        }
    }

    @Controller
    static class Controller1{
        @ResponseStatus(HttpStatus.OK)
        public ModelAndView foo(User user){
            System.out.println("foo");
            return null;
        }
    }

    static class User{
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
}

 测试代码:

public class A26 {

    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(WebConfig.class);

        
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("name", "张三");

        ServletInvocableHandlerMethod handlerMethod =
                new ServletInvocableHandlerMethod(new Controller1(), Controller1.class.getMethod("foo", User.class));

        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
        handlerMethod.setDataBinderFactory(factory);
        handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
        handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context));

        ModelAndViewContainer container = new ModelAndViewContainer();

       
        handlerMethod.invokeAndHandle(new ServletWebRequest(request), container);

        System.out.println(container.getModel());

        context.close();
    }

    public static HandlerMethodArgumentResolverComposite getArgumentResolvers(AnnotationConfigApplicationContext context){
        HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
        composite.addResolvers(
                new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(),false),
                new PathVariableMethodArgumentResolver(),
                new RequestHeaderMethodArgumentResolver(context.getDefaultListableBeanFactory()),
                new ServletCookieValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),
                new ExpressionValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),
                new ServletRequestMethodArgumentResolver(),
                new ServletModelAttributeMethodProcessor(false),
                new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())),
                new ServletModelAttributeMethodProcessor(true),
                new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(),true)
        );
        return composite;
    }
}

测试结果:

foo
{user=User{name='张三'}, org.springframework.validation.BindingResult.user=org.springframework.validation.BeanPropertyBindingResult: 0 errors}

廿六、@ControllerAdvice 之 @ModelAttribute

准备 @ModelAttribute 在整个 HandlerAdapter 调用过程中所处的位置

在廿五节的测试代码中添加如下代码:

RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
adapter.setApplicationContext(context);
adapter.afterPropertiesSet();
//获取模型工厂方法
Method getModelFactory = RequestMappingHandlerAdapter.class.getDeclaredMethod("getModelFactory", HandlerMethod.class, WebDataBinderFactory.class);
getModelFactory.setAccessible(true);
ModelFactory modelFactory = (ModelFactory) getModelFactory.invoke(adapter, handlerMethod, factory);
//初始化模型数据
modelFactory.initModel(new ServletWebRequest(request), container, handlerMethod);

 再次进行测试,测试结果如下:

foo
{a=aa, user=User{name='张三'}, org.springframework.validation.BindingResult.user=org.springframework.validation.BeanPropertyBindingResult: 0 errors}

总结:

  1. RequestMappingHandlerAdapter 初始化时会解析 @ControllerAdvice 中的 @ModelAttribute 方法

  2. RequestMappingHandlerAdapter 会以类为单位,在该类首次使用时,解析此类的 @ModelAttribute 方法

  3. 以上两种 @ModelAttribute 的解析结果都会缓存来避免重复解析

  4. 控制器方法调用时,会综合利用本类的 @ModelAttribute 方法和 @ControllerAdvice 中的 @ModelAttribute 方法创建模型工厂

廿七、返回值处理器

测试过程中未用内置tomcat这种方式,采用的是 FreeMarker模板引擎,直接生成输出文本 (HTML网页,电子邮件,配置文件,源代码等)

自定义一个配置类WebConfig,在其中配置FreeMarker的一些配置,具体实现如下:

@Configuration
public class WebConfig {

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setDefaultEncoding("utf-8");
        configurer.setTemplateLoaderPath("classpath:templates");
        return configurer;
    }

    @Bean // FreeMarkerView 在借助 Spring 初始化时,会要求 web 环境才会走 setConfiguration, 这里想办法去掉了 web 环境的约束
    public FreeMarkerViewResolver viewResolver(FreeMarkerConfigurer configurer) {
        FreeMarkerViewResolver resolver = new FreeMarkerViewResolver() {
            @Override
            protected AbstractUrlBasedView instantiateView() {
                FreeMarkerView view = new FreeMarkerView() {
                    @Override
                    protected boolean isContextRequired() {
                        return false;
                    }
                };
                view.setConfiguration(configurer.getConfiguration());
                return view;
            }
        };
        resolver.setContentType("text/html;charset=utf-8");
        resolver.setPrefix("/");
        resolver.setSuffix(".ftl");
        resolver.setExposeSpringMacroHelpers(false);
        return resolver;
    }
}

然后在 resources 下创建 templates 文件夹,用于存放模板,在其中自定义了四个模板,具体实现如下:

<!doctype html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>view1</title>
</head>
<body>
    <h1>Hello! ${name}</h1>
</body>
</html>
<!doctype html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>view2</title>
</head>
<body>
    <h1>Hello!</h1>
</body>
</html>
<!doctype html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>test3</title>
</head>
<body>
    <h1>Hello! ${user.name} ${user.age}</h1>
</body>
</html>
<!doctype html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>test4</title>
</head>
<body>
    <h1>Hello! ${user.name} ${user.age}</h1>
</body>
</html>

接下来自定义测试代码,其中定义控制器类Controller(定义需要测试的几种方法)、测试用到的实体类User、渲染视图的方法以及测试每种方法的测试方法,具体实现如下:

public class A27 {

    private static final Logger log = LoggerFactory.getLogger(A27.class);

    public static void main(String[] args)  throws Exception{
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(WebConfig.class);
        //1.测试返回类型为ModelAndView 抽取方法Ctrl+Alt+M
        //test1(context);
        //2.测试返回类型为string时,把它当做视图名
        //test2(context);
        //3.测试返回值添加了 @ModelAttribute 注解时,此时需要找到默认视图名
        //test3(context);
        //4.测试返回值不加 @ModelAttribute 注解且返回非简单类型时,测试需要找到默认视图名
        //test4(context);
        //5.测试返回值类型为 ResponseEntity 时,此时不走视图流程
        //test5(context);
        //6.测试返回值类型为 HttpHeaders 时,此时不走视图流程
        //test6(context);
        //7.测试返回值添加了@ResponseBody 注解时,此时不走视图流程
        test7(context);
    }

    private static void test7(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getMethod("test7");
        Controller controller = new Controller();
        //获取返回值
        Object returnValue = method.invoke(controller);
        HandlerMethod handlerMethod = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {//检查是否支持此类型的返回值
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            if (!container.isRequestHandled()) {
                renderView(context, container, webRequest);//渲染视图
            }else {
                for (String name : response.getHeaderNames()) {
                    System.out.println(name+"="+response.getHeader(name));
                }
                System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
            }
        }
    }

    private static void test6(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getMethod("test6");
        Controller controller = new Controller();
        //获取返回值
        Object returnValue = method.invoke(controller);
        HandlerMethod handlerMethod = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {//检查是否支持此类型的返回值
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            if (!container.isRequestHandled()) {
                renderView(context, container, webRequest);//渲染视图
            }else {
                for (String name : response.getHeaderNames()) {
                    System.out.println(name+"="+response.getHeader(name));
                }
                System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
            }
        }
    }

    private static void test5(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getMethod("test5");
        Controller controller = new Controller();
        //获取返回值
        Object returnValue = method.invoke(controller);
        HandlerMethod handlerMethod = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {//检查是否支持此类型的返回值
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            if (!container.isRequestHandled()) {
                renderView(context, container, webRequest);//渲染视图
            }else {
                System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
            }
        }
    }

    private static void test4(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getMethod("test4");
        Controller controller = new Controller();
        //获取返回值
        Object returnValue = method.invoke(controller);
        HandlerMethod handlerMethod = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setRequestURI("/test4");
        UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
        ServletWebRequest webRequest = new ServletWebRequest(request,new MockHttpServletResponse());
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {//检查是否支持此类型的返回值
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            renderView(context, container, webRequest);//渲染视图
        }
    }

    private static void test3(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getMethod("test3");
        Controller controller = new Controller();
        //获取返回值
        Object returnValue = method.invoke(controller);
        HandlerMethod handlerMethod = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setRequestURI("/test3");
        UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
        ServletWebRequest webRequest = new ServletWebRequest(request,new MockHttpServletResponse());
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {//检查是否支持此类型的返回值
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            renderView(context, container, webRequest);//渲染视图
        }
    }

    private static void test2(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getMethod("test2");
        Controller controller = new Controller();
        //获取返回值
        Object returnValue = method.invoke(controller);
        HandlerMethod handlerMethod = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(),new MockHttpServletResponse());
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {//检查是否支持此类型的返回值
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            renderView(context, container, webRequest);//渲染视图
        }
    }

    private static void test1(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getMethod("test1");
        Controller controller = new Controller();
        //获取返回值
        Object returnValue = method.invoke(controller);
        HandlerMethod handlerMethod = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(),new MockHttpServletResponse());
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {//检查是否支持此类型的返回值
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            renderView(context, container, webRequest);//渲染视图
        }
    }

    public static HandlerMethodReturnValueHandlerComposite getReturnValueHandler(){
        HandlerMethodReturnValueHandlerComposite composite =
                new HandlerMethodReturnValueHandlerComposite();
        composite.addHandler(new ModelAndViewMethodReturnValueHandler());
        composite.addHandler(new ViewNameMethodReturnValueHandler());
        composite.addHandler(new ServletModelAttributeMethodProcessor(false));
        composite.addHandler(new HttpEntityMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())));
        composite.addHandler(new HttpHeadersReturnValueHandler());
        composite.addHandler(new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())));
        composite.addHandler(new ServletModelAttributeMethodProcessor(true));
        return composite;
    }

    @SuppressWarnings("all")
    private static void renderView(AnnotationConfigApplicationContext context, ModelAndViewContainer container,
                                   ServletWebRequest webRequest) throws Exception {
        log.debug(">>>>>> 渲染视图");
        FreeMarkerViewResolver resolver = context.getBean(FreeMarkerViewResolver.class);
        String viewName = container.getViewName() != null ? container.getViewName() : new DefaultRequestToViewNameTranslator().getViewName(webRequest.getRequest());
        log.debug("没有获取到视图名, 采用默认视图名: {}", viewName);
        // 每次渲染时, 会产生新的视图对象, 它并非被 Spring 所管理, 但确实借助了 Spring 容器来执行初始化
        View view = resolver.resolveViewName(viewName, Locale.getDefault());
        view.render(container.getModel(), webRequest.getRequest(), webRequest.getResponse());
        System.out.println(new String(((MockHttpServletResponse) webRequest.getResponse()).getContentAsByteArray(), StandardCharsets.UTF_8));
    }


    static class Controller{
        private static final Logger log = LoggerFactory.getLogger(Controller.class);

        public ModelAndView test1(){
            log.debug("test1()");
            ModelAndView mav = new ModelAndView("view1");
            mav.addObject("name","张三");
            return mav;
        }

        public String test2(){
            log.debug("test2()");
            return "view2";
        }

        @ModelAttribute
        //@RequestMapping("/test3")
        public User test3(){
            log.debug("test3()");
            return new User("李四", 20);
        }

        public User test4() {
            log.debug("test4()");
            return new User("王五", 30);
        }

        public HttpEntity<User> test5() {
            log.debug("test5()");
            return new HttpEntity<>(new User("赵六", 40));
        }

        public HttpHeaders test6() {
            log.debug("test6()");
            HttpHeaders headers = new HttpHeaders();
            headers.add("Content-Type", "text/html");
            return headers;
        }

        @ResponseBody
        public User test7() {
            log.debug("test7()");
            return new User("钱七", 50);
        }
    }
    // 必须用 public 修饰, 否则 freemarker 渲染其 name, age 属性时失败
    public static class User{
        private String name;
        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}

测试结果为:

20:11:38.245 [main] DEBUG com.itheima.a27.A27$Controller - test1()
{name=张三}
view1
20:11:38.565 [main] DEBUG com.itheima.a27.A27 - >>>>>> 渲染视图
20:11:38.566 [main] DEBUG com.itheima.a27.A27 - 没有获取到视图名, 采用默认视图名: view1
20:11:38.604 [main] DEBUG com.itheima.a27.WebConfig$1$1 - View name 'view1', model {name=张三}
20:11:38.607 [main] DEBUG com.itheima.a27.WebConfig$1$1 - Rendering [/view1.ftl]
<!doctype html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>view1</title>
</head>
<body>
    <h1>Hello! 张三</h1>
</body>
</html>
20:11:38.624 [main] DEBUG com.itheima.a27.A27$Controller - test2()
{}
view2
20:11:38.625 [main] DEBUG com.itheima.a27.A27 - >>>>>> 渲染视图
20:11:38.625 [main] DEBUG com.itheima.a27.A27 - 没有获取到视图名, 采用默认视图名: view2
20:11:38.627 [main] DEBUG com.itheima.a27.WebConfig$1$1 - View name 'view2', model {}
20:11:38.627 [main] DEBUG com.itheima.a27.WebConfig$1$1 - Rendering [/view2.ftl]
<!doctype html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>view2</title>
</head>
<body>
    <h1>Hello!</h1>
</body>
</html>
20:11:38.629 [main] DEBUG com.itheima.a27.A27$Controller - test3()
{user=User{name='李四', age=20}}
null
20:11:38.644 [main] DEBUG com.itheima.a27.A27 - >>>>>> 渲染视图
20:11:38.645 [main] DEBUG com.itheima.a27.A27 - 没有获取到视图名, 采用默认视图名: test3
20:11:38.647 [main] DEBUG com.itheima.a27.WebConfig$1$1 - View name 'test3', model {user=User{name='李四', age=20}}
20:11:38.647 [main] DEBUG com.itheima.a27.WebConfig$1$1 - Rendering [/test3.ftl]
<!doctype html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>test3</title>
</head>
<body>
    <h1>Hello! 李四 20</h1>
</body>
</html>
20:11:38.657 [main] DEBUG com.itheima.a27.A27$Controller - test4()
{user=User{name='王五', age=30}}
null
20:11:38.658 [main] DEBUG com.itheima.a27.A27 - >>>>>> 渲染视图
20:11:38.659 [main] DEBUG com.itheima.a27.A27 - 没有获取到视图名, 采用默认视图名: test4
20:11:38.661 [main] DEBUG com.itheima.a27.WebConfig$1$1 - View name 'test4', model {user=User{name='王五', age=30}}
20:11:38.661 [main] DEBUG com.itheima.a27.WebConfig$1$1 - Rendering [/test4.ftl]
<!doctype html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>test4</title>
</head>
<body>
    <h1>Hello! 王五 30</h1>
</body>
</html>
20:11:38.662 [main] DEBUG com.itheima.a27.A27$Controller - test5()
20:11:38.687 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor - Using 'application/json', given [*/*] and supported [application/json, application/*+json]
20:11:38.688 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor - Writing [User{name='赵六', age=40}]
{}
null
{"name":"赵六","age":40}
20:11:38.694 [main] DEBUG com.itheima.a27.A27$Controller - test6()
{}
null
Content-Type=text/html

20:11:38.699 [main] DEBUG com.itheima.a27.A27$Controller - test7()
20:11:38.702 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Using 'application/json', given [*/*] and supported [application/json, application/*+json]
20:11:38.702 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Writing [User{name='钱七', age=50}]
{}
null
Content-Type=application/json
{"name":"钱七","age":50}

进程已结束,退出代码为 0

总结:

  1. 常见的返回值处理器

    • ModelAndView,分别获取其模型和视图名,放入 ModelAndViewContainer

    • 返回值类型为 String 时,把它当做视图名,放入 ModelAndViewContainer

    • 返回值添加了 @ModelAttribute 注解时,将返回值作为模型,放入 ModelAndViewContainer

      • 此时需找到默认视图名

    • 返回值省略 @ModelAttribute 注解且返回非简单类型时,将返回值作为模型,放入 ModelAndViewContainer

      • 此时需找到默认视图名

    • 返回值类型为 ResponseEntity 时

      • 此时走 MessageConverter,并设置 ModelAndViewContainer.requestHandled 为 true

    • 返回值类型为 HttpHeaders 时

      • 会设置 ModelAndViewContainer.requestHandled 为 true

    • 返回值添加了 @ResponseBody 注解时

      • 此时走 MessageConverter,并设置 ModelAndViewContainer.requestHandled 为 true

  2. 组合模式在 Spring 中的体现 + 1

廿八、MessageConverter

 在测试代码中分别测试对象转JSON形式,对象转XML形式,JSON形式转对象以及涉及多种MessageConverter的优先级顺序,具体实现如下:

public class A28 {

    public static void main(String[] args) throws IOException, NoSuchMethodException, HttpMediaTypeNotAcceptableException {
        test1();
        test2();
        test3();
        test4();
    }

    private static void test4() throws NoSuchMethodException, HttpMediaTypeNotAcceptableException, IOException {
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        ServletWebRequest webRequest = new ServletWebRequest(request,response);


        request.addHeader("Accept", "application/xml");
        response.setContentType("application/json");

        RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
                List.of(//在没有其他设置的前提下,谁在前,谁的优先级就高
                        new MappingJackson2HttpMessageConverter(),new MappingJackson2XmlHttpMessageConverter()
                ));
        processor.handleReturnValue(
                new User("张三", 18),
                new MethodParameter(A28.class.getMethod("user"),-1),
                new ModelAndViewContainer(),
                webRequest);
        System.out.println(new String(response.getContentAsByteArray(),StandardCharsets.UTF_8));
    }

    @ResponseBody
    //@RequestMapping(produces = "application/json")
    public User user(){
        return null;
    }

    private static void test3() throws IOException{
        MockHttpInputMessage message = new MockHttpInputMessage("""
                {
                    "name":"李四",
                    "age":20
                }
                """.getBytes(StandardCharsets.UTF_8));
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        if (converter.canRead(User.class, MediaType.APPLICATION_JSON)) {
            Object read = converter.read(User.class, message);
            System.out.println(read);
        }
    }

    private static void test2() throws IOException{
        MockHttpOutputMessage message = new MockHttpOutputMessage();
        MappingJackson2XmlHttpMessageConverter converter = new MappingJackson2XmlHttpMessageConverter();
        if (converter.canWrite(User.class, MediaType.APPLICATION_XML)) {
            converter.write(new User("李四", 20), MediaType.APPLICATION_XML, message);
            System.out.println(message.getBodyAsString());
        }
    }

    private static void test1() throws IOException{
        MockHttpOutputMessage message = new MockHttpOutputMessage();
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        if (converter.canWrite(User.class, MediaType.APPLICATION_JSON)) {
            converter.write(new User("张三", 18), MediaType.APPLICATION_JSON, message);
            System.out.println(message.getBodyAsString());
        }
    }

    public static class User {
        private String name;
        private int age;

        @JsonCreator
        public User(@JsonProperty("name") String name, @JsonProperty("age") int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}

测试结果为: 

{"name":"张三","age":18}
<User><name>李四</name><age>20</age></User>
User{name='李四', age=20}
21:18:22.060 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Found 'Content-Type:application/json' in response
21:18:22.068 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Writing [User{name='张三', age=18}]
{"name":"张三","age":18}

总结:

  1. MessageConverter 的作用

    • @ResponseBody 是返回值处理器解析的

    • 但具体转换工作是 MessageConverter 做的

  2. 如何选择 MediaType

    • 首先看 @RequestMapping 上有没有指定

    • 其次看 request 的 Accept 头有没有指定

    • 最后按 MessageConverter 的顺序, 谁能谁先转换

廿九、@ControllerAdvice 之 ResponseBodyAdvice

 ResponseBodyAdvice 增强 在整个 HandlerAdapter 调用过程中所处的位置

 演示ResponseBodyAdvice增强(ResponseBodyAdvice 返回响应体前包装 ):

准备一个统一的返回结果类Result:

@JsonInclude(JsonInclude.Include.NON_NULL)
public class Result {
    private int code;
    private String msg;
    private Object data;

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    @JsonCreator
    private Result(@JsonProperty("code") int code,@JsonProperty("data") Object data){
        this.code = code;
        this.data = data;
    }

    private Result(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public static Result ok(){
        return new Result(200, null);
    }

    public static Result ok(Object data){
        return new Result(200, data);
    }

    public static Result error(String msg){
        return new Result(500, "服务器内部错误"+msg);
    }
}

准备一个配置类WebConfig,在其中定义MyControllerAdvice类、控制器类MyController以及实体类User,具体实现如下:

@Configuration
public class WebConfig {

    @ControllerAdvice
    static class MyControllerAdvice implements ResponseBodyAdvice<Object> {
        //满足条件才转换
        public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
            if (returnType.getMethodAnnotation(ResponseBody.class) != null ||
                    AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null){
                //returnType.getContainingClass().isAnnotationPresent(ResponseBody.class)) {
                return true;
            }
            return false;
        }

        //将 User 或其他类型统一为 Result类型
        public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
            if(body instanceof Result){
                return body;
            }
            return Result.ok(body);
        }
    }


    //@Controller
    //@ResponseBody
    @RestController
    public static class MyController{

        public User user(){
            return new User("王五",18);
        }
    }

    public static class User{
        private String name;
        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}

测试代码:

public class A29 {

    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(WebConfig.class);

        ServletInvocableHandlerMethod handlerMethod =
                new ServletInvocableHandlerMethod(context.getBean(WebConfig.MyController.class),
                        WebConfig.MyController.class.getMethod("user" ));
        handlerMethod.setDataBinderFactory(new ServletRequestDataBinderFactory(Collections.emptyList(),null));
        handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
        handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context));
        handlerMethod.setHandlerMethodReturnValueHandlers(getReturnValueHandlers(context));

        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        ModelAndViewContainer container = new ModelAndViewContainer();
        handlerMethod.invokeAndHandle(new ServletWebRequest(request,response), container );

        System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
    }

    public static HandlerMethodArgumentResolverComposite getArgumentResolvers(AnnotationConfigApplicationContext context) {
        HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
        composite.addResolvers(
                new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), false),
                new PathVariableMethodArgumentResolver(),
                new RequestHeaderMethodArgumentResolver(context.getDefaultListableBeanFactory()),
                new ServletCookieValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),
                new ExpressionValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),
                new ServletRequestMethodArgumentResolver(),
                new ServletModelAttributeMethodProcessor(false),
                new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())),
                new ServletModelAttributeMethodProcessor(true),
                new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), true)
        );
        return composite;
    }

    public static HandlerMethodReturnValueHandlerComposite getReturnValueHandlers(AnnotationConfigApplicationContext context) {
        // 添加 advice
        List<ControllerAdviceBean> annotatedBeans = ControllerAdviceBean.findAnnotatedBeans(context);
        List<Object> collect = annotatedBeans.stream().filter(b -> ResponseBodyAdvice.class.isAssignableFrom(b.getBeanType()))
                .collect(Collectors.toList());

        HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite();
        composite.addHandler(new ModelAndViewMethodReturnValueHandler());
        composite.addHandler(new ViewNameMethodReturnValueHandler());
        composite.addHandler(new ServletModelAttributeMethodProcessor(false));
        composite.addHandler(new HttpEntityMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())));
        composite.addHandler(new HttpHeadersReturnValueHandler());
        composite.addHandler(new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter()), collect));
        composite.addHandler(new ServletModelAttributeMethodProcessor(true));
        return composite;
    }
}

测试结果为:

{"code":200,"data":{"name":"王五","age":18}}

成功将控制器类中的方法返回值从User对象包装成了Result对象

卅、异常处理

自定义四个控制器类,分别用来测试JSON、ModelAndView、嵌套异常、异常处理方法参数解析,具体实现如下:

static class Controller1{
        public void foo(){}

        @ExceptionHandler
        @ResponseBody
        public Map<String,Object> handle(ArithmeticException e){
            return Map.of("error",e.getMessage());
        }
    }

    static class Controller2{
        public void foo(){}

        @ExceptionHandler
        public ModelAndView handle(ArithmeticException e){
            return new ModelAndView("test2",Map.of("error",e.getMessage()));
        }
    }

    static class Controller3{
        public void foo(){}

        @ExceptionHandler
        @ResponseBody
        public Map<String,Object> handle(IOException e3){
            return Map.of("error",e3.getMessage());
        }
    }

    static class Controller4{
        public void foo(){}

        @ExceptionHandler
        @ResponseBody
        public Map<String,Object> handler(Exception e, HttpServletRequest request){
            System.out.println(request);
            return Map.of("error",e.getMessage());
        }
    } 

分别写出对应四种测试的测试代码:

public static void main(String[] args) throws NoSuchMethodException {
        ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
        resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
        resolver.afterPropertiesSet();
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        //1.测试JSON
        HandlerMethod handlerMethod = new HandlerMethod(new Controller1(), Controller1.class.getMethod("foo"));
        Exception e = new ArithmeticException("被零除");
        resolver.resolveException(request,response,handlerMethod,e);
        System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));

        //2.测试mav
        //HandlerMethod handlerMethod = new HandlerMethod(new Controller2(), Controller2.class.getMethod("foo"));
        //Exception e = new ArithmeticException("被零除");
        //ModelAndView mav = resolver.resolveException(request, response, handlerMethod, e);
        //System.out.println(mav.getModel());
        //System.out.println(mav.getViewName());

        //3.测试嵌套异常
        //HandlerMethod handlerMethod = new HandlerMethod(new Controller3(), Controller3.class.getMethod("foo"));
        //Exception e = new Exception("e1",new RuntimeException("e2",new IOException("e3")));
        //resolver.resolveException(request, response, handlerMethod, e);
        //System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));

        //4.测试异常处理方法参数解析
        //HandlerMethod handlerMethod = new HandlerMethod(new Controller4(), Controller4.class.getMethod("foo"));
        //Exception e = new Exception("e1");
        //resolver.resolveException(request, response, handlerMethod, e);
        //System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
    }

分别对4种情况进行测试,测试结果为:

{"error":"被零除"}

{error=被零除}
test2

{"error":"e3"}

org.springframework.mock.web.MockHttpServletRequest@1e683a3e
{"error":"e1"}

总结:

  1. 它能够重用参数解析器、返回值处理器,实现组件重用

  2. 它能够支持嵌套异常

卅一、@ControllerAdvice 之 @ExceptionHandler

自己创建ExceptionHandlerExceptionResolver来解析控制器类中方法异常时,需要在控制器方法上添加@ExceptionHandler注解,一旦控制器方法上没有添加该注解,就会解析不了该异常,此时可以通过在配置类中的ControllerAdvice类配置全局异常解析,并将ExceptionHandlerExceptionResolver交由Spring管理,具体实现如下:

@Configuration
public class WebConfig {

    @ControllerAdvice
    static class MyControllerAdvice{
        @ExceptionHandler
        @ResponseBody
        public Map<String,Object> handle(Exception e){
            return Map.of("error",e.getMessage());
        }
    }


    @Bean
    public ExceptionHandlerExceptionResolver resolver(){
        ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
        resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
        //resolver.afterPropertiesSet();
        return resolver;
    }
}

这里将ExceptionHandlerExceptionResolver交由Spring容器管理, 内部实现了InitializingBean接口,在InitializingBean接口中它会回调afterPropertiesSet()方法,所以其本身不用调afterPropertiesSet()方法

测试代码:

public static void main(String[] args) throws NoSuchMethodException {
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();


        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(WebConfig.class);
        ExceptionHandlerExceptionResolver resolver =
                context.getBean(ExceptionHandlerExceptionResolver.class);


        HandlerMethod handlerMethod = new HandlerMethod(new Controller5(), Controller5.class.getMethod("foo"));
        Exception e = new Exception("e1");
        resolver.resolveException(request, response, handlerMethod,e);
        System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
    }


    static class Controller5{
        public void foo(){}
    }
}

测试结果为:

{"error":"e1"}

总结:

  1. ExceptionHandlerExceptionResolver 初始化时会解析 @ControllerAdvice 中的 @ExceptionHandler 方法

  2. ExceptionHandlerExceptionResolver 会以类为单位,在该类首次处理异常时,解析此类的 @ExceptionHandler 方法

  3. 以上两种 @ExceptionHandler 的解析结果都会缓存来避免重复解析

卅二、Tomcat异常处理 

1.Tomcat异常处理

自定义一个配置类WebConfig,具体实现如下:

@Configuration
public class WebConfig {

    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory(){
        return new TomcatServletWebServerFactory();
    }

    @Bean
    public DispatcherServlet dispatcherServlet(){
        return new DispatcherServlet();
    }

    @Bean
    public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet){
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        registrationBean.setLoadOnStartup(1);
        return registrationBean;
    }

    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping(){
        return new RequestMappingHandlerMapping();
    }

    @Bean //注意默认的 RequestMappingHandlerAdapter 不会带 jackson 转换器
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter(){
        RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
        handlerAdapter.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
        return handlerAdapter;
    }

    @Controller
    public static class MyController {
        @RequestMapping("test")
        public ModelAndView test() {
            int i = 1 / 0;
            return null;
        }
}

 自定义测试代码,输出映射路径和方法信息,具体实现如下:

public class A32 {

    public static void main(String[] args) {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
        RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        handlerMapping.getHandlerMethods().forEach((RequestMappingInfo k, HandlerMethod v) -> {
            System.out.println("映射路径:" + k + "\t方法信息" + v);
        });
    }
}

控制台会输出:

映射路径:{ [/test]}	方法信息com.itheima.a32.WebConfig$MyController#test()

 打开浏览器,访问 localhost:8080/test 地址,会发现Tomcat提供的异常信息

 2.自定义错误地址

 在WebConfig配置类中再添加两个Bean,ErrorPageRegistrar用来修改Tomcat服务器默认错误地址,ErrorPageRegistrarBeanPostProcessor用来在webServerFactory创建好,没有初始化之前 找到所有的ErrorPageRegistrar

    @Bean //修改 Tomcat 服务器默认错误地址
    public ErrorPageRegistrar errorPageRegistrar(){ //出现错误,会出现请求转发 forward 跳转到 error地址
        return webServerFactory -> webServerFactory.addErrorPages(new ErrorPage("/error"));
    }

    @Bean //在webServerFactory创建好,没有初始化之前 找到所有的ErrorPageRegistrar
    public ErrorPageRegistrarBeanPostProcessor errorPageRegistrarBeanPostProcessor(){
        return new ErrorPageRegistrarBeanPostProcessor();
    }

在控制器类中创建一个方法,请求路径为设置的错误地址:

@RequestMapping("/error")
@ResponseBody
public Map<String,Object> error(HttpServletRequest request){
Throwable e = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
return Map.of("error",e.getMessage());
}

重新启动之后,控制台输出:

映射路径:{ [/error]}	方法信息com.itheima.a32.WebConfig$MyController#error(HttpServletRequest)
映射路径:{ [/test]}	方法信息com.itheima.a32.WebConfig$MyController#test()

重新访问 localhost:8080/test 地址

可以发现,已经跳转到error地址,只不过使用的请求转发的方式 forward,所以地址路径没有改变

3.BasicErrorController

 Spring Boot 又提供了一个 BasicErrorController,在WebConfig中再添加三个Bean,同时删除我们刚刚在控制器类中自定义的错误方法:

 @Bean
    public BasicErrorController basicErrorController(){// ⬅️ErrorProperties 封装环境键值, ErrorAttributes 控制有哪些错误信息
        ErrorProperties errorProperties = new ErrorProperties();
        errorProperties.setIncludeException(true);
        return new BasicErrorController(new DefaultErrorAttributes(), errorProperties);
    }

    @Bean
    public View error(){// ⬅️名称为 error 的视图, 作为 BasicErrorController 的 text/html 响应结果
        return new View() {
            @Override
            public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
                System.out.println(model);
                response.setContentType("text/html;charset=utf-8");
                response.getWriter().print("""
                        <h3>服务器内部错误</h3>
                        """
                );
            }
        };
    }

    @Bean
    public ViewResolver viewResolver(){ // ⬅️收集容器中所有 View 对象, bean 的名字作为视图名
        return new BeanNameViewResolver();
    }

重新启动之后,控制台输出:

映射路径:{ [/error]}	方法信息org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
映射路径:{ [/test]}	方法信息com.itheima.a32.WebConfig$MyController#test()
映射路径:{ [/error], produces [text/html]}	方法信息org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse)

用Postman来测试 localhost:8080/test 地址,返回结果为:

{
    "timestamp": 1654830786975,
    "status": 500,
    "error": "Internal Server Error",
    "exception": "java.lang.ArithmeticException",
    "path": "/test"
}

浏览器重新访问 localhost:8080/test 地址:

此时会发现,访问相同地址,出现了两种不同的结果,是因为 BasicErrorController 通过 Accept 头判断需要生成哪种 MediaType 的响应,具体生成哪种响应见下面总结

总结:

  • 我们知道 @ExceptionHandler 只能处理发生在 mvc 流程中的异常,例如控制器内、拦截器内,那么如果是 Filter 出现了异常,如何进行处理呢?

  • 在 Spring Boot 中,是这么实现的:

    1. 因为内嵌了 Tomcat 容器,因此可以配置 Tomcat 的错误页面,Filter 与 错误页面之间是通过请求转发跳转的,可以在这里做手脚

    2. 先通过 ErrorPageRegistrarBeanPostProcessor 这个后处理器配置错误页面地址,默认为 /error 也可以通过 ${server.error.path} 进行配置

    3. 当 Filter 发生异常时,不会走 Spring 流程,但会走 Tomcat 的错误处理,于是就希望转发至 /error 这个地址

      • 当然,如果没有 @ExceptionHandler,那么最终也会走到 Tomcat 的错误处理

    4. Spring Boot 又提供了一个 BasicErrorController,它就是一个标准 @Controller,@RequestMapping 配置为 /error,所以处理异常的职责就又回到了 Spring

    5. 异常信息由于会被 Tomcat 放入 request 作用域,因此 BasicErrorController 里也能获取到

    6. 具体异常信息会由 DefaultErrorAttributes 封装好

    7. BasicErrorController 通过 Accept 头判断需要生成哪种 MediaType 的响应

      • 如果要的不是 text/html,走 MessageConverter 流程

      • 如果需要 text/html,走 mvc 流程,此时又分两种情况

        • 配置了 ErrorViewResolver,根据状态码去找 View

        • 没配置或没找到,用 BeanNameViewResolver 根据一个固定为 error 的名字找到 View,即所谓的 WhitelabelErrorView

卅三、BeanNameUrlHandlerMapping 与 SimpleControllerHandlerAdapter

1.BeanNameUrlHandlerMapping 与 SimpleControllerHandlerAdapter

定义一个配置类WebConfig:

@Configuration
public class WebConfig {

    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory(){
        return new TomcatServletWebServerFactory(8080);
    }

    @Bean
    public DispatcherServlet dispatcherServlet(){
        return new DispatcherServlet();
    }

    @Bean
    public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet){
        DispatcherServletRegistrationBean servletRegistrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        servletRegistrationBean.setLoadOnStartup(1);
        return servletRegistrationBean;
    }

    @Bean //以 / 开头的 bean 的名字会被当作映射路径
    public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping(){
        return new BeanNameUrlHandlerMapping();
    }

    @Bean
    public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter(){
        return new SimpleControllerHandlerAdapter();
    }

    @Component("/c1")
    public static class Controller1 implements Controller {
        @Override
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            response.getWriter().print("this is c1");
            return null;
        }
    }

    @Component("/c2")
    public static class Controller2 implements Controller {
        @Override
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            response.getWriter().print("this is c2");
            return null;
        }
    }

    @Bean("/c3")
    public Controller controller3(){
        return (request,response) -> {
            response.getWriter().print("this is c3");
            return null;
        };
    }

其中BeanNameUrlHandlerMapping:会把以 / 开头的 bean 的名字会被当作映射路径  ,这些bean本身要被当做handler,所以要求实现Controller接口,SimpleControllerHandlerAdapter:调用 handler

测试代码:

public class A33 {
    public static void main(String[] args) {

        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    }
}

启动程序后,控制台输出:

'beanNameUrlHandlerMapping' {/c2=com.itheima.a33.WebConfig$Controller2@6f152006, /c1=com.itheima.a33.WebConfig$Controller1@3a393455, /c3=com.itheima.a33.WebConfig$$Lambda$190/0x0000000800d8fbe8@13518f37}

打开浏览器分别测试对应请求路径:

 2.自定义HandlerMapping和HandlerAdapter

 定义一个配置类WebConfig_1:

@Configuration
public class WebConfig_1 {

    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory(){
        return new TomcatServletWebServerFactory(8080);
    }

    @Bean
    public DispatcherServlet dispatcherServlet(){
        return new DispatcherServlet();
    }

    @Bean
    public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet){
        DispatcherServletRegistrationBean servletRegistrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        servletRegistrationBean.setLoadOnStartup(1);
        return servletRegistrationBean;
    }

    @Component
    static class MyHandlerMapping implements HandlerMapping{

        @Override
        public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
            String key = request.getRequestURI();
            Controller controller = collect.get(key);
            if (controller == null) {
                return null;
            }
            return new HandlerExecutionChain(controller);
        }
        @Autowired
        private ApplicationContext context;
        private Map<String,Controller> collect;
        @PostConstruct
        public void init(){
             collect = context.getBeansOfType(Controller.class).entrySet()
                    .stream().filter(e -> e.getKey().startsWith("/"))
                     .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
            System.out.println(collect);
        }
    }

    @Component
    static class MyHandlerAdapter implements HandlerAdapter {

        @Override
        public boolean supports(Object handler) {
            return handler instanceof Controller;
        }

        @Override
        public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            if (handler instanceof  Controller controller) {
                controller.handleRequest(request, response);
            }
            return null;
        }

        @Override
        public long getLastModified(HttpServletRequest request, Object handler) {
            return -1;
        }
    }

    @Component("/c1")
    public static class Controller1 implements Controller {
        @Override
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            response.getWriter().print("this is c1");
            return null;
        }
    }

    @Component("c2")
    public static class Controller2 implements Controller {
        @Override
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            response.getWriter().print("this is c2");
            return null;
        }
    }

    @Bean("/c3")
    public Controller controller3(){
        return (request,response) -> {
            response.getWriter().print("this is c3");
            return null;
        };
    }

}

测试代码:

public class A33_1 {
    public static void main(String[] args) {

        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig_1.class);
    }
}

其中去掉Controller2请求路径上的/

启动程序后,控制台输出:

{/c1=com.itheima.a33.WebConfig_1$Controller1@24d09c1, /c3=com.itheima.a33.WebConfig_1$$Lambda$193/0x0000000800d8c578@54c62d71}

打开浏览器再次分别访问对应请求路径,其中加/的c1和c3正常运行,结果和上面的运行结果一样,然而没有加/的c2就无法正常运行

卅四、RouterFunctionMapping 与 HandlerFunctionAdapter

定义一个配置类WebConfig:

@Configuration
public class WebConfig {
    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory(){
        return new TomcatServletWebServerFactory(8080);
    }

    @Bean
    public DispatcherServlet dispatcherServlet(){
        return new DispatcherServlet();
    }

    @Bean
    public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet){
        DispatcherServletRegistrationBean servletRegistrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        servletRegistrationBean.setLoadOnStartup(1);
        return servletRegistrationBean;
    }

    @Bean
    public RouterFunctionMapping routerFunctionMapping(){
        return new RouterFunctionMapping();
    }

    @Bean
    public HandlerFunctionAdapter handlerFunctionAdapter(){
        return new HandlerFunctionAdapter();
    }

    //静态方法可以使用静态导入,省略前边类型的名字
    @Bean
    public RouterFunction<ServerResponse> r1(){
        //return RouterFunctions.route(RequestPredicates.GET("/r1"), request -> ServerResponse.ok().body("this is r1"));
        return route(GET("/r1"), request -> ok().body("this is r1"));
    }

    @Bean
    public RouterFunction<ServerResponse> r2(){
        //return RouterFunctions.route(RequestPredicates.GET("/r1"), request -> ServerResponse.ok().body("this is r1"));
        return route(GET("/r2"), request -> ok().body("this is r2"));
    }
}

 其中RouterFunctionMapping:通过 RequestPredicate 条件映射,HandlerFunctionAdapter:调用 handler

测试代码:

public class A34 {

    public static void main(String[] args) {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
 }
}

启动程序后,控制台输出:

20:30:19.311 [main] DEBUG _org.springframework.web.servlet.HandlerMapping.Mappings - Mapped (GET && /r1) -> com.itheima.a34.WebConfig$$Lambda$196/0x0000000800d930c8@77d2e85
20:30:19.311 [main] DEBUG _org.springframework.web.servlet.HandlerMapping.Mappings - Mapped (GET && /r2) -> com.itheima.a34.WebConfig$$Lambda$197/0x0000000800d961f8@3ecd267f

打开浏览器分别访问测试路径:

卅五、SimpleUrlHandlerMapping 与 HttpRequestHandlerAdapter

1.SimpleUrlHandlerMapping 与 HttpRequestHandlerAdapter

定义一个配置类WebConfig:

@Configuration
public class WebConfig {

    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory(){
        return new TomcatServletWebServerFactory(8080);
    }

    @Bean
    public DispatcherServlet dispatcherServlet(){
        return new DispatcherServlet();
    }

    @Bean
    public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet){
        DispatcherServletRegistrationBean servletRegistrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        servletRegistrationBean.setLoadOnStartup(1);
        return servletRegistrationBean;
    }

    @Bean
    public SimpleUrlHandlerMapping simpleUrlHandlerMapping(ApplicationContext context){
        SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
        Map<String, ResourceHttpRequestHandler> map = context.getBeansOfType(ResourceHttpRequestHandler.class);
        handlerMapping.setUrlMap(map);
        System.out.println(map);
        return handlerMapping;
    }

    @Bean
    public HttpRequestHandlerAdapter handlerAdapter(){
        return new HttpRequestHandlerAdapter();
    }

    @Bean("/**")
    public ResourceHttpRequestHandler handler1(){
        ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
        handler.setLocations(List.of(new ClassPathResource("static/")));
        return handler;
    }

    @Bean("/img/**")
    public ResourceHttpRequestHandler handler2(){
        ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
        handler.setLocations(List.of(new ClassPathResource("images/")));
        return handler;
    }
}

 其中 SimpleUrlHandlerMapping :映射路径,不会在初始化时收集映射信息,需要手动收集,ResourceHttpRequestHandler:作为静态资源 handler,HttpRequestHandlerAdapter:调用此 handler

在resources下新建static目录和images目录,放入对应的静态资源

测试代码:

public class A35 {

    public static void main(String[] args) {
        AnnotationConfigServletWebServerApplicationContext context
                = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    }
}

打开浏览器,分别测试对应的路径 localhost:8080/r1.html localhost:8080/img/1.jpg

 2.静态资源解析优化

修改WebConfig配置类的部分代码,具体实现如下:

 @Bean("/**")
    public ResourceHttpRequestHandler handler1(){
        ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
        handler.setLocations(List.of(new ClassPathResource("static/")));
        handler.setResourceResolvers(List.of(
                new CachingResourceResolver(new ConcurrentMapCache("cache1")), //读取缓存资源的解析器
                new EncodedResourceResolver(), //读取压缩文件的解析器
                new PathResourceResolver()  //读取磁盘路径的解析器
        ));
        return handler;
    }

    @PostConstruct
    @SuppressWarnings("all")
    //生成html文件的压缩文件
    public void initGzip() throws IOException {
        Resource resource = new ClassPathResource("static");
        File dir = resource.getFile();
        for (File file : dir.listFiles(pathname -> pathname.getName().endsWith(".html"))) {
            System.out.println(file);
            try (FileInputStream fis = new FileInputStream(file); GZIPOutputStream fos = new GZIPOutputStream(new FileOutputStream(file.getAbsoluteFile() + ".gz"))) {
                byte[] bytes = new byte[8 * 1024];
                int len;
                while ((len = fis.read(bytes)) != -1) {
                    fos.write(bytes, 0, len);
                }
            }
        }
    }

其中CachingResourceResolver:读取缓存资源的解析器,进行缓存优化;EncodedResourceResolver:读取压缩文件的解析器,进行压缩优化(压缩文件需要手动生成);PathResourceResolver:读取磁盘路径的解析器,对原始资源进行解析

重新启动程序,打开浏览器测试对应路径 localhost:8080/r1.html ,在浏览器中按Ctrl+F5强制刷新页面,观察控制台输出:

[TRACE] 15:00:05.128 [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet   - GET "/r1.html", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 
[TRACE] 15:00:05.128 [http-nio-8080-exec-2] o.s.w.s.r.CachingResourceResolver   - Resource resolved from cache 
[TRACE] 15:00:05.135 [http-nio-8080-exec-2] o.s.w.s.r.ResourceHttpRequestHandler - Applying default cacheSeconds=-1 
[TRACE] 15:00:05.136 [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet   - No view rendering, null ModelAndView returned. 
[DEBUG] 15:00:05.136 [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet   - Completed 200 OK, headers={masked} 
[TRACE] 15:00:05.145 [http-nio-8080-exec-3] o.s.web.servlet.DispatcherServlet   - GET "/favicon.ico", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 
[TRACE] 15:00:05.151 [http-nio-8080-exec-3] o.s.w.s.r.ResourceHttpRequestHandler - Applying default cacheSeconds=-1 
[TRACE] 15:00:05.277 [http-nio-8080-exec-3] o.s.web.servlet.DispatcherServlet   - No view rendering, null ModelAndView returned. 
[DEBUG] 15:00:05.277 [http-nio-8080-exec-3] o.s.web.servlet.DispatcherServlet   - Completed 200 OK, headers={masked}

打开target,找到对应的static目录,可以发现对应的html文件都有相对应的压缩文件:

 3.欢迎页处理

修改WebConfig配置类部分代码 ,添加处理欢迎页的bean:

    @Bean
    public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext context){
        Resource resource = context.getResource("classpath:static/index.html");
        return new WelcomePageHandlerMapping(null, context, resource, "/**");
        //Controller 接口
    }

    @Bean
    public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter(){
        return new SimpleControllerHandlerAdapter();
    }
  1. 欢迎页支持静态欢迎页与动态欢迎页

  2. WelcomePageHandlerMapping :映射欢迎页(即只映射 '/')

    • 它内置的 handler ParameterizableViewController 作用是不执行逻辑,仅根据视图名找视图

    • 视图名固定为 forward:index.html

  3. SimpleControllerHandlerAdapter:调用 handler

    • 转发至 /index.html

    • 处理 /index.html 又会走上面的静态资源处理流程

重新启动程序,打开浏览器测试 localhost:8080 

 4.映射器与适配器小结

  1. HandlerMapping 负责建立请求与控制器之间的映射关系

    • RequestMappingHandlerMapping (与 @RequestMapping 匹配)

    • WelcomePageHandlerMapping (/)

    • BeanNameUrlHandlerMapping (与 bean 的名字匹配 以 / 开头)

    • RouterFunctionMapping (函数式 RequestPredicate, HandlerFunction)

    • SimpleUrlHandlerMapping (静态资源 通配符 /** /img/**)

    • 之间也会有顺序问题, boot 中默认顺序如上

  2. HandlerAdapter 负责实现对各种各样的 handler 的适配调用

    • RequestMappingHandlerAdapter 处理:@RequestMapping 方法

      • 参数解析器、返回值处理器体现了组合模式

    • SimpleControllerHandlerAdapter 处理:Controller 接口

    • HandlerFunctionAdapter 处理:HandlerFunction 函数式接口

    • HttpRequestHandlerAdapter 处理:HttpRequestHandler 接口 (静态资源处理)

    • 这也是典型适配器模式体现

卅六、MVC处理流程

当浏览器发送一个请求 http://localhost:8080/hello 后,请求到达服务器,其处理流程是:

  1. 服务器提供了 DispatcherServlet,它使用的是标准 Servlet 技术

    • 路径:默认映射路径为 /,即会匹配到所有请求 URL,可作为请求的统一入口,也被称之为前控制器

      • jsp 不会匹配到 DispatcherServlet

      • 其它有路径的 Servlet 匹配优先级也高于 DispatcherServlet

    • 创建:在 Boot 中,由 DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherServlet 的 bean

    • 初始化:DispatcherServlet 初始化时会优先到容器里寻找各种组件,作为它的成员变量

      • HandlerMapping,初始化时记录映射关系

      • HandlerAdapter,初始化时准备参数解析器、返回值处理器、消息转换器

      • HandlerExceptionResolver,初始化时准备参数解析器、返回值处理器、消息转换器

      • ViewResolver

  2. DispatcherServlet 会利用 RequestMappingHandlerMapping 查找控制器方法

    • 例如根据 /hello 路径找到 @RequestMapping("/hello") 对应的控制器方法

    • 控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给 DispatcherServlet

    • HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain(调用链)对象

  3. DispatcherServlet 接下来会:

    1. 调用拦截器的 preHandle 方法

    2. RequestMappingHandlerAdapter 调用 handle 方法,准备数据绑定工厂、模型工厂、ModelAndViewContainer、将 HandlerMethod 完善为 ServletInvocableHandlerMethod

      • @ControllerAdvice 全局增强点1️⃣:补充模型数据

      • @ControllerAdvice 全局增强点2️⃣:补充自定义类型转换器

      • 使用 HandlerMethodArgumentResolver 准备参数

        • @ControllerAdvice 全局增强点3️⃣:RequestBody 增强

      • 调用 ServletInvocableHandlerMethod

      • 使用 HandlerMethodReturnValueHandler 处理返回值

        • @ControllerAdvice 全局增强点4️⃣:ResponseBody 增强

      • 根据 ModelAndViewContainer 获取 ModelAndView

        • 如果返回的 ModelAndView 为 null,不走第 4 步视图解析及渲染流程

          • 例如,有的返回值处理器调用了 HttpMessageConverter 来将结果转换为 JSON,这时 ModelAndView 就为 null

        • 如果返回的 ModelAndView 不为 null,会在第 4 步走视图解析及渲染流程

    3. 调用拦截器的 postHandle 方法

    4. 处理异常或视图渲染

      • 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程

        • @ControllerAdvice 全局增强点5️⃣:@ExceptionHandler 异常处理

      • 正常,走视图解析及渲染流程

    5. 调用拦截器的 afterCompletion 方法

卅七、Boot骨架项目

如果是 linux 环境,用以下命令即可获取 spring boot 的骨架 pom.xml

curl -G https://start.spring.io/pom.xml -d dependencies=web,mysql,mybatis -o pom.xml

也可以使用 Postman 等工具实现

生成pom.xml文件之后 ,点击save按钮进行保存

若想获取更多用法,请参考

 curl https://start.spring.io

卅八、 Boot War项目

 1.构建Boot War项目

步骤1:创建模块,区别在于打包方式选择 war

 

 接下来勾选 Spring Web 支持

步骤2:编写控制器

@Controller
public class MyController {

    @RequestMapping("/hello")
    public String abc() {
        System.out.println("进入了控制器");
        return "hello";
    }
}

 步骤3:编写 jsp 视图,新建 webapp 目录和一个 hello.jsp 文件,注意文件名与控制器方法返回的视图逻辑名一致

src
    |- main
        |- java
        |- resources
        |- webapp
            |- hello.jsp

步骤4:配置视图路径,打开 application.properties 文件

spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp

 将来 prefix + 控制器方法返回值 + suffix 即为视图完整路径

2.用外置Tomcat测试

骨架生成的代码中,多了一个 ServletInitializer,它的作用就是配置外置 Tomcat 使用的,在外置 Tomcat 启动后,去调用它创建和运行 SpringApplication

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application){
        return application.sources(A11Application.class);
    }
}

配置本地Tomcat (相对内置Tomcat,启动速度较慢)

3.用内置Tomcat测试

如果用 mvn 插件 mvn spring-boot:run 或 main 方法测试

  • 必须添加如下依赖,因为此时用的还是内嵌 tomcat,而内嵌 tomcat 默认不带 jasper(用来解析 jsp)

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>provided</scope>
</dependency>

 不加这个jasper依赖,访问对应测试路径,浏览器会把Jsp文件当成普通文件下载下来

注意:此处需要点开F12 ,把禁用缓存勾选上,不要让其去浏览器缓存中找对应文件

卅九、boot执行流程 

1.SpringApplication 构造  

SpringApplication构造分为五步:

  1. 记录 BeanDefinition 源

  2. 推断应用类型

  3. 记录 ApplicationContext 初始化器

  4. 记录监听器

  5. 推断主启动类

演示SpringApplication构造的五步,具体实现如下:

@Configuration
public class A39_1 {
    public static void main(String[] args) throws Exception{
        System.out.println("1. 演示获取 Bean Definition源");
        SpringApplication spring = new SpringApplication(A39_1.class);
        spring.setSources(Set.of("classpath:b01.xml"));

        System.out.println("2. 演示推断应用类型");
        Method deduceFromClasspath = WebApplicationType.class.getDeclaredMethod("deduceFromClasspath");
        deduceFromClasspath.setAccessible(true);
        System.out.println("\t应用类型为:" + deduceFromClasspath.invoke(null));

        System.out.println("3. 演示 ApplicationContext 初始化器");
        spring.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
            @Override
            public void initialize(ConfigurableApplicationContext applicationContext) {
                if (applicationContext instanceof GenericApplicationContext gac) {
                    gac.registerBean("bean3", Bean3.class);
                }
            }
        });
        System.out.println("4. 演示监听器与事件");
        spring.addListeners(event -> System.out.println("\t事件为:"+event.getClass()));

        System.out.println("5. 演示主类推断");
        Method deduceMainApplicationClass = SpringApplication.class.getDeclaredMethod("deduceMainApplicationClass");
        deduceMainApplicationClass.setAccessible(true);
        System.out.println("主类是:"+deduceMainApplicationClass.invoke(spring));

        ConfigurableApplicationContext context = spring.run(args);
        //创建ApplicationContext
        //调用初始化器 对 ApplicationContext 做扩展
        //ApplicationContext.refresh
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println("name:"+ name + "来源于:"+context.getBeanFactory().getBeanDefinition(name).getResourceDescription());
        }
        context.close();
    }

    static class Bean1{}

    static class Bean2{}

    static class Bean3{}

    @Bean
    public Bean2 bean2(){
        return new Bean2();
    }

    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory(){
        return new TomcatServletWebServerFactory();
    }
}

测试过程中所用到的 b01.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="bean1" class="com.itheima.a39.A39_1.Bean1"/>

</beans>

测试结果为:

1. 演示获取 Bean Definition源
2. 演示推断应用类型
	应用类型为:SERVLET
3. 演示 ApplicationContext 初始化器
4. 演示监听器与事件
5. 演示主类推断
主类是:class com.itheima.a39.A39_1
	事件为:class org.springframework.boot.context.event.ApplicationStartingEvent
	事件为:class org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent
    事件为:class org.springframework.boot.context.event.ApplicationContextInitializedEvent
    事件为:class org.springframework.boot.context.event.ApplicationPreparedEvent
事件为:class org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent
	事件为:class org.springframework.context.event.ContextRefreshedEvent
    事件为:class org.springframework.boot.context.event.ApplicationStartedEvent
	事件为:class org.springframework.boot.availability.AvailabilityChangeEvent
	事件为:class org.springframework.boot.context.event.ApplicationReadyEvent
	事件为:class org.springframework.boot.availability.AvailabilityChangeEvent
name:org.springframework.context.annotation.internalConfigurationAnnotationProcessor来源于:null
name:org.springframework.context.annotation.internalAutowiredAnnotationProcessor来源于:null
name:org.springframework.context.annotation.internalCommonAnnotationProcessor来源于:null
name:org.springframework.context.event.internalEventListenerProcessor来源于:null
name:org.springframework.context.event.internalEventListenerFactory来源于:null
name:bean3来源于:null
name:a39_1来源于:null
name:bean1来源于:class path resource [b01.xml]
name:org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory来源于:null
name:bean2来源于:com.itheima.a39.A39_1
name:servletWebServerFactory来源于:com.itheima.a39.A39_1
	事件为:class org.springframework.boot.availability.AvailabilityChangeEvent
	事件为:class org.springframework.context.event.ContextClosedEvent

2.执行 run 方法

1.得到 SpringApplicationRunListeners (事件发布器)——  发布 application starting 事件1️⃣

public class A39_2 {
    public static void main(String[] args) throws Exception{

        //添加 app 监听器
        SpringApplication app = new SpringApplication();
        app.addListeners(event -> System.out.println(event.getClass()));

        //获取事件发送器实现类名
        List<String> names = SpringFactoriesLoader.loadFactoryNames(SpringApplicationRunListener.class, A39_2.class.getClassLoader());
        for (String name : names) {
            System.out.println(name);
            Class<?> aClass = Class.forName(name);
            Constructor<?> constructor = aClass.getConstructor(SpringApplication.class, String[].class);
            SpringApplicationRunListener publisher = (SpringApplicationRunListener) constructor.newInstance(app, args);

            //发布事件
            DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
            publisher.starting(bootstrapContext); // spring boot 开始启动
            publisher.environmentPrepared(bootstrapContext,new StandardEnvironment());// 环境信息准备完毕
            GenericApplicationContext context = new GenericApplicationContext();
            publisher.contextPrepared(context); //在 spring 容器创建,并调用初始化器之后,发送此事件
            publisher.contextLoaded(context); // 所有 bean definition 加载完毕
            context.refresh();
            publisher.started(context); // spring 容器初始化完毕(refresh 方法调用完毕)
            publisher.running(context); // spring boot 启动完毕
            publisher.failed(context, new Exception("出错了")); // spring boot 启动报错
        }
    }
}

 SpringApplicationRunListener 发布上述对应的7个事件(以org.springframework.boot.context.event开头的)

org.springframework.boot.context.event.EventPublishingRunListener
class org.springframework.boot.context.event.ApplicationStartingEvent
class org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent
class org.springframework.boot.context.event.ApplicationContextInitializedEvent
class org.springframework.boot.context.event.ApplicationPreparedEvent
class org.springframework.context.event.ContextRefreshedEvent
class org.springframework.boot.context.event.ApplicationStartedEvent
class org.springframework.boot.availability.AvailabilityChangeEvent
class org.springframework.boot.context.event.ApplicationReadyEvent
class org.springframework.boot.availability.AvailabilityChangeEvent
class org.springframework.boot.context.event.ApplicationFailedEvent

演示

        2.封装启动 args

        8.创建容器

        9.准备容器 —— 发布 application context 已初始化事件3️⃣

        10.加载 bean 定义  —— 发布 application prepared 事件4️⃣

        11.refresh 容器 —— 发布 application started 事件5️⃣

        12.执行 runner —— 发布 application ready 事件6️⃣,这其中有异常,发布 application         failed 事件7️⃣

//运行时请添加运行参数 --server.port=8080 debug
public class A39_3 {

    @SuppressWarnings("all")
    public static void main(String[] args) throws Exception {
        SpringApplication app = new SpringApplication();
        app.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
            @Override
            public void initialize(ConfigurableApplicationContext applicationContext) {
                System.out.println("执行初始化器增强...");
            }
        });

        System.out.println(">>>>>>>>>>>>>>>>>>> 2. 封装启动 args");
        DefaultApplicationArguments arguments = new DefaultApplicationArguments(args);

        System.out.println(">>>>>>>>>>>>>>>>>>> 8. 创建容器");
        GenericApplicationContext context = createApplicationContext(WebApplicationType.SERVLET);

        System.out.println(">>>>>>>>>>>>>>>>>>> 9. 准备容器");
        for (ApplicationContextInitializer initializer : app.getInitializers()) {
            initializer.initialize(context);
        }

        System.out.println(">>>>>>>>>>>>>>>>>>> 10. 加载 bean 定义");
        DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
        AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(beanFactory);
        reader.register(Config.class);
        XmlBeanDefinitionReader reader1 = new XmlBeanDefinitionReader(beanFactory);
        reader1.loadBeanDefinitions(new ClassPathResource("b03.xml"));
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);
        scanner.scan("com.itheima.a39.sub");

        System.out.println(">>>>>>>>>>>>>>>>>>> 11. refresh 容器");
        context.refresh();

        for (String name : context.getBeanDefinitionNames()) {
            System.out.println("名字:" + name + " 来源:" + beanFactory.getBeanDefinition(name).getResourceDescription());
        }

        System.out.println(">>>>>>>>>>>>>>>>>>> 12. 执行 runner");
        for (CommandLineRunner runner : context.getBeansOfType(CommandLineRunner.class).values()) {
            runner.run(args);
        }

        for (ApplicationRunner runner : context.getBeansOfType(ApplicationRunner.class).values()) {
            runner.run(arguments);
        }

    }


    private static GenericApplicationContext createApplicationContext(WebApplicationType type){
        GenericApplicationContext context = null;
        switch (type){
            case SERVLET -> context = new AnnotationConfigServletWebServerApplicationContext();
            case REACTIVE -> context = new AnnotationConfigReactiveWebApplicationContext();
            case NONE -> context = new AnnotationConfigApplicationContext();
        }
        return context;
    }

    static class Bean4{}

    static class Bean5{}

    static class Bean6{}

    @Configuration
    static class Config{
        @Bean
        public Bean5 bean5(){
            return new Bean5();
        }

        @Bean
        public ServletWebServerFactory servletWebServerFactory(){
            return new TomcatServletWebServerFactory();
        }

        @Bean
        public CommandLineRunner commandLineRunner(){
            return new CommandLineRunner() {
                @Override
                public void run(String... args) throws Exception {
                    System.out.println("commandLineRunner..." + Arrays.toString(args));
                }
            };
        }

        @Bean
        public ApplicationRunner applicationRunner(){
            return new ApplicationRunner() {
                @Override
                public void run(ApplicationArguments args) throws Exception {
                    System.out.println("applicationRunner..." + Arrays.toString(args.getSourceArgs()));
                    System.out.println(args.getOptionNames());
                    System.out.println(args.getOptionValues("server.port"));
                    System.out.println(args.getNonOptionArgs());
                }
            };
        }
    }
}

 测试过程中用到的b03.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="bean4" class="com.itheima.a39.A39_3.Bean4"/>

</beans>

在a39包下创建sub包,然后定义Bean7:

@Component
public class Bean7 {

}

运行需要添加运行参数  --server.port=8080 debug 

测试结果为:

>>>>>>>>>>>>>>>>>>> 2. 封装启动 args
>>>>>>>>>>>>>>>>>>> 8. 创建容器
>>>>>>>>>>>>>>>>>>> 9. 准备容器
执行初始化器增强...
>>>>>>>>>>>>>>>>>>> 10. 加载 bean 定义
>>>>>>>>>>>>>>>>>>> 11. refresh 容器
名字:org.springframework.context.annotation.internalConfigurationAnnotationProcessor 来源:null
名字:org.springframework.context.annotation.internalAutowiredAnnotationProcessor 来源:null
名字:org.springframework.context.annotation.internalCommonAnnotationProcessor 来源:null
名字:org.springframework.context.event.internalEventListenerProcessor 来源:null
名字:org.springframework.context.event.internalEventListenerFactory 来源:null
名字:a39_3.Config 来源:null
名字:bean4 来源:class path resource [b03.xml]
名字:bean7 来源:file [D:\Workspace\Html\Spring5\aspectj_01\target\classes\com\itheima\a39\sub\Bean7.class]
名字:org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory 来源:null
名字:bean5 来源:com.itheima.a39.A39_3$Config
名字:servletWebServerFactory 来源:com.itheima.a39.A39_3$Config
名字:commandLineRunner 来源:com.itheima.a39.A39_3$Config
名字:applicationRunner 来源:com.itheima.a39.A39_3$Config
>>>>>>>>>>>>>>>>>>> 12. 执行 runner
commandLineRunner...[--server.port=8080, debug]
applicationRunner...[--server.port=8080, debug]
[server.port]
[8080]
[debug]

 3.准备 Environment 添加命令行参数

public class Step3 {
    public static void main(String[] args) throws IOException {
        ApplicationEnvironment env = new ApplicationEnvironment();//系统环境变量,properties,yaml
        env.getPropertySources().addLast(new ResourcePropertySource(new ClassPathResource("application.properties")));
        env.getPropertySources().addFirst(new SimpleCommandLinePropertySource(args));
        for (PropertySource<?> propertySource : env.getPropertySources()) {
            System.out.println(propertySource);
        }
        System.out.println(env.getProperty("MAVEN_HOME"));

        System.out.println(env.getProperty("server.port"));//只对内嵌Tomcat有效
    }
}

测试结果为:

SimpleCommandLinePropertySource {name='commandLineArgs'}
PropertiesPropertySource {name='systemProperties'}
SystemEnvironmentPropertySource {name='systemEnvironment'}
ResourcePropertySource {name='class path resource [application.properties]'}
D:\Maven\apache-maven-3.8.4-bin\apache-maven-3.8.4
8080

默认只有 systemProperties 和 systemEnvironment,执行顺序为从上到下(找到就不向下继续找)

4.ConfigurationPropertySources 处理 —— 发布 application environment 已准备事件2️⃣

public class Step4 {

    public static void main(String[] args) throws IOException {
        ApplicationEnvironment env = new ApplicationEnvironment();
        env.getPropertySources().addLast(
                new ResourcePropertySource("step4",new ClassPathResource("step4.properties")));

        ConfigurationPropertySources.attach(env);
        for (PropertySource<?> propertySource : env.getPropertySources()) {
            System.out.println(propertySource);
        }
        System.out.println(env.getProperty("user.first-name"));
        System.out.println(env.getProperty("user.middle-name"));
        System.out.println(env.getProperty("user.last-name"));
    }

}

 测试过程中用到的 step4.properties 文件:

user.first-name=George
user.middle_name=Walker
user.lastName=Bush

测试结果为:

ConfigurationPropertySourcesPropertySource {name='configurationProperties'}
PropertiesPropertySource {name='systemProperties'}
SystemEnvironmentPropertySource {name='systemEnvironment'}
ResourcePropertySource {name='step4'}
George
Walker
Bush

5.通过 EnvironmentPostProcessorApplicationListener 进行 env 后处理

  • application.properties,由 StandardConfigDataLocationResolver 解析

  • spring.application.json

public class Step5 {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication();

        //List<String> names =
        //        SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, Step5.class.getClassLoader());
        //for (String name : names) {
        //    System.out.println(name);
        //}

        EventPublishingRunListener publisher = new EventPublishingRunListener(app, args);
        ApplicationEnvironment env = new ApplicationEnvironment();
        System.out.println(">>>>>>>>>>>>>>>>>>>> 增强前");
        for (PropertySource<?> propertySource : env.getPropertySources()) {
            System.out.println(propertySource);
        }
        publisher.environmentPrepared(new DefaultBootstrapContext(),env);
        System.out.println(">>>>>>>>>>>>>>>>>>>> 增强后");
        for (PropertySource<?> propertySource : env.getPropertySources()) {
            System.out.println(propertySource);
        }
    }

    private static void test1() {
        SpringApplication app = new SpringApplication();
        ApplicationEnvironment env = new ApplicationEnvironment();

        System.out.println(">>>>>>>>>>>>>>>>>>>> 增强前");
        for (PropertySource<?> propertySource : env.getPropertySources()) {
            System.out.println(propertySource);
        }

        ConfigDataEnvironmentPostProcessor postProcessor1 =
                new ConfigDataEnvironmentPostProcessor(new DeferredLogs(), new DefaultBootstrapContext());
        postProcessor1.postProcessEnvironment(env, app);

        System.out.println(">>>>>>>>>>>>>>>>>>>> 增强后");
        for (PropertySource<?> propertySource : env.getPropertySources()) {
            System.out.println(propertySource);
        }

        RandomValuePropertySourceEnvironmentPostProcessor postProcessor2 =
                new RandomValuePropertySourceEnvironmentPostProcessor(new DeferredLog());
        postProcessor2.postProcessEnvironment(env, app);

        System.out.println(">>>>>>>>>>>>>>>>>>>> 增强后");
        for (PropertySource<?> propertySource : env.getPropertySources()) {
            System.out.println(propertySource);
        }

        System.out.println(env.getProperty("server.port"));
        System.out.println(env.getProperty("random.int"));
    }
}

 测试结果为:

>>>>>>>>>>>>>>>>>>>> 增强前
PropertiesPropertySource {name='systemProperties'}
SystemEnvironmentPropertySource {name='systemEnvironment'}
>>>>>>>>>>>>>>>>>>>> 增强后
PropertiesPropertySource {name='systemProperties'}
OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}
RandomValuePropertySource {name='random'}
OriginTrackedMapPropertySource {name='Config resource 'class path resource [application.properties]' via location 'optional:classpath:/''}

6.绑定 spring.main 到 SpringApplication 对象

public class Step6 {

    //绑定 spring.main 前缀的 key value 至 SpringApplication,请通过 debug 查看
    public static void main(String[] args) throws IOException {

        SpringApplication application = new SpringApplication();
        ApplicationEnvironment env = new ApplicationEnvironment();
        env.getPropertySources().addLast(new ResourcePropertySource("step4",new ClassPathResource("step4.properties")));
        env.getPropertySources().addLast(new ResourcePropertySource("step6",new ClassPathResource("step6.properties")));

        //User user = Binder.get(env).bind("user", User.class).get();
        //System.out.println(user);

        //User user = new User();
        //Binder.get(env).bind("user", Bindable.ofInstance(user));
        //System.out.println(user);

        System.out.println(application);
        Binder.get(env).bind("spring.main", Bindable.ofInstance(application));
        System.out.println(application);
    }

    static class User{
        private String firstName;
        private String middleName;
        private String lastName;

        public String getFirstName() {
            return firstName;
        }

        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }

        public String getMiddleName() {
            return middleName;
        }

        public void setMiddleName(String middleName) {
            this.middleName = middleName;
        }

        public String getLastName() {
            return lastName;
        }

        public void setLastName(String lastName) {
            this.lastName = lastName;
        }

        @Override
        public String toString() {
            return "User{" +
                    "firstName='" + firstName + '\'' +
                    ", middleName='" + middleName + '\'' +
                    ", lastName='" + lastName + '\'' +
                    '}';
        }
    }
}

 测试过程中需要用到的 step6.properties 文件:

spring.main.banner-mode=off
spring.main.lazy-initialization=true

因为绑定的两个属性都是私有的成员变量,所以无法直接查看,此时需要通过断点来进行调试

 成功绑定 spring.main 前缀的 key value 至 SpringApplication

​​​​​​​

7.打印 banner

public class Step7 {
    public static void main(String[] args) {
        ApplicationEnvironment env = new ApplicationEnvironment();
        SpringApplicationBannerPrinter printer =
                new SpringApplicationBannerPrinter(new DefaultResourceLoader(),new SpringBootBanner());

        //测试文字 banner
        //env.getPropertySources().addLast(new MapPropertySource("custom", Map.of("spring.banner.location","banner1.txt")));
        //测试图片banner
        //env.getPropertySources().addLast(new MapPropertySource("custom", Map.of("spring.banner.image.location","banner2.png")));
        //版本号的获取
        System.out.println(SpringBootVersion.getVersion());
        printer.print(env, Step7.class, System.out);
    }
}

测试文字banner和对应版本号的结果为:

2.5.5


  @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  @@@@@@@@@@@@@@@@@@@#::::::::::::::::::::::::::::::::::::#@@@@@@@@@@@@@@@@@@@
  @@@@@@@@@@@@@@@@@#::::::::::::::::::::::::::::::::::::::::#@@@@@@@@@@@@@@@@@
  @@@@@@@@@@@@@@@@o::::::::::::::::::::::::::::::::::::::::::o@@@@@@@@@@@@@@@@
  @@@@@@@@@@@@@@@::::::::::::::::::::::::::::::::::::::::::::::@@@@@@@@@@@@@@@
  @@@@@@@@@@@@@@::::::::::::::::::::::::::::::::::::::::::::::::@@@@@@@@@@@@@@
  @@@@@@@@@@@@@:::::::::::::::::::::::@@@8:::::::::::::::::::::::@@@@@@@@@@@@@
  @@@@@@@@@@@@::::::::::::::::::::::o@@@@@@:::::::::::::::::::::::@@@@@@@@@@@@
  @@@@@@@@@@@:::::::::::::::@@@:::::o@@@@@@:::::@@@@:::::::::::::::@@@@@@@@@@@
  @@@@@@@@@&::::::::::::::@@@@@@::::o@@@@@@::::@@@@@@@::::::::::::::&@@@@@@@@@
  @@@@@@@@::::::::::::::@@@@@@@o::::o@@@@@@:::::@@@@@@@#::::::::::::::@@@@@@@@
  @@@@@@@::::::::::::::@@@@@@o::::::o@@@@@@::::::::@@@@@@::::::::::::::@@@@@@@
  @@@@@@::::::::::::::@@@@@8::::::::o@@@@@@:::::::::@@@@@@o:::::::::::::@@@@@@
  @@@@@::::::::::::::@@@@@@:::::::::o@@@@@@::::::::::@@@@@@::::::::::::::@@@@@
  @@@@::::::::::::::&@@@@@::::::::::o@@@@@@:::::::::::@@@@@:::::::::::::::@@@@
  @@@:::::::::::::::@@@@@o::::::::::o@@@@@@:::::::::::@@@@@&:::::::::::::::@@@
  @@::::::::::::::::@@@@@:::::::::::o@@@@@@:::::::::::#@@@@#::::::::::::::::@@
  @@::::::::::::::::@@@@@o::::::::::::@@@@::::::::::::@@@@@&::::::::::::::::@@
  @@&:::::::::::::::o@@@@@::::::::::::::::::::::::::::@@@@@::::::::::::::::&@@
  @@@o:::::::::::::::@@@@@#::::::::::::::::::::::::::@@@@@@:::::::::::::::o@@@
  @@@@@:::::::::::::::@@@@@#::::::::::::::::::::::::@@@@@@o::::::::::::::@@@@@
  @@@@@@:::::::::::::::@@@@@@:::::::::::::::::::::o@@@@@@:::::::::::::::@@@@@@
  @@@@@@@:::::::::::::::@@@@@@@o::::::::::::::::@@@@@@@@:::::::::::::::@@@@@@@
  @@@@@@@@::::::::::::::::@@@@@@@@@8:::::::o@@@@@@@@@@::::::::::::::::@@@@@@@@
  @@@@@@@@@:::::::::::::::::@@@@@@@@@@@@@@@@@@@@@@@#:::::::::::::::::@@@@@@@@@
  @@@@@@@@@@:::::::::::::::::::#@@@@@@@@@@@@@@@@&:::::::::::::::::::@@@@@@@@@@
  @@@@@@@@@@@8::::::::::::::::::::::::oooo::::::::::::::::::::::::8@@@@@@@@@@@
  @@@@@@@@@@@@@::::::::::::::::::::::::::::::::::::::::::::::::::@@@@@@@@@@@@@
  @@@@@@@@@@@@@@::::::::::::::::::::::::::::::::::::::::::::::::@@@@@@@@@@@@@@
  @@@@@@@@@@@@@@@::::::::::::::::::::::::::::::::::::::::::::::@@@@@@@@@@@@@@@
  @@@@@@@@@@@@@@@@::::::::::::::::::::::::::::::::::::::::::::@@@@@@@@@@@@@@@@
  @@@@@@@@@@@@@@@@@::::::::::::::::::::::::::::::::::::::::::@@@@@@@@@@@@@@@@@
  @@@@@@@@@@@@@@@@@@@::::::::::::::::::::::::::::::::::::::@@@@@@@@@@@@@@@@@@@
  @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

卌、Tomcat 内嵌容器

1.Tomcat基本结构 

Server
└───Service
    ├───Connector (协议, 端口)
    └───Engine
        └───Host(虚拟主机 localhost)
            ├───Context1 (应用1, 可以设置虚拟路径, / 即 url 起始路径; 项目磁盘路径, 即 docBase )
            │   │   index.html
            │   └───WEB-INF
            │       │   web.xml (servlet, filter, listener) 3.0
            │       ├───classes (servlet, controller, service ...)
            │       ├───jsp
            │       └───lib (第三方 jar 包)
            └───Context2 (应用2)
                │   index.html
                └───WEB-INF
                        web.xml 

2.Tomcat内嵌容器 

自定义一个HelloServlet :

public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        resp.getWriter().print("""
            <h1>hello</h1>
            """);
    }
}

演示代码:

@SuppressWarnings("all")
    public static void main(String[] args) throws IOException, LifecycleException {
        //1. 创建Tomcat对象
        Tomcat tomcat = new Tomcat();
        tomcat.setBaseDir("tomcat");

        //2.创建项目文件夹,即 docBase 文件夹
        File docBase = Files.createTempDirectory("boot.").toFile();
        docBase.deleteOnExit();//程序结束后自动删除

        //3. 创建 Tomcat项目,在 Tomcat 中称为 context
        Context context = tomcat.addContext("", docBase.getAbsolutePath());

        //4. 编程添加 servlet
        context.addServletContainerInitializer(new ServletContainerInitializer() {
            @Override
            public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
                HelloServlet helloServlet = new HelloServlet();
                servletContext.addServlet("aaa", helloServlet).addMapping("/hello");

                
            }
        }, Collections.emptySet());

        //5. 启动 Tomcat
        tomcat.start();

        //6. 创建连接器,设置监听端口
        Connector connector = new Connector(new Http11Nio2Protocol());
        connector.setPort(8080);
        tomcat.setConnector(connector);
    }

打开浏览器,访问 localhost:8080/hello 路径

3.内嵌Tomcat与Spring整合

修改演示代码:

@SuppressWarnings("all")
    public static void main(String[] args) throws IOException, LifecycleException {
        //1. 创建Tomcat对象
        Tomcat tomcat = new Tomcat();
        tomcat.setBaseDir("tomcat");

        //2.创建项目文件夹,即 docBase 文件夹
        File docBase = Files.createTempDirectory("boot.").toFile();
        docBase.deleteOnExit();//程序结束后自动删除

        //3. 创建 Tomcat项目,在 Tomcat 中称为 context
        Context context = tomcat.addContext("", docBase.getAbsolutePath());
        WebApplicationContext springContext = getApplicationContext();

        //4. 编程添加 servlet
        context.addServletContainerInitializer(new ServletContainerInitializer() {
            @Override
            public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
                HelloServlet helloServlet = new HelloServlet();
                servletContext.addServlet("aaa", helloServlet).addMapping("/hello");

                //DispatcherServlet dispatcherServlet = springContext.getBean(DispatcherServlet.class);
                //servletContext.addServlet("dispatcherServlet", dispatcherServlet).addMapping("/");
                for (ServletRegistrationBean registrationBean : springContext.getBeansOfType(ServletRegistrationBean.class).values()) {
                    registrationBean.onStartup(servletContext);
                }
            }
        }, Collections.emptySet());

        //5. 启动 Tomcat
        tomcat.start();

        //6. 创建连接器,设置监听端口
        Connector connector = new Connector(new Http11Nio2Protocol());
        connector.setPort(8080);
        tomcat.setConnector(connector);
    }

    public static WebApplicationContext getApplicationContext(){
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(Config.class);
        context.refresh();
        return context;
    }

    @Configuration
    static class Config{
        @Bean
        public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet){
            return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        }

        @Bean
        //这个例子必须为 DispatcherServlet 提供 AnnotationConfigWebApplicationContext ,否则会选择 XmlWebApplicationContext 实现
        public DispatcherServlet dispatcherServlet(WebApplicationContext applicationContext){
            return new DispatcherServlet(applicationContext);
        }

        @Bean
        public RequestMappingHandlerAdapter requestMappingHandlerAdapter(){
            RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
            handlerAdapter.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
            return handlerAdapter;
        }

        @RestController
        static class MyController{
            @GetMapping("hello2")
            public Map<String,Object> hello(){
                return Map.of("hello2","hello2,spring");
            }
        }
    }

 重启程序后,访问 localhost:8080/hello2 路径

卌一、自动配置类

1.自动配置类原理 

 演示代码为:

public class A41 {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.getDefaultListableBeanFactory().setAllowBeanDefinitionOverriding(false);//不允许相同bean覆盖,SpringBoot中就是这么做的
        context.registerBean("config", Config.class);
        context.registerBean( ConfigurationClassPostProcessor.class);
        context.refresh();

        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        System.out.println(context.getBean(Bean1.class));
    }

    @Configuration
    @Import(MyImportSelector.class)
    static class Config{//本项目的配置类
        @Bean
        public Bean1 bean1(){
            return new Bean1("本项目");
        }
    }

    static class MyImportSelector implements DeferredImportSelector{
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            List<String> names =
                    SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null);
            return  names.toArray(new String[0]);
        }
    }

    @Configuration
    static class AutoConfiguration1{//第三方的配置类
        @Bean
        @ConditionalOnMissingBean
        public Bean1 bean1(){
            return new Bean1("第三方");
        }
    }

    static class Bean1{
        private String name;

        public Bean1() {
        }

        public Bean1(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "Bean1{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }

    @Configuration
    static class AutoConfiguration2{//第三方的配置类
        @Bean
        public Bean2 bean2(){
            return new Bean2();
        }
    }

    static class Bean2{

    }

}

导入第三方的配置类,可以直接在本项目配置类上加@Import注解,在@Import注解中加入。一旦需要导入的第三方配置类数量多的时候,这样做会使代码变得十分十分臃肿,同时我们也不希望直接“写死”在Java代码中,而是写在配置文件中。

所以我们这个时候就需要进行第一步的优化,自定义MyImportSelector类,让其继承ImportSelector,实现其selectImports方法,在返回结果中加入对应的第三方配置类,这样虽然相较上面有所优化,但是问题依旧没有完全处理。

所以我们这个时候就需要进行第二步的优化,将要添加的第三方配置类名字添加到 spring.factories 文件中,该文件需要放在resources目录下的META-INF文件夹中。

com.itheima.a41.A41$MyImportSelector=\
com.itheima.a41.A41.AutoConfiguration1,\
com.itheima.a41.A41.AutoConfiguration2

因为导入第三方配置类的优先级高于本项目配置类,然而我们是希望本项目配置类的优先级较高,所以我们让 MyImportSelector 类继承 DeferredImportSelector,同时给第三方配置类中的方法添加@ConditionalOnMissingBean 注解(不添加这个注解会报错)

演示结果为:

config
org.springframework.context.annotation.ConfigurationClassPostProcessor
bean1
com.itheima.a41.A41$AutoConfiguration1
com.itheima.a41.A41$AutoConfiguration2
bean2
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Bean1{name='本项目'}

2.AopAutoConfiguration

演示代码: 

public class TestAopAuto {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        StandardEnvironment env = new StandardEnvironment();
        env.getPropertySources().addLast(new SimpleCommandLinePropertySource("--spring.aop.auto=true"));
        context.setEnvironment(env);
        //添加一些常见的后处理器
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());

        context.registerBean(Config.class);
        context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>");
        AnnotationAwareAspectJAutoProxyCreator creator
                = context.getBean("org.springframework.aop.config.internalAutoProxyCreator",AnnotationAwareAspectJAutoProxyCreator.class);
        System.out.println(creator.isProxyTargetClass());
    }

    @Configuration
    @Import(MyImportSelector.class)
    static class Config{

    }

    static class MyImportSelector implements DeferredImportSelector{
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[] {AopAutoConfiguration.class.getName()};
        }
    }
}

 演示结果为:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
com.itheima.a41.TestAopAuto$Config
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration$CglibAutoProxyConfiguration
org.springframework.aop.config.internalAutoProxyCreator
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
>>>>>>>>>>>>>>>>>>>>>>>>>
true

Spring Boot 是利用了自动配置类来简化了 aop 相关配置

  • AOP 自动配置类为 org.springframework.boot.autoconfigure.aop.AopAutoConfiguration

  • 可以通过 spring.aop.auto=false 禁用 aop 自动配置

  • AOP 自动配置的本质是通过 @EnableAspectJAutoProxy 来开启了自动代理,如果在引导类上自己添加了 @EnableAspectJAutoProxy 那么以自己添加的为准

  • @EnableAspectJAutoProxy 的本质是向容器中添加了 AnnotationAwareAspectJAutoProxyCreator 这个 bean 后处理器,它能够找到容器中所有切面,并为匹配切点的目标类创建代理,创建代理的工作一般是在 bean 的初始化阶段完成的

3.DataSourceAutoConfiguration

演示代码:

public class TestDataSourceAuto {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        StandardEnvironment env = new StandardEnvironment();
        env.getPropertySources().addLast(new SimpleCommandLinePropertySource(
              "--spring.datasource.url=jdbc:mysql:///db1",
                "--spring.datasource.username=root",
                "--spring.datasource.password=password"
        ));

        context.setEnvironment(env);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.registerBean( Config.class);
       
        context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            String resourceDescription = context.getBeanDefinition(name).getResourceDescription();
            if (resourceDescription != null) {
                System.out.println(name + " 来源:" + resourceDescription);
            }
        }
       
    }

    @Configuration
    @Import(MyImportSelector.class)
    static class Config{

    }

    static class MyImportSelector implements DeferredImportSelector{
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[]{
                    DataSourceAutoConfiguration.class.getName(),
                    MybatisAutoConfiguration.class.getName(),
                    DataSourceTransactionManagerAutoConfiguration.class.getName(),
                    TransactionAutoConfiguration.class.getName()
            };
        }
    }
}

 演示结果为:

dataSource 来源:class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]
hikariPoolDataSourceMetadataProvider 来源:class path resource [org/springframework/boot/autoconfigure/jdbc/metadata/DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration.class]
sqlSessionFactory 来源:class path resource [org/mybatis/spring/boot/autoconfigure/MybatisAutoConfiguration.class]
sqlSessionTemplate 来源:class path resource [org/mybatis/spring/boot/autoconfigure/MybatisAutoConfiguration.class]
transactionManager 来源:class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfiguration$JdbcTransactionManagerConfiguration.class]
org.springframework.transaction.config.internalTransactionAdvisor 来源:class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]
transactionAttributeSource 来源:class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]
transactionInterceptor 来源:class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]
org.springframework.transaction.config.internalTransactionalEventListenerFactory 来源:class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]
transactionTemplate 来源:class path resource [org/springframework/boot/autoconfigure/transaction/TransactionAutoConfiguration$TransactionTemplateConfiguration.class]
platformTransactionManagerCustomizers 来源:class path resource [org/springframework/boot/autoconfigure/transaction/TransactionAutoConfiguration.class]

总结: 

  • 对应的自动配置类为:org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

  • 它内部采用了条件装配,通过检查容器的 bean,以及类路径下的 class,来决定该 @Bean 是否生效

简单说明一下,Spring Boot 支持两大类数据源:

  • EmbeddedDatabase - 内嵌数据库连接池

  • PooledDataSource - 非内嵌数据库连接池

PooledDataSource 又支持如下数据源

  • hikari 提供的 HikariDataSource

  • tomcat-jdbc 提供的 DataSource

  • dbcp2 提供的 BasicDataSource

  • oracle 提供的 PoolDataSourceImpl

如果知道数据源的实现类类型,即指定了 spring.datasource.type,理论上可以支持所有数据源,但这样做的一个最大问题是无法订制每种数据源的详细配置(如最大、最小连接数等)

4.MybatisAutoConfiguration

 在演示代码所在目录下添加mapper目录,然后在mapper目录下自定义三个mapper接口,其中mapper1和mapper2接口有@Mapper注解,mapper3接口没有@Mapper注解

此时仍然获取不到bean,需要获取记录当前引导类包名,在上述演示代码中添加

//记录当前引导类包名
AutoConfigurationPackages.register(
     context.getDefaultListableBeanFactory(), TestDataSourceAuto.class.getPackageName());

重新启动程序,查看控制台是否有mapper1和mapper2这两个bean

mapper1 来源:file [D:\Workspace\Html\Spring5\aspectj_01\target\classes\com\itheima\a41\mapper\Mapper1.class]
mapper2 来源:file [D:\Workspace\Html\Spring5\aspectj_01\target\classes\com\itheima\a41\mapper\Mapper2.class]

总结:

  • MyBatis 自动配置类为 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

  • 它主要配置了两个 bean

    • SqlSessionFactory - MyBatis 核心对象,用来创建 SqlSession

    • SqlSessionTemplate - SqlSession 的实现,此实现会与当前线程绑定

    • ImportBeanDefinitionRegistrar 的方式扫描所有标注了 @Mapper 注解的接口

    • AutoConfigurationPackages 来确定扫描的包

  • 还有一个相关的 bean:MybatisProperties,它会读取配置文件中带 mybatis. 前缀的配置项进行定制配置

@MapperScan 注解的作用与 MybatisAutoConfiguration 类似,会注册 MapperScannerConfigurer 有如下区别

  • @MapperScan 扫描具体包(当然也可以配置关注哪个注解)

  • @MapperScan 如果不指定扫描具体包,则会把引导类范围内,所有接口当做 Mapper 接口

  • MybatisAutoConfiguration 关注的是所有标注 @Mapper 注解的接口,会忽略掉非 @Mapper 标注的接口

这里有同学有疑问,之前介绍的都是将具体类交给 Spring 管理,怎么到了 MyBatis 这儿,接口就可以被管理呢?

  • 其实并非将接口交给 Spring 管理,而是每个接口会对应一个 MapperFactoryBean,是后者被 Spring 所管理,接口只是作为 MapperFactoryBean 的一个属性来配置

​​​​​​​5.TransactionAutoConfiguration

  • 事务自动配置类有两个:

    • org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration

    • org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration

  • 前者配置了 DataSourceTransactionManager 用来执行事务的提交、回滚操作

  • 后者功能上对标 @EnableTransactionManagement,包含以下三个 bean

    • BeanFactoryTransactionAttributeSourceAdvisor 事务切面类,包含通知和切点

    • TransactionInterceptor 事务通知类,由它在目标方法调用前后加入事务操作

    • AnnotationTransactionAttributeSource 会解析 @Transactional 及事务属性,也包含了切点功能

  • 如果自己配置了 DataSourceTransactionManager 或是在引导类加了 @EnableTransactionManagement,则以自己配置的为准

6.自动配置类——MVC

演示代码:

public class TestMvcAuto {

    public static void main(String[] args) {
        AnnotationConfigServletWebServerApplicationContext context
                = new AnnotationConfigServletWebServerApplicationContext();
        context.registerBean(Config.class);
        context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            String description = context.getBeanDefinition(name).getResourceDescription();
            if (description != null ){
                System.out.println(name + "来源:" + description);
            }
        }
        context.close();
    }

    @Configuration
    @Import(MyImportSelector.class)
    static class Config{
    }

    static class MyImportSelector implements DeferredImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[]{
                    ServletWebServerFactoryAutoConfiguration.class.getName(),
                    DispatcherServletAutoConfiguration.class.getName(),
                    WebMvcAutoConfiguration.class.getName(),
                    ErrorMvcAutoConfiguration.class.getName()
            };
        }
    }
}

 演示结果:

tomcatServletWebServerFactory来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration$EmbeddedTomcat.class]
servletWebServerFactoryCustomizer来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfiguration.class]
tomcatServletWebServerFactoryCustomizer来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfiguration.class]
dispatcherServlet来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration$DispatcherServletConfiguration.class]
dispatcherServletRegistration来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration.class]
requestMappingHandlerAdapter来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
requestMappingHandlerMapping来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
welcomePageHandlerMapping来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
localeResolver来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
themeResolver来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
flashMapManager来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
mvcConversionService来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
mvcValidator来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
mvcContentNegotiationManager来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
mvcPatternParser来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
mvcUrlPathHelper来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
mvcPathMatcher来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
viewControllerHandlerMapping来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
beanNameHandlerMapping来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
routerFunctionMapping来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
resourceHandlerMapping来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
mvcResourceUrlProvider来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
defaultServletHandlerMapping来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
handlerFunctionAdapter来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
mvcUriComponentsContributor来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
httpRequestHandlerAdapter来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
simpleControllerHandlerAdapter来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
handlerExceptionResolver来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
mvcViewResolver来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
mvcHandlerMappingIntrospector来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
viewNameTranslator来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]
defaultViewResolver来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]
viewResolver来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]
requestContextFilter来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]
formContentFilter来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.class]
error来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]
beanNameViewResolver来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]
conventionErrorViewResolver来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration.class]
errorAttributes来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration.class]
basicErrorController来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration.class]
errorPageCustomizer来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration.class]
preserveErrorControllerTargetClassPostProcessor来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration.class]

其中一些重要bean:

  1. tomcatServletWebServerFactory:内嵌服务器工厂
  2. ServletWebServerFactoryAutoConfiguration :提供 ServletWebServerFactory ​​​​​​​

  3. DispatcherServletAutoConfiguration : 提供 DispatcherServlet 和  DispatcherServletRegistrationBean

  4. WebMvcAutoConfiguration : 配置 DispatcherServlet 的各项组件,提供的 bean 见过的有--多项 HandlerMapping 、HandlerAdapter 、HandlerAdapter

  5. ErrorMvcAutoConfiguration :提供的 bean 有 BasicErrorController

  6. MultipartAutoConfiguration :用来解析 multipart/form-data 格式的数据 ,提供了 org.springframework.web.multipart.support.StandardServletMultipartResolver

  7. HttpEncodingAutoConfiguration :POST 请求参数如果有中文,无需特殊设置,这是因为 Spring Boot 已经配置了org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter对应配置 server.servlet.encoding.charset=UTF-8,默认就是 UTF-8。当然,它只影响非 json 格式的数据

7.自定义自动配置类

演示代码:

public class A41_2 {

    public static void main(String[] args) {
        AnnotationConfigServletWebServerApplicationContext context
                = new AnnotationConfigServletWebServerApplicationContext();
        StandardEnvironment env = new StandardEnvironment();
        env.getPropertySources().addLast(new SimpleCommandLinePropertySource(
                "--spring.datasource.url=jdbc:mysql:///db1",
                "--spring.datasource.username=root",
                "--spring.datasource.password=password"
        ));

        context.setEnvironment(env);
        context.registerBean(Config.class);
        context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            String description = context.getBeanDefinition(name).getResourceDescription();
            if (description != null ){
                System.out.println(name + "来源:" + description);
            }
        }
        context.close();
    }

    @Configuration
    @EnableAutoConfiguration
    static class Config{

    }

    static class MyImportSelector implements DeferredImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null).toArray(new String[0]);
        }
    }

    @Configuration
    static class AutoConfiguration1{
        @Bean
        public Bean1 bean1(){
            return new Bean1();
        }
    }

    @Configuration
    static class AutoConfiguration2{
        @Bean
        public Bean2 bean2(){
            return new Bean2();
        }
    }

    static class Bean1{}

    static class Bean2{}
}

 这里的 @EnableAutoConfiguration 注解内部实际上是使用@Import注解( @Import({AutoConfigurationImportSelector.class}) ),AutoConfigurationImportSelector 内部依然是使用 SpringFactoriesLoader.loadFactoryNames()方法

演示过程中需要用到的 spring.factories 文件:

com.itheima.a41.A41_2$MyImportSelector=\
com.itheima.a41.A41_2.AutoConfiguration1,\
com.itheima.a41.A41_2.AutoConfiguration2


org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.itheima.a41.A41_2.AutoConfiguration1,\
com.itheima.a41.A41_2.AutoConfiguration2

演示结果包含如下自己自定义的bean,即为自动配置成功

bean1来源:class path resource [com/itheima/a41/A41_2$AutoConfiguration1.class]
bean2来源:class path resource [com/itheima/a41/A41_2$AutoConfiguration2.class]

总结:

  1. 自动配置类本质上就是一个配置类而已,只是用 META-INF/spring.factories 管理,与应用配置类解耦

  2. @Enable 打头的注解本质是利用了 @Import

  3. @Import 配合 DeferredImportSelector 即可实现导入,selectImports 方法的返回值即为要导入的配置类名

  4. DeferredImportSelector 的导入会在最后执行,为的是让其它配置优先解析

 卌二、条件装配底层

条件装配的底层是本质上是 @Conditional 与 Condition 这两个注解。引入自动配置类时,期望满足一定条件才能被 Spring 管理,不满足则不管理。

比如条件是【类路径下必须有 dataSource】这个 bean ,演示代码:

  public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("config", Config.class);
        context.registerBean(ConfigurationClassPostProcessor.class);

        context.refresh();

        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }

    @Configuration
    @Import(MyImportSelector.class)
    static class Config{

    }

    static class MyImportSelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[] {AutoConfiguration1.class.getName(),AutoConfiguration2.class.getName()};
        }
    }

    static class MyCondition1 implements Condition{ //存在 Druid
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            return ClassUtils.isPresent("com.alibaba.druid.pool.DruidDataSource", null);
        }
    }

    static class MyCondition2 implements Condition{ // 不存在 Druid
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            return !ClassUtils.isPresent("com.alibaba.druid.pool.DruidDataSource", null);
        }
    }

    @Configuration//第三方配置类
    @Conditional(MyCondition1.class)
    static class AutoConfiguration1{
        @Bean
        public Bean1 bean1(){
            return new Bean1();
        }
    }

    @Configuration//第三方配置类
    @Conditional(MyCondition2.class)
    static class AutoConfiguration2{
        @Bean
        public Bean2 bean2(){
            return new Bean2();
        }
    }

    static class Bean1{}

    static class Bean2{}

分别测试加入和去除 druid 依赖,观察 bean1 是否存在于容器

演示结果:

加入druid依赖:

config
org.springframework.context.annotation.ConfigurationClassPostProcessor
com.itheima.a42.A42$AutoConfiguration1
bean1

去除druid依赖:

config
org.springframework.context.annotation.ConfigurationClassPostProcessor
com.itheima.a42.A42$AutoConfiguration1
bean2

对代码进行优化,自定义一个组合注解,避免代码直接写死在Condition类中,降低代码耦合度,同时减少重复代码:

    @SuppressWarnings("all")
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("config", Config.class);
        context.registerBean(ConfigurationClassPostProcessor.class);

        context.refresh();

        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }

    @Configuration
    @Import(MyImportSelector.class)
    static class Config{

    }

    static class MyImportSelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[] {AutoConfiguration1.class.getName(),AutoConfiguration2.class.getName()};
        }
    }

    static class MyCondition1 implements Condition{ //存在 Druid
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnClass.class.getName());
            String className = attributes.get("className").toString();
            boolean exists = (boolean) attributes.get("exists");
            boolean present = ClassUtils.isPresent(className, null);
            return exists ? present : !present;
        }
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD,ElementType.TYPE})//表示该注解可以加在方法上,也可以加在类上
    @Conditional(MyCondition1.class)
    @interface ConditionalOnClass{
        boolean exists(); // true 判断存在 false 判断不存在

        String className(); // 要判断的类名
    }

    @Configuration//第三方配置类
    @ConditionalOnClass(className = "com.alibaba.druid.pool.DruidDataSource",exists = true)
    static class AutoConfiguration1{
        @Bean
        public Bean1 bean1(){
            return new Bean1();
        }
    }

    @Configuration//第三方配置类
    @ConditionalOnClass(className = "com.alibaba.druid.pool.DruidDataSource",exists = false)
    static class AutoConfiguration2{
        @Bean
        public Bean2 bean2(){
            return new Bean2();
        }
    }

    static class Bean1{}

    static class Bean2{}

再次测试加入和去除 druid 依赖,观察 bean1 是否存在于容器,演示结果同上

 卌三、FactoryBean 

FactoryBean 用于制造创建过程较为复杂的产品, 如 SqlSessionFactory  (现在用@Bean来代替,其具体等价功能)

 自定义Bean1类,用来测试Spring会不会走 创建 、依赖注入,具体实现如下:

public class Bean1 implements BeanFactoryAware {
    private static final Logger log = LoggerFactory.getLogger(Bean1.class);

    private Bean2 bean2;

    @Autowired
    private void setBean2(Bean2 bean2){
        log.debug("setBean2({})",bean2);
        this.bean2 = bean2;
    }

    public Bean2 getBean2(){
        return bean2;
    }

    @PostConstruct
    public void init(){
        log.debug("init..");
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        log.debug("setBeanFactory({})",beanFactory);
    }
}

自定义 Bean1FactoryBean 类,实现 FactoryBean 接口,实现其接口内的三个方法:

  • getObjectType() :决定了根据【类型】获取或依赖注入能否成功
  • isSingleton():决定了 getObject 方法调用一次还是多次(返回true:表示是单例)
  • getObject():产生“产品”
@Component("bean1")
public class Bean1FactoryBean implements FactoryBean<Bean1> {
    private static final Logger log = LoggerFactory.getLogger(Bean1FactoryBean.class);

    @Override
    public Bean1 getObject() throws Exception {
        Bean1 bean1 = new Bean1();
        log.debug("create bean: {}",bean1);
        return bean1;
    }

    //决定了根据【类型】获取或依赖注入能否成功
    public Class<?> getObjectType() {
        return Bean1.class;
    }

    //决定了 getObject 方法调用一次还是多次
    public boolean isSingleton() {
        return true;
    }
}

自定义 Bean1PostProcessor 实现 BeanPostProcessor 接口 ,分别对初始化前后进行一个增强:

@Component
public class Bean1PostProcessor implements BeanPostProcessor {

    private static final Logger log = LoggerFactory.getLogger(Bean1PostProcessor.class);

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("bean1") && bean instanceof Bean1) {
            log.debug("before [{}] init",beanName);
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("bean1") && bean instanceof Bean1) {
            log.debug("after [{}] init",beanName);
        }
        return bean;
    }
}

自定义Bean2:

@Component
public class Bean2 {
}

测试代码 ,其中按名字去获取工厂对象时,需要在 名字前加 &:

@ComponentScan
public class A43 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(A43.class);

        Bean1 bean1 = (Bean1) context.getBean("bean1");
        System.out.println(bean1);

        System.out.println(context.getBean(Bean1.class));

        System.out.println(context.getBean(Bean1FactoryBean.class));
        System.out.println(context.getBean("&bean1"));

        context.close();
    }

}

测试结果:

[DEBUG] 16:55:44.633 [main] com.itheima.a43.Bean1FactoryBean    - create bean: com.itheima.a43.Bean1@1ffaf86 
[DEBUG] 16:55:44.651 [main] com.itheima.a43.Bean1PostProcessor  - after [bean1] init 
com.itheima.a43.Bean1@1ffaf86
com.itheima.a43.Bean1@1ffaf86
com.itheima.a43.Bean1FactoryBean@3c77d488
com.itheima.a43.Bean1FactoryBean@3c77d488

FactoryBean 创建的产品:

  • 会认为创建、依赖注入、Aware 接口回调、前初始化这些都是 FactoryBean 的职责, 这些流程都不会走

  • 唯有后初始化的流程会走, 也就是产品可以被代理增强

  • 单例的产品不会存储于 BeanFactorysingletonObjects 成员中, 而是另一个 factoryBeanObjectCache 成员中

 卌四、@Indexed 

试验准备 :在 target/classes 创建 META-INF/spring.components,内容如下:

com.itheima.a44.Bean1=org.springframework.stereotype.Component
com.itheima.a44.Bean2=org.springframework.stereotype.Component

自定义Bean1、Bean2、Bean3:

@Component
public class Bean1 {
}

@Component
public class Bean2 {
}


@Component
public class Bean3 {
}

测试代码:

public class A44 {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        //组件扫描的核心类
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);

        scanner.scan(A44.class.getPackageName());

        for (String name : beanFactory.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }
}

 测试结果:

bean1
bean2
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory

可以发现,测试结果中 只有 bean1、bean2 ,没有 bean3,在 pom.xml文件中加入如下依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-indexer</artifactId>
    <optional>true</optional>
</dependency>

可以发现测试结果中有了 bean3

总结:

  1. 在编译时就根据 @Indexed 生成 META-INF/spring.components 文件

  2. 扫描时

    • 如果发现 META-INF/spring.components 存在, 以它为准加载 bean definition

    • 否则, 会遍历包下所有 class 资源 (包括 jar 内的)

  3. 解决的问题,在编译期就找到 @Component 组件,节省运行期间扫描 @Component 的时间

 卌五、Spring代理的特点

1.​​​​​​​探究 spring 代理的设计特点

自定义Bean1 、Bean2、MyAspect类,具体实现如下:

@Component
public class Bean1 {

    private static final Logger log = LoggerFactory.getLogger(Bean1.class);

    protected Bean2 bean2;

    protected boolean initialized;

    @Autowired
    public void setBean2(Bean2 bean2){
        log.debug("setBean2(Bean2 bean2)");
        this.bean2 = bean2;
    }

    @PostConstruct
    public void init(){
        log.debug("init");
        initialized = true;
    }

    public Bean2 getBean2(){
        log.debug("getBean2()");
        return bean2;
    }

    public boolean isInitialized() {
        log.debug("isInitialized()");
        return initialized;
    }

   
}
@Component
public class Bean2 {
}



@Aspect
@Component
public class MyAspect {

    //故意对所有方法增强
    @Before("execution(* com.itheima.a45.Bean1.*(..))")
    public void before(){
        System.out.println("before");
    }
}

测试代码:

@SpringBootApplication
public class A45 {

    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext context = SpringApplication.run(A45.class, args);

        Bean1 proxy = context.getBean(Bean1.class);

        /*
         * 1.演示 spring 代理的设计特点
         *      依赖注入和初始化影响的是原始对象
         *      代理与目标是两个对象,二者成员变量并不共用数据
         */

        showProxyAndTarget(proxy);

        System.out.println(">>>>>>>>>>>>>>>");
        System.out.println(proxy.getBean2());
        System.out.println(proxy.isInitialized());

       

        context.close();
    }

    public static void showProxyAndTarget(Bean1 proxy) throws Exception {
        System.out.println(">>>>> 代理中的成员变量");
        System.out.println("\tinitialized=" + proxy.initialized);
        System.out.println("\tbean2=" + proxy.bean2);

        if (proxy instanceof Advised advised) {
            System.out.println(">>>>> 目标中的成员变量");
            Bean1 target = (Bean1) advised.getTargetSource().getTarget();
            System.out.println("\tinitialized=" + target.initialized);
            System.out.println("\tbean2=" + target.bean2);
        }
    }
}

测试结果为:

>>>>> 代理中的成员变量
	initialized=false
	bean2=null
>>>>> 目标中的成员变量
	initialized=true
	bean2=com.itheima.a45.Bean2@6e4839c
>>>>>>>>>>>>>>>
before
[DEBUG] 15:19:17.588 [restartedMain] com.itheima.a45.Bean1               - getBean2() 
com.itheima.a45.Bean2@6e4839c
before
[DEBUG] 15:19:17.588 [restartedMain] com.itheima.a45.Bean1               - isInitialized() 
true

可以看出

  • 依赖注入和初始化影响的是原始对象,而不是代理对象,所以 cglib代理 不能用MethodProxy.invokeSuper()
  • 代理与目标是两个对象,二者成员变量并不共用数据

2.探究static方法、final方法、private方法可否得到增强

 在自定义Bean1中添加如下代码:

public void m1() {
        System.out.println("m1() 成员方法");
    }

    final public void m2() {
        System.out.println("m2() final 方法");
    }

    static public void m3() {
        System.out.println("m3() static 方法");
    }

    private void m4() {
        System.out.println("m4() private 方法");
    }

测试代码中添加如下代码:

 /*
         *  2.演示 static方法 、final方法 、private 方法均无法增强
         */

        proxy.m1();
        proxy.m2();
        proxy.m3();
        Method m4 = Bean1.class.getDeclaredMethod("m4");
        m4.setAccessible(true);
        m4.invoke(proxy);

测试结果为:

before
m1() 成员方法
m2() final 方法
m3() static 方法
m4() private 方法

可以看出,static 方法、final 方法、private 方法均无法增强

因为上一篇文章字数太多,无法更新后续内容,转到另外一篇博客:

深度学习Spring5底层原理(黑马学习随笔)--续_Dwight Howard_12的博客-CSDN博客

  • 15
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值