【JavaEE】AOP实现原理

news2024/9/25 17:12:58

概述

  • Spring AOP 是基于动态代理来实现AOP的, 此处主要介绍代理模式和Spring AOP的源码剖析

一.代理模式

  • 代理模式是一种常用的设计模式,它允许为其他对象提供代理,以控制对这个对象的访问。这种结构在不改变原始类的基础上,通过引入代理类来扩展功能,广泛应用于各种编程场景和框架中。

  • 使用代理前:
    在这里插入图片描述

  • 使用代理后:
    在这里插入图片描述

  • 在生活当中也有很多代理模式的例子:

    • 艺人经纪人: 广告商找艺人拍广告, 需要经过经纪人,由经纪人来和艺人进行沟通.
    • 房屋中介: 房屋进行租赁时, 卖方会把房屋授权给中介, 由中介来代理看房, 房屋咨询等服务.
    • 经销商: 厂商不直接对外销售产品, 由经销商负责代理销售.
    • 秘书/助理: 合作伙伴找老板谈合作, 需要先经过秘书/助理预约.
  • 代理模式的主要角色

    • Subject: 业务接口类. 可以是抽象类或者接口(不⼀定有)
    • RealSubject: 业务实现类. 具体的业务执行, 也就是被代理对象.
    • Proxy: 代理类. RealSubject的代理.

    比如房屋租赁
    Subject 就是提前定义了房东做的事情, 交给中介代理, 也是中介要做的事情
    RealSubject: 房东
    Proxy: 中介

  • 根据代理的创建时期, 代理模式分为静态代理动态代理.

    • 静态代理: 由程序员创建代理类或特定工具自动生成源代码再对其编译, 在程序运行前代理类的.class 文件就已经存在了.
    • 动态代理: 在程序运行时, 运用反射机制动态创建而成
    • 更形象的描述就是就是好比你去买房, 静态代理没买房前就已经分配好销售了(可以是根据一些因素进行划分), 动态代理就是买房的时候随机给你发分配销售

二. 静态代理

  • 静态代理是指在程序运行前就已经存在代理类的字节码文件,由程序员手动编写或特定工具自动生成源代码实现的代理方式。

代码实现

我们通过代码来加深对静态代理的理解. 以房租租赁为例:

  1. 定义接口(定义房东要做的事情, 也是中介需要做的事情)
public interface houseSubject {
    void rentHouse();
    void saleHouse();
}
  1. 实现接口(房东出租房子)
public class RealHouseSubject implements houseSubject{
    @Override
    public void rentHouse() {
        System.out.println("我是房东,我出租房子");
    }
    
    @Override
    public void saleHouse() {
        System.out.println("我是房东,我出售房子");
    }
}
  1. 代理(中介, 帮房东出租房子)
public class HouseProxy implements houseSubject{
    
    private RealHouseSubject target;
    
    public HouseProxy(RealHouseSubject realHouseSubject) {
        this.target = realHouseSubject;
    }

    @Override
    public void rentHouse() {
        //出租前
        System.out.println("我是中介,我开始代理");
        //出租房子
        target.rentHouse();
        //出租后
        System.out.println("我是中介,结束代理");
    }

    @Override
    public void saleHouse() {
        //出售前
        System.out.println("我是中介,我开始代理");
        //出售房子
        target.saleHouse();
        //出售后
        System.out.println("我是中介,结束代理");
    }
}

  1. 使用静态代理
public class Main {
    public static void main(String[] args) {
        //创建代理类
        HouseProxy houseProxy = new HouseProxy(new RealHouseSubject());
        //通过代理类访问目标方法
        houseProxy.rentHouse();
    }
}
  • 运行结果:
    在这里插入图片描述

上面这个代理实现方式就是静态代理(仿佛啥也没干). 从上述程序可以看出, 虽然静态代理也完成了对目标对象的代理, 但是由于代码都写死了, 对目标对象的每个方法的增强都是手动完成的,非常不灵活. 所以日常开发几乎看不到静态代理的场景

