java ioc依赖注入,Spring bean的实例化和IOC依赖注入详解

2023-11-12

前言

我们知道,IOC是Spring的核心。它来负责控制对象的生命周期和对象间的关系。

举个例子,我们如何来找对象的呢?常见的情况是,在路上要到处去看哪个MM既漂亮身材又好,符合我们的口味。就打听她们的电话号码,制造关联想办法认识她们,然后...这里省略N步,最后谈恋爱结婚。

IOC在这里就像婚介所,里面有很多适婚男女的资料,如果你有需求,直接告诉它你需要个什么样的女朋友就好了。它会给我们提供一个MM,直接谈恋爱结婚,完美!

下面就来看Spring是如何生成并管理这些对象的呢?

1、方法入口

org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons()方法是今天的主角,一切从它开始。

public void preInstantiateSingletons() throws BeansException {

//beanDefinitionNames就是上一节初始化完成后的所有BeanDefinition的beanName

List beanNames = new ArrayList(this.beanDefinitionNames);

for (String beanName : beanNames) {

RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);

if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {

//getBean是主力中的主力,负责实例化Bean和IOC依赖注入

getBean(beanName);

}

}

}

2、Bean的实例化

在入口方法getBean中,首先调用了doCreateBean方法。第一步就是通过反射实例化一个Bean。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {

// Instantiate the bean.

BeanWrapper instanceWrapper = null;

if (mbd.isSingleton()) {

instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);

}

if (instanceWrapper == null) {

//createBeanInstance就是实例化Bean的过程,无非就是一些判断加反射,最后调用ctor.newInstance(args);

instanceWrapper = createBeanInstance(beanName, mbd, args);

}

}

3、Annotation的支持

在Bean实例化完成之后,会进入一段后置处理器的代码。从代码上看,过滤实现了MergedBeanDefinitionPostProcessor接口的类,调用其postProcessMergedBeanDefinition()方法。都是谁实现了MergedBeanDefinitionPostProcessor接口呢?我们重点看三个

AutowiredAnnotationBeanPostProcessor

CommonAnnotationBeanPostProcessor

RequiredAnnotationBeanPostProcessor

记不记得在Spring源码分析(一)Spring的初始化和XML这一章节中,我们说Spring对annotation-config标签的支持,注册了一些特殊的Bean,正好就包含上面这三个。下面来看它们偷偷做了什么呢?

从方法名字来看,它们做了相同一件事,加载注解元数据。方法内部又做了相同的两件事

ReflectionUtils.doWithLocalFields(targetClass, new ReflectionUtils.FieldCallback()

ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback()

看方法的参数,targetClass就是Bean的Class对象。接下来就可以获取它的字段和方法,判断是否包含了相应的注解,最后转成InjectionMetadata对象,下面以一段伪代码展示处理过程。

public static void main(String[] args) throws ClassNotFoundException {

Class> clazz = Class.forName("com.viewscenes.netsupervisor.entity.User");

Field[] fields = clazz.getFields();

Method[] methods = clazz.getMethods();

for (int i = 0; i < fields.length; i++) {

Field field = fields[i];

if (field.isAnnotationPresent(Autowired.class)) {

//转换成AutowiredFieldElement对象,加入容器

}

}

for (int i = 0; i < methods.length; i++) {

Method method = methods[i];

if (method.isAnnotationPresent(Autowired.class)) {

//转换成AutowiredMethodElement对象,加入容器

}

}

return new InjectionMetadata(clazz, elements);

}

InjectionMetadata对象有两个重要的属性:targetClass ,injectedElements,在注解式的依赖注入的时候重点就靠它们。

public InjectionMetadata(Class> targetClass, Collection elements) {

//targetClass是Bean的Class对象

this.targetClass = targetClass;

//injectedElements是一个InjectedElement对象的集合

this.injectedElements = elements;

}

//member是成员本身,字段或者方法

//pd是JDK中的内省机制对象,后面的注入属性值要用到

protected InjectedElement(Member member, PropertyDescriptor pd) {

this.member = member;

this.isField = (member instanceof Field);

this.pd = pd;

}

说了这么多,最后再看下源码里面是什么样的,以Autowired 为例。

ReflectionUtils.doWithLocalFields(targetClass, new ReflectionUtils.FieldCallback() {

@Override

public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {

AnnotationAttributes ann = findAutowiredAnnotation(field);

if (ann != null) {

if (Modifier.isStatic(field.getModifiers())) {

if (logger.isWarnEnabled()) {

logger.warn("Autowired annotation is not supported on static fields: " + field);

}

return;

}

boolean required = determineRequiredStatus(ann);

currElements.add(new AutowiredFieldElement(field, required));

}

}

});

ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() {

@Override

public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {

Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);

if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {

return;

}

AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);

if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {

if (Modifier.isStatic(method.getModifiers())) {

if (logger.isWarnEnabled()) {

logger.warn("Autowired annotation is not supported on static methods: " + method);

}

return;

}

if (method.getParameterTypes().length == 0) {

if (logger.isWarnEnabled()) {

logger.warn("Autowired annotation should be used on methods with parameters: " + method);

}

}

boolean required = determineRequiredStatus(ann);

PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);

currElements.add(new AutowiredMethodElement(method, required, pd));

}

}

});

