Springboot源码分析之代理三板斧
时间:2019-08-24
本文章向大家介绍Springboot源码分析之代理三板斧,主要包括Springboot源码分析之代理三板斧使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
摘要:
在Spring
的版本变迁过程中,注解发生了很多的变化,然而代理的设计也发生了微妙的变化,从Spring1.x
的ProxyFactoryBean
的硬编码到Spring2.x
的Aspectj
注解,最后到了现在广为熟知的自动代理。
说明:
ProxyConfig
代理的相关配置类AdvisedSupport
实现了Advised
,封装了对Advice
和Advisor
的操作ProxyCreatorSupport
该类及其子类主要是利用代理工厂帮助创建jdk
或者cglib
的代理对象ProxyProcessorSupport
该类及其子类才是我们目前用得做多的,利用后置处理器来进行自动代理处理
ProxyFactoryBean
package com.github.dqqzj.springboot.aop;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @author qinzhongjian
* @date created in 2019-08-24 11:05
* @description: TODO
* @since JDK 1.8.0_212-b10
*/
@Component
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
if (!method.getName().equals("toString")) {
System.out.println(target.getClass().getName() + "#" + method.getName());
}
}
/**
* 代理的目标对象 效果同setTargetSource(@Nullable TargetSource targetSource)
* TargetSource targetSource = new SingletonTargetSource(aopService);
* 可以从容器获取,也可以类似下面这样直接new,使用区别需要熟悉spring机制。
* factoryBean.setTarget(new AopService());
*
* 设置需要被代理的接口 效果同factoryBean.setProxyInterfaces(new Class[]{AopService.class});
* 若没有实现接口,那就会采用cglib去代理
* 如果有接口不指定的话会代理所有的接口,否则代理指定的接口
*
* setInterceptorNames方法源代码中有这样的一句话:Set the list of Advice/Advisor bean names. This must always be set
* to use this factory bean in a bean factory.
*/
@Bean
public ProxyFactoryBean proxyFactoryBean(AopService aopService) {
ProxyFactoryBean factoryBean = new ProxyFactoryBean();
factoryBean.setTarget(aopService);
//factoryBean.setInterfaces(AopService.class);
factoryBean.setInterceptorNames("myMethodBeforeAdvice");
//是否强制使用cglib,默认是false的
//factoryBean.setProxyTargetClass(true);
return factoryBean;
}
}
源码分析:
@Override
@Nullable
public Object getObject() throws BeansException {
//根据我们配置的interceptorNames来获取对应的Advisor并加入通知器执行链中
initializeAdvisorChain();
if (isSingleton()) {
//生成singleton的代理对象,会利用DefaultAopProxyFactory去生成代理
//在内部如果你手动没有去设置需要被代理的接口,Spring会代理你所有的实现接口。
return getSingletonInstance();
}
else {
if (this.targetName == null) {
logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
"Enable prototype proxies by setting the 'targetName' property.");
}
//和单利非常类似 只不过没有缓存了
return newPrototypeInstance();
}
}
private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException {
if (this.advisorChainInitialized) {
return;
}
if (!ObjectUtils.isEmpty(this.interceptorNames)) {
// 最后一个不能是全局的suffix *,除非我们指定了targetSource之类的
if (this.interceptorNames[this.interceptorNames.length - 1].endsWith(GLOBAL_SUFFIX) &&
this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) {
throw new AopConfigException("Target required after globals");
}
for (String name : this.interceptorNames) {
// 如国拦截器的名称是以*结尾的,说明它要去全局里面都搜索出来
// 全局:去自己容器以及父容器中找,类型为Advisor.class的,名称是以这个名称为开头的prefix的Bean.
if (name.endsWith(GLOBAL_SUFFIX)) {
addGlobalAdvisor((ListableBeanFactory) this.beanFactory,
name.substring(0, name.length() - GLOBAL_SUFFIX.length()));
}
// 一般的情况下我们都是精确匹配
else {
Object advice;
if (this.singleton || this.beanFactory.isSingleton(name)) {
// 从容器里获取该bean
advice = this.beanFactory.getBean(name);
}
// 原型处理
else {
advice = new PrototypePlaceholderAdvisor(name);
}
addAdvisorOnChainCreation(advice, name);
}
}
}
this.advisorChainInitialized = true;
}
// 将advice对象添加到通知器链中
private void addAdvisorOnChainCreation(Object next, String name) {
// 这里调用namedBeanToAdvisor做了一下适配:成统一的Advisor
Advisor advisor = namedBeanToAdvisor(next);
addAdvisor(advisor);
}
//方法中首先会调用namedBeanToAdvisor(next)方法,将从ioc容器获取的普通对象转换成通知器Advisor对象
private Advisor namedBeanToAdvisor(Object next) {
try {
return this.advisorAdapterRegistry.wrap(next);
}
}
DefaultAdvisorAdapterRegistry
这个类还允许我们自定义适配器,然后注册到里面就行。
@Override
public void registerAdvisorAdapter(AdvisorAdapter adapter) {
this.adapters.add(adapter);
}
ProxyFactoryBean脱离IoC容器使用
ProxyFactory
说明:这个类一般是spring
自己内部使用的,我们自定义的话很难与容器进行整合,它一般都是返回的原型模式代理
AspectJProxyFactory
小结:
根据以上案例可以发现 都是首先进行AdvisedSupport的准备,然后交给子类ProxyCreatorSupport根据条件
得到JDK或者CGLIB的AopProxy,当代理对象被调用的时候在invoke或者intercept方法中会调用ProxyCreatorSupport的getInterceptorsAndDynamicInterceptionAdvice方法去初始化advice和各个方法之间的映射关系并缓存
同类方法代理不生效原因?
很多时候会发现代理方法和非代理方法在同一个类中调用不生效和调用顺序有关系,我们进行重构代码来分析一下原因
public class AspectJProxyFactoryApplication {
public static void main(String[] args) {
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new AopService());
// 注意:此处得MyAspect类上面的@Aspect注解必不可少
proxyFactory.addAspect(MyAspect.class);
//proxyFactory.setProxyTargetClass(true);//是否需要使用CGLIB代理
AopService proxy = proxyFactory.getProxy();
proxy.test();
}
}
@Aspect
public class MyAspect {
//@Pointcut("execution(* com.github..aop.*.*(..))")
@Pointcut("execution(* com.github..aop.AopService.hello(..))")
private void pointcut() {
}
@Before("pointcut()")
public void before() {
System.out.println("-----------MyAspect#before-----------");
}
}
@Service
public class AopService {
public String hello() {
System.out.println("hello, AopService");
return "hello, AopService";
}
public String test() {
System.out.println("test");
return hello();
}
}
答案就是不会生效,究竟是什么引起的呢?其实就是我上面的小结的最后一个知识点。
这个时候chain
没有我们的通知器在里面,
最终按照我们的程序执行,下面进行修改切点表达式,如果上面的例子看的咨询的话下面就可以忽略了,主要就是是否增强就是第一个入口函数能否匹配上我们的切点表达式后续的根本不会关心你是否能匹配上。
@Aspect
public class MyAspect {
@Pointcut("execution(* com.github..aop.*.*(..))")
//@Pointcut("execution(* com.github..aop.AopService.hello(..))")
private void pointcut() {
}
@Before("pointcut()")
public void before() {
System.out.println("-----------MyAspect#before-----------");
}
}
处理完后就会按照下面代码正常流程执行完
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
如果同一个类的方法调用都想让通知器生效怎么办?这个就必须要让通知添加到执行链中才行,根据上面所讲的内容就可以达到这个目的,然后继续用此代理对象来调用该内嵌方法。
原文地址:https://www.cnblogs.com/qinzj/p/11405826.html
- JavaScript之面向对象学习五(JS原生引用类型Array、Object、String等等)的原型对象介绍
- SQL学习之分组数据Group by
- SQL学习之数据列去空格函数
- 采用DIV+CSS布局对SEO优化有何好处?
- Sublime快速入门
- SQL学习之汇总数据之聚集函数
- Sedo榜单中,域名“加密世界”CryptoWorld.com七位数夺冠
- ExtJs学习笔记(20)-利用ExtJs的Ajax与服务端WCF交互
- 2018年热点分享:比特币到底是什么?
- JVM快速入门
- SQL学习之使用常用函数处理数据
- Javascript快速入门(上篇)
- SQL练习之不反复执行相同的计算
- SQL练习之求解填字游戏
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- Maven项目中常用jar包的坐标以及用法(个人总结)
- MyCat------基于java语言的数据库中间件
- Solr技术(附软件分享)
- VSFTPD技术
- Shiro——基于java的安全框架
- 电子圆二色谱(ECD)的理论计算
- 用 80 行 Javascript 代码构建自己的语音助手
- 手把手教你搭建分布式项目环境
- GraphQL两年实战避坑经验
- 基于Dubbo的服务提供者与消费者的发布(在虚拟机中)以及使用nginx对项目进行负载均衡优化
- 我们为什么不使用CSS框架
- java.lang.IllegalArgumentException: node to traverse cannot be null!
- Spring Data Jpa 异常:PropertyReferenceException: No property xxx found for type for type yyy
- Spring全家桶之SpringData——SpringData Redis(附相关jar包)
- Spring全家桶之SpringBoot——初级阶段