接下来新增需求: 中介又新增了其他业务, 我们修改接口(Subject)和业务实现类(RealSubject)时, 还需要修改代理类(Proxy). 同样的, 如果有新增接口(Subject)和业务实现类(RealSubject), 也需要对每一个业务实现类新增代理类(Proxy).

三.动态代理.

  • 动态代理是指我们不需要针对每个目标对象都单独创建一个代理对象, 而是把这个创建代理对象的工作推迟到程序运行时由JVM来实现. 也就是说动态代理在程序运行时, 根据需要动态创建生成.

  • 在Java中实现动态代理的方式主要有两种,一种是JDK动态代理,一种是CGLib. 接下来就主要介绍这两种方式.

3.1 JDK动态代理.

  • JDK动态代理的实现步骤:
      1. 定义一个接口及其实现类(静态代理中的 HouseSubject 和 RealHouseSubject )
      1. 自定义 InvocationHandler 并重写 invoke 方法,在 invoke 方法中我们会调用目标方法(被代理类的方法)并自定义一些处理逻辑.
      1. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[]
        interfaces,InvocationHandler h) 方法创建代理对象

JDK动态代理的代码实现

1. 实现 InvocationHandler 接口
public class JDKInvocationHandler implements InvocationHandler {
    private Object target;
    //目标对象
    public JDKInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("我是代理,开始代理");
        //通过反射调用目标函数
        Object result = method.invoke(target, args);
        System.out.println("我是代理,结束代理");
        return result;
    }
}
2. 创建一个代理对象并使用
public class Main {
    public static void main(String[] args) {
        /**
         *public static Object newProxyInstance(ClassLoader loader,
         *                                           Class<?>[] interfaces,
         *                                           InvocationHandler h) {
         * 加载代理类的classloader
         * 要实现的接口,
         * 代理要做的事情,InvocationHandler这个接口
         */
        //真实的目标对象
        RealHouseSubject targer = new RealHouseSubject();
        //动态生成代理对象
        houseSubject proxy = (houseSubject)Proxy.newProxyInstance(targer.getClass().getClassLoader(),
                new Class[]{houseSubject.class},
                new JDKInvocationHandler(targer));
        proxy.rentHouse();
        proxy.saleHouse();

JDK动态代理过程中涉及方法的参数讲解

  1. InvocationHandler接口
    InvocationHandler 接口是Java动态代理的关键接口之一, 它定义了一个单一方法 invoke() , 用于处理被代理对象的方法调用.
public interface InvocationHandler {
 /**
 * 参数说明
 * proxy:代理对象
 * method:代理对象需要实现的⽅法,即其中需要重写的⽅法
 * args:method所对应⽅法的参数
 */
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

通过实现 InvocationHandler 接口, 可以对被代理对象的方法进行功能增强.

  1. Proxy
    Proxy 类中使用频率最高的方法是: newProxyInstance() , 这个方法主要用来生成一个代理对象.
public static Object newProxyInstance(ClassLoader loader,
 									Class<?>[] interfaces,
 									InvocationHandler h)
 									throws IllegalArgumentException{
 		//...代码省略
 }
  • 这个方法一共有 3 个参数:
    • Loader: 类加载器, 用于加载代理对象.
    • interfaces : 被代理类实现的一些接口(这个参数的定义, 也决定了JDK动态代理只能代理实现了接口的一些类)
    • h : 实现了 InvocationHandler 接口的对象.

3.2 CGLib动态代理

  • CGLib动态代理的实现步骤:
    • 定义一个类(被代理类).
    • 自定义 MethodInterceptor 并重写 intercept 方法, intercept 用于增强目标
      方法,和 JDK 动态代理中的 invoke 方法类似.
    • 通过 Enhancer 类的 create()创建代理类.

CGLib动态代理的代码实现

1. 自定义 MethodInterceptor(方法拦截器)实现MethodInterceptor接口.
public class CGLibMethodInterceptor implements MethodInterceptor {
    private Object target;
    public CGLibMethodInterceptor(Object target) {
        this.target = target;
    }
    /**
     * 调用代理对象的方法
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("我是中介开始代理~~");
        Object result = method.invoke(target, args);
        //proxy.invokeSuper(obj, args);
        System.out.println("我是中介,结束代理~~");
        return result;
    }
}
2.创建代理类, 并使用
  • 目标对象,用来代理接口
//        目标对象 代理接口
        HouseSubject targer = new RealHouseSubject();
        houseSubject houseSubject = (houseSubject) Enhancer.create(targer.getClass(), new CGLibMethodInterceptor(targer));
        houseSubject.rentHouse();
        houseSubject.saleHouse();
  • 目标对象,用来代理类.
		HouseSubject targer = new RealHouseSubject();
//目标对象 代理类
        RealHouseSubject realHouseSubject = (RealHouseSubject) Enhancer.create(targer.getClass(), new CGLibMethodInterceptor(targer));
        realHouseSubject.rentHouse();
        realHouseSubject.saleHouse();

CGLib动态代理过程中涉及方法的参数讲解.

  1. MethodInterceptor
    MethodInterceptor 和 JDK动态代理中的 InvocationHandler 类似, 它只定义了一个方法 intercept() , 用于增强目标方法.
public interface MethodInterceptor extends Callback {
 	/**
 	* 参数说明:
 	* o: 被代理的对象
	 * method: ⽬标⽅法(被拦截的⽅法, 也就是需要增强的⽅法)
 	* objects: ⽅法⼊参
 	* methodProxy: ⽤于调⽤原始⽅法
	 */
 	Object intercept(Object o,
 				 Method method, 
 				 Object[] objects, 
 				 MethodProxy methodProxy) throws Throwable;
}
  1. Enhancer.create()
    Enhancer.create() 用来生成一个代理对象
public static Object create(Class type, Callback callback) {
 //...代码省略
}
  • 参数说明:
    • type: 被代理类的类型(类或接口)
    • callback: 自定义方法拦截器 MethodInterceptor

四.Spring AOP源码剖析

  • Spring AOP 主要基于两种方式实现的: JDK 及 CGLIB 的方式
  • Spring 源码过于复杂, 此处只摘出一些主要内容, 以了解为主.
    Spring对于AOP的实现,基本上都是靠 AnnotationAwareAspectJAutoProxyCreator 去完成. 生成代理对象的逻辑在父类 AbstractAutoProxyCreator 中
	protected Object createProxy(Class<?> beanClass, 
								@Nullable String beanName,
								@Nullable Object[] specificInterceptors, 
								TargetSource targetSource) {

		return buildProxy(beanClass, beanName, specificInterceptors, targetSource, false);
	}
private Object buildProxy(Class<?> beanClass, @Nullable String beanName,
			@Nullable Object[] specificInterceptors, TargetSource targetSource, boolean classOnly) {

		if (this.beanFactory instanceof ConfigurableListableBeanFactory clbf) {
			AutoProxyUtils.exposeTargetClass(clbf, beanName, beanClass);
		}
		//创建代理对象
		ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.copyFrom(this);
		/**
 		* 检查proxyTargetClass属性值,spring默认为false
 		* proxyTargetClass 检查接⼝是否对类代理, ⽽不是对接⼝代理
 		* 如果代理对象为类, 设置为true, 使⽤cglib代理
 		*/
		if (proxyFactory.isProxyTargetClass()) {
			//是否有设置cglib代理
			if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {
				//设置proxyTargetClass为true,使⽤cglib代理
				for (Class<?> ifc : beanClass.getInterfaces()) {
					proxyFactory.addInterface(ifc);
				}
			}
		}
		else {
			/**
 			* 如果beanClass实现了接⼝,且接⼝⾄少有⼀个⾃定义⽅法,则使⽤JDK代理
 			* 否则CGLIB代理(设置ProxyTargetClass为true )
		 	* 即使我们配置了proxyTargetClass=false, 经过这⾥的⼀些判断还是可能会将其
			设为true
 			*/
			if (shouldProxyTargetClass(beanClass, beanName)) {
				proxyFactory.setProxyTargetClass(true);
			}
			else {
				evaluateProxyInterfaces(beanClass, proxyFactory);
			}
		}

		Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
		proxyFactory.addAdvisors(advisors);
		proxyFactory.setTargetSource(targetSource);
		customizeProxyFactory(proxyFactory);

		proxyFactory.setFrozen(this.freezeProxy);
		if (advisorsPreFiltered()) {
			proxyFactory.setPreFiltered(true);
		}

		// Use original ClassLoader if bean class not locally loaded in overriding class loader
		ClassLoader classLoader = getProxyClassLoader();
		if (classLoader instanceof SmartClassLoader smartClassLoader && classLoader != beanClass.getClassLoader()) {
			classLoader = smartClassLoader.getOriginalClassLoader();
		}
		return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader));
	}
  • 代理工厂有一个重要的属性: proxyTargetClass, 默认值为false. 也可以通过程序设置
    在这里插入图片描述
  • 可以通过 @EnableAspectJAutoProxy(proxyTargetClass = true) 来设置

注意:
Spring Boot 2.X开始, 默认使用CGLIB代理. 可以通过配置项 spring.aop.proxy-target-class=false 来进行修改,设置默认为jdk代理. SpringBoot设置 @EnableAspectJAutoProxy 无效, 因为Spring Boot 默认使用AopAutoConfiguration进行装配

@SpringBootApplication
public class DemoApplication {
 	public static void main(String[] args) {
 		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
 		/**
 		* HouseProxy houseProxy = context.getBean(HouseProxy.class);
 		* 设置spring.aop.proxy-target-class=true cglib代理, 运⾏成功
 		* 设置spring.aop.proxy-target-class=false jdk代理, 运⾏失败, 不能代理类
 		* 因为 HouseProxy 是⼀个类, ⽽不是接⼝, 需要修改为
 		* HouseSubject houseProxy = (HouseSubject) context.getBean("realHouseSubject")
 		* 
 		*/
 		HouseProxy houseProxy = context.getBean(HouseProxy.class);
 		//HouseSubject houseProxy = (HouseSubject) context.getBean("realHouseSubject");//正确运⾏
 		System.out.println(houseProxy.getClass().toString());
 }
 }

使用context.getBean() 需要添加注解,使HouseProxy,RealHouseSubject被Spring管理测试AOP代理, 需要把这些类交给AOP管理(自定义注解或使用@Aspect)

我看点进去看代理工厂的代码

public class ProxyFactory extends ProxyCreatorSupport {
 	//...代码省略
 	//获取代理
 	public Object getProxy(@Nullable ClassLoader classLoader) {
		 //分两步 先createAopProxy,后getProxy
 		return createAopProxy().getProxy(classLoader);
 	}
 
