Sun过去的世界中的JDK 11和代理

使用JDK 11后,就sun.misc.Unsafe的第一种方法。 其中, defineClass方法已删除。 代码生成框架通常使用此方法在现有的类加载器中定义新的类。 尽管此方法易于使用,但它的存在也使JVM本质上不安全,正如其定义类的名称所暗示的那样。 通过允许在任何类加载器和程序包中定义一个类,就可以通过在其中定义一个类来获得对任何程序包的程序包范围访问,从而突破了原本封装的程序包或模块的边界。

为了删除sun.misc.Unsafe ,OpenJDK开始提供一种在运行时定义类的替代方法。 从版本9开始, MethodHandles.Lookup类提供了类似于不安全版本的方法defineClass 。 但是,仅对于与查找的宿主类位于同一包中的类,才允许使用类定义。 由于模块只能解析对某个模块拥有或已打开的包的查找,因此无法再将类注入到不打算提供此类访问权限的包中。

使用方法句柄查找,可以在运行时定义类foo.Qux ,如下所示:

MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandles.Lookup privateLookup = MethodHandles.privateLookupIn(foo.Bar.class, lookup);
byte[] fooQuxClassFile = createClassFileForFooQuxClass();
privateLookup.defineClass(fooQuxClassFile);

为了执行类定义,需要MethodHandles.Lookup的实例,可以通过调用MethodHandles::lookup方法来检索该MethodHandles::lookup 。 调用后一种方法对呼叫点敏感。 因此,返回的实例将代表从方法内部调用的类和包的特权。 要在另一个包中定义一个类,然后在当前包中定义一个类,则需要使用MethodHandles::privateLookupIn对此包中的类进行解析。 仅当此目标类的程序包与原始查找类位于同一模块中,或者此包显式打开到查找类的模块时,才有可能。 如果不满足这些要求,则尝试解决私有查找将引发IllegalAccessException ,从而保护JPMS隐含的边界。

当然,代码生成库也受此限制的约束。 否则,它们可能被用来创建和注入恶意代码。 而且由于方法句柄的创建对调用站点敏感,因此在不要求用户通过提供表示其模块特权的适当查找实例的情况下,不要求用户做一些其他工作的情况下就不可能合并新的类定义机制。

使用Byte Buddy时,所需的更改很小。 该库使用ClassDefinitionStrategy定义类,该类负责从其二进制格式加载类。 在Java 11之前,可以使用Reflection或sun.misc.Unsafe使用ClassDefinitionStrategy.Default.INJECTION定义一个类。 为了支持Java 11,此策略需要由ClassDefinitionStrategy.UsingLookup.of(lookup)代替,在ClassDefinitionStrategy.UsingLookup.of(lookup)中,提供的查找必须有权访问将驻留类的包。

将cglib代理迁移到Byte Buddy

截至目前,其他代码生成库尚未提供这种机制,并且不确定何时以及是否添加此类功能。 尤其是对于cglib而言,由于库的过时以及在不再更新且不会采用修改的遗留应用程序中的广泛使用,过去已证明API更改存在问题。 对于希望采用Byte Buddy作为更现代且积极开发的替代产品的用户,因此以下部分将介绍可能的迁移。

例如,我们使用一个方法为以下示例类生成代理:

public class SampleClass {public String test() { return "foo"; }
}

为了创建代理,通常将代理类作为子类,在其中所有方法都将被覆盖以调度侦听逻辑。 为此,作为示例,我们将一个值栏附加到原始实现的返回值上。

通常使用Enhancer类和MethodInterceptor一起定义cglib代理。 方法拦截器提供代理实例,代理方法及其参数。 最后,它还提供了MethodProxy的实例,该实例允许调用原始代码。

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) {return proxy.invokeSuper(obj, method, args) + "bar";}
});
SampleClass proxy = (SampleClass) enhancer.create();
assertEquals("foobar", proxy.test());

请注意,如果在代理实例上调用了诸如hashCodeequalstoString类的任何其他方法,则上述代码将引起问题。 前两个方法也将由拦截器分派,因此,当cglib尝试返回字符串类型的返回值时,将导致类强制转换异常。 相反, toString方法可以工作,但是会返回意外的结果,因为原始实现的前缀是bar作为返回值。

