Spring AOP编程官方文档解读之ProxyFactoryBean篇


Spring AOP编程官方文档解读目录



前言

在Spring中创建AOP代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean。 这样可以完全控制要应用的切入点和建议及其顺序。 但是,如果您不需要这样的控制,则可以使用一些更简单的选项。


提示:以下是本篇文章正文内容,下面案例可供参考

一、Basics

像其他Spring FactoryBean实现一样,ProxyFactoryBean引入了一个间接级别。 如果您使用名称foo定义ProxyFactoryBean,则引用foo的对象将不是ProxyFactoryBean实例本身,而是ProxyFactoryBean的getObject方法的实现所创建的对象。 此方法将创建一个包装目标对象的AOP代理。

使用ProxyFactoryBean或另一个支持IOC的类创建AOP代理的最重要好处之一是,这意味着Advice和PointCut也可以由IoC管理。 这是一项强大的功能,可实现某些其他AOP框架难以实现的方法。 例如,受益于依赖注入提供的所有可插入性,建议本身可以引用应用程序对象(目标之外,目标应该在任何AOP框架中可用)。

与Spring随附的大多数FactoryBean实现一样,ProxyFactoryBean类本身就是JavaBean。 其属性用于:

- 指定需要被代理的目标对象

- 指定是否使用CGLIB来进行代理

一些关键属性是从org.springframework.aop.framework.ProxyConfig(Spring中所有AOP代理工厂的超类)继承的。 这些关键属性包括:

private boolean proxyTargetClass = false;

private boolean optimize = false;

boolean opaque = false;

boolean exposeProxy = false;

private boolean frozen = false;

- proxyTargetClass:如果要替代目标类而不是目标类的接口,则为true。 如果此属性值设置为true,则将创建CGLIB代理

- optimize:控制是否对通过CGLIB创建的代理应用激进的优化。 除非人们完全了解相关AOP代理如何处理优化,否则不要盲目使用此设置。 当前仅用于CGLIB代理。 它对JDK动态代理无效。

- frozen:如果代理配置被冻结,则不再允许对该配置进行更改。 这是一个轻微的优化,对于在您不希望调用者在创建代理后能够(通过Advised接口)操纵代理的情况下很有用。 此属性的默认值为false,因此允许进行更改,例如添加其他Advice。

- exposeProxy:确定当前代理是否应该在ThreadLocal中公开,以便目标可以访问它。 如果目标需要获取代理,并且暴露代理属性设置为true,则目标可以使用AopContext.currentProxy方法。

ProxyFactoryBean特有的其他属性包括:

private String[] interceptorNames;

/**
 * Interfaces to be implemented by the proxy. Held in List to keep the order
 * of registration, to create JDK proxy with specified order of interfaces.
 */
private List<Class<?>> interfaces = new ArrayList<Class<?>>();

- proxyInterfaces:字符串类型接口名称数组。 如果未提供,则将使用目标类的CGLIB代理

- interceptorNames:Advisor,interceptor或其他要应用的advice的字符串数组。 顺序很重要,先到先得。 也就是说,列表中的第一个拦截器将是第一个能够拦截调用的拦截器。

名称是当前工厂中的bean名称,包括父工厂中的bean名称(byName)。 您不能在这里提及bean引用,因为这样做会导致ProxyFactoryBean忽略建议的单例设置。

The names are bean names in the current factory, including bean names from ancestor factories. You can’t mention bean references here since doing so would result in the ProxyFactoryBean ignoring the singleton setting of the advice.

名称支持通配符。可以在拦截器名称后加上星号(*)。通配符代表匹配所有字符。比如如下的配置中,interceptorNames会匹配所有beanName以global开头的Advisor、Advice或者Interceptor,所以global_debug和global_performance都能够匹配。

<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="service"/>
    <property name="interceptorNames">
        <list>
            <value>global*</value>
        </list>
    </property>
</bean>