 	protected final synchronized AopProxy createAopProxy() {
 		if (!this.active) {
 			activate();
 		}
 		return getAopProxyFactory().createAopProxy(this);
 	}
 	//...代码省略
}

createAopProxy的实现在 DefaultAopProxyFactory中

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
 	//...代码省略
 	@Override
 	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
 	/**
 	* 根据proxyTargetClass判断
 	* 如果⽬标类是接⼝, 使⽤JDK动态代理
 	* 否则使⽤cglib动态代理
 	*/
 	if (!NativeDetector.inNativeImage() && (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.");
 		}
 		if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
 			return new JdkDynamicAopProxy(config);
 		}
 			return new ObjenesisCglibAopProxy(config);
	 }
 		else {
 			return new JdkDynamicAopProxy(config);
 		}
 }
 //...代码省略
}

接下来就是创建代理了
JDK动态代理

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, 
Serializable {
 //...代码省略
 @Override
 public Object getProxy(@Nullable ClassLoader classLoader) {
 if (logger.isTraceEnabled()) {
 logger.trace("Creating JDK dynamic proxy: " + 
this.advised.getTargetSource());
 }
 return Proxy.newProxyInstance(determineClassLoader(classLoader), 
this.proxiedInterfaces, this);
 }
 //...代码省略
}

