继续创造,加速生长!这是我参与「日新方案 10 月更文应战」的第8天,点击查看活动概况
AOP (Aspect Orient Programming):直译过来便是 面向切面编程。AOP 是一种编程思想
用途:Transactions (事务调用办法前敞开事务, 调用办法后提交封闭事务 )、日志、功能(监控办法运转时间)、权限操控等
也便是对事务办法做了增强
1.1 Spring AOP环境介绍
**方针:**知道AOP基础环境,后边讲运用这个基础环境进行源码讲解
tips:
沿用ioC的工厂
1)引进起步依靠
compile(project(':spring-aop'))
compile(project(':spring-context'))
compile 'org.aspectj:aspectjweaver:1.9.2'
2)新建接口和完结
public interface Slaver {
void work();
}
import org.springframework.stereotype.Service;
@Service
public class SlaverImpl implements Slaver {
public void work() {
System.out.println("进入完结类 work.....");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3)新建切面类
package com.spring.test.aop.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
//将这个类声明为一个切面,需求将其放入IOC容器中
@Aspect//声明这是一个切面
@Component//声明这是一个组件
/**
* 履行次序
* @Around进入盘绕告诉...
* @Before进入前置告诉:[]
* 进入完结类 work.....
* @Around办法履行耗时>>>>>: 1001
* @After进入后置告诉...
* @AfterReturning进入终究告诉...End!
*/
public class SlaverAspect {
//盘绕告诉(连接到切入点开端履行,下一步进入前置告诉,在下一步才是履行操作办法)
@Around(value = "pointCut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("@Around进入盘绕告诉...");
long startTime = System.currentTimeMillis();
joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println(String.format("@Around办法履行耗时>>>>>: %s", endTime - startTime));
}
//前置告诉(进入盘绕后履行,下一步履行办法)
@Before(value = "pointCut()")
public void before(JoinPoint joinPoint) {
System.out.println("@Before进入前置告诉:" + Arrays.toString(joinPoint.getArgs()));
}
//反常告诉(犯错时履行)
@AfterThrowing(value = "pointCut()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
System.out.println("@AfterThrowing进入反常告诉" + Arrays.toString(joinPoint.getArgs()));
System.out.println("@AfterThrowing反常信息:" + ex);
}
//后置告诉(回来之前履行)
@After(value = "pointCut()")
public void after() {
System.out.println("@After进入后置告诉...");
}
//终究告诉(正常回来告诉,最后履行)
@AfterReturning(value = "pointCut()")
public void afterReturning() {
System.out.println("@AfterReturning进入终究告诉...End!");
}
//界说一个切入点 后边的告诉直接引进切入点办法pointCut即可
// @Pointcut("execution(public * com.spring.test.aop.impl.SlaverImpl.work())")
@Pointcut(value = "execution(* com.spring.test.aop.impl.SlaverImpl.*(..))")
public void pointCut() {
}
}
AspectJ 支持 5 种类型的告诉注解: @Before: 前置告诉, 在办法履行之前履行 @After: 后置告诉, 在办法履行之后履行 @AfterRunning: 回来告诉, 在办法回来成果之后履行 @AfterThrowing: 反常告诉, 在办法抛出反常之后 @Around: 盘绕告诉, 围绕着办法履行
execution:用于匹配办法履行的连接点;
4)新建装备文件
resources/application-aop.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
>
<!-- 使 AspectJ 的注解起效果 -->
<aop:aspectj-autoproxy/>
<!-- 扫描带有注解的类,交给ioc容器办理 -->
<context:component-scan base-package="com.spring.test.aop"/>
</beans>
常见错误
装备文件缺乏xsd的引证
5)新建入口类
public class Main {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath*:application-aop.xml");
Slaver slaver=(Slaver)context.getBean("slaverImpl");
slaver.work();
}
}
6)运转效果
1.2 SpringAOP和AspectJ联络
tips:
十个人有九 个人弄不懂的联系
Spring AOP:
Spring AOP旨在经过Spring IoC供给一个简略的AOP完结,以解决编码人员面对的最常出现的问题。这并不是完整的AOP解决方案,它只能用于Spring容器办理的beans。
AspectJ:
AspectJ是最原始的AOP完结技术,供给了完整的AOP解决方案。AspectJ更为健壮,相对于Spring AOP也显得更为复杂
总结
AOP是面向切面的一个思想
他有两种完结
1、Spring AOP
2、Aspectj
Spring AOP的完结没有AspectJ强壮
所以,Spring把Aspectj给集成(假如用,还需求独自引jar)进来了
可是;spring aop已能满意咱们的需求
在进行开发时分,这两个框架是完全兼容的
说白了,便是两个框架能一起运用,就看你项目需求用到的哪种程度了
简略的;spirng aop够用了,可是spring aop凭借了aspectj的注解功用,
在高档点,比方切面很多,上万个,这是就要用到aspectj的高档功用了
差异:AspectJ运用的是编译期和类加载时进行织入,Spring AOP运用的是运转时织入
**依靠:**假如运用@Aspect注解,在xml里加上<aop:aspectj-autoproxy />。可是这需求额外的jar包( aspectjweaver.jar)
由于spring直接运用AspectJ的注解功用,留意仅仅运用了它 的注解功用罢了。并不是中心功用 !
运转效果如下:
1.3 找到处理AOP的源头
1、Spring处理AOP源头在哪里(织入)
AspectJ编译期和类加载时进行织入、Spring AOP运用的是运转时织入
猜想:
1. 在容器启动时创立?
2.在getBean时创立?
容器启动
2、署理方针究竟长什么样?
将断点打在getBean的回来方针上,发现这并不是一个咱们界说的方针本身,而是一个Proxy
接下来,咱们会找到$Proxy生成的始末
3、我的接口是怎样被A0P办理上的 ?
com.spring.test.aop.aop.SlaverAspect#pointCut
//界说一个切入点 后边的告诉直接引进切入点办法pointCut即可
//参数:
//榜首个”*“符号;表明回来值的类型恣意
//.*(..)表明任何办法名,括号表明参数,两个点表明任何参数类型
@Pointcut(value = "execution(* com.spring.test.aop.impl.SlaverImpl.*(..))")
public void pointCut() {
System.out.println("@进入切点...");
}
tips:
当然是execution声明,改成 SlaverImpl.aaaa*(..) , 再来看getBean的方针,不再是proxy
断点一下……
1.4 署理方针是怎样生成的
1、AOP其实便是用的动态署理形式,创立署理
2、AOP织入源头在哪里
**方针:**经过源头找署理方针是怎么生成的
找到生成署理的当地Proxy.newProxyInstance
0)回忆助学
简略回忆一下,留意,这儿不仅仅是学习署理形式,还有必要搞懂它的完结办法,尤其是动态署理。
开端之前,咱们先有必要搞懂一件工作:
那便是:署理形式
设计形式【静态署理】 【动态署理】 回忆 (见第2小节)
1)从源头找署理方针生成
记住方针:
spring aop运用的便是署理形式,那咱们的方针就明确了:找到生成署理的当地Proxy.newProxyInstance
先来张流程图:
下面咱们沿着图中的调用链,找到aop 署理诞生的当地
tips:从后置处理器开端
为什么要从后置处理器入手?
很容易了解,没初始化好没法用,等你初始化好了功用完备了,我再下手,代替你
找到后置处理器
要点重视postProcessAfterInitialization
此处最好运用断点表达式,不然要循环很屡次
由于在refresh办法中的invokeBeanFactoryPostProcessors办法也会调用到这个当地
断点表达式:
重视点:
tips:
在BeanPostProcessor循环中,观察AnnotationAwareAspectJAutoProxyCreator
这货便是切面的后置处理器
AbstractAutowireCapableBeanFactory**#**applyBeanPostProcessorsAfterInitialization
//方针:循环一切的后置处理器进行调用
//留意:
//AOP调试,此处最好运用断点表达式,不然要循环很屡次
//由于在refresh办法中的invokeBeanFactoryPostProcessors办法也会调用到这个当地
@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
//aop
//此处getBeanPostProcessors()有8个内置后置处理器;生成署理会调用里边的 AnnotationAwareAspectJAutoProxyCreator
for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
//Aop调用AbstractAutoProxyCreator#postProcessAfterInitialization,
Object current = beanProcessor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
如上所见
也便是说AOP模块是经过完结BeanPostProcessor集成进来的
2)进入后置处理器
tips:
aop这是spring内置的一个后置处理器,收效在postProcessAfterInitialization办法
AbstractAutoProxyCreator#postProcessAfterInitialization
要点重视wrapIfNecessary
//假如当时的bean适合被署理,则需求包装指定的bean
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
if (bean != null) {
// 依据给定的bean的class和name构建一个key
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
// 假如当时的bean适合被署理,则需求包装指定的bean
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
阅历wrapIfNecessary办法,要点重视点有两个:
1是:getAdvicesAndAdvisorsForBean,找到哪些切面会效果在当时bean上,满意条件的抓出来!
2是:createProxy,生成署理,替代slaverImpl去做事
//方针
//1、判别当时bean是否现已生成过署理方针,或许是否是应该被略过的方针,是则直接回来,不然进行下一步
//2、拿到切面类中的一切增强办法(阻拦器:盘绕、前置、后置等)
//3、生成署理方针
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 判别是否为空
// 判别当时bean是否在TargetSource缓存中存在,假如存在,则直接回来当时bean
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
// 这儿advisedBeans缓存了不需求署理的bean(为false的),假如缓存中存在,则能够直接回来
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
//Infrastructure基础设施
// 用于判别当时bean是否为Spring系统自带的bean,自带的bean是
// 不必进行署理的;shouldSkip()则用于判别当时bean是否应该被略过
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
// 对当时bean进行缓存
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
//AOP:【要害点1】反射来过滤,看看哪些aspect的execution能匹配上当时bean
// ===【【【【留意!这货要分两步调试讲解,修改切面表达式做比照】】】】====
// 将SlaverAspect的 execution改成 SlaverImpl.aaa*(..) 试试,你将得到一个空数组!!!
// 匹配上的话列出前后和置换的办法(阻拦器:盘绕、前置、后置等)
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
//假如拿到的增强办法不为空
if (specificInterceptors != DO_NOT_PROXY) {
// 对当时bean的署理状态进行缓存
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 开端创立AOP署理
// ===【要害点2】===
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
// 缓存生成的署理bean的类型,而且回来生成的署理bean
this.proxyTypes.put(cacheKey, proxy.getClass());
//此处回来的署理和在Main函数中回来的是相同的
//阐明此处署理成功创立
return proxy;
}
//假如拿到的增强办法为空,缓存起来(运用false符号不需求署理)
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
3)开端创立署理方针
要点重视最下面的要害点:proxyFactory.getProxy(getProxyClassLoader())
//beanClass:方针方针class
//beanaName
//specificInterceptors:阻拦器里边的阻拦办法
//targetSource:方针资源
//方针:开端为bean创立署理
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
//为true,DefaultListableBeanFactory
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
//给当时的bd设置特点setAttribute("originalTargetClass",bean的class),进入
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
//创立一个默认的署理工厂DefaultAopProxyFactory,父类无参结构器
ProxyFactory proxyFactory = new ProxyFactory();
//proxyFactory经过复制装备进行初始化
//this为AbstractAutoProxyCreator方针,阐明AbstractAutoProxyCreator继承参数实践类型
proxyFactory.copyFrom(this);
/**
* isProxyTargetClass(),默认false
* true
*方针方针没有接口(只要完结类) – 运用CGLIB署理机制
* false
* 方针方针完结了接口 – 运用JDK署理机制(署理一切完结了的接口)
*/
if (!proxyFactory.isProxyTargetClass()) {
//来判别@EnableAspectJAutoProxy注解或许XML的proxyTargetClass参数(true或许false)
//看看用户有没有指定什么办法生成署理
// 假如没装备就为空,此处回来false
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
} else {
//评估接口的合理性,一些内部回调接口,比方InitializingBean等,不会被完结jdk署理
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
// 把advice(增强)类型的增强包装成advisor类型(强制类型转化)
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
//参加到署理工厂
proxyFactory.addAdvisors(advisors);
//设置要署理的类(方针类)
proxyFactory.setTargetSource(targetSource);
//子类完结, 定制署理
customizeProxyFactory(proxyFactory);
//用来操控署理工厂被设置后是否还答应修改告诉,缺省值为false
proxyFactory.setFrozen(this.freezeProxy);
//分明是false?? 此处留意,他是在子类AbstractAdvisorAutoProxyCreator重写了advisorsPreFiltered办法
if (advisorsPreFiltered()) {
//设置预过滤
proxyFactory.setPreFiltered(true);
}
//【要害点】经过类加载期获取署理;getProxyClassLoader为默认的类加载器
return proxyFactory.getProxy(getProxyClassLoader());
}
进入getProxy
//经过类加载期获取署理
public Object getProxy(@Nullable ClassLoader classLoader) {
//分别进入createAopProxy 和getProxy
return createAopProxy().getProxy(classLoader);
}
先看createAopProxy
查看回来jdk署理还是cglib署理
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// isOptimize:是否对署理进行优化
// isProxyTargetClass:值为true,运用CGLIB署理,默认false
// hasNoUserSuppliedProxyInterfaces:
//1、假如长度为0;也便是接口为空,回来false
//or(或的联系)
//2、假如接口类型不是SpringProxy类型的;回来flase
//假如条件不满意;直接走JDK动态署理(return)
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.");
}
// 假如targetClass是接口类,运用JDK来生成Proxy
//Tips
//假如方针方针完结了接口,默认情况下会采用JDK动态署理,
// 但也能够经过装备(proxy-target-class=true)强制运用CGLIB。
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
//jdk
return new JdkDynamicAopProxy(config);
}
//cglib
return new ObjenesisCglibAopProxy(config);
} else {
//jdk
return new JdkDynamicAopProxy(config);
}
}
再看getProxy
//获取终究的署理方针(由JDK生成;运转时织入)
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
//获取署理方针需求完结的接口(事务接口和内置接口)
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
//判别接口中是否重写了equals和hashCode办法
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
/**
* 榜首个参数是类加载器(方针类)
* 第二个参数是署理类需求完结的接口,即方针类完结的接口(含系统接口)(数组)
* 第三个是InvocationHandler,本类完结了此接口
*/
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
找到了!署理方针在bean初始化阶段安装进了spring
4)署理方针验证
tips:
前面创立完结了署理方针,下面咱们看下它调用的时分,是不是走了署理
这是咱们一开端的成果,下面咱们来再debug到work办法时,点击debug into试试,发现调到哪里去了???
结论:
没错!署理方针精准的调用了JdkDynamicAopProxy里边的invoke办法
这阐明jdk动态署理收效,可是它生成的proxy字节码在jvm里,咱们是看不到的,怎样破?
arthas上!
2)Arthas署理类验证
arthas,阿里神器,主页:arthas.aliyun.com/zh-cn/
获取之前,咱们需求做点小工作,打出咱们的 slaver的类路径,让arthas能找到它!
源码:
package com.aoptest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) throws InterruptedException {
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath*:application-aop.xml");
Slaver slaver=(Slaver)context.getBean("slaverImpl");
slaver.work();
System.out.printf(slaver.getClass().getName()); //打印当时方针的类称号:com.sun.proxy.$Proxy17
Thread.sleep(Integer.MAX_VALUE); // 有必要逗留在这儿,debug是不行的,arthas会连不上
}
}
敞开arthas:
- 很简略, java -jar arthas-boot.jar
- 启动后,在arthas里履行:jad com.sun.proxy.$Proxy17 反编译咱们的署理类
留意,jad后边的,换成你上一步操控台打印出来的
见证奇观的时间……
找到里边的work办法,成果它长这样……
那么,this.h呢?
杀死进程,回到debug形式,看看
没错,便是咱们的jdk动态署理类!
现在调用联系明确了,接下来,咱们就来分析下这个invoke
1.5 署理方针怎么调用
1)先看张图
署理方针调用链如下图 (职责链,先有个形象,很长,很长……)
2)了解职责链
代码中给咱们预备了一个职责链小demo,它便是咱们spring aop切面调用链的缩影
spring-aop-test 项目下的 com.spring.test.aop.chain 包。
履行里边的main办法
生活中的比如,类似于:
-
全公司站成一队,从队首开端签名
假如先签完再交给下一个,你便是前置阻拦。(签名 = 履行切面使命)
假如先交给下一个,等传回来的时分再签,你就完结了后置,回来的时分再补
-
一个个传究竟后,到达队尾,老板盖章(真实的事务逻辑得到履行)
-
然后一个个往回传(return),前面没签的那些家伙补上(后置切面使命得到履行)
3)invoke办法源码分析
经过上小节咱们知道,署理形式下履行的是以下invoke
org.springframework.aop.framework.JdkDynamicAopProxy#invoke
代码要点重视
// 2.从ProxyFactory(this.advised)中构建阻拦器链,包含了方针办法的一切切面办法
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass)
//职责链开端调用:ReflectiveMethodInvocation.proceed();要点重视!!!
retVal = invocation.proceed();
职责链构建:chain里边为职责链的具体使命
接下来,咱们具体看看职责链具体的处理逻辑
4)AOP中心之职责链
思考:
职责链调用,调用的什么?
职责链方针:
AOP职责链调用流程简图
留意:上图的色彩区别
黑色:表明正向调用,invoke的时分,在前或后履行自己的切面逻辑,然后推进职责链往下走
赤色:表明当时切面使命触发的点
备注:ExposeInvocationInterceptor 是spring帮咱们加上做上下文传递的,本图不触及(它在最前面)
留意,咱们的链条如下:
调试技巧:
从 ReflectiveMethodInvocation.invoke() 开端,对照上面的职责链嵌套调用图
每次经过debug into 进行查看,每到一环,留意对照图上的节点,看到那个invocation了!
案例:榜首环,遇proceed,再 debug into
依次循环,步步跟进,其乐无穷~~~
2 AOP基础 – 署理形式(助学)
2.1 设计形式之署理
背景
举个比如
假定咱们想约请一位明星,不联络明星
而是联络明星的经纪人,来到达约请的的意图
明星便是一个方针方针,他只要担任活动中的节目,而其他琐碎的工作就交给他的署理人(经纪人)来解决.
这便是署理思想在实际中的一个比如
什么是署理?
署理(Proxy)是一种设计形式
供给了对方针方针其他的拜访办法;即经过署理方针拜访方针方针.
这样做的好处是:能够在方针方针完结的基础上,增强额外的功用操作
署理形式分类
1、静态署理
2、动态署理
2.2 静态署理形式
静态署理在运用时:
需求界说接口、方针方针与署理方针
重要特点:
静态署理是由程序员创立或东西生成署理类的源码,再编译署理类。
接口
//接口
public interface IStartBusiness {
//约请明星歌唱
void sing();
}
方针方针,完结类
//方针方针,完结类
public class StartBusinessImpl implements IStartBusiness {
@Override
public void sing() {
System.out.println("sing>>>>>>>>>>>>>>>>>>>>>>>>>>>");
}
}
署理方针,静态署理
package com.spring.test.aop.pattern.proxy.staticproxy;
//署理方针,静态署理
public class AgentProxy implements IStartBusiness {
//署理类持有一个方针类的方针引证
private IStartBusiness iStartBusiness;
//结构注入方针方针
public AgentProxy(IStartBusiness iStartBusiness) {
this.iStartBusiness = iStartBusiness;
}
@Override
public void sing() {
//**********办法前增强****************
//do something
//将恳求分派给方针类履行;经过注入进入来的方针方针进行拜访
this.iStartBusiness.sing();
//do after
//**********办法后增强****************
}
}
静态署理测验
package com.spring.test.aop.pattern.proxy.staticproxy;
//静态署理测验
public class Test {
public static void main(String[] args) {
//方针方针
IStartBusiness target = new StartBusinessImpl();
//署理方针,把方针方针传给署理方针,建立署理联系
IStartBusiness proxy = new AgentProxy(target);
//调用的时分经过调用署理方针的办法来调用方针方针
proxy.sing();
}
}
输出
优点:
能够在被署理办法的履行前或后参加其他代码,完结诸如权限及日志的操作。
缺点:
1、假如接口添加一个办法,除了一切完结类需求完结这个办法外,一切署理类也需求完结此办法
总结:(记住两点)
1、只需求知道静态署理是在运转前署理类就现已被织入进去了
2、大规模运用静态署理难以保护(添加办法)
有没有其他的办法能够减少代码的保护,那便是动态署理?
2.3 动态署理形式
什么是动态署理?
动态署理类的源码是在程序运转期间由JVM依据反射等机制动态织入的,
所以;不存在署理类的字节码文件,直接进了虚拟机。(可是有办法给抓到他)
JDK中生成署理方针的API最重要类和接口有两个,如下
Proxy
InvocationHandler
1)署理父类Proxy回忆
地点包:java.lang.reflect.Proxy
这是 Java 动态署理机制生成的一切动态署理类的父类,
供给了一组静态办法来为一组接口动态地生成署理类及其方针。
Proxy类的静态办法(了解)
// 办法 1: 该办法用于获取指定署理方针所相关的调用处理器
static InvocationHandler getInvocationHandler(Object proxy)
// 办法 2:该办法用于获取相关于指定类装载器和一组接口的动态署理类的类方针
static Class getProxyClass(ClassLoader loader, Class[] interfaces)
// 办法 3:该办法用于判别指定类方针是否是一个动态署理类
static boolean isProxyClass(Class cl)
// 办法 4:该办法用于为指定类装载器、一组接口及调用处理器生成动态署理类实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
tips:重要
要点重视newProxyInstance
这三个参数十分重要
Spring Aop也是运用这个机制
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
留意该办法是在Proxy类中是静态办法,且接纳的三个参数依次为:
-
ClassLoader loader,
:指定当时方针方针运用类加载器 ;担任将类的字节码装载到 Java 虚拟机(JVM)中并为其界说类方针 -
Class<?>[] interfaces,
:方针方针完结的接口的类型,运用泛型办法承认类型 -
InvocationHandler h
:事情处理,履行方针方针的办法时,会触发事情处理器的办法,会把当时履行方针方针的办法作为参数传入
2)调用处理器接口回忆
java.lang.reflect.InvocationHandler
这是调用处理器接口,它自界说了一个 invoke 办法(只要一个)
用于集中处理在动态署理类方针上的办法调用,通常在该办法中完结对方针类的署理拜访。
每次生成动态署理类方针时都要指定一个对应的调用处理器方针。
1、方针方针(托付类)经过jdk的Proxy生成了署理方针、
2、客户端拜访署理方针,署理方针经过调用于处理器接口反射调用了方针方针办法
InvocationHandler的中心办法
仅仅一个办法
public interface InvocationHandler {
//榜首个参数既是署理类实例
//第二个参数是被调用的办法方针
// 第三个办法是调用参数
Object invoke(Object proxy, Method method, Object[] args)
}
3)动态署理代码编写
沿用上面的 比如
tips
署理方针不需求完结接口(事务),可是方针方针一定要完结接口,不然不能用动态署理
接口
方针方针接口
package com.spring.test.aop.pattern.proxy.dynamic;
//接口
public interface IStartBusiness {
//约请明星歌唱
void sing();
}
方针方针
//方针方针
public class StartBusinessImpl implements IStartBusiness {
@Override
public void sing() {
System.out.println("sing>>>>>>>>>>>>>>>>>>>>>>>>>>>");
}
}
创立动态署理方针
package com.spring.test.aop.pattern.proxy.dynamic;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//动态署理完结
public class DynamicProxy implements InvocationHandler {
// 这个便是咱们要署理的实在方针
private Object obj;
// 结构办法,给咱们要署理的实在方针赋初值
public DynamicProxy(Object object) {
this.obj = object;
}
//比较静态署理,动态署理减只需求完结一个接口即可完结,而静态署理每次都要完结新加的办法以及保护被署理办法
//榜首个参数既是署理类实例
//第二个参数是被调用的办法方针
// 第三个办法是调用参数
@Override
public Object invoke(Object object, Method method, Object[] args)
throws Throwable {
//********************办法前增强***************************
// 反射调用方针办法
return method.invoke(obj, args);
//********************办法后增强***************************
}
}
测验
package com.spring.test.aop.pattern.proxy.dynamic;
import com.spring.test.aop.pattern.proxy.staticproxy.IStartBusiness;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
//动态署理测验
//方针:
//1、知道怎么创立动态署理
//2、怎么调用了invoke办法(阻拦办法会用到当时知识点)
public class Test {
public static void main(String[] args) {
// 方针方针;要署理的实在方针
IStartBusiness target = new StartBusinessImpl();
// 咱们要署理哪个实在方针,就将该方针传进去,最后是经过该实在方针来调用其办法的
InvocationHandler handler = new DynamicProxy(target);
/*
* 榜首个参数:方针方针加载器
* 第二个参数:方针方针接口
* 第三个参数:完结InvocationHandler的署理类
*/
//生成署理方针
IStartBusiness iStartBusiness = (IStartBusiness) Proxy.newProxyInstance(target.getClass().getClassLoader(), target
.getClass().getInterfaces(), handler);
iStartBusiness.sing();
}
}
输出
总结
1、比较静态署理,动态署理减只需求完结一个接口即可完结,而静态署理每次都要完结新加的办法以及保护被署理办法
2、动态署理是靠Proxy.newProxyInstance() 生成的
3、动态署理在调用(iStartBusiness.sing())的时分,调用到了 implements InvocationHandler 的invoke
方针明确了:spring的署理就需求咱们找到 Proxy.newProxyInstance() 在哪里……
4)动态署理原理(了解)
tips:
触及JDK字节码,不在spring的源码范围内,感兴趣的同学自己了解一下
参考资料:www.baiyp.ren/JAVA%E5%8A%…
署理方针内部生成流程调用链,如下图
方针生成进程(中心)
Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); 进入newProxyInstance办法内部
java.lang.reflect.Proxy#newProxyInstance
要点重视中心办法getProxyClass0
//运用jdk署理工厂创立新的署理实例
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
//判别是否完结InvocationHandler接口,假如此处为空抛出反常
//当时h十分重要,由于在署理方针调用方针办法的时分,便是经过d的invoke办法反射调用的方针办法
//稍后会讲解
Objects.requireNonNull(h);
//克隆参数传来的 接口
final Class<?>[] intfs = interfaces.clone();
//系统内部的安全
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
//拜访权限的验证
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
*查找或生成指定的署理类;十分重要,要点重视
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
//署理权限的查看(此处是新出产的署理方针c1)
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//从生成的署理方针(仅仅class方针,还不是实例化方针)中取出结构器
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//运用结构器实例化(实例化方针)
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
进入getProxyClass0
java.lang.reflect.Proxy#getProxyClass0
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
//判别接口数组巨细,别大于65535,假如大于,提示接口超出约束
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
//经过接口和类加载器创立署理
//给定的接口存在,这将简略地回来缓存副本;
//不然,它将经过ProxyClassFactory创立署理类
return proxyClassCache.get(loader, interfaces);//也便是查询+创立的进程
}
继续进入get办法
java.lang.reflect.WeakCache#get
其他不要看
要点重视apply办法
public V get(K key, P parameter) {
Objects.requireNonNull(parameter);
expungeStaleEntries();
Object cacheKey = CacheKey.valueOf(key, refQueue);
// lazily install the 2nd level valuesMap for the particular cacheKey
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
if (valuesMap == null) {
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
// create subKey and retrieve the possible Supplier<V> stored by that
// subKey from valuesMap
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
while (true) {
if (supplier != null) {
// supplier might be a Factory or a CacheValue<V> instance
V value = supplier.get();
if (value != null) {
return value;
}
}
// else no supplier in cache
// or a supplier that returned null (could be a cleared CacheValue
// or a Factory that wasn't successful in installing the CacheValue)
// lazily construct a Factory
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}
if (supplier == null) {
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
// successfully installed Factory
supplier = factory;
}
// else retry with winning supplier
} else {
if (valuesMap.replace(subKey, supplier, factory)) {
// successfully replaced
// cleared CacheEntry / unsuccessful Factory
// with our Factory
supplier = factory;
} else {
// retry with current supplier
supplier = valuesMap.get(subKey);
}
}
}
}
进入apply办法(内部类)
java.lang.reflect.Proxy.ProxyClassFactory#apply
//意图
//1、判别;比方当时的class是否对加载器可见;拜访权限。是否public的
//2、生成class字节码文件
//3、调用本地办法;经过class字节码文件生成class方针
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class<?> interfaceClass = null;
try {
//当时的class是否对加载器可见
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
/*
* 验证类方针是否实践表明接口
*
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* 验证此接口不是重复的。
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
*/
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
//是否public的
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
// 假如没有非公共署理接口,请运用com.sun.proxy
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
/*
* 这儿需求留意下.他对应的是署理方针proxy后的数值;比方$proxy100
*这儿的100;便是此处原子生成
*/
long num = nextUniqueNumber.getAndIncrement();
//$proxy100;十分了解了;经过断点查看署理方针看到的,便是从这儿生成的
//proxyClassNamePrefix这个前缀便是 $
//这个proxyPkg便是com.sun.proxy
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
*运用ProxyGenerator里边的东西类,协助咱们生成署理类的class内容
留意。此处存储到byte数组;目前还不是class方针
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
//此处才是真实的class方针(留意;虽然是方针;可是还没有实例化)
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
}
}
中心代码参看以上注释
断点查看slaver.work()
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath*:application-aop.xml");
Slaver slaver = (Slaver) context.getBean("slaverImpl");
//运用署理调用了JdkDynamicAopProxy.invoke
slaver.work();
System.out.println("over>>>>>>>>>>");
留意里边的 $proxy17,全部都是在上面生成的
终究这个才是实例化的方针(如上)
往期干货:
-
Apache Druid数据查询套件详解计数、排名和分位数计算(送JSON-over-HTTP和SQL两种查询详解)
-
假如你还没玩过Docker Stack办理服务,你现已out了,(送Portainer集群办理教程)
-
怎样才能快速成为一名架构师?
-
怎么从Java工程师生长为架构师?
-
六种常用事务解决方案,你方唱罢,我登场(没有最好只要更好)
-
超具体教程,一文入门Istio架构原理及实战使用
-
【图解源码】Zookeeper3.7源码分析,Session的办理机制,Leader推举投票规矩,集群数据同步流程
-
【图解源码】Zookeeper3.7源码分析,包含服务启动流程源、网络通信、RequestProcessor处理恳求
-
【知其然,知其所以然】装备中心 Apollo源码分析
-
【推荐】我认为这是最完整的Apollo教程从入门到通晓
-
探针技术-JavaAgent 和字节码增强技术-Byte Buddy
-
100003字,带你解密 双11、618电商大促场景下的系统架构系统
-
【开悟篇】Java多线程之JUC从入门到通晓
-
12437字,带你深化探究RPC通讯原理
-
JVM调优实战演练,妈妈再也不同担心我的功能优化了
-
13651个字,给你解释清楚 JVM方针销毁
-
搞不懂JVM的类加载机制,JVM功能优化从何谈起?
-
4859字,609行,一次讲清楚JVM运转数据区
-
让实习生搭个Redis集群,差点把我”搭“进去~~~
-
我用Redis分布式锁,抢了瓶茅台,然后GG了~~
-
新来的,你说下Redis的耐久化机制,哪一种能解决咱们遇到的这个事务问题?
本文由
传智教育博学谷
教研团队发布。假如本文对您有协助,欢迎
重视
和点赞
;假如您有任何主张也可留言评论
或私信
,您的支持是我坚持创造的动力。转载请注明出处!