4、依赖注入

前面完成了在doCreateBean()方法Bean的实例化,接下来就是依赖注入。

Bean的依赖注入有两种方式,一种是配置文件,一种是注解式。

4.1、 注解式的注入过程

在上面第3小节,Spring已经过滤了Bean实例上包含@Autowired、@Resource等注解的Field和Method,并返回了包含Class对象、内省对象、成员的InjectionMetadata对象。还是以@Autowired为例,这次调用到AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues()。

首先拿到InjectionMetadata对象,再判断里面的InjectedElement集合是否为空,也就是说判断在Bean的字段和方法上是否包含@Autowired。然后调用InjectedElement.inject()。InjectedElement有两个子类AutowiredFieldElement、AutowiredMethodElement,很显然一个是处理Field,一个是处理Method。

4.1.1 AutowiredFieldElement

如果Autowired注解在字段上,它的配置是这样。

public class User {

@Autowired

Role role;

}

protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {

//以User类中的@Autowired Role role为例,这里的field就是

//public com.viewscenes.netsupervisor.entity.Role com.viewscenes.netsupervisor.entity.User.role

Field field = (Field) this.member;

Object value;

DependencyDescriptor desc = new DependencyDescriptor(field, this.required);

desc.setContainingClass(bean.getClass());

Set autowiredBeanNames = new LinkedHashSet(1);

TypeConverter typeConverter = beanFactory.getTypeConverter();

try {

//这里的beanName因为Bean,所以会重新进入populateBean方法,先完成Role对象的注入

//value == com.viewscenes.netsupervisor.entity.Role@7228c85c

value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);

}

catch (BeansException ex) {

throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);

}

if (value != null) {

//设置可访问,直接赋值

ReflectionUtils.makeAccessible(field);

field.set(bean, value);

}

}

4.1.2 AutowiredFieldElement

如果Autowired注解在方法上,就得这样写。

public class User {

@Autowired

public void setRole(Role role) {}

}

它的inject方法和上面类似,不过最后是method.invoke。感兴趣的小伙伴可以去翻翻源码。

ReflectionUtils.makeAccessible(method);

method.invoke(bean, arguments);

4.2、配置文件的注入过程

先来看一个配置文件,我们在User类中注入了id,name,age和Role的实例。

在Spring源码分析(一)Spring的初始化和XML这一章节的4.2 小节,bean标签的解析,我们看到在反射得到Bean的Class对象后,会设置它的property属性,也就是调用了parsePropertyElements()方法。在BeanDefinition对象里有个MutablePropertyValues属性。