<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
private boolean singleton = true;

- singleton:无论getObject方法被调用的频率如何,工厂是否应该返回单例。 一些FactoryBean实现提供了这种方法。 默认值是true。 如果要使用有状态的Advice(例如,对于有状态的mixins),请使用原型Advice,当前值设置为false。

二、案例

1.引入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>4.3.27.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>4.3.27.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.3.27.RELEASE</version>
</dependency>

2.创建Advice

package com.example.aop.advice;

import org.springframework.aop.MethodBeforeAdvice;

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

public class SimpleBeforeAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("方法参数 args = " + Arrays.toString(args));
        System.out.println("目标方法 method = " + method);
        System.out.println("目标对象 target = " + target);
        System.out.println("----------\uD83D\uDE04\uD83D\uDE04" + method.getName() + "增强-------------");
    }
}

3. 创建实体类、业务接口以及实现

package com.example.aop.pointcut;

public class User {

    private Long userId;
    private String name;
    private Integer age;

    public User(Long userId, String name, Integer age) {
        this.userId = userId;
        this.name = name;
        this.age = age;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public Long getUserId() {
        return userId;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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

package com.example.aop.advice.service;

import com.example.aop.pointcut.User;

import java.rmi.RemoteException;

public interface DemoService {

    void add(User user) throws RemoteException;

    User findById(Long userId);

    void deleteById(Long userId);
}

package com.example.aop.advice.service.impl;

import com.example.aop.advice.service.DemoService;
import com.example.aop.pointcut.User;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

import java.rmi.RemoteException;

public class DemoServiceImpl implements DemoService {

    @Order
    @Override
    public void add(User user) throws RemoteException {
        System.out.println("----add user----" + user);
    }

    @Override
    public User findById(Long userId) {
        User user = new User(userId, "周星星", 18);
        System.out.println("----findById----" + user);
        return user;
    }

    @Override
    public void deleteById(Long userId) {
        System.out.println("----deleteById----" + userId);
    }
}

4. 上下文配置

package com.example.aop.advice;

import com.example.aop.advice.service.DemoService;
import com.example.aop.advice.service.impl.DemoServiceImpl;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;


@ComponentScan("com.example.aop.advice")
@Configuration
public class RootConfig {

    @Bean
    public SimpleBeforeAdvice simpleBeforeAdvice() {
        return new SimpleBeforeAdvice();
    }

    @Bean
    public DemoService demoService() {
        return new DemoServiceImpl();
    }

    @Bean
    public Object demoServiceProxy() {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setInterceptorNames(new String[]{"simpleBeforeAdvice"});
        try {
            Class<?>[] proxyInterfaces = new Class<?>[]{DemoService.class};
            proxyFactoryBean.setProxyInterfaces(proxyInterfaces);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        proxyFactoryBean.setTargetName("demoService");
        return proxyFactoryBean;
    }
}

在以上的配置中在定义demoServiceProxy的时候设置interceptorNames参数和targetName必须为Bean的名称。
在这里插入图片描述

在这里插入图片描述
在定义demoServiceProxy的时候有几点需要注意:

  • 为了方便在获取demoServiceProxy的时候不需要强制转换,尝试在定义时强制转换
@Bean
public DemoService demoServiceProxy() {
    ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
    proxyFactoryBean.setInterceptorNames(new String[]{"simpleBeforeAdvice"});
    try {
        Class<?>[] proxyInterfaces = new Class<?>[]{DemoService.class};
        proxyFactoryBean.setProxyInterfaces(proxyInterfaces);
    } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
    }
    proxyFactoryBean.setTargetName("demoService");
    return (DemoService) proxyFactoryBean;
}

此时会抛出异常java.lang.ClassCastException: org.springframework.aop.framework.ProxyFactoryBean cannot be cast to com.example.aop.advice.service.DemoService.
在这里插入图片描述

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'demoServiceProxy' defined in com.example.aop.advice.RootConfig: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.aop.advice.service.DemoService]: Factory method 'demoServiceProxy' threw exception; nested exception is java.lang.ClassCastException: org.springframework.aop.framework.ProxyFactoryBean cannot be cast to com.example.aop.advice.service.DemoService
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:599)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1176)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1071)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:511)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:481)
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:312)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:308)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:757)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542)
	at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:84)
	at com.example.aop.advice.TestMain.main(TestMain.java:18)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.aop.advice.service.DemoService]: Factory method 'demoServiceProxy' threw exception; nested exception is java.lang.ClassCastException: org.springframework.aop.framework.ProxyFactoryBean cannot be cast to com.example.aop.advice.service.DemoService
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:189)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:588)
	... 13 more
