java 切面_实用|AOP切面编程手段大汇总

c6a23ee22cb7b62d57397ffc4aa14c7b.gif

点击上方"欧学长的架构成长之路" 关注我

前言

首先说一下什么是AOP?

        AOP就是面向切面编程,它是一个思想,通过切面,我们可以将那些反复出现的代码抽取出来,放在一个地方统一处理,提高代码的复用性。AOP的一大好处就是解耦。以下几种方式实现AOP:

1自定义注解+@Aspect

2拦截器

3过滤器

4.JDK动态代理和CGlib

5.设计模型--静态代理

*.基于非侵入式运行时AOP方案(篇幅问题,不细说,感兴趣的朋友可以自行百度阿里开源的jvm-Sandbox)

自定义注解+@Aspect 实现日志记录

1.首先你需要先引入pom依赖。(springboot2.x默认使用的代理是cglib代理)

<dependency>    <groupId>org.springframework.bootgroupId>    <artifactId>spring-boot-starter-aopartifactId>dependency><dependency>    <groupId>com.google.code.gsongroupId>    <artifactId>gsonartifactId>    <version>2.8.5version>dependency>

注意: 

  •     在application.properties中也不需要添加spring.aop.auto=true,这个默认就是true,值为true就是启用@EnableAspectJAutoProxy注解了。 

  •    你不需要手工添加在启动类上添加 @EnableAspectJAutoProxy 注解。 

  •   当你需要使用CGLIB来实现AOP的时候,需要配置spring.aop.proxy-target-class=true,这个默认值是false,不然默认使用的是标准Java的实现(JDK动态代理基于接口代理)。

    2.自定义日志注解(使用Java元注解,Java5.0定义了4个标准的meta-annotation类型)

@Retention(RetentionPolicy.RUNTIME) //定义为运行时使用注解@Target({ElementType.METHOD})//在方法上使用注解@Documented//注解将包含javaDoc中public @interface WebLog {    /**     * 日志描述信息     * @return     */     //定义一个属性,默认作为空字符串    String description() default "";}

3.配置AOP切面类

@Aspect@Component //将这个类交给Spring管理public class WebLogAspect {    private final static Logger logger = LoggerFactory.getLogger(WebLogAspect.class);    /** 换行符 */    private static final String LINE_SEPARATOR = System.lineSeparator();    /** 以自定义 @WebLog 注解为切点 */    @Pointcut("@annotation(site.exception.aspect.WebLog)") //<=全路径    public void webLog() {}    /**     * 在切点之前织入     * @param joinPoint     * @throws Throwable     */    @Before("webLog()")    public void doBefore(JoinPoint joinPoint) throws Throwable {        // 开始打印请求日志        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();        HttpServletRequest request = attributes.getRequest();        // 获取 @WebLog 注解的描述信息        String methodDescription = getAspectLogDescription(joinPoint);        // 打印请求相关参数        logger.info("========================================== Start ==========================================");        // 打印请求 url        logger.info("URL: {}", request.getRequestURL().toString());        // 打印描述信息        logger.info("Description : {}", methodDescription);        // 打印 Http method        logger.info("HTTP Method : {}", request.getMethod());        // 打印调用 controller 的全路径以及执行方法        logger.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());        // 打印请求的 IP        logger.info("IP : {}", request.getRemoteAddr());        // 打印请求入参        logger.info("Request Args : {}", new Gson().toJson(joinPoint.getArgs()));    }    /**     * 在切点之后织入     * @throws Throwable     */    @After("webLog()")    public void doAfter() throws Throwable {        // 接口结束后换行,方便分割查看        logger.info("=========================================== End ===========================================" + LINE_SEPARATOR);    }    /**     * 环绕     * @param proceedingJoinPoint     * @return     * @throws Throwable     */    @Around("webLog()")    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {        long startTime = System.currentTimeMillis();       //执行切点后,会去依次调用 @Before -> 接口逻辑代码(之后,执行完doAround方法) -> @After -> @AfterReturning;        Object result = proceedingJoinPoint.proceed();        // 打印出参        logger.info("Response Args  : {}", new Gson().toJson(result));        // 执行耗时        logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);        return result;    }    /**     * 获取切面注解的描述     *     * @param joinPoint 切点     * @return 描述信息     * @throws Exception     */    public String getAspectLogDescription(JoinPoint joinPoint)            throws Exception {        String targetName = joinPoint.getTarget().getClass().getName();        String methodName = joinPoint.getSignature().getName();        Object[] arguments = joinPoint.getArgs();        Class targetClass = Class.forName(targetName);        Method[] methods = targetClass.getMethods();        StringBuilder description = new StringBuilder("");        for (Method method : methods) {            if (method.getName().equals(methodName)) {                Class[] clazzs = method.getParameterTypes();                if (clazzs.length == arguments.length) {                    description.append(method.getAnnotation(WebLog.class).description());                    break;                }            }        }        return description.toString();    }}