CGLIB动态代理

class CglibAopProxy implements AopProxy, Serializable {
        //...代码省略
        @Override
        public Object getProxy(@Nullable ClassLoader classLoader) {

            //...代码省略

            // Configure CGLIB Enhancer...
            Enhancer enhancer = createEnhancer();

            // Generate the proxy class and create a proxy instance.
            return createProxyClassAndInstance(enhancer, callbacks);

        }
        //...代码省略
    }

五.总结

  1. 什么是AOP?
  2. Spring AOP的实现方式有哪些?
    • 基于注解
    • 基于XML
    • 基于代理
  3. Spring AOP的实现原理.?
  4. Spring 使用哪种代理方式?
  5. JDK和CGLib的区别?

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

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

相关文章

前端的页面代码

根据老师教的前端页面的知识&#xff0c;加上我也是借鉴了老师上课所说的代码&#xff0c;马马虎虎的写出了页面。如下代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</ti…

Gitea 仓库事件触发Jenkins远程构建

文章目录 引言I Gitea 仓库事件触发Jenkins远程构建1.1 Jenkins配置1.2 Gitea 配置引言 应用场景:项目部署 I Gitea 仓库事件触发Jenkins远程构建 Gitea支持用于仓库事件的Webhooks 1.1 Jenkins配置 高版本Jenkins需要关闭跨域限制和开启匿名用户访问 在Jenkins启动前加入…