Caused by: java.lang.ClassCastException: org.springframework.aop.framework.ProxyFactoryBean cannot be cast to com.example.aop.advice.service.DemoService
	at com.example.aop.advice.RootConfig.demoServiceProxy(RootConfig.java:36)
	at com.example.aop.advice.RootConfig$$EnhancerBySpringCGLIB$$c0897864.CGLIB$demoServiceProxy$0(<generated>)
	at com.example.aop.advice.RootConfig$$EnhancerBySpringCGLIB$$c0897864$$FastClassBySpringCGLIB$$46a982a9.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:358)
	at com.example.aop.advice.RootConfig$$EnhancerBySpringCGLIB$$c0897864.demoServiceProxy(<generated>)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162)
	... 14 more

主要的原因在于这个代理类并不是目标接口类型,只是代理类通过getObject方法获取的才是目标接口类型。

  • 基于以上的问题,在定义时返回getObject对象
@Bean
public DemoService demoServiceProxy() {
    ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
    proxyFactoryBean.setInterceptorNames(new String[]{"simpleBeforeAdvice"});
    try {
        Class<?>[] proxyInterfaces = new Class<?>[]{DemoService.class};
        proxyFactoryBean.setProxyInterfaces(proxyInterfaces);
    } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
    }
    proxyFactoryBean.setTargetName("demoService");
    return (DemoService) proxyFactoryBean.getObject();
}

此时也会抛出异常java.lang.IllegalStateException: No BeanFactory available anymore (probably due to serialization) - cannot resolve interceptor names [simpleBeforeAdvice]

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'demoServiceProxy' defined in com.example.aop.advice.RootConfig: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.aop.advice.service.DemoService]: Factory method 'demoServiceProxy' threw exception; nested exception is java.lang.IllegalStateException: No BeanFactory available anymore (probably due to serialization) - cannot resolve interceptor names [simpleBeforeAdvice]
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:599)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1176)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1071)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:511)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:481)
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:312)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:308)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:757)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542)
	at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:84)
	at com.example.aop.advice.TestMain.main(TestMain.java:18)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.aop.advice.service.DemoService]: Factory method 'demoServiceProxy' threw exception; nested exception is java.lang.IllegalStateException: No BeanFactory available anymore (probably due to serialization) - cannot resolve interceptor names [simpleBeforeAdvice]
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:189)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:588)
	... 13 more
Caused by: java.lang.IllegalStateException: No BeanFactory available anymore (probably due to serialization) - cannot resolve interceptor names [simpleBeforeAdvice]
	at org.springframework.aop.framework.ProxyFactoryBean.initializeAdvisorChain(ProxyFactoryBean.java:422)
	at org.springframework.aop.framework.ProxyFactoryBean.getObject(ProxyFactoryBean.java:242)
	at com.example.aop.advice.RootConfig.demoServiceProxy(RootConfig.java:36)
	at com.example.aop.advice.RootConfig$$EnhancerBySpringCGLIB$$c0897864.CGLIB$demoServiceProxy$0(<generated>)
	at com.example.aop.advice.RootConfig$$EnhancerBySpringCGLIB$$c0897864$$FastClassBySpringCGLIB$$46a982a9.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:358)
	at com.example.aop.advice.RootConfig$$EnhancerBySpringCGLIB$$c0897864.demoServiceProxy(<generated>)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162)
	... 14 more

