Spring学习(六)
AOP和OOP
1、OOP:Object-Oriented Programming,面向对象程序设计,是静态的,一旦类写死了就不能改变了,要更改就得修改代码重新编译,父类类型引用指向对象来实现动态性。核心思想是将客观存在的不同事物抽象成相互独立的类,然后把与事物相关的属性和行为封装到类里,并通过继承和多态来定义类彼此间的关系,最后通过操作类的实例来完成实际业务逻辑的功能需求。
2、AOP:Aspect-Oriented Programming,面向切面编程,以OOP为基础修复本身不具备的能力具有动态语言的特点,和OOP互为补充,Aop的最大意义是在不改变原来代码的前提下,也不对源代码做任何协议接口要求, 对OOP而实现了类似插件的方式,来修改源代码,给源代码插入新的执行代码。核心思想是将业务逻辑中与类不相关的通用功能切面式的提取分离出来,让多个类共享一个行为,一旦这个行为发生改变,不必修改类,而只需要修改这个行为即可。Spring的AOP其代码实质,即代理模式的应用。
3、OOP和AOP的区别
- 面向目标不同:简单来说OOP是面向名词领域,AOP面向动词领域。
- 思想结构不同:OOP是纵向结构,AOP是横向结构。
- 注重方面不同:OOP注重业务逻辑单元的划分,AOP偏重业务处理过程的某个步骤或阶段。
4、OOP和AOP的联系:两者之间是一个相互补充和完善的关系
5、AOP优点:利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
6、AOP应用
日志记录、事务处理、异常处理、安全控制和性能统计方面。
在Spring中提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务和事务进行内聚性的开发。
AOP的重要概念
1、切面 : 切点(Pointcut) + Advice【 在哪里 、加什么 】
2、Advice: 在 切点 选中的 连接点 "加入" 的 代码 就是 Advice【确定 加什么 】
3、切点( Pointcut ) : 用来 筛选 连接点 的条件就是切点, 类似于sql语句,where条件就可以理解为切点【 确定 在哪里加 ,在那些方法里面加入代码】
4、连接点( Join Point ) : 执行点 + 方位信息 所有被拦截的方法被加入了其他的方法,通过代理的方法将想要加入的想要的代码,加入的代码和原有的代码形成了连接点。一个方法有5个连接点,四个方法就有20个
- 一个正在执行的方法 (执行点) 执行前 (方位信息)
- 一个正在执行的方法 (执行点) 执行前、后 (方位信息)
- 一个正在执行的方法 (执行点) 执行并返回后 (方位信息)
- 一个正在执行的方法 (执行点) 执行抛出异常后 (方位信息)
- 一个正在执行的方法 (执行点) 执行后 (方位信息)
5、方位信息
- 方法执行前 ( before ) 、
- 执行前和执行后,执行前后都有环绕,两个结合起来才可以使用,两者之间是有联系的 ( around ) 、
- 正常执行并返回后 ( after-returning ) 、
- 方法抛出异常后 ( after-throwing ) 、
- 方法执行后 ( after )
6、执行点:一个可以执行的方法在执行时就是一个执行点
7、代理目标: 哪个对象将被其它对象所代理 ( 谁将被别人代理 ) [ target ]
8、代理对象: 哪个对象将要去代理别的对象 ( 谁将去代理别人 ) [ proxy ]
代理模式
1、什么是代理呢?
例如:苹果公司的手机交由富士康工厂按照苹果公司的要求去生产手机,富士康工厂生产的手机由京东按照苹果公司的要求去完成销售,京东就可以看成是富士康的代理商(代理对象),而富士康就是代理目标。消费者在京东买手机,感觉是由京东提供的手机,实际上是由富士康生产的,由京东代理,这种思想就是代理思想
2、具体代码实例
IPhone 苹果手机类
package ecut.aop.proxy; public class IPhone { public IPhone(){
super();
System.out.println( "爱疯" );
} public void charge() {
System.out.println( "charge" );
} public void call() {
System.out.println( "call" );
} public void message() {
System.out.println( "message" );
} }
AppleCompany 苹果公司接口
package ecut.aop.proxy; public interface AppleCompany {
//由苹果公司去设计然后交由富士康生产
public IPhone create() ; }
FoxconnFactory 富士康工厂类
package ecut.aop.proxy; public class FoxconnFactory implements AppleCompany {
//需要实现苹果公司接口
@Override
public IPhone create() {
System.out.println( "富士康" );
IPhone phone = new IPhone();
return phone ;
} @Override
public String toString() {
return "FoxconnFactory";
} }
JingDong 京东类
package ecut.aop.proxy; public class JingDong implements AppleCompany { private FoxconnFactory factory ; @Override
public IPhone create() {
System.out.println( "京东" );
return factory.create() ;
}
//通过getter , setter 方法注入FoxconnFactory
public FoxconnFactory getFactory() {
return factory;
} public void setFactory(FoxconnFactory factory) {
this.factory = factory;
} }
Main主类
package ecut.aop.proxy; public class Main { public static void main(String[] args) {
//被代理的目标target, 京东买的手机由富士康生产
FoxconnFactory factory = new FoxconnFactory();
//京东是富士康的代理商,代理对象proxy,代理商和代理对象都实现了苹果公司的这个接口(前提),富士康按照苹果公司要求生产,京东按照苹果公司的要求买手机
JingDong jd = new JingDong(); jd.setFactory(factory);
//相当于消费者买手机
IPhone p = jd.create(); System.out.println( p ); p.call(); } }
先调用JingDong的create方法,实际上调用的FoxconnFactory的create方法(调用的proxy方法的时候调用target方法,表面上感觉是proxy调用的实际上target完成具体实现的)。用JingDong之所以可以代理FoxconnFactory,是因为FoxconnFactory和JingDong都实现了AppleCompany,这个是代理的前提
3、代理模式代码的主要特点是:不改变原有类的前提下,在原有类某些方法执行前后,插入任意代码。所以代理模式需要写新的类对原有的类进行包装。Struts2中的拦截器,Spring中的赖加载都是用代理模式实现
4、代理模式目前实现的方式有三种:
- 静态代理:需要增强原有类的哪个方法,就需要对在代理类中包装哪个方法。个人理解,从功能上来说,原有类和代理类不一定要实现共同接口,但是为了赋予代理和和被代理类之间的逻辑关系,增加程序的可读性,可理解性,逻辑性,增加代理对象和被代理对象之间的关系,以更加符合面向对象编程是思维,而应该实现共同接口。
- 动态代理:使用反射机制,方法和对象都是传入的变量,就可以经过传入的对象和方法而动态调用被代理对象的任何方法,jdk中提供了实现此动态代理的api,被代理类必须实现接口
- Cglib代理:返回对象是代理对象的子类,不需要代理对象实现接口。当调用原对象方法时,实际上调用的是代理子类的方法。
JDK动态代理
1、目的:通过反射实现动态产生一个代理对象来代理富士康工厂(代理目标)
2、动态代理主要是通过Proxy类中的newProxyInstance创建一个代理对象,newProxyInstance方法需要传三个参数ClassLoader loader,Class<?>[] interfaces,InvocationHandler h。
- ClassLoader loader:代理目标对应的 类的 "类加载器"
- Class<?>[] interfaces:代理目标对应的类所直接实现的接口
- InvocationHandler h:请求指派器,将代理对象的请求派遣个代理目标
3、InvocationHandler 请求指派器的作用就是作用就是要将代理对象的请求派遣个代理目标(调用的proxy方法的时候调用target方法,表面上感觉是proxy调用的实际上target完成具体实现的)。首先要通过匿名内部类实现InvocationHandler接口,并实现接口中的方法invoke( Object proxy , Method method , Object[] args )
- Object proxy:代理对象
- Method method:调用的方法
- Object[] args:方法中要传入的参数
在invoke方法中要是proxy和target产生联系通过method.invoke( target , args ) 来实现将代理对象的请求派遣个代理目标
4、测试代码如下
package ecut.aop.proxy; import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays; public class ProxyTest { public static void main(String[] args) { // 代理目标 ( 谁将被别人代理 )
final Object target = new FoxconnFactory(); // 代理目标 的类型 : 获得 被代理的 对象的 运行时 类型,jdk代理的缺陷(前提)代理的目标类得实现接口,Spring提供了子类代理父类(Cglib)
Class<?> targetClass = target.getClass(); /** 用 代理目标 类型对应的类加载器 去加载 运行阶段动态产生的 代理类 */
ClassLoader loader = targetClass.getClassLoader() ; // 获得 代理目标 对应的 类的 "类加载器" /** 指示 将来产生 动态代理类 所需要实现的接口 */
Class<?>[] interfaces = targetClass.getInterfaces() ; // 获得 代理目标 对应的类所直接实现的接口
System.out.println( Arrays.toString( interfaces ) ); /** 请求指派器 (快递公司),调用的proxy方法的时候调用target方法,给你感觉是proxy调用的实际上target实现的,是使proxy 和 target产生联系,InvocationHandler将请求派给target
* method被调用的方法是谁, args 方法中的参数"借尸还魂"匿名内部类 */
InvocationHandler handler = new InvocationHandler(){
@Override
//proxy对象
public Object invoke( Object proxy , Method method , Object[] args ) throws Throwable {
//target.method(args);//被调用的是method方法;传入的参数是args;调用的是target所引用的对象的method方法
Object result = method.invoke( target , args ) ; // target.create();指派给target,由target调用方法 return result;
}
} ;
// 代理对象 ( 谁将去代理别人 )Proxy动态代理类的父类Proxy.newProxyInstance创建一个代理对象
Object proxy = Proxy.newProxyInstance( loader , interfaces , handler ) ; System.out.println( proxy ); // proxy.toString()会被指派,通过InvocationHandler.invoke方法指派给target.toString(),因此返回的是target的tostring方法 // proxy.getClass()不会被指派。代理类 : ( 在 运行阶段 动态产生 ( 没有 .java 文件、没有 .class 文件 ,直接产生字节码放在内存中去) )
Class<?> proxyClass = proxy.getClass(); System.out.println( proxyClass ); // 所有的 由 java.lang.refrect.Proxy 产生的 动态代理类 的直接父类都是 Proxy
System.out.println( proxyClass.getSuperclass() ); // 获得 动态代理类 所实现过的 接口 ( 在 创建 代理对象时指定的数组中有哪些接口,这里就有哪些接口 )
System.out.println( Arrays.toString( proxyClass.getInterfaces() ) ); System.out.println( "~~~构造方法~~~~~~~~~~~~" ); Constructor<?>[] cons = proxyClass.getDeclaredConstructors();
for( Constructor<?> c : cons ){
System.out.println( c );
} System.out.println( "~~~ 属性 ( Field ) ~~~~~~~~~~~~" );
//属性都由private static修饰 ,代理对象 $Proxy0继承了父类的Proxy类中的protected InvocationHandler h属性,其他m0,m1....都与方法相对应
Field[] fields = proxyClass.getDeclaredFields();
for( Field c : fields ){
System.out.println( c );
} System.out.println( "~~~ 方法 ( Method ) ~~~~~~~~~~~~" );
//重写了toString,hashCode,equals方法,都由public final修饰
Method[] methods = proxyClass.getDeclaredMethods();
for( Method c : methods ){
System.out.println( c );
} } }
运行结果如下:
[interface ecut.aop.proxy.AppleCompany]
FoxconnFactory
class com.sun.proxy.$Proxy0
class java.lang.reflect.Proxy
[interface ecut.aop.proxy.AppleCompany]
~~~构造方法~~~~~~~~~~~~
public com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
~~~ 属性 ( Field ) ~~~~~~~~~~~~
private static java.lang.reflect.Method com.sun.proxy.$Proxy0.m1
private static java.lang.reflect.Method com.sun.proxy.$Proxy0.m2
private static java.lang.reflect.Method com.sun.proxy.$Proxy0.m3
private static java.lang.reflect.Method com.sun.proxy.$Proxy0.m0
~~~ 方法 ( Method ) ~~~~~~~~~~~~
public final boolean com.sun.proxy.$Proxy0.equals(java.lang.Object)
public final java.lang.String com.sun.proxy.$Proxy0.toString()
public final int com.sun.proxy.$Proxy0.hashCode()
public final ecut.aop.proxy.IPhone com.sun.proxy.$Proxy0.create()
由以上输出可以推测出来代理类的类名是$Proxy0,且有一个有参构造,参数是InvocationHandler类型,且有四个属性,四个方法,还继承了父类的Proxy类中的protected InvocationHandler h属性,推测其他m0,m1....都与方法相对应。$Proxy0源码可能类似于
Proxy类
public class Proxy { protected InvocationHandler h ; // 父类中 public 修饰的 和 protected 修饰 都可以被子类继承 protected Proxy( InvocationHandler h ) {
this.h = h ;
} }
$Proxy0类
package com.sun.proxy ; public class $Proxy0 extends Proxy implements AppleCompany { public $Proxy0( InvocationHandler h ){
super( h );
} private static Method m0 ;
private static Method m1 ;
private static Method m2 ;
private static Method m3 ; /* .......... */ static {
Class<?> c = $Proxy0.class ;
m0 = c.getMethod( "equals" , Object.class );
m1 = c.getMethod( "toString" );
m2 = c.getMethod( "hashCode" );
m3 = c.getMethod( "create" ); /* .......... */
} public final boolean equals( Object another ) {
Object[] args = new Object[]{ another };
return h.invoke( this , m0 , args );
} public final String toString() {
Object[] args = new Object[ 0 ];
return h.invoke( this , m1 , args );
} public final int hashCode() {
Object[] args = new Object[ 0 ];
return h.invoke( this , m2 , args );
} public final IPhone create() {
Object[] args = new Object[ 0 ];
return h.invoke( this , m3 , args );
} }
5、动态代理的实现和源码分析
查看Proxy类的newProxyInstance方法,从生成对象方法中,我们看到三个关键的地方:
- Class<?> cl = getProxyClass0(loader, interfaces);//得到代理类
- final Constructor<?> cons = cl.getConstructor(constructorParams);
- newInstance(cons, h);//将InvocationHandler h传入代理对象中
首先是通过Proxy.newProxyInstance( loader , interfaces , handler ) 将handler传入了$Proxy0 中的 InvocationHandler h属性中,然后当我们调用方法时候实际上是通过h.invoke( this , m3 , args )来实现的即调用handler里面的invoke方法,而在handler的invoke方法中通过method.invoke( target , args )方法,最终调用的是target里面的方法。
结合动态代理理解AOP相关概念
1、理解Advice测试案例
package ecut.aop.proxy; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; public class ProxyTest2 { public static void main(String[] args) { // 代理目标 ( 谁将被别人代理 )
final Object target = new FoxconnFactory(); // 代理目标 的类型 : 获得 被代理的 对象的 运行时 类型
Class<?> targetClass = target.getClass(); /** 用 代理目标 类型对应的类加载器 去加载 运行阶段动态产生的 代理类 */
ClassLoader loader = targetClass.getClassLoader() ; // 获得 代理目标 对应的 类的 "类加载器" /** 指示 将来产生 动态代理类 所需要实现的接口 */
Class<?>[] interfaces = targetClass.getInterfaces() ; // 获得 代理目标 对应的类所实现的接口 /** 请求指派器 */
InvocationHandler handler = new InvocationHandler(){
@Override
public Object invoke( Object proxy , Method method , Object[] args ) throws Throwable {
System.out.println( "方法[ " + method.getName() + " ]将要执行了" ); // Advice
Object result = method.invoke( target , args ) ; // target.create();
System.out.println( "方法[ " + method.getName() + " ]执行结束并返回了: " + result ); // Advice
return result;
}
} ; // 代理对象 ( 谁将去代理别人 ),这四个方法执行其前后就被我们拦截下来了 ,方法前后增加了输出语句,没有动方法,只是在动态代理类handler里面增加了代码
//我们所增加的代码如果按照规范来封装好就是Advice
Object proxy = Proxy.newProxyInstance( loader , interfaces , handler ) ; if( proxy instanceof AppleCompany ) {
AppleCompany ac = (AppleCompany) proxy ;
System.out.println( ac == proxy ); IPhone p = ac.create();
System.out.println( p ); System.out.println( ac.toString() ); System.out.println( ac.hashCode() ); System.out.println( ac.equals(target) );//target.equals(target);
} } }
这四个方法执行其前后就被我们拦截下来了 ,方法前后增加了输出语句,没有动方法,只是在动态代理类handler里面增加了代码 ,我们所增加的代码如果按照规范来封装好就是Advice。
理解连接点测试案例:
package ecut.aop.proxy; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; public class ProxyTest3 { public static void main(String[] args) { // 代理目标 ( 谁将被别人代理 )
final Object target = new FoxconnFactory(); Class<?> targetClass = target.getClass(); ClassLoader loader = targetClass.getClassLoader() ; Class<?>[] interfaces = targetClass.getInterfaces() ; InvocationHandler handler = new InvocationHandler(){
@Override
public Object invoke( Object proxy , Method method , Object[] args ) throws Throwable { System.out.println( "方法[ " + method.getName() + " ]将要执行了" ); // before Object result = null ;
try{
long start = System.nanoTime(); // around
result = method.invoke( target , args ) ; // 执行点
long end = System.nanoTime(); // around
System.out.println( "执行用时: " + ( end - start ) + " 毫微妙" ); // around
System.out.println( "执行后并返回: " + result); // after-returing
} catch ( Throwable t ){
System.out.println( "发生错误: " + t.getMessage() ); // after-throwing
} System.out.println( "方法[ " + method.getName() + " ]执行结束" ); // after return result;
}
} ; Object proxy = Proxy.newProxyInstance( loader , interfaces , handler ) ; if( proxy instanceof AppleCompany ) {
AppleCompany ac = (AppleCompany) proxy ;
System.out.println( ac == proxy );
//通过create调用了target中create的方法
IPhone p = ac.create();
System.out.println( p );
//代理对象 $Proxy0中的四个方法是可执行的,方法在执行时候就可以视为是一个执行点
String s = ac.toString() ;
System.out.println( s );
} } }
连接点 ( Join Point ) : 执行点 + 方位信息 所有被拦截的方法被加入了其他的方法,通过代理的方法将想要加入的想要的代码,加入的代码和原有的代码形成了连接点
执行点就是一个可以执行的方法在执行时就是一个执行点,当create方法在执行的时候就是一个执行点,方位信息就是加入Advice的位置就是方位信息,执行点加上分为信息就是连接点。
理解切点测试案例:
package ecut.aop.proxy; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; public class ProxyTest4 { public static void main(String[] args) { // 代理目标 ( 谁将被别人代理 )
final Object target = new FoxconnFactory(); Class<?> targetClass = target.getClass(); ClassLoader loader = targetClass.getClassLoader() ; Class<?>[] interfaces = targetClass.getInterfaces() ; InvocationHandler handler = new InvocationHandler(){
@Override
//只保留了before和after的方位信息,20个连接点里挑选了8个,增加了了判断语句后只对create方法增加Advice,这些筛选条件就是切点
public Object invoke( Object proxy , Method method , Object[] args ) throws Throwable { if( "create".equals( method.getName() ) ) {
System.out.println( "方法[ " + method.getName() + " ]将要执行了" ); // before
} Object result = method.invoke( target , args ) ; // 执行点 if( "create".equals( method.getName() ) ){
System.out.println( "方法[ " + method.getName() + " ]执行结束" ); // after
} return result;
}
} ; Object proxy = Proxy.newProxyInstance( loader , interfaces , handler ) ; if( proxy instanceof AppleCompany ) {
AppleCompany ac = (AppleCompany) proxy ;
System.out.println( ac == proxy ); IPhone p = ac.create();
System.out.println( p ); String s = ac.toString() ;
System.out.println( s ); System.out.println( ac.hashCode() );
} } }
切点就是筛选连接点的条件,比如增加了了判断语句后只对create方法增加Advice,这些筛选条件就是切点。
参考博客链接:
https://blog.csdn.net/pdsygt/article/details/46433537
https://blog.csdn.net/u011266694/article/details/78918394
转载请于明显处标明出处:
https://www.cnblogs.com/AmyZheng/p/9264215.html
Spring学习(六)的更多相关文章
- Spring学习(六)-----Spring使用@Autowired注解自动装配
Spring使用@Autowired注解自动装配 在上一篇 Spring学习(三)-----Spring自动装配Beans示例中,它会匹配当前Spring容器任何bean的属性自动装配.在大多数情况下 ...
- spring学习 六 spring与mybatis整合
在mybatis学习中有两种配置文件 :全局配置文件,映射配置文件.mybatis和spring整合,其实就是把mybatis中的全局配置文件的配置内容都变成一个spring容器的一个bean,让sp ...
- Spring学习六(事物管理)
参考链接 http://www.mamicode.com/info-detail-1248286.html http://www.cnblogs.com/wangdaqian/archive/2017 ...
- spring学习六
1: @Valid 注解 @NotNull(message="名字不能为空") private String userName; @Max(value=120,message ...
- spring学习六----------Bean的配置之Aware接口
© 版权声明:本文为博主原创文章,转载请注明出处 Aware Spring提供了一些以Aware结尾的接口,实现了Aware接口的bean在被初始化后,可以获取相应的资源 通过Aware接口,可以对S ...
- Spring学习(六)--渲染Web视图
一.将模型数据渲染为Html 在上一篇文章中,我们所编写的控制器方法都没有直接产生浏览器中渲染所需的HTML.这些方法只是将数据填充到模型中,然后将模型传递给一个用来渲染的视图.这些方法会返回一个St ...
- Spring学习六:自定义Event事件
Spring 中的自定义事件 编写和发布自己的自定义事件有许多步骤.按照在这一章给出的说明来编写,发布和处理自定义 Spring 事件. 步骤 描述 1 创建一个名称为 SpringExample 的 ...
- (转)MyBatis框架的学习(六)——MyBatis整合Spring
http://blog.csdn.net/yerenyuan_pku/article/details/71904315 本文将手把手教你如何使用MyBatis整合Spring,这儿,我本人使用的MyB ...
- Spring学习笔记(六)—— SSH整合
一.整合原理 二.整合步骤 2.1 导包 [hibernate] hibernate/lib/required hibernate/lib/jpa 数据库驱动 [struts2] struts-bla ...
随机推荐
- P1553 数字反转(升级版)(copy(),reverse(),find(),substr(),erase())
题目描述 给定一个数,请将该数各个位上数字反转得到一个新数. 这次与 NOIp2011 普及组第一题不同的是:这个数可以是小数,分数,百分数,整数.整数反转是将所有数位对调:小数反转是把整数部分的数反 ...
- Linux网络课程学习第五天
学习心得: 通过本章节课学习收获很多,不仅学会了使用vim编辑器以及编写简单的shell脚本.从一个从未接触过Linux系统的我一下学会并掌握了这么多自我感觉进步还是挺大的.但是还是要坚持学下去,毕竟 ...
- STL-set&&multiset 集合
#include <iostream> #include <cstdio> #include <set> // 仿函数的原型 //struct greaters / ...
- 给你的网站添加谷歌AMP、百度MIP、神马MIP链接自动提交功能
我们在做网站的时候,经常会听到别人说SEO优化,网站优化等等.但是我们经常听的云里雾里的,但是经过我们运营一段时间之后,我们慢慢的就会熟悉了,知道什么是SEO.SEO中文译名为搜索引擎优化,既然是叫搜 ...
- 双向链表的简单Java实现-sunziren
写在前面,csdn的那篇同名博客就是我写的,我把它现在在这边重新发布,因为我实在不想用csdn了,那边的广告太多了,还有就是那个恶心人的“阅读更多”按钮,惹不起我躲得起. 在上次分享完单向链表的简单编 ...
- Tomcat开启JMX监控
搭建模拟环境: 操作系统:centos7内存:1Gjdk:1.8.0_131tomcat:8.0.48 环境准备我们这里就不直接演示了,直接配置tomcat的jmx 1.进入到tomcat的bin目录 ...
- python常见函数积累
shape() 返回数组或者数据框有多少行或者多少列 import numpy as np x = np.array([[1,2,5],[2,3,5],[3,4,5],[2,3,6]]) #输出数组的 ...
- Canvas如何绘制精美的图?
一.Canvas的基本使用 首先在使用Canvas一般先在<body>中添加: <canvas id="></canvas> 然后使用Js进行获取canv ...
- Winform递归绑定树节点
/// <summary> /// 绑定树节点 /// </summary> /// <param name="pid"></param& ...
- hdu 6182A Math Problem(快速幂)
You are given a positive integer n, please count how many positive integers k satisfy kk≤nkk≤n. Inp ...