微前端基础知识

1. 前言 随着Web应用程序规模的日益扩大和复杂性的增加&#xff0c;传统的前端开发模式逐渐显现出其在维护、扩展以及团队协作方面的局限性。微前端作为一种新兴的前端架构模式&#xff0c;正是为了应对这些挑战而诞生的。 微前端&#xff08;Micro-Frontends&#xff09;并没有…

matine组件库踩坑日记 --- react

Mantine实践 一 禁忌核心css样式二 添加轮播图扩展组件 一 禁忌核心css样式 import React from react import ReactDOM from react-dom/client import { BrowserRouter } from react-router-dom; import App from ./App.jsx import ./index.css import mantine/core/styles.cs…

收银系统源码-会员功能

随着新零售时代不断更新迭代&#xff0c;私域会员已经成为很多连锁门店必要的选择。自然离开不了一套能高效管理会员的收银系统。今天给大家推荐一下&#xff0c;智慧新零售收银系统的会员功能。 了解更多查看下文&#xff1a; 门店收银系统源码-CSDN博客文章浏览阅读2.6k次&…

开源项目:机遇与挑战共存的创新之路

开源项目&#xff1a;机遇与挑战共存的创新之路 开源&#xff08;Open Source&#xff0c;开放源码&#xff09;被非盈利软件组织&#xff08;美国的Open Source Initiative协会&#xff09;注册为认证标记&#xff0c;并对其进行了正式的定义&#xff0c;用于描述那些源码可以…

倒计时 2 周!CommunityOverCode Asia 2024 IoT Community 专题部分

CommunityOverCode 是 Apache 软件基金会&#xff08;ASF&#xff09;的官方全球系列大会&#xff0c;其前身为 ApacheCon。自 1998 年以来&#xff0c;在 ASF 成立之前&#xff0c;ApacheCon 已经吸引了各个层次的参与者&#xff0c;在 300 多个 Apache 项目及其不同的社区中探…

线性回归(梯度下降)

首先说案例&#xff1a; 房子的价格和所占面积有着很大的关系&#xff0c;假如现在有一些关于房子面积和价格的数据&#xff0c;我要如何根据已经有的数据来判断未知的数据呢&#xff1f; 假如x(房屋面积)&#xff0c;y(房屋价格) x[ 56 72 69 88 102 86 76 79 94 74] y[92, …

struts2如何防止XSS脚本攻击(XSS防跨站脚本攻击过滤器)

只需要配置一个拦截器即可解决参数内容替换 一、配置web.xml <filter><filter-name>struts-xssFilter</filter-name><filter-class>*.*.filters.XssFilter</filter-class></filter><filter-mapping><filter-name>struts-xss…