抛出异常的方法为org.springframework.aop.framework.ProxyFactoryBean#initializeAdvisorChain异常的意思就是当前对象中beanFactory为空,但是类ProxyFactoryBean是有继承BeanFactoryAware这个接口的。那为啥这里会为null呢?因为BeanFactoryAware起作用的时机是一个bean在初始化的过程中。初始化是在实例化之后的,而现在通过demoServiceProxy来定义的其实是实例化过程中的构造过程。一句话,实例化是在初始化之前的,因为beanFactory属性为null很明显了。所以上面这种配置,也是不行的。

  • 通过类型注入或者获取服务类失败
    按照以上的配置,然后我们通过如下方式来获取DemoService实例的时候会报错
DemoService demoService = applicationContext.getBean(DemoService.class);
Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.aop.advice.service.DemoService' available: expected single matching bean but found 2: demoService,demoServiceProxy
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1038)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:345)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:340)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1094)
	at com.example.aop.advice.TestMain.main(TestMain.java:20)

从以上信息来看,是存在两个这样的bean,demoService,demoServiceProxy,这两个其实一个是target对象,一个是proxy对象,在实际中,应该尽量去屏蔽target对象,使用proxy对象。修改配置类

package com.example.aop.advice;

import com.example.aop.advice.service.DemoService;
import com.example.aop.advice.service.impl.DemoServiceImpl;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;


@ComponentScan("com.example.aop.advice")
@Configuration
public class RootConfig {

    @Bean
    public SimpleBeforeAdvice simpleBeforeAdvice() {
        return new SimpleBeforeAdvice();
    }

    @Bean
    public Object demoService() {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setInterceptorNames(new String[]{"simpleBeforeAdvice"});
        try {
            Class<?>[] proxyInterfaces = new Class<?>[]{DemoService.class};
            proxyFactoryBean.setProxyInterfaces(proxyInterfaces);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
//       直接设置目标类
        proxyFactoryBean.setTarget(new DemoServiceImpl());
        return proxyFactoryBean;
    }
}

首先取消DemoServiceImpl对象的注册,然后在配置ProxyFactoryBean的时候通过target来指定目标对象。此时就能满足byType、byName来获取服务类了。

当然也可以在factorybean中获取beanFactory,首先RootConfig实现BeanFactoryAware接口;相对于demoService方法而言,rootConfig是factoryBean,在构建demoService的Bean之前首先会创建rootConfig对应的Bean。随意如下配置也可以

package com.example.aop.advice;

import com.example.aop.advice.service.DemoService;
import com.example.aop.advice.service.impl.DemoServiceImpl;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;


@ComponentScan("com.example.aop.advice")
@Configuration
public class RootConfig implements BeanFactoryAware {

    @Bean
    public SimpleBeforeAdvice simpleBeforeAdvice() {
        return new SimpleBeforeAdvice();
    }

    @Bean
    public DemoService demoService() {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setBeanFactory(beanFactory);
        proxyFactoryBean.setInterceptorNames(new String[]{"simpleBeforeAdvice"});
        try {
            Class<?>[] proxyInterfaces = new Class<?>[]{DemoService.class};
            proxyFactoryBean.setProxyInterfaces(proxyInterfaces);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
//       直接设置目标类
        proxyFactoryBean.setTarget(new DemoServiceImpl());
        return (DemoService) proxyFactoryBean.getObject();
    }

    private BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}

三、基于JDK or 基于CGLIB

在上面的案例当中,我们通过ProxyFactoryBean实现了DemoServiceImpl目标对象增强。而所谓的增强其实是基于代理的,那么以上的案例中,是基于JDK还是CGLIB的代理呢?
在这里插入图片描述
从结果可以看出,这是基于JDK动态代理的。

本部分是有关ProxyFactoryBean如何选择为特定目标对象(将被代理)创建基于JDK和CGLIB的代理之一的说明。

