0%

Spring中被代理对象内部调用

Spring中被代理对象内部调用

 众所周知,Spring AOP是基于动态代理实现的,最近被同事问到在被代理对象中直接进行内部调用,拦截器或切面不生效的问题,在这里做一个简单的记录。

情景重现

Spring版本:5.1.6

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class Impl implements Service{

public void a(){
b();
}

public void b(){

}

}

 如上所示,在Impl类外部调用a方法时,针对b方法的拦截器或切面并不能生效。原因是这种情形下,this代表被代理对象,调用并没有通过代理对象执行。

解决方案一

  1. 确保使用CGLIB并将代理对象暴露给用户。

    1
    @EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
  2. 获取代理对象,强制转型为被代理对象(这里牵扯到CGlLIB的特点,代理对象其实是被代理对象的子类实例)

1
2
3
4
//继续使用上面的例子
public void a(){
((Impl)AopContext.currentProxy()).b();
}

解决方案二

Bean加载后,向内部注入增强对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Service
public class Impl implements Service{

@Autowired //注入上下文
private ApplicationContext context;

private Impl proxy;//表示代理对象,不是目标对象

@PostConstruct //③ 初始化方法
private void setSelf() {
//此种方法不适合于prototype Bean,因为每次getBean返回一个新的Bean
proxy = context.getBean(Impl.class);
}

public void a(){
proxy.b();
}

public void b(){

}

}

解决方案三

方案二需要类自己实现注入增强对象的方法,可以通过BeanPostProcessor在目标对象中注入,这样便提高了代码复用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public interface BeanSelfAware {  
void setSelf(Object proxy);
}

@Service
public class Impl implements Service, BeanSelfAware{
private Impl proxy;//表示代理对象,不是目标对象

private void setSelf() {
//此种方法不适合于prototype Bean,因为每次getBean返回一个新的Bean
proxy = context.getBean(Impl.class);
}

public void a(){
proxy.b();
}

public void b(){

}

}

@Component
public class InjectBeanSelfProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//如果Bean实现了BeanSelfAware标识接口,就将代理对象注入
if(bean instanceof BeanSelfAware) {
//即使是prototype Bean也可以使用此种方式
((BeanSelfAware) bean).setSelf(bean);
}
return bean;
}
}