4.使用注解

@PostMapping("/user")@WebLog(description="用户请求接口")public User userLogin(@RequestBody User user){  logger.info("user login ...");  return user; }

特别说明

d55161d23e7307445dd361ad50d87250.png

多切面如何指定优先级?

假设说我们的服务中不止定义了一个切面,比如说我们针对 Web 层的接口,不止要打印日志,还要校验 token 等。要如何指定切面的优先级呢?也就是如何指定切面的执行顺序?

我们可以通过 @Order(i)注解来指定优先级,注意:i 值越小,优先级则越高。

假设说我们定义上面这个日志切面的优先级为 @Order(10), 然后我们还有个校验 token 的切面 CheckTokenAspect.java,我们定义为了 @Order(11), 那么它们之间的执行顺序如下

a68365db00910c8b6441965e6e74c155.png

         spring借鉴了AspectJ的切面,以提供注解驱动的AOP,本质上它依然是Spring基于代理的AOP,只是编程模型与AspectJ完全一致,这种风格的好处就是不需要使用XML进行配置。

通过拦截器实现

拦截器拦截的是URL

拦截器有三个方法,相对于过滤器更加细致,有被拦截逻辑执行前、后等。Spring中拦截器有三个方法:preHandle,postHandle,afterCompletion。

@Configurationpublic class HomeOpenHandlerConfigration extends WebMvcConfigurerAdapter {    //关键,将拦截器作为bean写入配置中    @Bean    public HomeOpenInterceptor myInterceptor(){        return new HomeOpenInterceptor();    }        @Override    public void addInterceptors(InterceptorRegistry registry) {        registry.addInterceptor(myInterceptor()).addPathPatterns("/api/open/portal/**")        .excludePathPatterns("/api/open/footerInfo").excludePathPatterns("/api/open/portal/template/default");        super.addInterceptors(registry);    }}
/** * 首页外放拦截器 * */@Componentpublic class HomeOpenInterceptor extends HandlerInterceptorAdapter {        @Autowired    private PortalCommonService portalCommonService;    @Autowired    private ApplicationProperties applicationProperties;        @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)            throws Exception {        //判断是否需要拦截        Boolean flag = false;        if(flag){            //判断是否允许不登录的情况下 访问主页            //如果不允许匿名访问返回401               throw new UnauthenticatedException();        }        //否则允许直接放过,不进行任何拦截        return true;    }}

过滤器的实现

过滤器拦截的是URL

Spring中自定义过滤器(Filter)一般只有一个方法,返回值是void,当请求到达web容器时,会探测当前请求地址是否配置有过滤器,有则调用该过滤器的方法(可能会有多个过滤器),然后才调用真实的业务逻辑,至此过滤器任务完成。过滤器并没有定义业务逻辑执行前、后等,仅仅是请求到达就执行。

特别注意:过滤器方法的入参有request,response,FilterChain,其中FilterChain是过滤器链,使用比较简单,而request,response则关联到请求流程,因此可以对请求参数做过滤和修改,同时FilterChain过滤链执行完,并且完成业务流程后,会返回到过滤器,此时也可以对请求的返回数据做处理。

@Component@Order(1) //注解,配合 @WebFilter 注解使用,用于多个过滤器时定义执行顺序,值越小越先执行。@WebFilter(urlPatterns = "/*", filterName = "test")public class TestFilter implements Filter {   @Override  public void init(FilterConfig arg0) throws ServletException {    System.out.println("过滤器初始化");  }   @Override  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)      throws IOException, ServletException {    System.out.printf("过滤器实现");    System.out.println(((HttpServletRequest) servletRequest).getRequestURI());    filterChain.doFilter(servletRequest, servletResponse);  }   @Override  public void destroy() {    System.out.println("过滤器销毁了");  } }

3.JDK动态代理 

@SuppressWarnings("restriction")public class JavaProxyTest {    public static void main(String[] args) throws Exception {        JavaProxyInterface javaProxyInterface = new ConcreteClass();                JavaProxyInterface newJavaProxyInterface = (JavaProxyInterface) Proxy.newProxyInstance(                JavaProxyTest.class.getClassLoader(), new Class[] { JavaProxyInterface.class },                new MyInvocationHandler(javaProxyInterface));        //这里可以看到这个类以及被代理,在执行方法前会执行aopMethod()。这里需要注意的是oneDay()方法和oneDayFinal()的区别。oneDayFinal的方法aopMethod执行1次,oneDay的aopMethod执行1次        newJavaProxyInterface.gotoSchool();        newJavaProxyInterface.gotoWork();        newJavaProxyInterface.oneDayFinal();        newJavaProxyInterface.oneDay();    }}/*** InvocationHandler 的一个实现,实际上处理代理的逻辑在这里*/class MyInvocationHandler implements InvocationHandler {    JavaProxyInterface javaProxy;    public MyInvocationHandler(JavaProxyInterface javaProxy) {        this.javaProxy = javaProxy;    }    private void aopMethod() {        System.out.println("before method");    }  //继承方法,代理时实际执行的犯法,如果要实现原方法,则需要调用method.invoke(javaProxy, args),这里还调用了一个aopMethod(),可以类比于Spring中的切面before注解。    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        aopMethod();        return method.invoke(javaProxy, args);    }}/*** 需要一个最顶层接口,必须*/interface JavaProxyInterface {    void gotoSchool();    void gotoWork();    void oneDay();    void oneDayFinal();}/*** 需要被代理的类,实现了顶层接口,非必须*/class ConcreteClass implements JavaProxyInterface {    @Override    public void gotoSchool() {        System.out.println("gotoSchool");    }    @Override    public void gotoWork() {        System.out.println("gotoWork");    }    @Override    public void oneDay() {        gotoSchool();        gotoWork();    }    @Override    public final void oneDayFinal() {        gotoSchool();        gotoWork();    }}

底层实现部分代码:

// proxyName 为类名,interfaces为顶层接口Classbyte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces); File file = new File("D:/testProxy/Ddd.class");FileOutputStream fileOutputStream = new FileOutputStream(file);fileOutputStream.write(bs);fileOutputStream.flush();fileOutputStream.close();

CGlib动态代理

public class CglibProxyTest {    public static void main(String[] args) throws Exception {        CglibTestSon CglibTestSon = new CglibTestSon();        Enhancer enhancer = new Enhancer();        Callback s = new MthdInvoker(CglibTestSon);        enhancer.setSuperclass(CglibTestSon.class);        Callback callbacks[] = new Callback[] { s };        enhancer.setCallbacks(callbacks);        CglibTestSon CglibTestSon2 = (CglibTestSon) enhancer.create();        CglibTestSon2.gotoHome();        CglibTestSon2.gotoSchool();    //这里可以看到这个类以及被代理,在执行方法前会执行aopMethod()。这里需要注意的是oneDay()方法和onedayFinal()的区别。onedayFinal的方法aopMethod执行2次,oneDay的aopMethod执行1次 ,注意这里和jdk的代理的区别        CglibTestSon2.oneday();        CglibTestSon2.onedayFinal();    }}/*** 需要被代理的类,不需要实现顶层接口*/class CglibTestSon {    public CglibTestSon() {    }    public void gotoHome() {        System.out.println("============gotoHome============");    }    public void gotoSchool() {        System.out.println("===========gotoSchool============");    }    public void oneday() {        gotoHome();        gotoSchool();    }    public final void onedayFinal() {        gotoHome();        gotoSchool();    }}/*** 可以类比于jdk动态代理中的InvocationHandler ,实际上被代理后重要的类,实际上后续执行的就是intercept里的方法,如果需要执行原来的方法,则调用 method.invoke(s, args);这里也加了一个aopMethod();*/class MthdInvoker implements MethodInterceptor {    private CglibTestSon s;    public MthdInvoker(CglibTestSon s) {        this.s = s;    }    private void aopMethod() {        System.out.println("i am aopMethod");    }    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {        aopMethod();        Object a = method.invoke(s, args);        return a;    }}

CGlib底层实现部分代码:

byte[] bs = DefaultGeneratorStrategy.INSTANCE.generate(enhancer);FileOutputStream fileOutputStream = new FileOutputStream("D:/testProxy/Cc.class");fileOutputStream.write(bs);fileOutputStream.flush();fileOutputStream.close();

    简单来看就是先生成新的class文件,然后加载到jvm中,然后使用反射,先用class取得他的构造方法,然后使用构造方法反射得到他的一个实例。

标红的是最复杂的。然后cglib的实现原理基本一致,唯一的区别在于生成新的class文件方式和结果不一样。

4.静态代理模式的实现AOP

Font.java

package com.java.proxy; import lombok.Data; @Datapublic class Font {   private String name;}

FontProvider.java

package com.java.proxy;  public interface FontProvider {    Font getFont(String name);    void printName(String name);}

代理类CachedFontProvider.java

/** * 给FontProvider的getFont添加缓存功能,用静态代理来实现 * */public class CachedFontProvider implements FontProvider {   private FontProvider fontProvider;    private Map cached = new HashMap();      public CachedFontProvider() {    fontProvider = new FontProviderFromDisk();  }     @Override  public Font getFont(String name) {    System.out.println("静态代理getFont()");    Font font = cached.get(name);    if(font == null) {      font = fontProvider.getFont(name);      cached.put(name, font);    }    return font;  }     @Override  public void printName(String name) {    System.out.println("静态代理printName()");    fontProvider.printName(name);;      }   }

工厂类ProviderFactory.java

/** * 每个字体都增加了缓存功能,其实工厂就是用的缓存字体提供器,跟io一样 * 使用代理(静态),已经避免了再去修改每个字体提供器(这违反了开闭原则,而且工作量很大,容易出错;而且如果要增加别的功能 * 比如日志打印,权限检查,异常处理,每个都要去修改,代码重复,而且很麻烦) *  * ② 然而为什么要用动态代理? *考虑以下各种情况,有多个提供类,每个类都有getXxx(String name)方法, *每个类都要加入缓存功能,使用静态代理虽然也能实现,但是也是略显繁琐,需要手动一一创建代理类。 */public class ProviderFactory {   public static FontProvider getFontProvider() {    return new CachedFontProvider();  }  }

测试类;

public class Business {  public static void main(String[] args) {     FontProvider fontProvider = ProviderFactory.getFontProvider();          Font font = fontProvider.getFont("微软雅黑");          System.out.println(font);          fontProvider.printName("代理模式实现AOP");  } }

总结

        三者功能类似,但各有优势,从过滤器--》拦截器--》切面,拦截规则越来越细致,执行顺序依次是过滤器、拦截器、切面。一般情况下数据被过滤的时机越早对服务的性能影响越小,因此我们在编写相对比较公用的代码时,优先考虑过滤器,然后是拦截器,最后是aop。比如权限校验,一般情况下,所有的请求都需要做登陆校验,此时就应该使用过滤器在最顶层做校验;日志记录,一般日志只会针对部分逻辑做日志记录,而且牵扯到业务逻辑完成前后的日志记录,因此使用过滤器不能细致地划分模块,此时应该考虑拦截器,然而拦截器也是依据URL做规则匹配,因此相对来说不够细致,因此我们会考虑到使用AOP实现,AOP可以针对代码的方法级别做拦截,很适合日志功能。

点个“在看”表示朕

已阅

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

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

相关文章

php可以控制硬件吗,控制面板的作用是设置硬件接口吗?

错误&#xff0c;控制面板的作用是对系统进行有关的设置。控制面板是一个系统文件夹&#xff0c;用来提供各种对计算机系统进行设置和管理的工具&#xff1b;使用控制面板可以对系统进行设置与管理&#xff0c;例如设置系统环境参数的默认值和属性&#xff0c;添加新的应用程序…

不知道工作组名称怎样加入_剩米饭不知道怎样做?试试泡菜炒饭,再也不用担心米饭做多了...

剩米饭不知道怎样做&#xff1f;试试泡菜炒饭&#xff0c;再也不用担心米饭做多了东北的朋友这几天连续经历了春天&#xff0c;初夏和冬天&#xff0c;甚至在一天中看到了雪、雨和冰雹。这变化莫测的天气让大家一时间慌了神&#xff0c;不知到底要穿些什么。由于楼上都已经停了…

php元素浮动会产生哪些影响,css浮动带来什么问题

css浮动带来的影响&#xff1a;1、由于浮动元素脱离了文档流&#xff0c;所以父元素的高度无法被撑开&#xff0c;影响了与父元素同级的元素&#xff1b;若没有给父元素设置高度&#xff0c;那么父元素就不会在显示屏上显示。2、浮动元素不再占用原文档流的位置&#xff0c;它会…

linux将字符串转小写_小猿圈总结Linux常见命令(一)

科技发展的今天&#xff0c;互联网不断的发达&#xff0c;很多人学习Linux云计算的时候回因为记不住一些命令从而去找度娘&#xff0c;有时候因为因为找不到linux的命令而烦恼&#xff0c;下面是小猿圈linux讲师给大家总结的linux常见命令&#xff0c;希望对你有所帮助。1、cd命…

cnpm安装webpack_Webpack(一)介绍

一、Webpack是什么、为什么要使用它简单来说&#xff0c;Webpack是一个打包工具。站在2018年的角度&#xff0c;成为一个优秀的前端工程师&#xff0c;除了要会写页面样式和动态效果之外&#xff0c;还需要会用主流的单页面框架、Node.js、简单的前端的性能优化等等。加上现在一…

bytes数组转string指定编码_一篇文章弄懂Python中所有数组数据类型

前言数组类型是各种编程语言中基本的数组结构了&#xff0c;本文来盘点下Python中各种“数组”类型的实现。listtuplearray.arraystrbytesbytearray其实把以上类型都说成是数组是不准确的。这里把数组当作一个广义的概念&#xff0c;即把列表、序列、数组都当作array-like数据类…

sklearn保存svm分类模型_【菜菜的sklearn】07 支持向量机(上)

小伙伴们大家好~o(&#xffe3;▽&#xffe3;)ブ&#xff0c;我是菜菜&#xff0c;这里是我的sklearn课堂第7期&#xff0c;今天分享的内容是支持向量机&#xff08;上&#xff09;&#xff0c;下周还有下篇哦~我的开发环境是Jupyter lab&#xff0c;所用的库和版本大家参考&a…

unity 敌人自动攻击和寻路_Unity暑期萌新入门:环境篇

大家好&#xff0c;新一期又跟大家见面了。上一节我们完成了角色的移动控制&#xff0c;然而John只能在空白的场景中移动。因此接下来这一节我们将添加关卡、调节光照&#xff0c;让John来到阴森的鬼屋。然后设置NavMesh(导航网格&#xff0c;现在先听个概念就好)&#xff0c;为…

element 表格宽度自适应_Java 设置Word中的表格自适应的3种方式

概述在Word创建表格时&#xff0c;可设置表格“自动调整”&#xff0c;有3种情况&#xff0c;通过Java程序设置可调用相应的方法来实现&#xff0c;即&#xff1a;根据内容调整表格AutoFitBehaviorType.Auto_Fit_To_Contents根据窗口调整表格AutoFitBehaviorType.Auto_Fit_To_W…

源码里没有configure_深入源码理解.NET Core中Startup的注册及运行

开发.NET Core应用&#xff0c;直接映入眼帘的就是Startup类和Program类&#xff0c;它们是.NET Core应用程序的起点。通过使用Startup&#xff0c;可以配置化处理所有向应用程序所做的请求的管道&#xff0c;同时也可以减少.NET应用程序对单一服务器的依赖性&#xff0c;使我们…

oracle查询慢怎么优化,Oracle查询优化-怎样建立索引优化下面的查询语句啊

下面是转换出来的查询语句SELECT *FROM (SELECT "Project1"."C1" AS "C1","Project1"."ID" AS "ID","Project1"."NVC_ORDERBY" AS "NVC_ORDERBY","Project1"."I_ST…

复试情报准备

英语自我介绍&#xff0c;介绍完老师会根据你的回答用英语问你问题&#xff0c;比如介绍一下你的本科学校&#xff0c;或者家乡什么的。计网过一遍&#xff0c;会问两道题。接下来是重点&#xff0c;我当时是根据我成绩单&#xff0c;问了我本科学过的科目&#xff0c;比如pyth…

oracle创建索引01652,建立数据表快照导致ora-01652异常

建立数据表快照导致ora-01652错误由于源表过大&#xff0c;数据查询速度较慢&#xff0c;在做后台的相关查询的时候较慢&#xff0c;于是决定创建数据快照&#xff0c;提高查询速度&#xff0c;快照创建语句如下&#xff1a;CREATE SNAPSHOT sn_ydmobilebankREFRESH COMPLETE S…

用python批量下载网络图片_python 批量下载网页里的图片

import requests import sys,re #设置提取图片url 的正则表达式 imgre re.compile(r" #存放找到的 图片url的列表 all_img_urls [] #图片下载后存放位置 save_path r‘/root‘ #获取指定网页中的图片url def get_img_url(tmpurl,tmpre,allimgurl,timeout10): headers …

alter table add column多个字段_利用Python将多个excel合并到一个文件中

数据岗位的小伙伴可能经常会遇到这样一个问题&#xff1a;多个来源返回的数据怎么整合到一个文件中&#xff1f;手动经常会出错&#xff0c;下面介绍一种利用Python处理的方式&#xff1a;前期准备&#xff1a;1、多个excel需要进行数据整理&#xff0c;保证文件的结构一致&…

结构体中vector自动为0_面试题:你是如何选择顺序存储数据结构的?

作者&#xff1a;Tarun Telang 来源&#xff1a;https://dzone.com/articles/arraylist-or-linkedlist本文为Java开发人员选择适当的顺序数据结构提供指导。ArrayList 和 LinkedList 是 Java 集合框架中用来存储对象引用列表的两个类。ArrayList 和 LinkedList 都实现 List 接口…

数字填图问题matlab上机实验报告,数学建模实验报告数字填图问题

数字填图问题一、实验目的及意义本实验旨在通过生活中几个常见的数字填图问题的探究&#xff0c;探究这类问题的逻辑推理解法和计算机解法&#xff0e;二、实验内容1. 数字填图的逻辑推理&#xff1b;2. 数字填图的计算机解法。三、实验步骤1.开启软件平台——MA TLAB&#xff…

c++ 函数指针_进化论——从函数指针到被结构封装的函数指针及参数的应用举例...

↑↑↑ 点击上方公众号名称关注&#xff0c;不放过任何转变的机会。✎ 编 者 悟 语借口再小也会瓦解人的意志。文 章 导 读今天带大家用下函数指针&#xff0c;然后将函数指针和函数参数封装到结构体中&#xff0c;接着将数据用动态分配和静态分配的方式赋值给相应的函数&#…

python怎么做软件界面_python – 如何自定义桌面应用程序的标题栏和窗口

我如何自定义标题栏(包括&#xff1a;关闭,最大化,最小化按钮,标题)和用PyQt编写的桌面应用程序框架,使其看起来像下面的图像&#xff1f;我需要一种方法来指定我想用于标题栏元素的颜色(按钮,文本标题和条形和按钮的背景颜色).我需要更改其窗口的代码&#xff1a; import sys …

python绘制社会关系网络图_Python networkx 网络图绘制

简单演示import networkx as nx import matplotlib.pyplot as plt # 定义空图 g nx.Graph() # 增加节点 g.add_node(1) g.add_node(A) g.add_nodes_from([2, 3]) g.add_edge(2, 3) g.add_edges_from([(1, 2), (1, 3)]) nx.draw(g, with_labelsTrue) plt.show() 一次增加多个点…