MutablePropertyValues:

//propertyValueList就是有几个property 节点

List propertyValueList:

PropertyValue:

name //对应配置文件中的name ==id

value //对应配置文件中的value ==1001

PropertyValue:

name //对应配置文件中的name ==name

value //对应配置文件中的value ==网机动车

上图就是BeanDefinition对象里面MutablePropertyValues属性的结构。既然已经拿到了property的名称和值,注入就比较简单了。从内省对象PropertyDescriptor中拿到writeMethod对象,设置可访问,invoke即可。PropertyDescriptor有两个对象readMethodRef、writeMethodRef其实对应的就是get set方法。

public void setValue(final Object object, Object valueToApply) throws Exception {

//pd 是内省对象PropertyDescriptor

final Method writeMethod = this.pd.getWriteMethod());

writeMethod.setAccessible(true);

final Object value = valueToApply;

//以id为例 writeMethod == public void com.viewscenes.netsupervisor.entity.User.setId(java.lang.String)

writeMethod.invoke(getWrappedInstance(), value);

}

5、initializeBean

在Bean实例化和IOC依赖注入后,Spring留出了扩展,可以让我们对Bean做一些初始化的工作。

5.1、Aware

Aware是一个空的接口,什么也没有。不过有很多xxxAware继承自它,下面来看源码。如果有需要,我们的Bean可以实现下面的接口拿到我们想要的。

//在实例化和IOC依赖注入完成后调用

