国内最全IT社区平台 联系我们 | 收藏本站
华晨云阿里云优惠2
您当前位置:首页 > php框架 > 框架设计 > Spring AOP 最终版实现

Spring AOP 最终版实现

来源:程序员人生   发布时间:2016-06-21 08:55:56 阅读次数:3466次

引言


Spring AOP 是面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统1保护的1种技术。AOP是OOP的延续,是软件开发中的1个热门,也是Spring框架中的1个重要内容,是函数式编程的1种衍生范型。利用AOP可以对业务逻辑的各个部份进行隔离,从而使得业务逻辑各部份之间的耦合度下降,提高程序的可重用性,同时提高了开发的效力。


其实,我们在系统中通过AOP实现权限、日志、异常等等非业务服务横切到我们的业务服务中,并且在不修改代码的情势,通常情况要是需要修改XML文件的,而且,当我们修改了非业务服务的时候,所有的业务服务中的代码都会修改。


代码实现:


目录:



AOPClient:


package com.tgb.client; import com.tgb.config.ClassPathXmlApplicationContext; import com.tgb.config.ContainerBeans; import com.tgb.dao.UserDao; import com.tgb.domain.User; /** * AOP效果测试 * @ClassName: AopClientTest * @Description: * @author [qmx] * @date * */ public class AopClient { public static void main(String[] args) throws Exception { ContainerBeans factory = new ClassPathXmlApplicationContext(); User user = new User(); user.setUserName("hanyankun"); Object proxyObject = factory.getBean("UserDao"); UserDao userDao = (UserDao) proxyObject; System.out.println("----启用aop1"); // userDao.before(user); userDao.update(user); userDao.save(user); // } }

ClientTest:


package com.tgb.client; import com.tgb.config.ClassPathXmlApplicationContext; import com.tgb.config.ContainerBeans; import com.tgb.dao.StudentDao; import com.tgb.dao.TeacherDao; import com.tgb.daoImpl.StudentDaoImpl; import com.tgb.domain.User; /** * 容器效果测试,测试增删对象 * @ClassName: ClientTest * @Description: TODO(这里用1句话描写这个类的作用) * @author [qmx] * @date * */ public class ClientTest { public static void main(String[] args) throws Exception { ContainerBeans containerBeans = new ClassPathXmlApplicationContext();// new // 为 // 装配容器进程 User user = new User(); user.setUserName("hanyankun"); // 参数设置 containerBeans.printAllbeanId(); System.out.println("\n"); //增加对象 StudentDao studentrDao = new StudentDaoImpl(); containerBeans.put("studentDao", studentrDao); System.out.println("----放入对象-studentDao---"); containerBeans.printAllbeanId(); System.out.println("\n"); //获得添加的对象 System.out.println("-----拿出来的studentDao-履行方法--syaName-"); StudentDao studentrDaoBean = (StudentDao) containerBeans.getBean("studentDao"); TeacherDao teacherDao = (TeacherDao) containerBeans.getBean("TeacherDao"); teacherDao.save(user); studentrDaoBean.syaName(); System.out.println("\n"); //删除对象测试 System.out.println("-----删除对象TeacherDao 和 UserDao--"); containerBeans.remove("TeacherDao"); containerBeans.remove("UserDao"); containerBeans.printAllbeanId(); } }

AspectCachBean:


package com.tgb.config; /** * @ClassName: AspectCachBean * @Description: * 缓存服务类,实现了对缓存的,前置,后置, 保存的方法 * @author [qmx] * @date * */ public class AspectCachBean { /** * * @Title: cacheBefore * @Description: 缓存前置增强方法 * @param @param proxy 代理参数 * @return void 返回类型 * @throws */ public void cacheBefore(Object proxy) { System.out.println("---这是切入 类AspectCachBean cacheBefore()-方法--"); } /** * * @Title: cacheAfter * @Description: 缓存后置增强方法 * @param @param proxy 返回的代理参数 * @return void 返回类型 * @throws */ public static void cacheAfter(Object proxy) { System.out.println("---这是切入 类AspectCachBean cacheAfter()-方法--"); } /** * @Title: cacheSave * @Description: 缓存保存方法 * @param @param proxy 代理参数 * @return void 返回类型 * @throws */ public void cacheSave(Object proxy){ System.out.println("---这是切入 类AspectCachBean cacheSave()-方法--"); } }

AspectCertifiyBean:


package com.tgb.config; /** * * @ClassName: AspectCertifiyBean * @Description: 认证服务类,提供了认证前, 认证后,认证保存的方法 * @author [qmx] * @date * */ public class AspectCertifiyBean { /** * * @Title: certifiyBefore * @Description: 认证前置增强方法 * @param @param proxy 被代理对象的参数 * @return void 返回类型 * @throws */ public void certifiyBefore(Object proxy) { System.out.println("---这是切入 类AspectCertifiyBean certifiyBefore()-方法--"); } /** * * @Title: certifyAfter * @Description: 认证后置增强方法 * @param @param proxy 被认证对象参数 * @return void 返回类型 * @throws */ public void certifyAfter(Object proxy) { System.out.println("---这是切入 类AspectCertifiyBean certifyAfter()-方法--"); } /** * * @Title: certifySave * @Description:认证保存增强方法 * @param @param proxy 被认证对象参数 * @return void 返回类型 * @throws */ public void certifySave(Object proxy) { System.out.println("---这是切入 类AspectCertifiyBean certifySave()-方法--"); } }

ClassPathXmlApplicationContext:


package com.tgb.config; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeSet; import org.jdom.Document; import org.jdom.Element; import org.jdom.input.SAXBuilder; /** * * @ClassName: ClassPathXmlApplicationContext * @Description: 容器组装类, 装载 业务容器 和 服务容器。 分别将其中的 颗粒装载到各自的 beans 中 * 同时提供了对容器的增删,遍历等公共方法 * @author [qmx] * @date * */ public class ClassPathXmlApplicationContext implements ContainerBeans { // 业务容器beans private Map<String, Object> businessBeans = new HashMap<String, Object>(); // 公共服务容器beans private Map<String, Object> aspectBeans = new HashMap<String, Object>(); //关系集合配置 private Map<String, Object> relationBeans = new HashMap<String, Object>(); // 设置是不是需要aop private boolean isAop = true; /** * * <p>Title: 构造函数 </p> * <p>Description: 构造函数加载所有配置文件,初始化每一个容器内对象</p> * @throws Exception */ public ClassPathXmlApplicationContext() throws Exception { SAXBuilder sb = new SAXBuilder(); // 扫描业务文档,将xml转为文档对象 Document businessDoc = sb.build(Thread.currentThread().getContextClassLoader() .getResourceAsStream("business.xml")); // 扫描切入文档,将xml转为文档对象 Document aspectDoc = sb.build(Thread.currentThread().getContextClassLoader() .getResourceAsStream("aspecbeans.xml")); //扫描关系文档,将xml转为文档对象 Document reliationDoc = sb.build(Thread.currentThread().getContextClassLoader() .getResourceAsStream("relationbean.xml")); // 设置切面容器 getAspectBeans(aspectDoc); // 设置业务容器bean getBusinessDoc(businessDoc); //关系集合设置 getRelationBeans(reliationDoc); } /*** * 设置业务容器装配 * * @param doc * @throws ClassNotFoundException * @throws IllegalAccessException * @throws InstantiationException * @throws SecurityException * @throws NoSuchMethodException * @throws InvocationTargetException * @throws IllegalArgumentException */ private void getBusinessDoc(Document businessDoc) throws InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException { Element root = businessDoc.getRootElement();//读取文档根目录 List list = root.getChildren("bean"); // 调用设备对象方法,装配业务对象 putAllBeans(list, businessBeans); } /** * 获得关系集合中 业务容器与切面容器的关系 * @param reliationDoc */ private void getRelationBeans(Document reliationDoc) { Element root = reliationDoc.getRootElement(); //读取文档根目录 Element aopElement = (Element) root.getChildren("aop").get(0); //获得aop节点信息 isAop = Boolean.parseBoolean(aopElement.getAttributeValue("isaop")); //aop节点属性 List aopBeforeList = root.getChildren("aspectbefore");// 前置增强节点 List aopAfterList = root.getChildren("aspectafter");//后置增强 //辨别增强节点是不是有配置,放入bean关系容器 if (aopBeforeList != null) { relationBeans.put("aspectbefore", aopBeforeList); } if (aopAfterList != null) { relationBeans.put("aspectafter", aopAfterList); } } /** * 设置切入容器装配对象 * * @param aspectDoc 切入配置文件 * @throws InstantiationException * @throws IllegalAccessException * @throws ClassNotFoundException * @throws InvocationTargetException * @throws IllegalArgumentException * @throws SecurityException * @throws NoSuchMethodException */ private void getAspectBeans(Document aspectDoc) throws InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException { Element root = aspectDoc.getRootElement(); List aspectElements = root.getChildren("aspectbean");// 读取切入配置文件 putAllBeans(aspectElements, aspectBeans); } /** * 对象装配方法 * * @param list * 读取的配置文件 * @param allBeans * 设置装配的容器对象 * @throws InstantiationException * @throws IllegalAccessException * @throws ClassNotFoundException * @throws NoSuchMethodException * @throws SecurityException * @throws IllegalArgumentException * @throws InvocationTargetException */ public void putAllBeans(List list, Map<String, Object> allBeans) throws InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException { for (int i = 0; i < list.size(); i++) { //获得传入父亲节点中的每一个子节点,为行element Element element = (Element) list.get(i); //获得子节点中的id属性 String id = element.getAttributeValue("id"); //获得子节点中的class属性 String clazz = element.getAttributeValue("class"); //实例化class Object o = Class.forName(clazz).newInstance(); //将实例化的class放入容器 allBeans.put(id, o); //for循环获得 bean中的 属性property for (Element propertyElement : (List<Element>) element .getChildren("property")) { //获得property属性中的name属性 String name = propertyElement.getAttributeValue("name"); // userDAO //获得property属性中的ref属性 String bean = propertyElement.getAttributeValue("ref"); // //获得子属性的试题 Object beanObject = allBeans.get(bean);// UserDAOImpl //调用 依赖实体中的set方法(为籽实体的方法) String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1); // 获得依赖注入对象的父类,如: userDaoImp 获得的为祈父类接口 userDao{eanObject.getClass().getInterfaces()[0]} Method m = o.getClass().getMethod(methodName, //若依赖对象没有父类接口,该方法中的参数需要修改成类本身援用 beanObject.getClass().getInterfaces()[0]); m.invoke(o, beanObject); //调用o中 set方法,设置注入对象 } } } /** * 获得容器中指定对象 * * @param id * 对象名称如: getBean("user") * @return */ public Object getBean(String id) { //读取是不是配置aop节点属性,若是返回aop代理类 if (!isAop) { return businessBeans.get(id); } return new JDKDynamicProxy(businessBeans.get(id), aspectBeans, businessBeans,relationBeans) .getProxy(); } /** * 容器中放入对象 * * @param k * @param v * @return */ public Object put(String k, Object v) { return businessBeans.put(k, v); } /** * 打印容器中所有对象类型 */ public void printTypeName() { Set<String> hashSet = new HashSet<String>(); //set集合获得map中的所有值 Set<Entry<String, Object>> entryset = businessBeans.entrySet(); { //iterator获得迭代属性 Iterator iterator = entryset.iterator(); //while循环获得每一个值 while (iterator.hasNext()) { Entry<String, Object> entry = (Entry<String, Object>) iterator.next(); hashSet.add(entry.getValue().getClass().getSimpleName()); } } for (String setType : hashSet) { System.out.println(setType); } } /** * 获得容器中所有对象 * * @return Map<string(对象类型),Object(对象)> */ public Map<String, Object> getAllBean() { Map<String, Object> beanList = new HashMap<String, Object>(); //获得得带属性 Iterator iterator = businessBeans.entrySet().iterator(); //while循环获得每一个值 while (iterator.hasNext()) { Entry<String, Object> entry = (Entry<String, Object>) iterator.next(); beanList.put(entry.getValue().getClass().getSimpleName(), entry.getValue()); } return beanList; } /*** * 删除指定对象 */ public void remove(String id) { businessBeans.remove(id); } /*** * 打印所有注入对象 */ public void printAllbeanId() { Set<Entry<String, Object>> entryset = businessBeans.entrySet(); Set<String> linkSet = new TreeSet<String>(); { Iterator iterator = entryset.iterator(); while (iterator.hasNext()) { Entry<String, Object> entry = (Entry<String, Object>) iterator.next(); linkSet.add(entry.getKey()); // System.out.println(entry.getKey()); } System.out.println("容器中含有对象是" + this.size() + "个,分别是:"); System.out.println(linkSet.toString()); } } /** * 获得容器中对象的个数 */ public int size() { return businessBeans.size(); } }


ContainerBeans:


package com.tgb.config; import java.util.Map; /** * * @ClassName: ContainerBeans * @Description: 容器接口,提供容器公共服务方法, 增加,删除,遍历,获得对象,遍历类型,容器大小等方法 * @author [qmx] * @date * */ public interface ContainerBeans { /** * 获得容器中指定对象 * * @param id * 对象名称如: getBean("user") * @return */ public Object getBean(String id); /** * 容器中放入对象 * * @param k * @param v * @return */ public Object put(String k, Object v); /** * 打印容器中所有对象类型 */ public void printTypeName(); /** * 获得容器中所有对象 返回类型 Map<string(对象类型),Object(对象)> * @return Map<string(对象类型),Object(对象)> */ public Map<String, Object> getAllBean(); /** * 获得容器所有bean */ public void printAllbeanId(); /** * * @Title: remove * @Description: 删除容器指定对象 * @param @param id 删除对象的id * @return void 返回类型 * @throws */ public void remove(String id); /** * 容器中对象的数量 * @return */ public int size(); }

JDKDynamicProxy:


package com.tgb.config; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.jdom.Element; /** * * @ClassName: JDKDynamicProxy * @Description: AOP实现对业务容器 的增强,对业务容器中每一个对象增强 服务类中的方法,根据 关系容器配置, * 实现特性方法增强 * @author [qmx] * @date * */ public class JDKDynamicProxy implements InvocationHandler { private Object target;//被代理对象 private Map<String, Object> aspectBeans; // 服务容器 private Map<String, Object> businessBeans;// 业务容器 private Map<String, Object> relationBeans;// 关系容器 /*** * * @param target * 被代理对象 * @param aspectBeans * 切容器 * @param businessBeans * 业务容器 * @param relationBeans * 关系集合 */ public JDKDynamicProxy(Object target, Map<String, Object> aspectBeans, Map<String, Object> businessBeans, Map<String, Object> relationBeans) { this.target = target; this.aspectBeans = aspectBeans; this.businessBeans = businessBeans; this.relationBeans = relationBeans; } /** * @Title: getProxy * @Description: 创建被代理对象 * @param @return * @return T 返回类型 被代理对象 类型 * @throws */ @SuppressWarnings("unchecked") public <T> T getProxy() { return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target .getClass().getInterfaces(), this); } // 回调注册切入对象方法 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { List beforeList = (List) relationBeans.get("aspectbefore");// 获得关系容器中的关系 invokeAspectName(beforeList, method, args);// 调用切面类中匹配方法 Object result = method.invoke(target, args);// 调用 被代理类本身方法 return result; } /** * 拦截方法匹配规则 * * @param beforeList * 拦截器的所有对象 * @param method * @param args * @throws NoSuchMethodException * @throws SecurityException * @throws IllegalAccessException * @throws IllegalArgumentException * @throws InvocationTargetException */ public void invokeAspectName(List beforeList, Method method, Object[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { //判断关系容器中是不是指定了特性方法拦截,若无,则对 代理对象 履行所有服务类方法增强 if (beforeList != null && beforeList.size() != 0) { for (int i = 0; i < beforeList.size(); i++) { Element element = (Element) beforeList.get(i); String aspectClass = element.getAttributeValue("aspectId");// 获得容器中切入类名称 String aspectName = element.getAttributeValue("aspectMethod");// 履行的切入方法 if (aspectBeans.get(aspectClass) == null) { System.out.println("未找到" + aspectClass + "切入类,请查看配置的切入类名称是不是正确"); return; } Class clazz = aspectBeans.get(aspectClass).getClass(); // 获得切入类 String elementMethod = element.getAttributeValue("method");// 获得被切入类方法 //1 关系容器中某个拦截配置,若未声明履行某个服务类中的方法,则履行所有服务类中方法, //2 若未指定某个拦截方法,但指定了被拦截对象的方法,则所有服务类只对该方法拦截 //3 if (aspectName == null) { //声明了拦截某个对象方法,履行指定方法拦截 if (method.getName() != null) { if (method.getName().equals(elementMethod)) { //,履行该服务类中所有方法 getAllMethod(clazz, aspectClass, args); } } //履行所有服务类中的所有方法 aspactAllClass(aspectClass, args == null ? new Object[1] : args); } else { // 声明切入方法,则履行指定切入方法 if (method.getName().equals(elementMethod)) { Method jinectmethod = clazz.getMethod(aspectName, Object.class); // 反射调用切入类方法 jinectmethod.invoke(aspectBeans.get(aspectClass), args == null ? new Object[1] : args); } aspactAllClass(aspectClass, args == null ? new Object[1] : args); } } } else { //默许履行所有服务类中的方法增强 Iterator aspectClass = aspectBeans.entrySet().iterator(); while (aspectClass.hasNext()) { Entry<String, Object> entry = (Entry<String, Object>) aspectClass.next(); Class clazz = entry.getValue().getClass();// 获得服务类 // 获得服务类中的所有公共方法 Method[] methods = clazz.getDeclaredMethods(); for (int j = 0; j < methods.length; j++) { // 反射获得服务类中每一个方法名称,获得该服务类方法 Method jinectmethod = clazz.getMethod(methods[j].getName(), Object.class); jinectmethod.invoke(entry.getValue(), args == null ? new Object[1] : args); } } } } /** * * @Title: aspactAllClass * @Description: 除本身,履行其他所有服务类中方法。 * @param @param aspectId * @param @param args * @param @throws NoSuchMethodException * @param @throws SecurityException * @param @throws IllegalAccessException * @param @throws IllegalArgumentException * @param @throws InvocationTargetException 设定文件 * @return void 返回类型 * @throws */ public void aspactAllClass(String aspectId, Object[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Iterator aspectClass = aspectBeans.entrySet().iterator(); while (aspectClass.hasNext()) { Entry<String, Object> entry = (Entry<String, Object>) aspectClass.next(); // if (!aspectId.equals(entry.getKey())) { Class clazz = entry.getValue().getClass();// 获得切入类 Method[] methods = clazz.getDeclaredMethods(); for (int j = 0; j < methods.length; j++) { // 反射获得服务类中每一个方法名称,获得该服务类方法 Method jinectmethod = clazz.getMethod(methods[j].getName(), Object.class); // 反射调用切入类方法 jinectmethod.invoke(entry.getValue(), args == null ? new Object[1] : args); } } } } /** * * @Title: getAllMethod * @Description: 履行某个服务类中的所有方法, * @param @param clazz 服务类 * @param @param aspectClass aop关系集合中设定履行 拦截的方法 * @param @param args 被拦截对象的参数 * @param @throws IllegalAccessException * @param @throws IllegalArgumentException * @param @throws InvocationTargetException * @param @throws NoSuchMethodException * @param @throws SecurityException 设定文件 * @return void 返回类型 * @throws */ public void getAllMethod(Class clazz, String aspectClass, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { // 获得服务类中的所有公共方法 Method[] methods = clazz.getDeclaredMethods(); for (int j = 0; j < methods.length; j++) { // 反射获得服务类中每一个方法名称,获得该服务类方法 Method jinectmethod = clazz.getMethod(methods[j].getName(), Object.class); // 反射调用服务类中方法 jinectmethod.invoke(aspectBeans.get(aspectClass), args == null ? new Object[1] : args); } } }

StudentDao:


package com.tgb.dao; /** * studendao,打印方法 * @ClassName: StudentDao * @Description: TODO(这里用1句话描写这个类的作用) * @author [qmx] * @date * */ public interface StudentDao { public void syaName(); }

TeacherDao:


package com.tgb.dao; import com.tgb.domain.User; /* * TeacherDao对象,增加删除,保存方法 */ public interface TeacherDao { void save(User user); void update(User user); public void delete( User user); }

UserDao:


package com.tgb.dao; import com.tgb.domain.User; /* * userdao对象,增加删除,保存方法 */ public interface UserDao { void save(User user); void update(User user); public void delete( User user); }

StudentDaoImpl:


package com.tgb.daoImpl; import com.tgb.dao.StudentDao; /* * StudentDaoImpl对象,打印方法 */ public class StudentDaoImpl implements StudentDao { @Override public void syaName() { System.out.println("----my name is hanyk--"); } }

TeacherDaoImpl:


package com.tgb.daoImpl; import com.tgb.dao.TeacherDao; import com.tgb.dao.UserDao; import com.tgb.domain.User; /* * TeacherDaoImpl对象,增加删除,保存方法 */ public class TeacherDaoImpl implements TeacherDao { @Override public void save(User user) { System.out.println( "这是业务类 "+this.getClass()+"-----的 save()方法-----"); } @Override public void update(User user) { System.out.println( "这是业务类 "+this.getClass()+"-----的 update()方法-----"); } @Override public void delete(User user) { System.out.println( "这是业务类 "+this.getClass()+"-----的 delete()方法-----"); } }

UserDaoImpl:


package com.tgb.daoImpl; import com.tgb.dao.UserDao; import com.tgb.domain.User; /* * userdao对象,增加删除,保存方法 */ public class UserDaoImpl implements UserDao { @Override public void save(User user) { System.out.println( "这是业务类 "+this.getClass()+"-----的 userDao.save()方法-----"); } @Override public void update(User user) { System.out.println( "这是业务类 "+this.getClass()+"-----的 userDao.update()方法-----"); } @Override public void delete(User user) { System.out.println( "这是业务类 "+this.getClass()+"-----的 userDao.delete()方法-----"); } }

User:


package com.tgb.domain; public class User { private String userName; private String password; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }

aspectbeans.xml:


<!--该文件 对是服务类的配置,所有服务类都需要在该文件进行注册--> <beans> <!--服务类配置容器,配置缓存服务类,全名称 --> <aspectbean id="aspectCachBean" class="com.tgb.config.AspectCachBean"></aspectbean> <!-- <aspectbean id="aspectCertifiyBean" class="com.tgb.configra.AspectCertifiyBean"></aspectbean> --> </beans>

business.xml:


<!--该文件是业务类的配置--> <beans> <!--用户bean注入--> <bean id="UserDao" class="com.tgb.daoImpl.UserDaoImpl" /> <!--教师bean注入--> <bean id="TeacherDao" class="com.tgb.daoImpl.TeacherDaoImpl" /> </beans>

relationbean.xml:


<!--aop关系配置文件--> <beans> <!-- 是不是启用aop --> <aop isaop="true"></aop> <!-- 配置业务颗粒和服务类的增强关系method为业务类匹配的方法,可用正则表达式进行(未实现) ,aspectMethod为服务类拦截方法 --> <aspectbefore aspectId="aspectCachBean" method="update" aspectMethod="cacheSave" ></aspectbefore> </beans>









生活不易,码农辛苦
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠
程序员人生
------分隔线----------------------------
分享到:
------分隔线----------------------------
关闭
程序员人生