spring高级篇(二)

1、Aware和InitializingBean

        Aware和InitializingBean都与Bean的生命周期管理相关。

        Aware接口:

  • 概念: Aware接口是Spring框架中的一个标记接口,它表示一个类能够感知到(aware of)Spring容器的存在及其特定的环境。Spring框架提供了多个Aware接口,如ApplicationContextAware、BeanFactoryAware、BeanNameAware等。
  • 区别:Aware接口通过回调方法的方式,让实现了该接口的类能够获取到Spring容器的一些资源或信息,比如ApplicationContextAware可以获取到Spring应用上下文,BeanFactoryAware可以获取到Bean工厂,BeanNameAware可以获取到Bean的名称等。
  • 联系:Aware接口可以与InitializingBean接口一起使用,通过Aware接口获取到Spring容器的资源后,可以在初始化Bean时使用这些资源。

        InitializingBean接口:

  • 概念: InitializingBean接口是Spring框架中的一个接口,实现该接口的类可以在Bean初始化完成后执行特定的逻辑。
  • 区别:InitializingBean接口中定义了一个初始化方法afterPropertiesSet(),当Bean的所有属性被设置后,Spring容器会调用该方法。在这个方法中,可以执行一些初始化操作,比如数据加载、资源初始化等。
  • 联系:InitializingBean接口可以与Aware接口一起使用,通过Aware接口获取到Spring容器的资源后,在InitializingBean的afterPropertiesSet()方法中进行必要的初始化操作。
1.1、案例一

        下面的MyBean类分别实现了BeanNameAware, ApplicationContextAware,InitializingBean三个接口:

public class MyBean implements BeanNameAware, ApplicationContextAware,InitializingBean {private static final Logger log = LoggerFactory.getLogger(MyBean.class);/***  重写BeanNameAware的方法,可以获取当前bean的名称*/@Overridepublic void setBeanName(String name) {log.info("bean is {},bean name is:{}", this,name);}/*** 重写ApplicationContext的方法,允许Bean实现类获取对Spring应用上下文(ApplicationContext)的引用。*/@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {log.info("bean is {},bean applicationContext is:{}", this,applicationContext);}/*** 重写InitializingBean的方法用于在Bean初始化完成后执行特定的逻辑。*/@Overridepublic void afterPropertiesSet() throws Exception {log.info("bean is {},init", this);}
}
public class A05 {public static void main(String[] args) {GenericApplicationContext context = new GenericApplicationContext();context.registerBean("mybean", MyBean.class);context.refresh();context.close();}
}

        上面的功能,通过@Autowired @PostConstruct 等注解也可以实现,为什么还要实现接口?原因是因为,解析@Autowired @PostConstruct 等注解需要通过后处理器,而Aware等接口属于Spring的内置功能,不需要任何扩展(在上述案例的主类中,使用的是GenericApplicationContext,不会加任何后处理器)。扩展在某些时候可能会失效,而内置功能不会失效。

        演示使用@Autowired @PostConstruct 等注解实现同等功能:

public class MyBean2 {private static final Logger log = LoggerFactory.getLogger(MyBean2.class);@Autowiredpublic void getApplicationContext(ApplicationContext applicationContext) {log.info("bean is {},bean applicationContext is:{}", this,applicationContext);}@PostConstructpublic void init(){log.info("init bean");}
}

        在主类中需要加入后处理器:

public class A05 {public static void main(String[] args) {GenericApplicationContext context = new GenericApplicationContext();context.registerBean("mybean2", MyBean2.class);context.registerBean(AutowiredAnnotationBeanPostProcessor.class);context.registerBean(CommonAnnotationBeanPostProcessor.class);context.refresh();context.close();}
}

        运行结果与实现ApplicationContextAware,InitializingBean相同。

1.2、注解失效分析

        演示一种注解失效的情况:

/*** 演示后处理器扩展失效的情况*/
@Configuration
public class MyConfig1 {private static final Logger log = LoggerFactory.getLogger(MyConfig1.class);@Autowiredpublic void setApplicationContext(ApplicationContext applicationContext){log.info("注入 Application Context");}@PostConstructpublic void init(){log.info("init");}/*** 想要获取MyConfig1中的BeanFactoryPostProcessor,就先要把MyConfig1类先创建好* 提前创建导致扩展功能失效 因为1、beanFactory后处理器 2、bean后处理器 这样的顺序* @return*/@Beanpublic BeanFactoryPostProcessor beanPostProcessor(){return beanFactory -> {log.info("beanPostProcessor");};}
}
public class A05Application {public static void main(String[] args) {/*@AutoWired等注解是spring的扩展功能,需要通过后处理器实现而实现BeanNameAware, ApplicationContextAware,InitializingBean 等接口是spring的内置功能为了防止扩展功能失效*/GenericApplicationContext context = new GenericApplicationContext();context.registerBean("myConfig", MyConfig1.class);context.registerBean(AutowiredAnnotationBeanPostProcessor.class);context.registerBean(CommonAnnotationBeanPostProcessor.class);context.registerBean(ConfigurationClassPostProcessor.class);//执行顺序:1、beanFactory后处理器 2、bean后处理器 3、初始化所有单例context.refresh();context.close();}
}

        此时发现,只有beanPostProcessor()成功执行

        原因在于,调用GenericApplicationContext的.refresh(); 方法初始化时,其执行顺序是:

        1、beanFactory后处理器

        2、bean后处理器

        3、初始化所有单例

        如下图所示:

        如果要初始化BeanFactoryPostProcessor,首先要把 MyConfig1类创建完成。相当于下图的顺序:

        很明显此时是先执行了类的初始化,最后再注册后处理器。这样就导致了无法解析@Autowired @PostConstruct 等注解。但是在创建类及初始化时,会执行Aware和InitializingBean相关接口:

/*** 演示后处理器扩展失效的情况解决方案* 使用实现接口的方式*/
@Configuration
public class MyConfig2 implements ApplicationContextAware, InitializingBean {private static final Logger log = LoggerFactory.getLogger(MyConfig2.class);/*** 想要获取MyConfig1中的BeanFactoryPostProcessor,就先要把MyConfig1类先创建好* 提前创建导致扩展功能失效 因为1、beanFactory后处理器 2、bean后处理器 这样的顺序* @return*/@Beanpublic BeanFactoryPostProcessor beanPostProcessor(){return beanFactory -> {log.info("beanPostProcessor");};}@Overridepublic void afterPropertiesSet() throws Exception {log.info("init");}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {log.info("注入 Application Context");}
}

           此时可以观察到,初始化和注入Application Context的操作都成功执行。

2、初始化和销毁

2.1、初始化和销毁的方式

        Bean初始化和销毁通常有三种方式:

  • 通过特定的生命周期回调方法来实现。主要涉及到以下两个接口:InitializingBean: 这是 Spring 框架提供的一个接口,其中定义了afterPropertiesSet()方法,该方法会在 Bean 的所有属性都被设置之后进行回调,用于执行自定义的初始化逻辑。DisposableBean: 这也是 Spring 框架提供的接口,其中定义了一个destroy()方法,该方法会在 Spring 容器销毁该 Bean 之前进行回调,用于执行自定义的销毁逻辑。
  • 使用 @PostConstruct @PreDestroy 注解
  • 通过设置@Bean注解的initMethod和destroyMethod

        初始化的三种方式:

/*** 演示三种初始化的执行顺序*/
public class Bean1 implements InitializingBean {private static final Logger log = LoggerFactory.getLogger(Bean1.class);@PostConstructpublic void init1(){log.info(" @PostConstruct方式");}/*** Invoked by the containing {@code BeanFactory} after it has set all bean properties* and satisfied {@link BeanFactoryAware}, {@code ApplicationContextAware} etc.* <p>This method allows the bean instance to perform validation of its overall* configuration and final initialization when all bean properties have been set.** @throws Exception in the event of misconfiguration (such as failure to set an*                   essential property) or if initialization fails for any other reason*/@Overridepublic void afterPropertiesSet() throws Exception {log.info("实现InitializingBean接口方式");}public void init3(){log.info("@Bean设置initMethod方式");}
}

        销毁的三种方式:

/*** 演示三种销毁方法的执行顺序*/
public class Bean2 implements DisposableBean {private static final Logger log = LoggerFactory.getLogger(Bean2.class);@PreDestroypublic void destory1(){log.info("@PreDestroy方式");}/*** Invoked by the containing {@code BeanFactory} on destruction of a bean.** @throws Exception in case of shutdown errors. Exceptions will get logged*                   but not rethrown to allow other beans to release their resources as well.*/@Overridepublic void destroy() throws Exception {log.info("实现DisposableBean接口方式");}public void destory3(){log.info("@Bean设置destroyMethod方式");}
}
 2.2、初始化和销毁的顺序
@SpringBootApplication
public class A06Application {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(A06Application.class, args);context.close();}@Bean(initMethod = "init3")public Bean1 bean1(){return new Bean1();}@Bean(destroyMethod = "destory3")public Bean2 bean2(){return new Bean2();}
}

         三种方式的先后顺序:

3、Bean的Scope

        在 Spring 中,Scope(作用域)是指 Bean 的生命周期范围,即在应用程序中创建的 Bean 对象在何时被创建、存在多久以及何时被销毁。Spring 框架提供了多种作用域,每种作用域都决定了 Bean 实例的生命周期和可见性。

        列举一下常见的作用域:

  • Singleton(单例):在 Singleton 作用域下,Spring 容器中的每个 Bean 都只有一个实例,这个实例在整个应用程序的生命周期中都是共享的。默认情况下,所有在 Spring 容器中声明的 Bean 都是 Singleton 作用域,Spring的Bean默认是单例的。
  • Prototype(多例):在 Prototype 作用域下,每次向 Spring 容器请求该 Bean 时,都会创建一个新的实例。每次请求都会返回一个新的 Bean 实例,这些实例彼此之间互不影响。
  • Request(请求):在 Web 应用程序中,每个 HTTP 请求都会创建一个新的 Bean 实例,该实例仅在当前 HTTP 请求内有效。这意味着每个请求都会拥有一个独立的 Bean 实例,适用于 Web 应用程序中需要与请求相关联的 Bean。
  • Session(会话):在 Web 应用程序中,每个 HTTP 会话(Session)都会创建一个新的 Bean 实例,该实例在整个会话期间都是有效的。这意味着在同一个会话中,多个请求共享相同的 Bean 实例。此外还有一个全局会话,但仅在使用基于portlet的Web环境时才有意义。
  • Application(应用):该作用域是在一个 ServletContext 范围内有效,它将 Spring Bean 的生命周期绑定到 ServletContext 的生命周期,一个应用中的所有请求都共享同一个 Bean 实例。

        下面通过案例演示Application、Request、Session三种作用域:

@Scope("application")
@Component
public class BeanForApplication {private static final Logger log = LoggerFactory.getLogger(BeanForApplication.class);@PreDestroypublic void destory(){log.info("destory");}
}
@Scope("request")
@Component
public class BeanForRequest {private static final Logger log = LoggerFactory.getLogger(BeanForRequest.class);@PreDestroypublic void destory(){log.info("Destory");}
}
@Component
@Scope("session")
public class BeanForSession {private static final Logger log = LoggerFactory.getLogger(BeanForSession.class);@PreDestroypublic void destroy() {log.info("destory");}
}
@Controller
public class TestController {@Autowired@Lazyprivate BeanForApplication beanForApplication;@Autowired@Lazyprivate BeanForRequest beanForRequest;@Autowired@Lazyprivate BeanForSession beanForSession;/*** 在同一个浏览器内,每次请求beanForRequest都会不一样* 在不同的浏览器中,每次请求beanForRequest都不一样,第一次请求时beanForSession不一样* 同一个程序下,beanForApplication都是一样的** 每次请求beanForRequest创建 销毁* beanForSession 到期时销毁* @param req* @param httpSession*/@GetMapping("/test")public void test(HttpServletRequest req, HttpSession httpSession) {System.out.println("BeanForApplication:"+beanForApplication);System.out.println("BeanForRequest:"+beanForRequest);System.out.println("BeanForSession:"+beanForSession);}
}

        主启动类:

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

        使用同一浏览器,第一次执行:

        第二次执行:

可以得出结论:同一浏览器(http会话)中,每次请求都会创建一个新的 Bean 实例,该实例仅在当前 HTTP 请求内有效。

        使用不同浏览器,浏览器一:

         浏览器二:

可以得出结论:不同浏览器(http会话)都会产生一个新的实例,并且在每个会话中,每次请求都会创建一个新的 Bean 实例,该实例仅在当前 HTTP 请求内有效。

3.1、Scope失效现象

        定义两个多例Bean:

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

        在配置类中通过@Autowired注入F1和F2

@Component
public class E {@Autowiredprivate F1 f1;@Autowiredprivate F2 f2;public F1 getF1() {return f1;}public F2 getF2() {return f2;}
}

        在主类中获取三次F1和F2

@ComponentScan("com.itbaima.a08_1")
public class A08Test {private static final Logger log = LoggerFactory.getLogger(A08Test.class);public static void main(String[] args) {AnnotationConfigApplicationContext context =  new AnnotationConfigApplicationContext(A08Application.class);E e = context.getBean(E.class);log.info("{}",e.getF1());log.info("{}",e.getF1());log.info("{}",e.getF1());System.out.println("<<<<<<<<<<<<<<<<<<<<<");log.info("{}",e.getF2());log.info("{}",e.getF2());log.info("{}",e.getF2());}
}

        期望的结果是每次获取到的F1和F2都是不同的实例:

        与预期结果不符。为什么定义的Bean是多例,每次却获取到了同一个实例?

当我们将一个 Prototype Bean 注入到一个 Singleton Bean 中时, Prototype Bean 注入的是该 Prototype Bean 的实例,而不是一个代理对象。这意味着,虽然 Prototype Bean 是多例的,但在 Singleton Bean 中的引用始终是同一个实例,导致 Prototype Bean 的多例特性失效。

    @Autowiredprivate F1 f1;@Autowiredprivate F2 f2;

        这就是单例注入多例Bean失效问题

3.1.1、解决方法一

        使用@Lazy 注解。 @Lazy 注解可以延迟 Bean 的初始化,即在第一次被请求时才进行初始化,而不是在容器启动时就创建。

        Spring 容器在初始化 Singleton Bean 时不会立即初始化注入的 Prototype Bean,而是在首次使用 Prototype Bean 时才进行初始化,从而保证每次获取的都是新的实例。

    @Autowired@Lazyprivate F1 f1;

        F1每次都是不同的实例:

3.1.2、解决方法二

        在声明多例时:

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

        告诉 Spring 在注入 Prototype Bean 时使用代理对象,而不是直接注入 Prototype Bean 的实例。这样,每次从 Singleton Bean 中获取 Prototype Bean 时,实际上会通过代理对象获取一个新的 Prototype Bean 实例,从而保证了 Prototype Bean 的多例特性。

3.1.3、解决方法三

        在 Singleton Bean 中通过 ApplicationContext 手动获取 Prototype Bean。每次需要使用 Prototype Bean 时,都通过 ApplicationContext 获取新的实例。

     //通过applicationContext 的 getBean@Autowiredprivate ApplicationContext applicationContext;public F4 getF4(){return applicationContext.getBean(F4.class);}
3.1.4、解决方法四

       使用ObjectFactory。ObjectFactory是 Spring 提供的一种工厂模式,它的实现是延迟加载的,所以每次调用 getObject()方法都会返回一个新的 Prototype Bean 实例,其原理与@Lazy类似,都是实现延迟加载。

//通过对象工厂的方式
@Autowired
private ObjectFactory<F3> f3;public F3 getF3(){return f3.getObject();
}

