Spring源码学习-5.ProxyFactoryBean实现与源代码分析
来源:互联网 发布:怎么看淘宝店网址 编辑:程序博客网 时间:2024/06/05 07:19
1.AOP设计原理
proxyConfig<-AdvisedSupport<-proxyCreatorSupport<-(AspectJProxyFactory+ ProxyFactory + ProxyFactoryBean)
在这个继承关系中,ProxyConfig是最底层的类,这是一个数据基类,为子类提供配置属性
AdvisedSupport: 封装了AOP对通知和通知器的操作
proxyCreatorSupport:子类创建AOP对象的一个辅助类
AspectJProxyFactory:集成AOP和AspectJ
ProxyFactory :封装AOP功能,需要编程式实现
ProxyFactoryBean封装AOP ,在IoC中实现声明式配置
2.第一步:得到AOP代理对象
ProxyFactoryBean生成Aop代理对象
1.配置ProxyFactoryBean
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<span style="white-space:pre"></span>xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"<span style="white-space:pre"></span>xmlns:tx="http://www.springframework.org/schema/tx"<span style="white-space:pre"></span>xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd<span style="white-space:pre"></span>http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd<span style="white-space:pre"></span>http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd<span style="white-space:pre"></span>http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"><span style="white-space:pre"></span><bean id="testAdvisor" class="day0815.AopAdvisor"><span style="white-space:pre"></span></bean><span style="white-space:pre"></span><bean id="ttt" class="day0815.AopTestMethod"></bean><span style="white-space:pre"></span><bean id="testAOP" class="org.springframework.aop.framework.ProxyFactoryBean"><span style="white-space:pre"></span><property name="proxyInterfaces" ><span style="white-space:pre"></span><value>day0815.TestProxyInterface</value><span style="white-space:pre"></span></property><span style="white-space:pre"></span><property name="target"><span style="white-space:pre"></span><ref bean="ttt"/><span style="white-space:pre"></span></property><span style="white-space:pre"></span><property name="interceptorNames" ><list><value>testAdvisor</value></list></property><span style="white-space:pre"></span></bean></beans>
target是需要增强的对象
Demo中的代码为
package day0815;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class SpringTest {public static void main(String[] args) {ApplicationContext a = new ClassPathXmlApplicationContext("classpath:ac20150811.xml");TestProxyInterface aopTestMethod = (TestProxyInterface) a.getBean("testAOP");aopTestMethod.service();}}//=============================================package day0815;public interface TestProxyInterface {void service();}//==============================================package day0815;public class AopTestMethod implements TestProxyInterface{@Overridepublic void service(){System.out.println("Service method");}}<pre name="code" class="java">//=============================================package day0815;import java.lang.reflect.Method;import org.springframework.aop.MethodBeforeAdvice;public class AopAdvisor implements MethodBeforeAdvice{@Overridepublic void before(Method arg0, Object[] arg1, Object arg2)throws Throwable {// TODO Auto-generated method stubSystem.out.println("BeforeMethod~~");}}
2.生成代理对象
生成代理对象的入口是getObject.由于ProxyFactoryBean也是一个FactoryBean ,
singleton和Prototype生成过程是不同的
//ProxyFactoryBean的实现是以getObject为入口实现的public Object getObject() throws BeansException {initializeAdvisorChain();//初始化通知器if (isSingleton()) {//如果是单例,以单例的方式生成aopProxy代理,Bean的默认就是单例,所以进入这个方法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();}}
下面是通知器初始过程,
advisorChainInitialized是是否已经初始化的标志,如果已经初始化就返回
private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException {//如果已经初始化,就返回if (this.advisorChainInitialized) {return;}if (!ObjectUtils.isEmpty(this.interceptorNames)) {if (this.beanFactory == null) {throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " +"- cannot resolve interceptor names " + Arrays.asList(this.interceptorNames));}// Globals can't be last unless we specified a targetSource using the property...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");}// Materialize interceptor chain from bean names.//添加Advisor链的调用,通过interceptorNames进行的for (int i = 0; i < this.interceptorNames.length; i++) {String name = this.interceptorNames[i];if (logger.isTraceEnabled()) {logger.trace("Configuring advisor or advice '" + name + "'");}if (name.endsWith(GLOBAL_SUFFIX)) {if (!(this.beanFactory instanceof ListableBeanFactory)) {throw new AopConfigException("Can only use global advisors or interceptors with a ListableBeanFactory");}//addGlobalAdvisor((ListableBeanFactory) this.beanFactory,name.substring(0, name.length() - GLOBAL_SUFFIX.length()));}else {// If we get here, we need to add a named interceptor.// We must check if it's a singleton or prototype.//需要加入命名的拦截器AdviceObject advice = null;if (this.singleton || this.beanFactory.isSingleton(this.interceptorNames[i])) {// Add the real Advisor/Advice to the chain.//调用getBean加载通知器或者通知advice = this.beanFactory.getBean(this.interceptorNames[i]);}else {// It's a prototype Advice or Advisor: replace with a prototype.// Avoid unnecessary creation of prototype bean just for advisor chain initialization.advice = new PrototypePlaceholderAdvisor(this.interceptorNames[i]);}addAdvisorOnChainCreation(advice, this.interceptorNames[i]);}}}//已初始化,不需要再次初始化this.advisorChainInitialized = true;}
//生成代理代理对象private synchronized Object getSingletonInstance() {if (this.singletonInstance == null) {//获取target属性this.targetSource = freshTargetSource();if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {// Rely on AOP infrastructure to tell us what interfaces to proxy.//根据aop框架来判断需要代理的接口Class targetClass = getTargetClass();if (targetClass == null) {throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");}//设置需要代理的接口setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));}// Initialize the shared singleton instance.super.setFrozen(this.freezeProxy);this.singletonInstance = getProxy(createAopProxy());}return this.singletonInstance;}获取代理对象
public Object getProxy(ClassLoader classLoader) {return createAopProxy().getProxy(classLoader);}
AopProxy是一个接口,他把代理对象的实现与框架分隔开
有两个子类实现了AopProxy
一个是JdkDynamicProxy,另一个是Cglib2AopProxy
createAopProxy生成aopProxy对象
//ProxyCreatorSupport类中的方法protected final synchronized AopProxy createAopProxy() {if (!this.active) {activate();}return getAopProxyFactory().createAopProxy(this);}
这里利用AopProxyFactory来创建AopProxy
//DefaultAopProxyFactory类中的方法public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {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.");}//如果support是接口,用jdk来生成代理if (targetClass.isInterface()) {return new JdkDynamicAopProxy(config);}if (!cglibAvailable) {throw new AopConfigException("Cannot proxy target class because CGLIB2 is not available. " +"Add CGLIB to the class path or specify proxy interfaces.");}//否则用cglib来生成return CglibProxyFactory.createCglibProxy(config);}else {return new JdkDynamicAopProxy(config);}}
3.JDK和cglib生成AOP代理对象
1.使用jdk生成
//JDK生成代理对象public Object getProxy(ClassLoader classLoader) {if (logger.isDebugEnabled()) {logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());}Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);//生成代理对象return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);}//实现InvocationHandler接口需要实现的方法public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {MethodInvocation invocation = null;Object oldProxy = null;boolean setProxyContext = false;TargetSource targetSource = this.advised.targetSource;Class targetClass = null;Object target = null;try {if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {// The target does not implement the equals(Object) method itself.return (equals(args[0]) ? Boolean.TRUE : Boolean.FALSE);}if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {// The target does not implement the hashCode() method itself.return new Integer(hashCode());}if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&method.getDeclaringClass().isAssignableFrom(Advised.class)) {// Service invocations on ProxyConfig with the proxy config...return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);}Object retVal = null;if (this.advised.exposeProxy) {// Make invocation available if necessary.oldProxy = AopContext.setCurrentProxy(proxy);setProxyContext = true;}// May be <code>null</code>. Get as late as possible to minimize the time we "own" the target,// in case it comes from a pool.target = targetSource.getTarget();if (target != null) {targetClass = target.getClass();}// Get the interception chain for this method.List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);// Check whether we have any advice. If we don't, we can fallback on direct// reflective invocation of the target, and avoid creating a MethodInvocation.if (chain.isEmpty()) {// We can skip creating a MethodInvocation: just invoke the target directly// Note that the final invoker must be an InvokerInterceptor so we know it does// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);}else {// We need to create a method invocation...invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);// Proceed to the joinpoint through the interceptor chain.retVal = invocation.proceed();}// Massage return value if necessary.if (retVal != null && retVal == target && method.getReturnType().isInstance(proxy) &&!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {// Special case: it returned "this" and the return type of the method// is type-compatible. Note that we can't help if the target sets// a reference to itself in another returned object.retVal = proxy;}return retVal;}finally {if (target != null && !targetSource.isStatic()) {// Must have come from TargetSource.targetSource.releaseTarget(target);}if (setProxyContext) {// Restore old proxy.AopContext.setCurrentProxy(oldProxy);}}}
2.使用cglib生成代理对象
//Cglib2AopProxy类中的方法//使用cglib来生成代理对象public Object getProxy(ClassLoader classLoader) {if (logger.isDebugEnabled()) {logger.debug("Creating CGLIB2 proxy: target source is " + this.advised.getTargetSource());}try {//IoC容器中配制的Target对象Class rootClass = this.advised.getTargetClass();Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");Class proxySuperClass = rootClass;//如果这个类是cglib产生的,proxySuperClass = rootClass.getSuperclass();if (AopUtils.isCglibProxyClass(rootClass)) {proxySuperClass = rootClass.getSuperclass();Class[] additionalInterfaces = rootClass.getInterfaces();for (int i = 0; i < additionalInterfaces.length; i++) {Class additionalInterface = additionalInterfaces[i];this.advised.addInterface(additionalInterface);}}// Validate the class, writing log messages as necessary.validateClassIfNecessary(proxySuperClass);//验证代理对象的接口//生成Enhancer// Configure CGLIB Enhancer...Enhancer enhancer = createEnhancer();if (classLoader != null) {enhancer.setClassLoader(classLoader);if (classLoader instanceof SmartClassLoader &&((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {enhancer.setUseCache(false);}}enhancer.setSuperclass(proxySuperClass);enhancer.setStrategy(new UndeclaredThrowableStrategy(UndeclaredThrowableException.class));enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));enhancer.setInterceptDuringConstruction(false);Callback[] callbacks = getCallbacks(rootClass);enhancer.setCallbacks(callbacks);enhancer.setCallbackFilter(new ProxyCallbackFilter(this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));Class[] types = new Class[callbacks.length];for (int x = 0; x < types.length; x++) {types[x] = callbacks[x].getClass();}enhancer.setCallbackTypes(types);//创建一个cglib代理的实例// Generate the proxy class and create a proxy instance.Object proxy;if (this.constructorArgs != null) {proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);}else {proxy = enhancer.create();}return proxy;}catch (CodeGenerationException ex) {throw new AopConfigException("Could not generate CGLIB subclass of class [" +this.advised.getTargetClass() + "]: " +"Common causes of this problem include using a final class or a non-visible class",ex);}catch (IllegalArgumentException ex) {throw new AopConfigException("Could not generate CGLIB subclass of class [" +this.advised.getTargetClass() + "]: " +"Common causes of this problem include using a final class or a non-visible class",ex);}catch (Exception ex) {// TargetSource.getTarget() failedthrow new AopConfigException("Unexpected AOP exception", ex);}}
4.第二步:AOP拦截器调用的实现
1.JDK
拦截器的设置在使用jdk或者cglib生成代理对象时就完成了,
如果是JDK的Proxy来实现代理对象的话,那么需要在InvocationHandler中来设置回调
public Object getProxy(ClassLoader classLoader) {if (logger.isDebugEnabled()) {logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());}Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);//生成代理对象return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);//这个类继承了InvocationHandler接口,所以在这个类的invoke中 可以看到配置的过程}
//JdkDynamicAopProxy类invoke方法public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {MethodInvocation invocation = null;Object oldProxy = null;boolean setProxyContext = false;TargetSource targetSource = this.advised.targetSource;Class targetClass = null;Object target = null;try {if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {// The target does not implement the equals(Object) method itself.//如果目标没实现equals方法return (equals(args[0]) ? Boolean.TRUE : Boolean.FALSE);}if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {// The target does not implement the hashCode() method itself.//如果目标没实现hashcodereturn new Integer(hashCode());}if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&method.getDeclaringClass().isAssignableFrom(Advised.class)) {// Service invocations on ProxyConfig with the proxy config...//根据代理对象的配置调用服务return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);}Object retVal = null;if (this.advised.exposeProxy) {// Make invocation available if necessary.oldProxy = AopContext.setCurrentProxy(proxy);setProxyContext = true;}// May be <code>null</code>. Get as late as possible to minimize the time we "own" the target,// in case it comes from a pool.//得到目标对象target = targetSource.getTarget();if (target != null) {targetClass = target.getClass();}//得到定义好的连接器链// Get the interception chain for this method.List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);// Check whether we have any advice. If we don't, we can fallback on direct//构造MethodInvocation// reflective invocation of the target, and avoid creating a MethodInvocation.if (chain.isEmpty()) {// We can skip creating a MethodInvocation: just invoke the target directly// Note that the final invoker must be an InvokerInterceptor so we know it does// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.//没有设定拦截器,就直接调用target的对应方法retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);}else {// We need to create a method invocation...//如果有设置拦截器,我们需调用拦截器后,再调用目标对象的方法invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);// Proceed to the joinpoint through the interceptor chain.//沿着拦截器继续前进retVal = invocation.proceed();}// Massage return value if necessary.if (retVal != null && retVal == target && method.getReturnType().isInstance(proxy) &&!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {// Special case: it returned "this" and the return type of the method// is type-compatible. Note that we can't help if the target sets// a reference to itself in another returned object.retVal = proxy;}return retVal;}finally {if (target != null && !targetSource.isStatic()) {// Must have come from TargetSource.targetSource.releaseTarget(target);}if (setProxyContext) {// Restore old proxy.AopContext.setCurrentProxy(oldProxy);}}}
2.CGLIB
如果是CGLIB的话,需要根据cglib使用要求,在dynamic-AdvisedInterceptor中完成.
CGLIB与jdk的实现类似,只是通过构造ReflectMethod-Invocation对象来完成
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {MethodInvocation invocation = null;Object oldProxy = null;boolean setProxyContext = false;Class targetClass = null;Object target = null;try {Object retVal = null;if (this.advised.exposeProxy) {// Make invocation available if necessary.oldProxy = AopContext.setCurrentProxy(proxy);setProxyContext = true;}// May be <code>null</code>. Get as late as possible to minimize the time we// "own" the target, in case it comes from a pool.target = getTarget();if (target != null) {targetClass = target.getClass();}List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);// Check whether we only have one InvokerInterceptor: that is,// no real advice, but just reflective invocation of the target.if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {// We can skip creating a MethodInvocation: just invoke the target directly.// Note that the final invoker must be an InvokerInterceptor, so we know// it does nothing but a reflective operation on the target, and no hot// swapping or fancy proxying.retVal = methodProxy.invoke(target, args);}else {// We need to create a method invocation...invocation = new CglibMethodInvocation(proxy, target, method, args,targetClass, chain, methodProxy);// If we get here, we need to create a MethodInvocation.retVal = invocation.proceed();}retVal = massageReturnTypeIfNecessary(proxy, target, method, retVal);return retVal;}finally {if (target != null) {releaseTarget(target);}if (setProxyContext) {// Restore old proxy.AopContext.setCurrentProxy(oldProxy);}}}
3.对于目标方法的调用
jdk的实现方法是使用反射得出的,而cglib是通过MethodProxy完成的
5.AOP拦截器链的调用
1.处理AOP配置的通知基本步骤:
(1).获取AOP配置的通知Advice:
在AOP动态代理对象的回调方法中,都需要使用以下方式获取AOP配置的通知,并将获取到的通知和目标对象、代理对象等一起封装为ReflectiveMethodInvocation对象
//获取AOP配置的通知 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); …… //根据获取的通知、目标对象等创建ReflectiveMethodInvocation //如果是CGLIB方式,则创建CglibMethodInvocation对象: //new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy); invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); //沿着获取的通知链,递归调用所有配置的AOP通知 retVal = invocation.proceed();
(2).创建ReflectiveMethodInvocation对象:
a.创建CglibMethodInvocation对象:
(3).处理AOP配置的通知器:Spring通过调用ReflectiveMethodInvocation类来处理AOP配置的通知,CglibMethodInvocation继承ReflectiveMethodInvocation,因此JDK和CGLIB方式都是通过调用ReflectiveMethodInvocation的proceed()方法来处理通知的,处理通知的源码如下:
/处理AOP配置的通知 public Object proceed() throws Throwable { //如果拦截器链中通知已经调用完毕 if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { //这个方法调用AopUtils.invokeJoinpointUsingReflection方法, //通过反射机制直接调用目标对象方法 return invokeJoinpoint(); } //获取AOP配置的通知,在ReflectiveMethodInvocation初始化构方法中将获 //取到的AOP通知赋值给interceptorsAndDynamicMethodMatchers变量 Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); //如果获取的通知器或通知是动态匹配方法拦截器类型 if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { //动态匹配方法拦截器 InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice; if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) { //如果匹配,调用拦截器的方法 return dm.interceptor.invoke(this); } else { //如果不匹配,递归调用proceed()方法,知道拦截器链被全部调用为止 return proceed(); } } else { //如果不是动态匹配方法拦截器,则切入点在构造对象之前进行静态匹配,调用 //拦截器的方法 return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); } }
advisor是怎么产生的那?
0 0
- Spring源码学习-5.ProxyFactoryBean实现与源代码分析
- Spring源码分析-初识ProxyFactoryBean(五)
- Spring源码分析之ProxyFactoryBean方式实现Aop功能的分析
- 浅析Spring AOP源码(十四) 分析ProxyFactoryBean
- Spring BeanNameAutoProxyCreator 与 ProxyFactoryBean区别
- Spring源代码分析(10)---ProxyFactoryBean(旁敲侧击的AOP时代终于来临)
- 做一个合格的程序猿之浅析Spring AOP源码(十四) 分析ProxyFactoryBean
- Spring的AOP实现方式—ProxyFactoryBean配置方式实现源码剖析
- Spring AOP之ProxyFactoryBean与BeanNameAutoProxyCreator
- Spring学习笔记 —— AOP(面向切面编程) 之使用ProxyFactoryBean实现AOP
- Spring源码学习-5.AOP原理分析
- 使用spring的ProxyFactoryBean来实现权限控制
- spring aop(五)--ProxyFactoryBean创建代理的实现
- spring 直接使用ProxyFactoryBean 实现AOP 流程小结
- Spring中的ProxyFactoryBean
- Spring源码分析:实现AOP- -
- Spring实现事务源码分析
- Spring实现AOP源码分析
- zabbix 报警机制
- 字符串匹配的KMP算法
- Tomcat请求处理过程(Tomcat源码解析五)
- 关于Java数据库查询结果保存到DBF的小小总结^_^
- button样式设置
- Spring源码学习-5.ProxyFactoryBean实现与源代码分析
- C/C++知识要点6——定义只能在堆/栈上生成对象的类
- Mac环境下svn的使用
- owncloud源码分析4--上传代码分析
- Struts2 Hello World 实例
- C#微信结合百度api获取当前用户的地理位置
- hdoj-1010-Tempter of the Bone【深搜+剪枝】
- JavaScript与jQuery中获取屏幕的宽度和高度的常用方法以及HTML中精确定位
- js判断ie浏览器