在Byte Buddy中,代理不是专门的概念,但可以使用库的通用代码生成DSL进行定义。 对于与cglib最相似的方法,使用MethodDelegation提供最简单的迁移路径。 这样的委派以用户定义的拦截器类为目标,方法调用将调度到该类:

public class SampleClassInterceptor {public static String intercept(@SuperCall Callable<String> zuper) throws Exception {return zuper.call() + "bar";}
}

上面的拦截器首先通过Byte Buddy按需提供的帮助程序实例调用原始代码。 使用Byte Buddy的代码生成DSL来实现对此拦截器的委托,如下所示:

SampleClass proxy = new ByteBuddy().subclass(SampleClass.class).method(ElementMatchers.named("test")).intercept(MethodDelegation.to(SampleClassInterceptor.class)).make().load(someClassLoader, ClassLoadingStrategy.UsingLookup.of(MethodHandles.privateLookupIn(SampleClass.class, MethodHandles.lookup())).getLoaded().getDeclaredConstructor().newInstance();
assertEquals("foobar", proxy.test());

除了cglib之外,Byte Buddy还需要使用ElementMatcher指定方法过滤器。 尽管在cglib中完全有可能进行过滤,但它非常麻烦并且没有明确要求,因此很容易被遗忘。 在Byte Buddy中,仍然可以使用ElementMatchers.any()匹配器拦截所有方法,但是通过要求指定这样的匹配器,希望提醒用户做出有意义的选择。

使用上述匹配器,每当调用名为test的方法时,都会使用所讨论的方法委派将调用委派给指定的拦截器。

但是,引入的拦截器将无法分派不返回字符串实例的方法。 实际上,代理的创建将产生由Byte Buddy发出的异常。 但是,完全有可能定义一个更通用的拦截器,该拦截器可应用于与cglib的MethodInterceptor提供的方法类似的任何方法:

public class SampleClassInterceptor {@RuntimeTypepublic static Object intercept(@Origin Method method,@This Object self,@AllArguments Object[] args,@SuperCall Callable<String> zuper) throws Exception {return zuper.call() + "bar";}
}

当然,由于在这种情况下不使用拦截器的其他参数,因此可以省略它们,从而使代理更有效。 Byte Buddy仅在需要时才按需提供参数。

由于上述代理是无状态的,因此将拦截方法定义为静态。 同样,这是一个简单的优化,因为Byte Buddy否则需要在代理类中定义一个字段,该字段保存对拦截器实例的引用。 但是,如果需要实例,则可以使用MethodDelegation.to(new SampleClassInterceptor())将委托定向到实例的成员方法。

缓存代理类以提高性能

使用字节伙伴时,不会自动缓存代理类。 这意味着每次运行上述代码时,都会生成并加载一个新类。 由于代码生成和类定义是昂贵的操作,因此这当然效率低下,如果可以重复使用代理类,则应避免这种情况。 在cglib中,如果两次增强的输入相同,则返回先前生成的类,这通常在两次运行同一代码段时是正确的。 然而,由于通常可以更容易地计算高速缓存密钥,因此该方法相当容易出错并且通常效率低下。 使用字节伙伴,可以使用专用的缓存库(如果已有的话)。 另外,Byte Buddy还提供了TypeCache ,它通过用户定义的缓存键为类实现了简单的缓存。 例如,可以使用以下代码使用基类作为键来缓存以上类的生成:

TypeCache<Class<?>> typeCache = new TypeCache<>(TypeCache.Sort.SOFT);
Class<?> proxyType = typeCache.findOrInsert(classLoader, SampleClass.class, () -> new ByteBuddy().subclass(SampleClass.class).method(ElementMatchers.named("test")).intercept(MethodDelegation.to(SampleClassInterceptor.class)).make().load(someClassLoader, ClassLoadingStrategy.UsingLookup.of(MethodHandles.privateLookupIn(SampleClass.class, MethodHandles.lookup())).getLoaded()
});

不幸的是,Java中的缓存类带来了一些警告。 如果创建了代理,则它当然会继承它所代理的类的子类,从而使该基类不适合进行垃圾收集。 因此,如果代理类被强引用,则密钥也将被强引用。 这将使高速缓存无用,并为内存泄漏打开。 因此,必须通过构造函数参数指定的内容来轻而易举地引用代理类。 将来,如果Java引入了星历作为参考类型,则可能会解决此问题。 同时,如果不存在代理类垃圾回收的问题,则可以使用ConcurrentMap在不存在时计算值。

扩展代理类的可用性

为了使用代理类的重用,将代理类重构为无状态并将状态隔离到实例字段中通常是有意义的。 然后可以在侦听期间使用上述依赖项注入机制来访问此字段,例如,以使后缀值可针对每个代理实例进行配置:

public class SampleClassInterceptor {public static String intercept(@SuperCall Callable<String> zuper, @FieldValue("qux") String suffix) throws Exception {return zuper.call() + suffix;}
}

上面的拦截器现在接收字段qux的值作为第二个参数,可以使用Byte Buddy的类型创建DSL声明它:

TypeCache<Class<?>> typeCache = new TypeCache<>(TypeCache.Sort.SOFT);
Class<?> proxyType = typeCache.findOrInsert(classLoader, SampleClass.class, () -> new ByteBuddy().subclass(SampleClass.class).defineField(“qux”, String.class, Visibility.PUBLIC).method(ElementMatchers.named("test")).intercept(MethodDelegation.to(SampleClassInterceptor.class)).make().load(someClassLoader, ClassLoadingStrategy.UsingLookup.of(MethodHandles.privateLookupIn(SampleClass.class, MethodHandles.lookup())).getLoaded()
});

现在,可以使用Java反射在每个实例创建后在每个实例上设置该字段值。 为了避免反射,DSL还可以用于实现一些接口,该接口声明用于所提及字段的设置方法,可以使用Byte Buddy的FieldAccessor实现来实现。

加权代理运行时和创建性能

最后,在使用Byte Buddy创建代理时,需要考虑一些性能。 在生成代码时,需要在代码生成本身的性能与所生成代码的运行时性能之间进行权衡。 与cglib或其他proxing库相比,Byte Buddy通常旨在创建尽可能高效地运行的代码,这可能需要更多时间来创建此类代码。 这是基于这样的假设,即大多数应用程序运行时间很长,但是一次只能创建代理,但是代理不适用于所有类型的应用程序。

与cglib的一个重要区别是,Byte Buddy为每个方法生成一个专用的超级调用委托,该方法被拦截,而不是单个MethodProxy 。 这些附加的类需要花费更多的时间来创建和加载,但是使这些类可用可以为每个方法执行带来更好的运行时性能。 如果在循环中调用代理方法,则这种差异很快就很关键。 但是,如果运行时性能不是主要目标,并且在短时间内创建代理类更重要,则以下方法可避免完全创建其他类:

public class SampleClassInterceptor {public static String intercept(@SuperMethod Method zuper, @This Object target, @AllArguments Object[] arguments) throws Exception {return zuper.invoke(target, arguments) + "bar";}
}

模块化环境中的代理

对拦截器使用简单形式的依赖注入,而不是依赖于特定于库的类型,例如cglib的
MethodInterceptor ,Byte Buddy在模块化环境中提供了另一个优势:由于生成的代理类将直接引用拦截器类,而不是引用特定于库的调度程序类型(例如cglib的MethodInterceptor ,因此被代理类的模块不需要读取Byte Buddy的模块。 对于cglib,代理类模块必须读取cglib的模块,该模块定义了MethodInterceptor接口,而不是实现该接口的模块。 对于使用cglib作为传递依赖的库的用户,这很可能是不直观的,特别是如果将后者依赖视为不应公开的实现细节。

在某些情况下,代理类的模块读取提供拦截器的框架模块甚至是不可能或不希望的。 对于这种情况,Byte Buddy还提供了一种解决方案,通过使用它来完全避免这种依赖性
Advice组件。 该组件可用于以下示例中的代码模板:

public class SampleClassAdvice {@Advice.OnMethodExitpublic static void intercept(@Advice.Returned(readOnly = false) String returned) {returned += "bar";}
}

上面的代码看起来似乎没有多大意义,实际上,它将永远不会执行。 该类仅用作Byte Buddy的字节代码模板,后者可读取带注释的方法的字节代码,然后将其内联到生成的代理类中。 为此,必须对上述方法的每个参数进行注释,以代表代理方法的值。 在上述情况下,注释定义了参数,以定义方法的返回值,在给定模板的情况下,将bar添加为后缀。 给定此建议类,可以如下定义代理类:

new ByteBuddy().subclass(SampleClass.class).defineField(“qux”, String.class, Visibility.PUBLIC).method(ElementMatchers.named(“test”)).intercept(Advice.to(SampleClassAdvice.class).wrap(SuperMethodCall.INSTANCE)).make()

通过将建议包装在SuperMethodCall周围,​​将在对覆盖方法的调用完成后内联上述建议代码。 要在原始方法调用之前内联代码,可以使用OnMethodEnter批注。

9和10之前的Java版本上的支持代理

在为JVM开发应用程序时,通常可以依靠在特定版本上运行的应用程序也可以在更高版本上运行。 即使使用了内部API,也已经有很长时间了。 但是,由于删除了此内部API,从Java 11开始,这种情况不再适用,在Java 11上,依赖于sun.misc.Unsafe代码生成库将不再起作用。 同时,通过MethodHandles.Lookup类定义MethodHandles.Lookup用于版本9之前的JVM。

对于Byte Buddy,用户有责任使用与当前JVM兼容的类加载策略。 为了支持所有JVM,需要进行以下选择:

ClassLoadingStrategy<ClassLoader> strategy;
if (ClassInjector.UsingLookup.isAvailable()) {Class<?> methodHandles = Class.forName("java.lang.invoke.MethodHandles");Object lookup = methodHandles.getMethod("lookup").invoke(null);Method privateLookupIn = methodHandles.getMethod("privateLookupIn", Class.class, Class.forName("java.lang.invoke.MethodHandles$Lookup"));Object privateLookup = privateLookupIn.invoke(null, targetClass, lookup);strategy = ClassLoadingStrategy.UsingLookup.of(privateLookup);
} else if (ClassInjector.UsingReflection.isAvailable()) {strategy = ClassLoadingStrateg.Default.INJECTION;
} else {throw new IllegalStateException(“No code generation strategy available”);
}

上面的代码使用反射来解析方法句柄查找并对其进行解析。 这样做,可以在Java 9之前的JDK上编译和加载代码。不幸的是,由于MethodHandles::lookup是调用站点敏感的,因此Byte Buddy无法实现此代码,因此必须在驻留在其中的类中定义以上内容用户的模块,而不在Byte Buddy中。

最后,值得考虑的是完全避免类注入。 代理类也可以使用ClassLoadingStrategy.Default.WRAPPER策略在自己的类加载器中定义。 该策略不使用任何内部API,并且可以在任何JVM版本上使用。 但是,必须牢记创建专用类加载器的性能成本。 最后,即使代理类的软件包名称与代理类相同,通过在不同的类加载器中定义代理,JVM也不会将其运行时软件包视为等同,因此不允许覆盖任何软件包,私人方法。

最后的想法

最后一点,我想表达我的观点,尽管迁移成本很高,但退出sun.misc.Unsafe是朝着更安全,模块化的JVM迈出的重要一步。 在删除此非常强大的类之前,可以使用sun.misc.Unsafe仍然提供的特权访问来绕过JPMS设置的任何边界。 如果不进行此删除,则JPMS会付出额外封装带来的所有不便,而无法依靠它。

JVM上的大多数开发人员很可能永远不会遇到这些附加限制的任何问题,但是如上所述,代码生成和代理库需要适应这些更改。 对于cglib,不幸的是,这确实意味着道路的尽头。 Cglib最初被建模为Java内置代理API的更强大版本,在该版本中,它要求代理类引用其自己的调度程序API,这与Java API要求引用其类型的方式类似。 但是,这些后一种类型驻留在java.base模块中,该模块始终由任何模块读取。 因此,Java代理API仍然可以正常运行,而cglib模型则无法修复。 过去,这已经使cglib成为OSGi环境中的难题,但是对于JPMS,作为库的cglib不再起作用。 Javassist提供的相应代理API存在类似问题。

这种变化的好处是,JVM最终提供了一个稳定的API,用于在应用程序的运行时定义类,这是一种依赖内部API二十多年的常见操作。 除了我认为仍然需要更灵活方法的Javaagents以外,这意味着在所有代理用户完成此最终迁移之后,可以保证将来的Java版本始终能够正常工作。 鉴于cglib的开发多年来一直处于休眠状态,并且该库受到许多限制,因此无论如何,今天的库用户最终迁移都是不可避免的。 Javassist代理可能也是如此,因为后者库在近半年内也没有提交。

翻译自: https://www.javacodegeeks.com/2018/04/jdk-11-and-proxies-in-a-world-past-sun-misc-unsafe.html

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

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

相关文章

java中 private final_Java笔记:final与private关键字

记录一个有趣的现象&#xff0c;private修饰的方法子类是访问不了的&#xff0c;且类中所有private修饰的方法都隐式的指定为final(可以对private方法添加final修饰词&#xff0c;但是这并不能给该方法增加任何额外的意义)&#xff0c;final修饰的方法是不可以被重写的。但是如…

Java,JavaFX的流畅设计风格进度栏

按照承诺&#xff0c;刚刚发布的Java JavaFX主题JMetro 4.6版为进度栏带来了新样式。 进度栏有两种可能的状态&#xff1a;确定和不确定&#xff0c;新的JMetro版本具有这两种状态。 在本文中&#xff0c;我还将详细介绍一些我在JMetro中遵守的API设计原则。 JMetro API设计原…

安卓最新系统_成纺移动校园(移动办公系统)V3.2.1 安卓最新版

成纺移动校园(移动办公系统)是额一个非常实用的办公工具。您可以使用该软件及时浏览最新的校园信息&#xff0c;同时涵盖许多功能&#xff0c;例如时间表查询&#xff0c;会议安排&#xff0c;校园地图&#xff0c;校车等。有需要的用户欢迎来绿色先锋网下载。 成纺移动校园简介…

java时间日期格式器_JAVA基础类库(二)-----日期、时间类和格式器

Date类public classDateTest{public static voidmain(String[] args){Date d1 newDate();//获取当前时间之后100ms的时间Date d2 new Date(System.currentTimeMillis() 10000);System.out.println(d1);System.out.println(d2);//比较d1,d2是否相等,相等返回0&#xff0c;大于…

pyqt 获取 UI 中组件_你想知道的React组件设计模式这里都有(上)

本文梳理了容器与展示组件、高阶组件、render props这三类React组件设计模式往期回顾&#xff1a;HBaseCon Asia 2019 Track 3 概要回顾随着 React 的发展&#xff0c;各种组件设计模式层出不穷。React 官方文档也有不少相关文章&#xff0c;但是组织稍显凌乱&#xff0c;本文就…

jvm上的随机数与熵_向您的JVM添加一些熵

jvm上的随机数与熵能否生成真正的随机数取决于系统中的熵。 有人声称&#xff0c;这可以通过掷骰子来保证。 其他人认为&#xff0c;用此主体替换OpenJDK的java.math.Random.nextInt&#xff08;&#xff09;方法将有所帮助&#xff1a; public int nextInt() {return 14; }资…

typora导出word指定样式_(二)最简洁的Markdowd编辑器:Typora

&#xff08;提醒&#xff1a;前面都是介绍和语法&#xff0c;想下载了就能用的直接看最后总结&#xff09;大家好&#xff0c;半瓶醋同学又来误人子弟了。现在办公文档或者邮件的处理&#xff0c;一般都是用微软的office word或者邮件自带的编辑器。但是用word或者邮件自带编辑…

cc java开发环境搭建_Windows系统下java开发环境搭建

总的来说&#xff0c;开发环境是程序员工作的基础&#xff0c;没了他&#xff0c;IT工作就没得开展了。话不多说&#xff0c;今天提供的教程是Windows系统下Java开发环境的搭建&#xff0c;具体如下1.下载并安装JDK(JAVA Development Kit)JDK是整个java开发的核心&#xff0c;它…

使用数据库中的Java流制作数据透视表

来自数据库行和表的原始数据不能为人类读者提供太多了解。 相反&#xff0c;如果我们对数据执行某种聚合&#xff0c;则人类更有可能看到数据模式 在展示给我们之前。 数据透视表是聚合的一种特定形式&#xff0c;我们可以在其中应用排序&#xff0c;求平均值或求和之类的操作…

java map的keyset_Java Map keySet()用法及代码示例

此方法用于返回此映射中包含的键的Set视图。该集合由Map支持&#xff0c;因此对Map的更改会反映在集合中&#xff0c;反之亦然。用法:Set keySet()参数&#xff1a;此方法没有参数。返回值&#xff1a;此方法返回一个包含指定映射键的集合。下面的程序显示int keySet()方法的实…

asynchttpclient 超时_dnf这才是混子的毕业套装,却发现超时空漩涡不买账!

dnf这才是混子的毕业套装&#xff0c;却发现超时空漩涡不买账&#xff01;按道理来说&#xff0c;光兵和帕拉丁穿这套装备去混团是最好的&#xff0c;结果现在超时空漩涡不要&#xff01;虽然说兵法套是95最好的魂之涛&#xff0c;但是这个混子套属性真心弱爆&#xff0c;不如正…

统计多维数组php_PHP多维数组中统计元素个数

Array([0] > Array([0] > Array([0] > Array([id] > 12[name] > 1)[1] > Array([id] > 28[name] > 2).....)[1] > Array([0] > Array([id] > 121[name] > 2)[1] > Array([id] > 281[name] > 4)...))....)我想统计name对应的值出现…

mongodb web_MongoDB和Web应用程序

mongodb web当今时代是数据以非常大的速度增长的时代。 数据存储不是问题&#xff0c;是的&#xff0c;但是它的结构化和存储方式可能会增加或减少所需数据块的查找时间。 不断增长的非结构化数据的用例 脸书&#xff1a; 活跃用户达7.5亿&#xff0c;互联网用户中有三分之一…

win7亮度怎么调_揭秘极米NEW Z8X投影仪怎么样?千万不要上当?!!!!【揭秘反馈

反馈测评极米NEW Z8X投影仪怎么样?求真实点评注意事项极米NEW Z8X投影仪怎么样?靠谱真实回答 外形外观&#xff1a;简约时尚大气&#xff0c;手感不错&#xff01;\n投影亮度&#xff1a;1080P、4K&#xff0c;都能做到&#xff01;\n投影色彩&#xff1a;非常好&#xff0c;…

php的完整代码块,简单测试了一下php中的代码块、内部类等知识

简单测试了一下php中的代码块、内部类等知识<?php class a{public $b;public function print_result(){//普通代码块&#xff0c;但已经不是java中的代码块{$c变量;echo 普通代码块&#xff1f;;}echo $c;}//错误的语法&#xff0c;Parse error: syntax error, unexpected …

Paw 百度ai_直面落地!百度EasyDL产业智能创新大赛成果覆盖能源、交通、水利民生重业...

物体检测模型实现高压线路隐患检测、图像分类实现短视频快速剪辑和量产、文本情感分类辅助潜在心理疾病患者自发检测、图片识别车辆轮轴数监管车辆载重……每一个创想都能简单快速实现&#xff0c;没有AI开发基础的小伙伴们也能做到&#xff01;这一切都缘于百度零门槛AI开发平…

Java 9、10及更高版本:Java平台的未来

您紧跟Java平台新功能的秘密武器 自去年9月发布Java 9以来&#xff0c;感觉整个平台都经历了重大变化。 在我们甚至无法确定Java 9所能提供的一切之前&#xff0c;我们已经在标记Java 10的发布。现在&#xff0c;我们已经开始期待Java 11在2018年9月发布。 Oracle决定为Java平…

php同时抢购 代码,浅谈PHP实现大流量下抢购方案

要求要有小时分钟秒的实时倒计时的显示&#xff0c;用户端修改日期时间不会影响到倒计时的正常显示(也就是以服务器时间为准)。其实这和很多的考试等系统的时间限制功能同样的要求。总不能用ajax每秒都获取服务器时间吧&#xff0c;所以实时倒计时一定要用javascript实现。这很…

c fread 快读 详解_奔驰ACC(自适应巡航系统)详解

什么是ACC自适应巡航&#xff1f;ACC自适应巡航( Adaptive Cruise Control )&#xff0c;又可称为智能巡航控制系统&#xff0c;简称ACC系统&#xff0c;它是在传统巡航控制基础上发展起来的新一代汽车驾驶员辅助驾驶系统。它将汽车自动巡航控制系统CCS 和车辆前向撞击报警系统…

php使用邮件找回密码,php利用Zend_Mail发送邮件(实现邮件重设密码功能)

[php]代码库<?php include_once conn/conn.php;require_once Zend/Mail.php;//调用发送邮件的文件require_once Zend/Mail/Transport/Smtp.php;//调用SMTP验证文件$reback 0;$name $_GET[foundname];$question $_GET[question];$answer $_GET[answer];$sql "sele…