4、动态代理

        Spring 中的动态代理是指在运行时生成代理对象,而不是在编译时就确定代理对象的类型。Spring 中常用的动态代理有两种方式:基于 JDK 的动态代理和基于 CGLIB 的动态代理。

  • 基于 JDK 的动态代理:JDK 动态代理是通过 Java 的反射机制来实现的。当目标类实现了接口时,Spring 就会使用 JDK 动态代理来创建代理对象。在运行时,Spring 创建一个实现了目标接口的代理类,并且这个代理类会持有一个InvacationHandler对象,通过 InvacationHandler 的invoke()方法来实现对目标方法的增强。在invoke()方法中,可以在目标方法执行前后插入额外的逻辑,实现 AOP 的功能。(AOP是基于动态代理的)
  • 基于 CGLIB 的动态代理:当目标类没有实现接口时,Spring 会使用 CGLIB(Code Generation Library)来创建代理对象。它可以在运行时动态生成一个目标类的子类,并重写目标类中的方法。在子类中,可以在目标方法执行前后插入额外的逻辑,实现 AOP 的功能。

        两者之间的区别与联系:

        实现方式:

  • JDK 动态代理是基于 Java 的反射机制来实现的,它要求目标类必须实现至少一个接口。
  • CGLIB 动态代理则是通过生成目标类的子类来实现的,因此不要求目标类实现接口。

        即:JDK动态代理需要目标类和代理类实现同一个接口,CGLIB动态代理时会生成目标类的子类。

        性能:

  • 通常情况下,JDK 动态代理的性能比 CGLIB 动态代理略高,因为它不需要生成子类,直接利用接口来进行代理。
  • CGLIB 动态代理的性能略低,因为它需要生成目标类的子类,并且在运行时对目标类的方法进行重写。

        但是从是否反射的角度上,CGLIB因为无需反射,性能是高于JDK动态代理的

        适用范围

  • JDK 动态代理适用于那些实现了接口的类,因为 JDK 动态代理是基于接口的。
  • CGLIB 动态代理则适用于那些没有实现接口的类,它可以直接对类进行代理。

        两者的共同点:

        JDK 动态代理和 CGLIB 动态代理的目的都是为了实现对目标类的增强,例如实现 AOP(面向切面编程)。在 Spring 中,无论是使用 JDK 动态代理还是 CGLIB 动态代理,开发者都是通过配置来决定使用哪种代理方式。Spring 会根据目标类是否实现接口来自动选择使用 JDK 动态代理还是 CGLIB 动态代理。JDK 动态代理和 CGLIB 动态代理都是在运行时生成代理对象,动态地在目标方法的执行前后插入额外的逻辑,实现对目标方法的增强。

        补充:动态代理与静态代理的区别:

静态代理:在编译时就已经确定代理类和被代理类的关系,代理类是通过手动编写代码或者工具生成的,代理类和被代理类在编译时就确定了。

动态代理:在运行时动态生成代理对象,不需要预先知道代理类和被代理类的关系,可以动态地在运行时创建代理对象,使得代理类的生成更加灵活。

4.1、JDK动态代理

        首先创建一个接口:

public interface Inter {void foo();
}

        实现类:

public class InterImpl implements Inter {private static final Logger log = LoggerFactory.getLogger(InterImpl.class);@Overridepublic void foo() {log.info("foo");}
}

        测试类:

public class JdkProxyDemo {private static final Logger log = LoggerFactory.getLogger(JdkProxyDemo.class);public static void main(String[] args) {InterImpl inter = new InterImpl();//模拟jdk动态代理ClassLoader classLoader = JdkProxyDemo.class.getClassLoader();//加载运行期间动态生成的字节码Inter instance = (Inter) Proxy.newProxyInstance(classLoader, new Class[]{Inter.class}, (o, method, objects) -> {System.out.println("before");//通过反射调用 参数一:调用方法所在的对象 参数二:调用方法所需的参数Object invoke = method.invoke(inter, args);System.out.println("after");return invoke;});instance.foo();}
}

       newProxyInstance( ClassLoader loader, Class<?>[] interfaces,InvocationHandler h ) 是Proxy类下的一个静态方法:

  • 参数一:类加载器,用于加载动态生成的代理类。在 Java 中,类加载器负责将类的字节码加载到 JVM 中,并生成对应的 Class 对象。动态代理需要动态生成代理类,因此需要一个类加载器来加载这些类。
  • 参数二:代理对象需要实现的接口列表。动态代理只能代理接口,因此这个参数是一个接口数组,指定了代理对象需要实现的接口列表。代理对象将会实现这些接口,并且在调用接口方法时会委托给 InvacationHandler 处理。
  • 参数三:调用处理器,用于处理代理对象的方法调用。InvacationHandler 是一个接口,它包含一个 invoke()方法,用于在代理对象上调用方法时执行的逻辑。当代理对象的方法被调用时,invoke()方法会被调用,传入代理对象、被调用方法和方法参数,开发者可以在这个方法中实现自定义的逻辑,比如记录日志、执行额外的操作等。

       简而言之,可以用目标类获取一个类加载器,还需要传入被代理对象所实现的接口,并且在InvacationHandler内编写被代理类方法增强的逻辑,以及通过反射调用被代理类中的某个方法。

4.1.1、JDK动态代理的模拟实现

        模拟JDK动态代理的实现,首先创建一个待增强的目标类及接口:

public interface Foo {void foo();int bar();
}
public class Target implements Foo{private static final Logger log = LoggerFactory.getLogger(Target.class);@Overridepublic void foo() {log.info("foo");}@Overridepublic int bar() {log.info("bar");return 100;}
}

        上面提到过,当目标类实现了接口时,Spring 就会使用 JDK 动态代理来创建代理对象。在运行时,Spring 创建一个实现了目标接口的代理类。 所以创建一个代理类实现Foo接口:

/*** 实现Foo接口,重写foo()和bar()方法*/
public class $Proxy0 implements Foo {@Overridepublic void foo() {//foo方法增强逻辑//调用目标方法}@Overridepublic int bar() {//bar方法增强逻辑//调用目标方法return 0;}
}

        在测试类中:

/*** 模拟jdk动态代理的实现*/
public class A11Application {private static final Logger log = LoggerFactory.getLogger(A11Application.class);public static void main(String[] args) {//在创建$Proxy0对象时,通过有参构造指定 增强的具体逻辑$Proxy0 proxy0 = new $Proxy0();proxy0.bar();proxy0.foo();}}

        这样写已经可以简单的实现Target中foo()方法和bar()方法的增强,但是,此时的增强逻辑是在编译时就写死的,很显然不符合动态代理的概念。我们需要的是,在运行期间动态的对目标类中的方法进行增强。