1.5.1抽象java入门

前言&#xff1a; 1.5.0版本中&#xff0c;我们熟练使用Git三个可视化操作&#xff08;签出&#xff0c;提交&#xff0c;对比&#xff09;&#xff0c;再加上1.4.0版本的新建&#xff0c;总计使用四个Git可视化操作&#xff1b;对java编程的学习&#xff0c;总结&#xff0c;…

vector 介绍

1.简述vector 首先我们要大致弄明白vector是一个什么东西,其实vector就是之前我们学过的顺序表,这里直接使用就行了. 定义vector-------->vector<typename> arr 此时的这种定义vector可以理解成为一个数组,而typename可以是各种数据类型,比如string,int,double....…

react启用mobx @decorators装饰器语法

react如果没有经过配置&#xff0c;直接使用decorators装饰器语法会报错&#xff1a; Support for the experimental syntax ‘decorators’ isn’t currently enabled 因为react默认是不支持装饰器语法&#xff0c;需要做一些配置来启用装饰器语法。 step1: 在 tsconfig.js…

宪法学学习笔记(个人向) Part.6

宪法学学习笔记(个人向) Part.6 5. 国家机构 概述 国家机构是国家为了实现其管理社会、维护社会秩序职能而建立起来的国家机关的总和&#xff1b;它包括&#xff1a; 立法机关&#xff08;全国人大及其常委会&#xff09;;行政机关&#xff08;国务院和地方人民政府&#xff09…

文心一言:如何备考软考架构师

提问&#xff1a;软考高级:架构师指导&#xff0c;有哪些考点、重点考点有哪些&#xff1f;需要如何备考&#xff1f;案例分析题如何做&#xff1f;论文如何写&#xff1f; 文心一言回答1700字如下&#xff1a; 针对软考高级架构师的考试&#xff0c;以下是对考点、重点考点、备…

33. 322.零钱兑换,279.完全平方数,139.单词拆分,关于多重背包,你该了解这些!,背包问题总结篇!

class Solution { public:int coinChange(vector<int>& coins, int amount) {if(amount 0) return 0;// dp[j]&#xff1a;凑足总额为j所需钱币的最少个数为dp[j]vector<int> dp(amount 1, INT_MAX); //注意初始化方式&#xff01;&#xff01;&#xff01;&…

包管理器冲突-jupyter安装配置

当你使用不同的包管理器安装相同的包&#xff0c;可能存在冲突&#xff0c;比如安装jupyter notebook在本地运行&#xff0c;你可能会遇到这种错误&#xff1a; TypeError: create.<locals>.Validator.__init__() got an unexpected keyword argument registry 甚至你可…

客家菜餐馆点菜小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;菜系管理&#xff0c;菜品信息管理&#xff0c;我的订单管理&#xff0c;桌号管理&#xff0c;退款信息管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;菜品信息&#…

vue + element ui 实现侧边栏导航栏折叠收起

首页布局如下 要求点击按钮,将侧边栏收缩, 通过 row 和 col 组件&#xff0c;并通过 col 组件的 span 属性我们就可以自由地组合布局。 折叠前 折叠后 <template><div class"app-layout" :class"{ collapse: app.isFold }"><div class&…

医疗级微型导轨:保障医疗行业手术安全!

微型直线导轨能成为一种专为医疗行业设备运用的高精度线性运动设备&#xff0c;在现代医疗领域&#xff0c;精准的位置控制和平稳的运动对于确保医疗设备的高效性能至关重要。那么&#xff0c;医疗行业对微型导轨有哪些要求呢&#xff1f; 1、精度&#xff1a;在手术过程中&…

蔡仲杨摄影入门到高手

描述 蔡仲杨&#xff0c;一个富有才华的老师&#xff01; 对于大家的学习有不可多得的帮助。 内容 目前主要的内容以摄影为主&#xff0c;对于学习摄影有比较大的帮助&#xff01; 但是网络上面错综复杂&#xff0c;很多老旧的版本影响学习&#xff01; 而这里我整理了相关…