  • 如果ProxyFactoryBean的proxyInterfaces属性已设置为一个或多个完全限定的接口名称,则将创建基于JDK的代理。 创建的代理将实现proxyInterfaces属性中指定的所有接口。 如果目标类恰好实现比proxyInterfaces属性中指定的接口更多的接口,那很好,但是返回的代理将不会实现那些其他接口。

以上的场景是proxyInterfaces属性实现了一个接口,此时基于JDK动态代理。
如果是基于两个接口呢?
创建接口以及实现类

package com.example.aop.advice.service;

import com.example.aop.pointcut.User;

import java.rmi.RemoteException;

public interface UserService {

    User update(User user) throws RemoteException;
}
package com.example.aop.advice.service.impl;

import com.example.aop.advice.service.UserService;
import com.example.aop.pointcut.User;

import java.rmi.RemoteException;

public class UserServiceImpl implements UserService {

    @Override
    public User update(User user) throws RemoteException {
        System.out.println("---UserService-add user----" + user);
        return user;
    }
}

修改配置类

@Bean
public Object demoService() {
    ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
    proxyFactoryBean.setInterceptorNames(new String[]{"simpleBeforeAdvice"});
    try {
        Class<?>[] proxyInterfaces = new Class<?>[]{DemoService.class, UserService.class};
        proxyFactoryBean.setProxyInterfaces(proxyInterfaces);
    } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
    }
    // 直接设置目标类
    proxyFactoryBean.setTarget(new DemoServiceImpl());
    return proxyFactoryBean;
}

在参数proxyInterfaces中添加了接口UserService.class,但是目标对象其实并没有实现这个接口。情况会是怎样的呢?

此时是基于JDK动态代理的,代理对象也实现了新增的接口,但是不存在新增接口的方法。

在这里插入图片描述
尝试调用新增接口的方法,异常信息如下

Exception in thread "main" org.springframework.aop.AopInvocationException: AOP configuration seems to be invalid: tried calling method [public abstract com.example.aop.pointcut.User com.example.aop.advice.service.UserService.update(com.example.aop.pointcut.User) throws java.rmi.RemoteException] on target [com.example.aop.advice.service.impl.DemoServiceImpl@273e7444]; nested exception is java.lang.IllegalArgumentException: object is not an instance of declaring class
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:341)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
	at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:56)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
	at com.sun.proxy.$Proxy12.update(Unknown Source)
	at com.example.aop.advice.TestMain.main(TestMain.java:22)
Caused by: java.lang.IllegalArgumentException: object is not an instance of declaring class
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
	... 7 more

如果要保证可以调用目标方法,那么目标对象也必须实现指定的方法。目标对象也实现接口UserService并添加方法实现

@Override
public User update(User user) throws RemoteException {
    System.out.println("---UserService-add user----" + user);
    return user;
}

执行结果正常。
在这里插入图片描述
如果目标类实现了指定接口,但是在参数proxyInterfaces当中并不指定

package com.example.aop.advice;

public interface UsagTracker {
    
}

目标类实现以上接口但是不修改原来配置
在这里插入图片描述
如果尝试强转,将会抛出异常

Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy12 cannot be cast to com.example.aop.advice.UsagTracker
	at com.example.aop.advice.TestMain.main(TestMain.java:19)
  • 如果尚未设置ProxyFactoryBean的proxyInterfaces属性,但是目标类确实实现了一个(或多个)接口,则ProxyFactoryBean将自动检测到目标类实际上至少实现了一个接口,并且会基于JDK动态代理。 实际代理的接口将是目标类实现的所有接口。 实际上,这与简单地将目标类实现的每个接口的列表提供给proxyInterfaces属性相同。 但是,这明显减少了工作量,并且不太容易出现错别字。