        引入一个InvacationHandler接口:

public interface InvacationHandler {/*** @param proxy 代理类对象* @param method 需要增强的方法* @param objects 需要增强的方法的参数* @return* @throws Throwable*/Object invoke($Proxy0 proxy, Method method, Object[] objects) throws Throwable;}

        应该在测试类创建$Proxy0对象时,将InvacationHandler作为参数传入,在InvacationHandler中动态的编写增强的逻辑,在$Proxy0中加入构造:

InvacationHandler invacationHandler;public $Proxy0(InvacationHandler invacationHandler) {this.invacationHandler = invacationHandler;
}

          foo()方法和bar()方法中调用invacationHandler的invoke()方法,其中第二个参数,需要我们在$Proxy0类加载时,初始化带增强的方法:

    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());}}

         $Proxy0类改造完成:

public class $Proxy0 implements Foo {InvacationHandler invacationHandler;public $Proxy0(InvacationHandler invacationHandler) {this.invacationHandler = invacationHandler;}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());}}@Overridepublic void foo() {try {invacationHandler.invoke(this,foo,new Object[0]);} catch (RuntimeException | Error e) {throw e;}catch (Throwable throwable){throw new UndeclaredThrowableException(throwable);}}@Overridepublic int bar() {try {Object result = invacationHandler.invoke(this, bar, new Object[]{100});return ((int) result);} catch (RuntimeException | Error e) {throw e;}catch (Throwable throwable){throw new UndeclaredThrowableException(throwable);}}
}

        在创建 $Proxy0对象时,就需要通过创建invacationHandler匿名内部类的方式传递一段逻辑:

        最终测试类:

public class A11Application {private static final Logger log = LoggerFactory.getLogger(A11Application.class);public static void main(String[] args) {//在创建$Proxy0对象时,通过有参构造指定 增强的具体逻辑$Proxy0 proxy0 = new $Proxy0(new InvacationHandler() {@Overridepublic Object invoke($Proxy0 proxy, Method method, Object[] objects) throws Throwable {System.out.println("before");//通过反射调用Target中的方法,objectsObject invoke = method.invoke(new Target(), objects);return invoke;}});proxy0.bar();proxy0.foo();}}
4.1.2、JDK动态代理源码

        下面我们借助arthas工具查看JDK动态代理的源码实现:

        安装工具:cmd打开终端->curl -O https://alibaba.github.io/arthas/arthas-boot.jar

        运行工具:java -jar arthas-boot.jar

        注意:使用工具时保持程序处在运行状态,且需要获取要查看类的类名信息

        选择对应的类:


        得到生成代理类的源码:

/** Decompiled with CFR.** Could not load the following classes:*  com.itbaima.a10.jdkproxy.Inter*/
package com.sun.proxy;import com.itbaima.a10.jdkproxy.Inter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy2
extends Proxy
implements Inter {private static Method m1;private static Method m2;private static Method m3;private static Method m0;public $Proxy2(InvocationHandler invocationHandler) {super(invocationHandler);}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m3 = Class.forName("com.itbaima.a10.jdkproxy.Inter").getMethod("foo", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);return;}catch (NoSuchMethodException noSuchMethodException) {throw new NoSuchMethodError(noSuchMethodException.getMessage());}catch (ClassNotFoundException classNotFoundException) {throw new NoClassDefFoundError(classNotFoundException.getMessage());}}public final boolean equals(Object object) {try {return (Boolean)this.h.invoke(this, m1, new Object[]{object});}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final String toString() {try {return (String)this.h.invoke(this, m2, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final int hashCode() {try {return (Integer)this.h.invoke(this, m0, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final void foo() {try {this.h.invoke(this, m3, null);return;}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}
}

        与JDK动态代理的模拟实现类似。不同点在于,源码中加上了方法中共有的.equals()、.hashCode()、.toString()方法。

        4.1.3、JDK动态代理字节码生成(了解)

        JDK生成代理类,并没有经历源码阶段,编译阶段,而是直接生成了字节码(ASM)。ASM技术是在运行期间动态生成字节码。

        可以通过插件查看生成的字节码:

        准备一个继承了反射包下Proxy类的代理类和接口:

public class $Proxy0 extends Proxy implements Foo {protected $Proxy0(InvocationHandler h) {super(h);}@Overridepublic void foo(){try {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());}}
}
public interface Foo {void foo() throws Throwable;
}

        然后将代理类和接口进行编译(Ctrl+Shift+F9)

        右键如图所示,生成文件。

        文件中一些字节码指令的简单理解:

        构建一个类:

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

        定义类中的方法和成员变量:

 {fv = cw.visitField(ACC_STATIC, "foo", "Ljava/lang/reflect/Method;", null, null);fv.visitEnd();
}

        生成byte字节码数组:

return cw.toByteArray();

        然后我们将$Proxy0Dump字节码写入$Proxy0.class文件:

public class TestProxy {public static void main(String[] args) throws Exception {//将$Proxy0Dump字节码写入$Proxy0.class文件byte[] dump = $Proxy0Dump.dump();FileOutputStream fileOutputStream = new FileOutputStream("$Proxy0.class");fileOutputStream.write(dump,0,dump.length);fileOutputStream.close();}
}

        发现字节码文件和$Proxy0一样:

        整体流程:

public class TestProxy {public static void main(String[] args) throws Throwable {//生成$Proxy0Dump字节码byte[] dump = $Proxy0Dump.dump();//加载$Proxy0Dump字节码ClassLoader classLoader = new ClassLoader(){@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {return super.defineClass(name,dump,0,dump.length);}};//得到类对象Class<?> loadClass = classLoader.loadClass("com.itheima.$Proxy0");//利用反射创建对象Constructor<?> constructor = loadClass.getConstructor(InvacationHandler.class);Foo foo = (Foo) constructor.newInstance(new InvacationHandler() {@Overridepublic Object invoke($Proxy0 proxy, Method method, Object[] objects) throws Throwable {System.out.println("before invoke");System.out.println("调用方法");return null;}});foo.foo();}
}
        4.1.4、JDK反射优化

        JDK动态代理中,当调用invoke() 方法到达一定次数时(17)次,会触发反射优化。(类似于JUC锁批量重偏向)

public class TestMethodInvoke {public static void main(String[] args) throws Exception {Method foo = TestMethodInvoke.class.getMethod("foo", int.class);for (int i = 0; i < 17; i++) {foo.invoke(null,i);show(i,foo);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+"次调用");}
}

         前16次反射调用的是JDK本地的api,效率较低。

        第十七次优化成了正常调用方法,没有通过反射,所以性能较高:

4.2、CGLIB 动态代理
4.2.1、CGLIB 动态代理的模拟实现

        编写目标类:

/*** 待增强的目标类*/
public class Target {public void save1(){System.out.println("save1 ...");}public void save2(int i){System.out.println("save2 ..."+ i);}public void save3(long j){System.out.println("save3 ..."+ j);}
}

        编写代理类,CGLIB 动态代理则是通过生成目标类的子类来实现的,不需要目标类和代理类实现一个共同的接口

/*** CGLIB 动态代理则是通过生成目标类的子类来实现的*/
public class MockCglibProxy extends Target{MethodInterceptor interceptor;public void setInterceptor(MethodInterceptor interceptor) {this.interceptor = interceptor;}static Method save1;static Method save2;static Method save3;static {try {save1 = Target.class.getMethod("save1");save2 = Target.class.getMethod("save2", int.class);save3 = Target.class.getMethod("save3", long.class);} catch (NoSuchMethodException e) {throw new NoSuchMethodError(e.getMessage());}}@Overridepublic void save1() {try {interceptor.intercept(this,save1,new Object[0],null);} catch (Throwable e) {throw new RuntimeException(e);}}@Overridepublic void save2(int i) {try {interceptor.intercept(this,save2,new Object[]{10},null);} catch (Throwable e) {throw new RuntimeException(e);}}@Overridepublic void save3(long j) {try {interceptor.intercept(this,save3,new Object[]{20L},null);} catch (Throwable e) {throw new RuntimeException(e);}}}

        测试类:

public class TestProxy {public static void main(String[] args) {Target target = new Target();MockCglibProxy mockCglibProxy = new MockCglibProxy();mockCglibProxy.setInterceptor(new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("before");return method.invoke(target,objects);}});target.save1();target.save2(0);target.save3(0);}
}

         而CGLIB和JDK动态代理最大的区别在于:

        方法代理参数,可以保证每次方法都是正常调用,而不是通过反射。

4.2.2、CGLIB 动态代理的原理

        首先我们将interceptor.intercept() 方法中所需的MethodProxy方法参数进行补充创建:

        定义原有方法:

    public void saveSuper1(){super.save1();}public void saveSuper2(int i){super.save2(i);}public void saveSuper3(long j){super.save3(j);}

        初始化MethodProxy:

  //初始化methodProxystatic MethodProxy saveSuper1;static MethodProxy saveSuper2;static MethodProxy saveSuper3;

        创建对象:

 static {try {save1 = Target.class.getMethod("save1");save2 = Target.class.getMethod("save2", int.class);save3 = Target.class.getMethod("save3", long.class);//创建methodProxy对象//参数一:目标类 参数二:代理类 参数三:方法参数 参数四:增强方法 参数五:原有方法saveSuper1 = MethodProxy.create(Target.class, MockCglibProxy.class,"()V","save1","saveSuper1");saveSuper2 = MethodProxy.create(Target.class, MockCglibProxy.class,"(I)V","save2","saveSuper2");saveSuper3 = MethodProxy.create(Target.class, MockCglibProxy.class,"(J)V","save3","saveSuper3");} catch (NoSuchMethodException e) {throw new NoSuchMethodError(e.getMessage());}}

        将创建的MethodProxy对象传入增强方法中:

  //以下是带增强的方法。。。。@Overridepublic void save1() {try {interceptor.intercept(this,save1,new Object[0],saveSuper1);} catch (Throwable e) {throw new RuntimeException(e);}}@Overridepublic void save2(int i) {try {interceptor.intercept(this,save2,new Object[]{10},saveSuper2);} catch (Throwable e) {throw new RuntimeException(e);}}@Overridepublic void save3(long j) {try {interceptor.intercept(this,save3,new Object[]{20L},saveSuper3);} catch (Throwable e) {throw new RuntimeException(e);}}

        在主类中,使用

  • methodProxy.invoke(); 内部无反射,结合目标使用
  • methodProxy.invokeSuper() 内部无反射,结合代理使用

        在调用methodProxy.invoke();methodProxy.invokeSuper() 时,又会产生一个新的代理类,从而避免反射:

        生成的代理类的父类是FastClass类型:

简单来说,FastClass的作用:为代理类中的每个方法生成一个索引,并将方法的调用逻辑封装在一个数组中。当代理类的方法被调用时,CGLIB 会根据方法的索引直接定位到对应的调用逻辑,从而避免了每次都要通过反射查找方法的开销。

        所以FastClass是CGLIB避免反射的关键。

        下面同样通过模拟FastClass实现类的方式进行分析,首先模拟结合目标使用的methodProxy.invoke(); 对应的FastClass实现类:

  • public int getIndex(Signature signature) 方法:目的是为了获取方法的编号,是在代理类中MethodProxy.create()创建时执行。
  • public Object invoke(int index, Object target, Object[] args)方法:目的是执行对应编号的方法,在主类调用method.invoke()时执行

        在public Object invoke(int index, Object target, Object[] args)方法中,没有使用反射的方式。

/*** 模拟FastClass的子类实现,配合目标使用,对应 method.invoke*/
public class TargetFastClass {Signature s1 = new Signature("save1","()V");Signature s2 = new Signature("save2","(I)V");Signature s3 = new Signature("save3","(J)V");/*** 获取方法的编号* 执行时机: 代理类中 MethodProxy.create创建时* @param signature* @return*/public int getIndex(Signature signature){if (s1.equals(signature)){return 0;}else if (s2.equals(signature)){return 1;}else if (s3.equals(signature)){return 2;}return -1;}/*** 执行编号对应的方法* 执行时机 调用method.invoke* @param index* @param target* @param args* @return*/public  Object invoke(int index, Object target, Object[] args){if (index == 0){//正常调用目标的方法,没有通过反射((Target) target).save1();}else if (index == 1){((Target) target).save2(((int) args[0]));}else if (index == 2){((Target) target).save3(((long) args[0]));}return null;}}

        然后模拟结合目标使用的methodProxy.invokeSuper(); 对应的FastClass实现类:

/*** 模拟FastClass的子类实现,配合代理类使用,对应 methodProxy.invokeSuper* 生成时机:代理对象中MethodProxy.create的执行*/
public class ProxyFastClass {Signature s1 = new Signature("saveSuper1","()V");Signature s2 = new Signature("saveSuper2","(I)V");Signature s3 = new Signature("saveSuper3","(J)V");/*** 获取代理类中方法的编号(有三个增强方法,三个原始方法)* 获取的都是原始方法的编号* 执行时机: 代理类中 MethodProxy.create创建时* @param signature* @return*/public int getIndex(Signature signature){if (s1.equals(signature)){return 0;}else if (s2.equals(signature)){return 1;}else if (s3.equals(signature)){return 2;}return -1;}/*** 执行编号对应的方法* 执行时机 调用method.invoke* @param index* @param proxy* @param args* @return*/public  Object invoke(int index, Object proxy, Object[] args){if (index == 0){//正常调用目标的方法,没有通过反射((MockCglibProxy) proxy).saveSuper1();}else if (index == 1){((MockCglibProxy) proxy).saveSuper2(((int) args[0]));}else if (index == 2){((MockCglibProxy) proxy).saveSuper3(((long) args[0]));}return null;}public static void main(String[] args) {ProxyFastClass fastClass = new ProxyFastClass();int index = fastClass.getIndex(new Signature("saveSuper1","()V"));System.out.println(index);fastClass.invoke(index,new MockCglibProxy(),new Object[0]);}
}

        此时public int getIndex(Signature signature) 方法获取的是代理类中三个原始方法的序号。(代理类中一共有六个方法,三个原始方法,三个增强方法),原因是增强的逻辑在主类中已经通过.setInterceptor() 传入,不需要再次调用代理中的增强方法。并且如果再次调用代理中的增强方法,会发生interceptor.intercept() 循环调用的问题。

4.2.3、与JDK动态代理对比

        CGLIB与JDK动态代理性能上的区别在于,JDK动态代理是在达到一定的次数后取消反射机制,正常调用方法。而CGLIB运用methodProxy.invoke()和methodProxy.invokeSuper() 进行了优化,保证方法无需反射。

        JDK调用一个方法就对应一个代理。而CGLIB是一个代理类对应两个FastClass(配合目标、配合代理),每个FastClass可以对应多个方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/1210.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

sublime运行编译C和Java

1.先安装终端 参照以下教程 如何在 Sublime 文本编辑器中使用终端&#xff1f;_sublime终端窗口怎么打开-CSDN博客 可能遇到的问题&#xff1a;有些sublime text3可能并没有显示“package control”。这个问题对于笔者来说是有些吊诡的&#xff0c;因为之前一开始安装时是能…

【大模型系列】大模型评价指标总结

文章目录 1 图生文 (Image-to-Text)1.1 BLEU&#xff1a;基于准确率&#xff0c;得分越高越好1.2 METEOR&#xff1a;基于准确率和召回率&#xff0c;得分越高越好1.3 ROUGE&#xff1a;得分越高越好1.4 CIDEr&#xff1a;得分越高越好1.5 SPICE&#xff1a;得分越高越好1.6 Hu…

文本美学:text-image打造视觉吸引力

当我最近浏览 GitHub 时&#xff0c;偶然发现了一个项目&#xff0c;它能够将文字、图片和视频转化为文本&#xff0c;我觉得非常有趣。于是我就花了一些时间了解了一下&#xff0c;发现它的使用也非常简单方便。今天我打算和家人们分享这个发现。 项目介绍 话不多说&#xf…

4.2冰达机器人:视觉实例-机器人视觉循线、视觉实例-调整循线颜色

4.2.10a视觉实例-机器人视觉循线 本节内容演示一个机器人视觉的视觉循线实例 准备工作&#xff1a;布置一块区域作为循线场所&#xff0c;如下图所示。用蓝色胶带在地面贴一条路线&#xff08;机器人极限转弯半径0.5m&#xff0c;不要贴得过于曲折&#xff09;&#xff0c;将…

【超级简单】vscode进入服务器的docker容器

前提 1、已经运行docker容器 2、已经用vscode链接服务器 在vscode中安装的插件 Dev Containers docker 在容器中安装的依赖 yum install openssh-server yum install openssh-clientsvscode进入服务器的docker容器 找到自己的容器&#xff0c;右键点击&#xff0c;找到…

苍穹外卖day1--开发环境搭建

整体结构 前端&#xff1a;管理端&#xff08;Web&#xff09; 用户端&#xff08;小程序&#xff09; 后端&#xff1a;后端服务&#xff08;Java&#xff09; 前端工程基于ngnix运行 启动nginx&#xff1a;双击nginx.exe即可启动nginx服务&#xff0c;访问端口号为80 后端…

Xinstall:实现注册后自动绑定,提升用户体验

在移动互联网时代&#xff0c;App的注册与绑定流程对于用户体验至关重要。繁琐的注册步骤和手动绑定操作往往会让用户望而却步&#xff0c;导致用户流失。为了解决这一问题&#xff0c;Xinstall品牌推出了注册后自动绑定功能&#xff0c;极大提升了用户体验。 Xinstall的自动…

用户的流失预测分析

项目背景 随着电信行业的持续发展&#xff0c;运营商们开始更加关注如何扩大他们的客户群体。研究表明&#xff0c;获取新客户所需的成本要远高于保留现有客户的成本。因此&#xff0c;在激烈的竞争中&#xff0c;保留现有客户成为了一个巨大的挑战。在电信行业中&#xff0c;…

再拓信创版图-Smartbi 与东方国信数据库完成兼容适配认证

近日&#xff0c;思迈特商业智能与数据分析软件 [简称&#xff1a;Smartbi Insight] V11与北京东方国信科技股份有限公司 &#xff08;以下简称东方国信&#xff09;CirroData-OLAP分布式数据库V2.14.1完成兼容性测试。经双方严格测试&#xff0c;两款产品能够达到通用兼容性要…

TBWeb开发版V3.2.6免授权无后门Chatgpt系统源码下载及详细安装教程

TBWeb系统是基于 NineAI 二开的可商业化 TB Web 应用&#xff08;免授权&#xff0c;无后门&#xff0c;非盗版&#xff0c;已整合前后端&#xff0c;支持快速部署&#xff09;。相比稳定版&#xff0c;开发版进度更快一些。前端改进&#xff1a;对话页UI重构&#xff0c;参考C…

数据可视化(六):Pandas爬取NBA球队排名、爬取历年中国人口数据、爬取中国大学排名、爬取sina股票数据、绘制精美函数图像

Tips&#xff1a;"分享是快乐的源泉&#x1f4a7;&#xff0c;在我的博客里&#xff0c;不仅有知识的海洋&#x1f30a;&#xff0c;还有满满的正能量加持&#x1f4aa;&#xff0c;快来和我一起分享这份快乐吧&#x1f60a;&#xff01; 喜欢我的博客的话&#xff0c;记得…

面试(05)————Redis篇

目录 一、项目中哪些地方使用了redis 问题一&#xff1a;发生了缓存穿透该怎么解决&#xff1f; 方案一&#xff1a;缓存空数据 方案二&#xff1a;布隆过滤器 模拟面试 问题二&#xff1a; 发生了缓存击穿该怎么解决&#xff1f; 方案一&#xff1a;互斥锁 方案二&#xff…

Python数据可视化:频率统计条形图countplot()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 Python数据可视化&#xff1a; 频率统计条形图 countplot() [太阳]选择题 请问关于以下代码表述正确的选项是&#xff1f; import seaborn as sns import matplotlib.pyplot as plt data { …

断言(Assertion)在IT技术中的确切含义— 基于四类典型场景的分析

当“断言”&#xff08;Assertion&#xff09;一词成为IT术语时&#xff0c;语义的混沌性和二义性也随之而生。那么&#xff0c;何为断言&#xff1f;断言何为&#xff1f;实际上&#xff0c;只需分析四种典型场景&#xff0c;确切答案和准确描述就将自然显现。 在SAML&#xf…

Scikit-Learn

机器学习中的重要角色 Scikit-Leran&#xff08;官网&#xff1a;https://scikit-learn.org/stable/&#xff09;&#xff0c;它是一个基于 Python 语言的机器学习算法库。Scikit-Learn 主要用 Python 语言开发&#xff0c;建立在 NumPy、Scipy 与 Matplotlib 之上&#xff0c;…

【python】使用python和selenium实现某平台自动化上传作品的全步骤

第一&#xff0c;我们需要下载python并安装 下载地址&#xff1a;https://www.python.org/downloads/release/python-3123/ 3.x版本的python自带pip工具&#xff0c;因此不需要额外下载。 ModuleNotFoundError: No module named seleniumpip用于下载python适用的各类模块&…

Proxy 代理

意图 为其它对象提供一种代理以控制这个对象的访问。 结构 Proxy保存一个引用使得代理可以访问实体&#xff1b;提供一个与Subject的接口相同的接口&#xff0c;使代理可以用来替代实体&#xff1b;控制实体的存取&#xff0c;并可能负责创建和删除它&#xff1b;其他功能依赖…

用户体验至上:独立站脱颖而出的关键要素解析

在数字化时代&#xff0c;独立站成为了许多品牌和企业展示自身形象、推广产品、建立客户联系的重要平台。然而&#xff0c;要想在众多的独立站中脱颖而出&#xff0c;吸引并留住用户&#xff0c;良好的用户体验至关重要。本文Nox聚星将和大家探讨如何做好独立站的用户体验&…

【Linux深造日志】运维工程师必会Linux常见命令以及周边知识!

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 哈喽各位宝子们好啊&#xff01;我是博主鸽芷咕。日志这个东西我相信大家都不陌生&#xff0c;在 linxu/Windows 系统…

【自定义类型详解】完结篇——联合体(共用体)与枚举详解

先赞后看已成习惯&#xff01;&#xff01;&#xff01; 联合体 1. 联合体的定义 联合体又叫共用体&#xff0c;它是一种特殊的数据类型&#xff0c;允许您在相同的内存位置存储不同的数据类型。给联合体其中⼀个成员赋值&#xff0c;其他成员的值也会跟着变化。 联合体的结…