private void invokeAwareMethods(final String beanName, final Object bean) {

if (bean instanceof Aware) {

//让我们的Bean可以拿到自身在容器中的beanName

if (bean instanceof BeanNameAware) {

((BeanNameAware) bean).setBeanName(beanName);

}

//可以拿到ClassLoader对象

if (bean instanceof BeanClassLoaderAware) {

((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader());

}

//可以拿到BeanFactory对象

if (bean instanceof BeanFactoryAware) {

((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);

}

if (bean instanceof EnvironmentAware) {

((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());

}

if (bean instanceof EmbeddedValueResolverAware) {

((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);

}

if (bean instanceof ResourceLoaderAware) {

((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);

}

if (bean instanceof ApplicationEventPublisherAware) {

((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);

}

if (bean instanceof MessageSourceAware) {

((MessageSourceAware) bean).setMessageSource(this.applicationContext);

}

if (bean instanceof ApplicationContextAware) {

((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);

}

......未完

}

}

做法如下:

public class AwareTest1 implements BeanNameAware,BeanClassLoaderAware,BeanFactoryAware{

public void setBeanName(String name) {

System.out.println("BeanNameAware:" + name);

}

public void setBeanFactory(BeanFactory beanFactory) throws BeansException {

System.out.println("BeanFactoryAware:" + beanFactory);

}

public void setBeanClassLoader(ClassLoader classLoader) {

System.out.println("BeanClassLoaderAware:" + classLoader);

}

}

//输出结果

BeanNameAware:awareTest1

BeanClassLoaderAware:WebappClassLoader

context: /springmvc_dubbo_producer

delegate: false

repositories:

/WEB-INF/classes/

----------> Parent Classloader:

java.net.URLClassLoader@2626b418

BeanFactoryAware:org.springframework.beans.factory.support.DefaultListableBeanFactory@5b4686b4: defining beans ...未完

5.2、初始化

Bean的初始化方法有三种方式,按照先后顺序是,@PostConstruct、afterPropertiesSet、init-method

5.2.1 @PostConstruct

这个注解隐藏的比较深,它是在CommonAnnotationBeanPostProcessor的父类InitDestroyAnnotationBeanPostProcessor调用到的。这个注解的初始化方法不支持带参数,会直接抛异常。

if (method.getParameterTypes().length != 0) {

throw new IllegalStateException("Lifecycle method annotation requires a no-arg method: " + method);

}

public void invoke(Object target) throws Throwable {

ReflectionUtils.makeAccessible(this.method);

this.method.invoke(target, (Object[]) null);

}

5.2.2 afterPropertiesSet

这个要实现InitializingBean接口。这个也不能有参数,因为它接口方法就没有定义参数。

boolean isInitializingBean = (bean instanceof InitializingBean);

if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {

if (logger.isDebugEnabled()) {

logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");

}

((InitializingBean) bean).afterPropertiesSet();

}

5.2.3 init-method

ReflectionUtils.makeAccessible(initMethod);

initMethod.invoke(bean);

6、注册

registerDisposableBeanIfNecessary()完成Bean的缓存注册工作,把Bean注册到Map中。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

java ioc依赖注入,Spring bean的实例化和IOC依赖注入详解 的相关文章

  • Java设计之道——通过UML理解23种设计模式

    文章目录 UML类图 泛化 generalize 与实现 realize 聚合 aggregation 与组合 composition 关联 association 与依赖 dependency 23种设计模式 创建型模式 结构型模式 行为
  • webpack使用CMD、AMD、CommonJS、ES6区别以及运用

    之前在网上浏览器的webpack模块化很乱 而且也没有把CMD模块化使用讲清楚的 终于忍不下去 将几种模块化以一种清晰的方式写出来 这也是我一直想做的事 下面就来讲讲CMD AMD commonJs es6模块化的运用 CMD CMD是se
  • 编译出现:failed to compile bitcode for ***: Debug: SDK path: /Applications/Xcode.app/Contents

    解决方法 MyProjectTarget gt Build Settings gt Enable Bitcode NO
  • Notepad++添加读取十六进制插件HexEditor

    下载地址 https github com chcg NPP HexEdit releases 安装方式 下载压缩包 例如 HexEditor 0 9 5 19 x64 zip 解压到本地目录 将文件夹更名为HexEditor 打开Note
  • Linux基础服务9——Tomcat

    文章目录 一 基本了解 二 安装tomcat 三 常用文件 3 1 bin目录文件 3 2 conf目录文件 3 2 1 server xml文件 3 2 2 tomcat users xml 文件 四 分离部署lamt架构 4 1 安装h
  • 螺旋矩阵的实现

    螺旋矩阵就是形如如下图的矩阵 看到矩阵知道它由四条边组成 这样的话就有这样的n 2个环 但是如果是奇数矩阵的话在最中间需要特殊处理一下 代码如下 include
  • 攻防世界之Web新手练习篇

    前言 天行健 君子以自强不息 地势坤 君子以厚德载物 周易 第一次接触CTF比较晚 学期已过近半 第一次做题就是在攻防世界 这里题目适合刚接触的萌新 写此篇博客 以为纪念 首页 目录 View source robots backup co
  • CentOS 初体验三: Yum 安装、卸载软件

    转载请标明出处 http blog csdn net zhaoyanjun6 article details 78894974 本文出自 赵彦军的博客 一 Yum 简介 Yum 全称为 Yellow dog Updater Modified
  • Unity3D服务器端使用PhysX计算物理

    本文的最终目的 当设计师在Unity3D中制作好游戏场景后 为Gameobject拖好Collider 通过我们写的工具导出这份场景的Collider配置 在服务器端能够生成一份一模一样的物理世界 从而由权威服务器去计算物理 诸如子弹有没有
  • FM/AM收音机原理

    收音机这东西很早就开始用了 但一直都没有了解过它的原理 听说是很简单 下面记录一些笔记 1 基本概念 收音机是一种小型的无线电接收机 主要用于接受无线电广播节目 收听无线电发射台 首先说一下收音机的种类 按解调方式和波长可以分为以下几类 调
  • VSCode加入Anaconda大家庭

    此文章首发于公众号Python for Finance 链接 https mp weixin qq com s rZsopAtS8UZ32aohWuhVBA 问题描述 我安装的是Anaconda 但最近发现VScode编辑ipynb非常好使
  • Ubuntu虚拟机找不到终端

    快捷键 Ctrl Alt T 即可打开终端窗口 或者按 Ctrl Alt F1 F6 均可进入终端 模拟终端 不显示桌面 想要右键打开终端 apt get install nautilus open terminal 注销系统重新登录 即可
  • 局域网场景下Android客户端实现同数据库连接通信

    实现 局域网场景下 Android客户端连接数据库 并可进行增删改查 直接连接 项目工程引入JDBC驱动 直接连接 JDBC是Java Data Base Connectivity的缩写 即 java数据库连接 一组Java语言编写的类和接
  • JXL(JExcelApi)相关资源

    1 主页 2 下载页面 Download JExcelApi 3 JXL API Online 4 全面挖掘Java Excel API 使用方法 1 5 全面挖掘Java Excel API 使用方法 2 6 使用JAVA透過JXL JE
  • 在虚拟机上安装macOS和Xcode

    最近要开发iOS软件 开发软件的操作系统需要是macOS 开发工具是Xcode 虽然实验室有苹果电脑 但是还是想在自己电脑上安装macOS虚拟机和Xcode软件 这样的话在宿舍或者在家都能开发 按照网上的教程成功安装了macOS和Xcode
  • git 提交分支报错(不能提交分支)

    git 提交新分支报错 报错信息如下 Compressing objects 100 100 100 done Writing objects 100 229 229 25 18 KiB 3 15 MiB s done Total 229
  • 淘宝x-sign算法demo示例

    用xposed hook这个方法就可以拿到对应的签名 需要可以留言 public String getMtopApiSign HashMap
  • 图文并茂:推荐算法架构——粗排

    导语 粗排是介于召回和精排之间的一个模块 是典型的精度与性能之间trade off的产物 理解粗排各技术细节 一定要时刻把精度和性能放在心中 本篇将深入重排这个模块进行阐述 一 总体架构 粗排是介于召回和精排之间的一个模块 它从召回获取上万
  • bilibili粉丝显示器

    代码地址 https github com hungtcs lab bilibili follower viewer ArduinoJSON https arduinojson org esp8266 oled ssd1306 https

随机推荐

  • Spirng @Conditional 条件注解的使用

    1 简介 Conditional 是 Spring 4 0 提出的一个新的注解 当标注的对象满足所有的条件时 才能注册为 Spring 中的 bean Conditional 源码中的定义如下 Target ElementType TYPE
  • C# Datagridview 标题完全居中

    DataGridView标题完全居中 标题文字居中 this dataGridView1 ColumnHeadersDefaultCellStyle Alignment DataGridViewContentAlignment Middle
  • Java 语言 TreeMap

    Java中的TreeMap是一种基于红黑树实现的排序映射表 它可以存储键值对 其中键和值都可以是任意类型的对象 TreeMap提供了快速的插入 删除和查找操作 具有高效的性能 并且可以根据键进行排序 因此在Java编程中非常常见 本文将介绍
  • 博士申请

    合适的工作难找 最新的招聘信息也不知道 AI 求职为大家精选人工智能领域最新鲜的招聘信息 助你先人一步投递 快人一步入职 南洋理工大学 南洋理工大学 Nanyang Technological University 是新加坡的一所世界著名研
  • 解决 Python 库安装提示:ModuleNotFoundError: No module named ‘windows‘. 问题解决方法

    在安装pyMouse PyKeyboard的时候报错我们可以尝试以下代码 应该有用 pip install PyUserInput
  • unity对象池系统

    当游戏场景中出现大量的可重复利用的物体时 通过Destory来销毁再创建会触发不必要的GC回收机制 浪费性能 我们可以利用unity自带的对象池系统 从而节约性能来得到同样的效果 为了使用这个对象池系统 我写了一个瞬间产生多枚子弹的测试脚本
  • 权限认证。。

    链接 手摸手 带你用vue撸后台 系列二 登录权限篇 掘金 juejin cn 前端权限控制 一 前端权限管理及动态路由配置方案 ONEO阿喔哟的博客 CSDN博客 检查员工是否具有特权 param requestTokenBO 请求令牌B
  • 如何快速算出一个数有多少个因子(c++)

    如何快速算出一个数有多少个 多少种 因子 c int count int n int sum 1 for int i 2 i i lt n i if n i 0 int tmp 0 while n i 0 n i tmp sum sum t
  • Piecewise混沌映射/PWLCM混沌映射(含MATLAB代码)

    一 Piecewise混沌映射 PWLCM混沌映射 混沌映射是生成混沌序列的一种方法 常见的混沌映射方式有 Logistic映射 Tent映射 Circle映射 而 Piecewise映射作为混沌映射的典型代表 数学形式简单 具有遍历性和随
  • python文件操作(with open)——读取行、写操作

    一 基础语法 1 打开文件 这里只介绍一种常用方式 但是打开文件方式有很多种 掌握一种最适合自己的即可 推荐使用这种方式 因为不需要close 具体原因往下看 看到示例就懂了 打开文件的模式有很多种 r 读 w 写等 此处不做详细介绍 采用
  • sublime text3取消自动换行!

    菜单栏中取消view gt word wrap的勾选也可以取消其代码的自动换行 菜单栏选择preferences gt Setting User中添加 word wrap false 即可
  • 膜拜(离散化差分模板题)

    题目描述 小鱼有 n 名优秀的粉丝 粉丝们得知小鱼将会在一条直线上出现 打算去膜他 为了方便 粉丝们在这条直线上建立数轴 第 i 名粉丝有一个侦查区间 li ri 如果小鱼在 j li j ri 处出现 这名粉丝将立刻发现并膜他 小鱼希望膜
  • python 3 中文URL编码转换问题

    链接里面含中文 转成URL编码 先引入模块 from urllib request import quote gt gt gt ff 摄像头 gt gt gt ff quote ff gt gt gt ff E6 91 84 E5 83 8
  • sql 还原数据库 错误3154

    在SQL Server2005及以下版本做数据库备份还原时 需要首先建立数据库 然后才能进行数据库还原操作 而在SQL Server2005以上版本做数据库还原时 不需要建立数据库 可以直接进行数据库还原操作 否则执行数据库还原操作时会报3
  • 求阶乘之和(循环版)(利用阶乘函数)

    请编写函数 用循环方法求阶乘之和 SumFac n 0 1 2 3 n include
  • uniapp uview 登录页

  • DETRs Beat YOLOs on Real-time Object Detection论文详解

    论文题目 DETRs Beat YOLOs on Real time Object Detection 论文地址 https arxiv org abs 2304 08069 论文代码 mirrors facebookresearch Co
  • jmeter:linux环境运行jmeter并生成报告

    是一个java开发的利用多线程原理来模拟并发进行性能测试的工具 一般来说 GUI模式只用于创建脚本以及用来debug 执行测试时建议使用非GUI模式运行 这篇博客 介绍下在linux环境利用jmeter进行性能测试的方法 以及如何生成测试报
  • matplotlib绘图与可视化2

    文章目录 前言 一 使用pandas和seaborn绘图 1 1 折线图 1 2 柱状图 1 3 直方图和密度图 1 4 散点图或点图 1 5 分面网格和分类数据 总结 前言 matplotlib是一个相当底层的工具 你可以从其基本组件中组
  • java ioc依赖注入,Spring bean的实例化和IOC依赖注入详解

    前言 我们知道 IOC是Spring的核心 它来负责控制对象的生命周期和对象间的关系 举个例子 我们如何来找对象的呢 常见的情况是 在路上要到处去看哪个MM既漂亮身材又好 符合我们的口味 就打听她们的电话号码 制造关联想办法认识她们 然后