在这里插入图片描述

保持以上目标类关系不变,修改配置类,不设置proxyInterfaces属性

@Bean
public Object demoService() {
    ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
    proxyFactoryBean.setInterceptorNames(new String[]{"simpleBeforeAdvice"});
    // 直接设置目标类
    proxyFactoryBean.setTarget(new DemoServiceImpl());
    return proxyFactoryBean;
}

此时运行测试是不会有异常的。说明此时被代理对象实现了所有被代理对象的接口。

  • 如果ProxyFactoryBean的proxyTargetClass属性已设置为true,则将创建基于CGLIB的代理。即使将ProxyFactoryBean的proxyInterfaces属性设置为一个或多个完全限定的接口名称,proxyTargetClass属性设置为true的事实也会使基于CGLIB的代理生效。

修改配置如下

@Bean
public Object demoService() {
    ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
    proxyFactoryBean.setInterceptorNames(new String[]{"simpleBeforeAdvice"});
    // 直接设置目标类
    proxyFactoryBean.setTarget(new DemoServiceImpl());
    // 启动CGLIB
    proxyFactoryBean.setProxyTargetClass(true);
    return proxyFactoryBean;
}

在这里插入图片描述
可以看到,此时代理类是基于CGLIB的,而这代理对象实现了被代理对象的所有接口。此时即使制定proxyFactoryBeanproxyInterfaces属性也不会改变任何的接口。

  • 如果要代理的目标对象的类(以下简称为目标类)没有实现任何接口,则将创建基于CGLIB的代理。 这是最简单的情况,因为JDK代理基于接口,并且没有接口意味着JDK代理甚至不可能。 即便此时proxyTargetClass参数设置为false。

假设现在需要对如下类进行增强

package com.example.aop.advice.service;

public class OrderService {

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

配置类如下

@Bean
public Object orderService() {
    ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
    proxyFactoryBean.setInterceptorNames(new String[]{"simpleBeforeAdvice"});
    // 直接设置目标类
    proxyFactoryBean.setTarget(new OrderService());
    return proxyFactoryBean;
}

在这里插入图片描述
可以看出此时是基于CGLIB进行代理的。

其实针对以上的场景综合来看无非以下情况:

  1. 目标对象实现了接口,那么代理方式取决于参数proxyTargetClass,为true则CGLIB,为false则JDK动态代理
  2. 目标对象没有实现任何接口,那么使用CGLIB,与参数proxyTargetClass没有任何关系。

对应的源码org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
	if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
		Class<?> targetClass = config.getTargetClass();
		if (targetClass == null) {
			throw new AopConfigException("TargetSource cannot determine target class: " +
					"Either an interface or a target is required for proxy creation.");
		}
		if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
			return new JdkDynamicAopProxy(config);
		}
		return new ObjenesisCglibAopProxy(config);
	}
	else {
		return new JdkDynamicAopProxy(config);
	}
}

针对二者中选择哪一种可参考:

1. 只要你愿意,可以在任何情况下都使用CGLIB,即便是面向接口编程.CGLIB代理通过在运行时生成目标类的子类来工作。 Spring配置此生成的子类以将方法调用委托给原始目标:该子类用于实现Decorator模式,并编织Advice。
2. final修饰的方法是没有办法被增强的。因为final修饰的方法是无法被子类覆盖的。
3. CGLIB代理和动态代理之间几乎没有性能差异。 从Spring 1.0开始,动态代理要快一些。 但是,将来可能会改变。 在这种情况下,性能不应作为决定性的考虑因素。


总结

由于自动代理注解方式的方便,通过ProxyFactoryBean接口来创建代理在实际工作中使用可能比较少,但是对这一块的学习可以加深对动态代理的理解,尤其是对于阅读和理解相关源码、理解原理有作用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

lang20150928

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

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

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

打赏作者

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

抵扣说明:

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

余额充值