关于注解Annotation第二篇
写一个注解使用类,如下:
public class Test {
@Code(author = "mazhi",date="20170611")
private void process() {
}
}
使用Javac提供的api来运行Test类。在javac的Launcher类中编写如下代码:
JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
String commonPath = "H:\\program workspace\\project\\Compiler_javac\\";
String str27 = commonPath+"test\\com\\test01\\Test.java";
javac.run(System.in, null, null,
//"-verbose",
//"-Xprint",
"-XprintRounds",
"-XprintProcessorInfo",
"-processor","com.test01.CodeProcessor",
"-d", "C:\\Users\\Administrator.PC-20160319ZQPU\\workspace\\tmp\\tmpdir\\",
str27);
-Xprint表示打印出注解类,也就是Code类的代码。
-XprintRounds 打印出每一次Round的情况
-processor指定了注解处理器,注意这个包名必须在源代码目录下,不能在test目录或者其它路径下,否则不能加载到classpath中。
-d表示输出路径,CodeProcessor注解处理类生成的类路径,在运行CodeProcessor后生成的类名为GenerateTest如下:
package com.test01;
class TestAutogenerate {
protected TestAutogenerate() {}
protected final void message() {
//process()
//@com.test01.Code(date=20170611, author=mazhi)
System.out.println("author:mazhi");
System.out.println("date:20170611");
}
}
下面来看javac的具体处理过程。
1、在javac的compile方法中会调用initProcessAnnotations(processors)方法
initProcessAnnotations(processors)方法的具体代码如下:
/**
* Check if we should process annotations.
* If so, and if no scanner is yet registered, then set up the DocCommentScanner
* to catch doc comments, and set keepComments so the parser records them in the compilation unit.
*
* @param processors user provided annotation processors to bypass
* discovery, {@code null} means that no processors were provided
*/
public void initProcessAnnotations(Iterable<? extends Processor> processors) {
// Process annotations if processing is not disabled and there
// is at least one Processor available.
if (options.isSet(PROC, "none")) {
processAnnotations = false;
} else if (procEnvImpl == null) {
procEnvImpl = new JavacProcessingEnvironment(context, processors);
processAnnotations = procEnvImpl.atLeastOneProcessor();
if (processAnnotations) {
options.put("save-parameter-names", "save-parameter-names");
reader.saveParameterNames = true;
keepComments = true;
genEndPos = true;
if (taskListener != null)
taskListener.started(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING));
log.deferDiagnostics = true;
} else { // free resources
procEnvImpl.close();
}
}
}
最主要的逻辑就是在创建JavacProcessingEnvironment对象,这个对象的构造函数中会调用initProcessorIterator(context, processors);
主要的代码如下:
private void initProcessorIterator(Context context, Iterable<? extends Processor> processors) {
Log log = Log.instance(context);
Iterator<? extends Processor> processorIterator;
if (options.isSet(XPRINT)) {
try {
Processor processor = PrintingProcessor.class.newInstance();
processorIterator = List.of(processor).iterator();
} catch (Throwable t) {
AssertionError assertError = new AssertionError("Problem instantiating PrintingProcessor.");
assertError.initCause(t);
throw assertError;
}
} else if (processors != null) {
processorIterator = processors.iterator();
} else {
String processorNames = options.get(PROCESSOR);
JavaFileManager fileManager = context.get(JavaFileManager.class);
try {
// If processorpath is not explicitly set, use the classpath.
processorClassLoader = fileManager.hasLocation(ANNOTATION_PROCESSOR_PATH)
? fileManager.getClassLoader(ANNOTATION_PROCESSOR_PATH)
: fileManager.getClassLoader(CLASS_PATH);
/*
* If the "-processor" option is used, search the appropriate
* path for the named class. Otherwise, use a service
* provider mechanism to create the processor iterator.
*/
if (processorNames != null) {
processorIterator = new NameProcessIterator(processorNames, processorClassLoader, log);
} else {
processorIterator = new ServiceIterator(processorClassLoader, log);
}
} catch (SecurityException e) {
/*
* A security exception will occur if we can't create a classloader.
* Ignore the exception if, with hindsight, we didn't need it anyway
* (i.e. no processor was specified either explicitly, or implicitly,
* in service configuration file.) Otherwise, we cannot continue.
*/
processorIterator = handleServiceLoaderUnavailability("proc.cant.create.loader", e);
}
}
discoveredProcs = new DiscoveredProcessors(processorIterator);
}
不难看出这个逻辑通过对-Xprint命令进行判断,如果设置了,那么就会打印注解类的代码(注意不是使用注解类的代码),其中打印已经有了默认的注解处理实现类PrintingProcessor。
最后一个else循环中对-processor命令进行了处理,得到的值就是我们指定的注解处理类CodeProcessor。接着往下面的逻辑看,从两处查找注解处理类Processor,这里用到了迭代器模式,有NameProcessIterator与ServiceIterator。
(1)NameProcessIterator主要处理-processor命令指定的注解处理器,指定多个时使用逗号分割,这样就可以通过这个注解处理器迭代获取
(2)ServiceIterator主要处理 /META-INF/services/下指定的注解处理器,在前面说过。
最后一段代码生成一个DiscoveredProcessors对象。
2、在initProcessAnnotations(processors)方法中调用JavacProcessingEnvironment的atLeastOneProcessor()方法
public boolean atLeastOneProcessor() {
return discoveredProcs.iterator().hasNext();
}
DiscoveredProcessor是一个双层的迭代器,具体代码如下:
// TODO: These two classes can probably be rewritten better...
/**
* This class holds information about the processors that have
* been discoverd so far as well as the means to discover more, if
* necessary. A single iterator should be used per round of
* annotation processing. The iterator first visits already
* discovered processors then fails over to the service provider
* mechanism if additional queries are made.
*/
class DiscoveredProcessors implements Iterable<ProcessorState> {
// 省略内部的ProcessorStateIterator迭代器
Iterator<? extends Processor> processorIterator;
ArrayList<ProcessorState> procStateList;
public ProcessorStateIterator iterator() {
return new ProcessorStateIterator(this); // 创建一个内部的迭代器ProcessorStateIterator类对象
}
DiscoveredProcessors(Iterator<? extends Processor> processorIterator) {
this.processorIterator = processorIterator;
this.procStateList = new ArrayList<ProcessorState>();
}
/**
* Free jar files, etc. if using a service loader.
*/
public void close() {
if (processorIterator != null &&
processorIterator instanceof ServiceIterator) {
((ServiceIterator) processorIterator).close();
}
}
}
DiscoveredProcessors内部的迭代器如下:
class ProcessorStateIterator implements Iterator<ProcessorState> {
DiscoveredProcessors psi;
Iterator<ProcessorState> innerIter;
boolean onProcInterator;
ProcessorStateIterator(DiscoveredProcessors psi) {
this.psi = psi;
this.innerIter = psi.procStateList.iterator();
this.onProcInterator = false;
}
public ProcessorState next() {
if (!onProcInterator) {
if (innerIter.hasNext())
return innerIter.next();
else
onProcInterator = true;
}
if (psi.processorIterator.hasNext()) {
ProcessorState ps = new ProcessorState(psi.processorIterator.next(),
log, source, JavacProcessingEnvironment.this);
psi.procStateList.add(ps);
return ps;
} else
throw new NoSuchElementException();
}
public boolean hasNext() {
if (onProcInterator)
return psi.processorIterator.hasNext();
else
return innerIter.hasNext() || psi.processorIterator.hasNext();
}
public void remove () {
throw new UnsupportedOperationException();
}
/**
* Run all remaining processors on the procStateList that
* have not already run this round with an empty set of annotations.
*/
public void runContributingProcs(RoundEnvironment re) {
if (!onProcInterator) {
Set<TypeElement> emptyTypeElements = Collections.emptySet();
while(innerIter.hasNext()) {
ProcessorState ps = innerIter.next();
if (ps.contributed)
callProcessor(ps.processor, emptyTypeElements, re);
}
}
}
}
重点注意如上next()方法的一个处理逻辑,优先返回procStateList中的注解处理器。
这个类涉及到另外一个重要的类ProcessorState,主要的代码逻辑段如下:
/**
* State about how a processor has been used by the tool. If a
* processor has been used on a prior round, its process method is
* called on all subsequent rounds, perhaps with an empty set of
* annotations to process. The {@code annotatedSupported} method
* caches the supported annotation information from the first (and
* only) getSupportedAnnotationTypes call to the processor.
*/
static class ProcessorState {
public Processor processor;
public boolean contributed;
private ArrayList<Pattern> supportedAnnotationPatterns;
private ArrayList<String> supportedOptionNames;
ProcessorState(Processor p, Log log, Source source, ProcessingEnvironment env) {
processor = p;
contributed = false;
try {
processor.init(env);
checkSourceVersionCompatibility(source, log);
supportedAnnotationPatterns = new ArrayList<Pattern>();
for (String importString : processor.getSupportedAnnotationTypes()) {
supportedAnnotationPatterns.add(importStringToPattern(importString,
processor,
log));
}
supportedOptionNames = new ArrayList<String>();
for (String optionName : processor.getSupportedOptions() ) {
if (checkOptionName(optionName, log))
supportedOptionNames.add(optionName);
}
} catch (ClientCodeException e) {
throw e;
} catch (Throwable t) {
throw new AnnotationProcessingError(t);
}
}
// 省略一些方法
}
3、调用processAnnotations()方法运行注解处理器
这个方法的运行时机是完成词法和语法分析,并且输入符号表的过程完成后进行调用。
主要执行了代码:procEnvImpl.doProcessing(context, roots, classSymbols, pckSymbols)
主要的逻辑代码段如下:
Round round = new Round(context, roots, classSymbols);
boolean errorStatus;
boolean moreToDo;
do {
// Run processors for round n
round.run(false, false);
// Processors for round n have run to completion.
// Check for errors and whether there is more work to do.
errorStatus = round.unrecoverableError();
moreToDo = moreToDo();
round.showDiagnostics(errorStatus || showResolveErrors);
// Set up next round.
// Copy mutable collections returned from filer.
round = round.next(
new LinkedHashSet<JavaFileObject>(filer.getGeneratedSourceFileObjects()),
new LinkedHashMap<String,JavaFileObject>(filer.getGeneratedClasses()));
// Check for errors during setup.
if (round.unrecoverableError())
errorStatus = true;
} while (moreToDo && !errorStatus);
// run last round
round.run(true, errorStatus);
round.showDiagnostics(true);
3.1、创建一个Round类对像,主要的逻辑是调用了findAnnotationsPresent()方法,代码如下:
/** Find the set of annotations present in the set of top level
* classes and package info files to be processed this round. */
void findAnnotationsPresent() {
ComputeAnnotationSet annotationComputer = new ComputeAnnotationSet(elementUtils);
// Use annotation processing to compute the set of annotations present
annotationsPresent = new LinkedHashSet<TypeElement>();
for (ClassSymbol classSym : topLevelClasses)
annotationComputer.scan(classSym, annotationsPresent);
for (PackageSymbol pkgSym : packageInfoFiles)
annotationComputer.scan(pkgSym, annotationsPresent);
}
其中的topLevelClasses的值如下截图:

运行完成后的annotationsPresent值如下截图。

可以看到为process()方法找到了对应的注解类Code。下面来看查找Test()与process()方法注解的具体过程。
涉及到一个类ComputeAnnotationSet,代码如下:
/**
* Computes the set of annotations on the symbol in question.
* Leave class public for external testing purposes.
*/
public static class ComputeAnnotationSet extends ElementScanner7<Set<TypeElement>, Set<TypeElement>> {
final Elements elements;
public ComputeAnnotationSet(Elements elements) {
super();
this.elements = elements;
}
@Override
public Set<TypeElement> visitPackage(PackageElement e, Set<TypeElement> p) {
// Don't scan enclosed elements of a package
return p;
}
@Override
public Set<TypeElement> scan(Element e, Set<TypeElement> p) {
for (AnnotationMirror annotationMirror : elements.getAllAnnotationMirrors(e) ) {
Element e2 = annotationMirror.getAnnotationType().asElement();
p.add((TypeElement) e2);
}
return super.scan(e, p);
}
}
调用scan()方法时elements对象为JavacElements类对象,前面介绍过。由于Test的ClassSymbol没有注解,所以不走for循环。继续调用super.scan(e,p)方法继续执行查找注解元素。
关于Element中定义的访问者方法<R, P> R accept(ElementVisitor<R, P> v, P p);
但是只有TypeSymbol,PackageSymbol、ClassSymbol、VarSymbol、DelegatedSymbol实现了这个方法。
同样最终会使用scan方法来处理process()方法,其中e为MethodSymbol类型,如下截图:

其中getAllAnnotationMirrors()方法的代码如下:
/**
* Returns all annotations of an element, whether inherited or directly present.
* @param e the element being examined
* @return all annotations of the element
*/
public List<com.sun.tools.javac.code.attribute.Compound> getAllAnnotationMirrors(Element e) {
Symbol sym = cast(Symbol.class, e);
// 返回Symbol中的attribute_field属性
List<com.sun.tools.javac.code.attribute.Compound> annos = sym.getAnnotationMirrors();
while (sym.getKind() == ElementKind.CLASS) {
Type sup = ((ClassSymbol) sym).getSuperclass();
if (sup.typeTag != TypeTags.CLASS || sup.isErroneous() ||
sup.typeSymbol == syms.objectType.typeSymbol) {
break;
}
sym = sup.typeSymbol;
List<com.sun.tools.javac.code.attribute.Compound> oldAnnos = annos;
for (com.sun.tools.javac.code.attribute.Compound anno : sym.getAnnotationMirrors()) {
if (isInherited(anno.type) &&
!containsAnnoOfType(oldAnnos, anno.type)) {
annos = annos.prepend(anno);
}
}
}
return annos;
}
返回后进入循环获取的annotatonMirror如下截图:

最后调用这个对象的getAnnotationType()方法,返回对象后调用asElement()方法,也就是获取type的tsym属性值,从上图可看出是Code类。
3.2、调用Round类对象的round.run(false, false)方法
/** Run a processing round. */
void run(boolean lastRound, boolean errorStatus) {
printRoundInfo(lastRound);
try {
if (lastRound) {
filer.setLastRound(true);
Set<Element> emptyRootElements = Collections.emptySet(); // immutable
RoundEnvironment renv = new JavacRoundEnvironment(true,
errorStatus,
emptyRootElements,
JavacProcessingEnvironment.this);
discoveredProcs.iterator().runContributingProcs(renv);
} else {
discoverAndRunProcs(context, annotationsPresent, topLevelClasses, packageInfoFiles);
}
} finally {
if (taskListener != null)
taskListener.finished(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING_ROUND));
}
nMessagerErrors = messager.errorCount();
}
因为首次运行时lastRound值为false,所以运行discoverAndRunProcs()方法,具体代码如下:
private void discoverAndRunProcs(Context context,
Set<TypeElement> annotationsPresent,
List<ClassSymbol> topLevelClasses,
List<PackageSymbol> packageInfoFiles) {
Map<String, TypeElement> unmatchedAnnotations = new HashMap<String, TypeElement>(annotationsPresent.size());
for(TypeElement a : annotationsPresent) {
unmatchedAnnotations.put(a.getQualifiedName().toString(), a);
}
// Give "*" processors a chance to match
if (unmatchedAnnotations.size() == 0)
unmatchedAnnotations.put("", null);
DiscoveredProcessors.ProcessorStateIterator psi = discoveredProcs.iterator();
// TODO: Create proper argument values; need past round
// information to fill in this constructor. Note that the 1
// st round of processing could be the last round if there
// were parse errors on the initial source files; however, we
// are not doing processing in that case.
Set<Element> rootElements = new LinkedHashSet<Element>();
rootElements.addAll(topLevelClasses);
rootElements.addAll(packageInfoFiles);
rootElements = Collections.unmodifiableSet(rootElements);
RoundEnvironment renv = new JavacRoundEnvironment(false,
false,
rootElements,
JavacProcessingEnvironment.this);
while(unmatchedAnnotations.size() > 0 && psi.hasNext() ) {
ProcessorState ps = psi.next();
Set<String> matchedNames = new HashSet<String>();
Set<TypeElement> typeElements = new LinkedHashSet<TypeElement>();
for (Map.Entry<String, TypeElement> entry: unmatchedAnnotations.entrySet()) {
String unmatchedAnnotationName = entry.getKey();
if (ps.annotationSupported(unmatchedAnnotationName) ) {
matchedNames.add(unmatchedAnnotationName);
TypeElement te = entry.getValue();
if (te != null)
typeElements.add(te);
}
}
if (matchedNames.size() > 0 || ps.contributed) {
boolean processingResult = callProcessor(ps.processor, typeElements, renv);
ps.contributed = true;
ps.removeSupportedOptions(unmatchedProcessorOptions);
if (printProcessorInfo || verbose) {
log.printNoteLines("x.print.processor.info",
ps.processor.getClass().getName(),
matchedNames.toString(),
processingResult);
}
if (processingResult) {
unmatchedAnnotations.keySet().removeAll(matchedNames);
}
}
}
unmatchedAnnotations.remove("");
if (lint && unmatchedAnnotations.size() > 0) {
// Remove annotations processed by javac
unmatchedAnnotations.keySet().removeAll(platformAnnotations);
if (unmatchedAnnotations.size() > 0) {
log = Log.instance(context);
log.warning("proc.annotations.without.processors",unmatchedAnnotations.keySet());
}
}
// Run contributing processors that haven't run yet
psi.runContributingProcs(renv);
// Debugging
if (options.isSet("displayFilerState"))
filer.displayState();
}
3.2、调用next()方法生成新的Round对象
由于生成了新的源代码类GenerateTest,所以需要继续进行处理,但这次调用的构造方法与上次不同,如下:
/** Create a new round. */
private Round(Round prev,
Set<JavaFileObject> newSourceFiles, Map<String,JavaFileObject> newClassFiles) {
this(prev.nextContext(),
prev.number+1,
prev.nMessagerErrors,
prev.compiler.log.nwarnings);
this.genClassFiles = prev.genClassFiles;
List<JCCompilationUnit> parsedFiles = compiler.parseFiles(newSourceFiles);
roots = cleanTrees(prev.roots).appendList(parsedFiles);
// Check for errors after parsing
if (unrecoverableError())
return;
enterClassFiles(genClassFiles);
List<ClassSymbol> newClasses = enterClassFiles(newClassFiles);
genClassFiles.putAll(newClassFiles);
enterTrees(roots);
if (unrecoverableError())
return;
topLevelClasses = join(
getTopLevelClasses(parsedFiles),
getTopLevelClassesFromClasses(newClasses));
packageInfoFiles = join(
getPackageInfoFiles(parsedFiles),
getPackageInfoFilesFromClasses(newClasses));
findAnnotationsPresent();
}
同样也调用了findAnnotationPresent()方法。这样创建了Round后继续调用run()方法。
上一篇提到了实现Annotation注解处理器的一些重要的类,如Message,Filer。
Message用来报告错误,警告和其他提示信息。
JavacFilter用来创建新的源文件,class文件以及辅助文件。
下面来看看javac中的具体实现。
(1)JavacFiler
首先看下JavacFiler的继承体系,其中主要包括两大块,操作流和表示的文件类型。


(2)JavacRoundEnvironment
sdddddd
(3)JavacProcessingEnvironment
举个例子来看一看注解定义类在语法树中用哪些语法节点来表示以及如何组织的。
@Retention(RetentionPolicy.RUNTIME) // 注解会在class中存在,运行时可通过反射获取
@Target(ElementType.METHOD) // 目标是方法
@Documented // 文档生成时,该注解将被包含在javadoc中,可去掉
public @interface TestAnnotation {
public String name() default "helloworld";
}
查看JCClassDecl语法节点(注解类也被当作一个类来表示)的jcModifiers属性,截图如下。

jcModifiers中的flags属性值为8705,必写为这样的表示:
(1<<13)|(1<<0)|(1<<9)
查看Flags中关于各个Modifiers指定的常量,可知是public和interface且还有个专门表示annotation的静态常量,如下:
/** Flag that marks attribute interfaces 标记属性接口, added in classfile v49.0.
* 应该只能修饰注解定义类
*/
public static final int ANNOTATION = 1<<13;
然后看一下JCMethodDecl属性,截图如下:

使用如上的注解,如下:
public class C {
@TestAnnotation(name = "小明")
public Integer test(int a, String name, Object c) {
return 1;
}
}
截图如下:

关于注解Annotation第二篇的更多相关文章
- 关于注解Annotation第一篇
注解的定义格式如下: public @interface 注解名 {定义体} 定义体就是方法的集合,每个方法实则是声明了一个配置参数.方法的名称作为配置参数的名称,方法的返回值类型就是配置参数的类型. ...
- 【第二篇】ASP.NET MVC快速入门之数据注解(MVC5+EF6)
目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...
- JUnit扩展:引入新注解Annotation
发现问题 JUnit提供了Test Suite来帮助我们组织case,还提供了Category来帮助我们来给建立大的Test Set,比如BAT,MAT, Full Testing. 那么什么情况下, ...
- SSH整合 第二篇 工程初建
SSH整合,第二篇. 创建工程 这里只是测试和理解hibernate.建立Java工程就好了. 1.hibernate-4.2.21.jar hibernate包下的required,即\hibern ...
- [转]Android开源项目第二篇——工具库篇
本文为那些不错的Android开源项目第二篇--开发工具库篇,主要介绍常用的开发库,包括依赖注入框架.图片缓存.网络相关.数据库ORM建模.Android公共库.Android 高版本向低版本兼容.多 ...
- Android开源项目第二篇——工具库篇
本文为那些不错的Android开源项目第二篇——开发工具库篇,**主要介绍常用的开发库,包括依赖注入框架.图片缓存.网络相关.数据库ORM建模.Android公共库.Android 高版本向低版本兼容 ...
- Java注解Annotation学习
学习注解Annotation的原理,这篇讲的不错:http://blog.csdn.net/lylwo317/article/details/52163304 先自定义一个运行时注解 @Target( ...
- spring beans源码解读之--Bean的注解(annotation)
随着spring注解的引入,越来越多的开发者开始使用注解,这篇文章将对注解的机制进行串联式的讲解,不求深入透彻,但求串起spring beans注解的珍珠,展示给大家. 1. spring beans ...
- JAVA提高五:注解Annotation
今天我们学习JDK5.0中一个非常重要的特性,叫做注解.是现在非常流行的一种方式,可以说因为配置XML 比较麻烦或者比容易查找出错误,现在越来越多的框架开始支持注解方式,比如注明的Spring 框架, ...
随机推荐
- day10(IO流汇总)
字节流 (Reader,Writer) 输入流 FileReader public class Demo { public static void main(String[] args) throw ...
- IDEA14添加SVN
今天升级IDEA14,发现SVN变了,折腾了好一会才搞好.记录一下,希望其他人能少走弯路.主要是IDEA14的svn是用命令行工具进行操作,这样对以后SVN的兼容性也会好很多,这个在他们官方的博客上也 ...
- MAC将 /etc/sudoers文件修改错后的几种解决方法
文件修改错误后难以再次修改的原因: 1.修改此文件必须是root权限 2.此文件出现问题时sudo命令不可用 3.默认情况下MAC系统不启用root用户 解决的方法: 一.启用root用户,使用roo ...
- jQuery之noConflict() 方法
jQuery 核心 - noConflict() 方法,运行这个函数将变量 $ 的控制权让渡给第一个实现它的那个库.这有助于确保jQuery不会与其他库的$对象发生冲突. noConflict() 方 ...
- 为上海莫大型重工企业提供基于TFS的软件研发流程管理培训
这周,和微软公司的朋友一起,受上海莫大型重工企业的要求,为企业软件部门一个60多人的软件团队提供了为其2天的全流程培训,培训基于微软Team Foundation Server 2017(TFS 20 ...
- Python 数据结构与算法——链表
#构造节点类 class Node(object): def __init__(self,data=None,_next=None): ''' self.data:为自定义的数据 self.next: ...
- OC 数组以及字符串拼接与分割
//@""空的字符串对象-------分割 NSString * ptr = @"I am a man"; NSArray * array = [ptr com ...
- .net core 生成二维码
其实生成二维码的组件有很多种,如:QrcodeNet,ZKWeb.Fork.QRCoder,QRCoder等 我选QRCoder,是因为小而易用.支持大并发生成请求.不依赖任何库和网络服务. 既然是. ...
- Tasks遇到的一些坑,关于在子线程中对线程权限认证。
一般情况下,不应该在执行多线程认证的时候对其子线程进行身份认证,如:A线程的子线程B和子线程C. 当使用 Parallel.ForEach方法时,只有自身线程能够拥有相对应的权限,其子线程权限则为NU ...
- CSS精灵技术
在CSDN中浏览博客时,在博客的结束有上一篇和下一篇的按钮,当我们把鼠标放上去的时候,可以看到这两个按钮会进行颜色的改变,这种技术称为CSS精灵技术.通过查看源发现,其实他是通过超级链接的伪类实现的, ...