AOP的应用以及代码实现(详细剖析)

8 篇文章 9 订阅
订阅专栏

目录

1. 简介

1.1 IOC的主要流程/功能

1.2 AOP的概念图

1.3 AOP 流程图

2. 基本准备工作

------------------------------配置文件实现AOP----------------------------

3.AOP配置文件方式的入门

4. 切入点的表达式(不建议用*号)

-----------------------------注解方式实现AOP----------------------------

5.AOP注解方式入门程序

5.1  配置xml扫描注解

5.2 配置注解

5.3 给切面类添加注解 @Aspect,编写增强的方法,使用通知类型注解声明

5.4 测试类


1. 简介

1.1 IOC的主要流程/功能

1.2 AOP的概念图

1.3 AOP 流程图

1.4 spring 4-5中aop的区别

 

假设一个类Demo

--------------------------AOP相关术语------------------------------

  • Joinpoint(连接点) 类里面有哪些方法可以增强这些方法称为连接点
  • Pointcut(切入点) -- 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义,实际要增强的方法。
  • Advice(通知/增强)-- 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)。实际增强的逻辑部分就是通知
    • 前置通知 目标方法执行前,进行增强

    • 后置通知 目标方法执行成功后,进行增强。

    • 环绕通知 目标方法执行前后,都可以进行增强。目标对象的方法需要手动执行。

    • 异常通知 目标方法执行失败后,进行增强。(发生异常的时候才会执行,否则不执行)。一旦方法异常,就会通知。

    • 最终通知 目标方法执行成功或者失败,进行增强。类似于finally  

  • Aspect(切面)-- 是 切入点+通知 的结合,以后咱们自己来编写和配置的。通知应用到切入点的一个过程

2. 基本准备工作

AspectJ是一个面向 切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,AspectJ实际上是对AOP编程思想的一个实践.

------------------------------配置文件实现AOP----------------------------

3.AOP配置文件方式的入门

3.1 创建maven项目,坐标依赖

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!--AOP联盟-->
    <dependency>
        <groupId>aopalliance</groupId>
        <artifactId>aopalliance</artifactId>
        <version>1.0</version>
    </dependency>
    <!--Spring Aspects-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <!--aspectj-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.3</version>
    </dependency>
</dependencies>

3.2 创建被增强的类User.java

package com.qcby;

import org.springframework.stereotype.Component;

public class User {
   public void add(){
//        int a= 10/0;
        System.out.println("add方法");
    }
}

3.3 定义切面类 UserPorxy.java

package com.qcby;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;


public class UserPorxy {
//    增强/通知  ---》前置通知
    public void before(){
        System.out.println("切面类before先执行");
    }

//    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//        System.out.println("这是在add方法执行前执行的(around)。。。。");
//
//        // 执行被增强的方法
//        proceedingJoinPoint.proceed();
//
//        System.out.println("这是在add方法执行后执行的(around)。。。。");
//    }
    // 无论切入点成功或者失败都会进程执行,类似于finally

//    public void after(){
//        System.out.println("这是在add方法执行后执行的(after)。。。。");
//    }
    // 后置通知 : 和 after一样都是在方法执行之后执行,但是 after 无论切入点成功或者失败都会进程执行

//    public void afterReturning(){
//        System.out.println("afterReturning");
//    }

//    public void afterThrowing(){
//        System.out.println("afterThrowing....");
//    }


}

3.4 将目标类配置到Spring中 SpringConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">


<bean id="user" class="com.qcby.User"/>
<bean id="userPorxy" class="com.qcby.UserPorxy"/>


<!--配置切面-->
<aop:config>
    <!--配置切面 = 切入点 + 通知组成-->
    <aop:aspect ref="userProxy">
        <!--前置通知:UserServiceImpl的save方法执行前,会增强-->
        <!--pointcut:后边是切入点表达式,作用是知道对对面的那个方法进行增强-->
        <aop:before method="before" pointcut="execution(public void com.aopImpl.User.add())"/>
               <!--环绕通知-->
<!--                <aop:around method="around" pointcut="execution(public void com.qcby.User.add())"/>-->
                <!--最终通知-->
<!--                <aop:after method="after" pointcut="execution(public void com.qcby.User.add())"/>-->
                <!--后置通知-->
<!--                <aop:after-returning method="afterReturning" pointcut="execution(public void com.qcby.User.add())"/>-->
                <!--异常通知-->
<!--                <aop:after-throwing method="afterThrowing" pointcut="execution(public void com.qcby.User.add())"/>
         
    </aop:aspect>
</aop:config>


</beans>

3.4 完成测试 DemoTest.java

package com.qcby.test;

import com.qcby.User;
import javafx.application.Application;
import jdk.nashorn.internal.ir.CallNode;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class DemoTest {
    @Test
    public void aopTest(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("SpringConfig.xml");
        User user = (User) ac.getBean("user");
        user.add();
    }

}

五种类型要分别测试,测试完一个注释一个

4. 切入点的表达式(不建议用*号)

 @Pointcut("execution(public * com.qcby.demo.proxyaop.controller..*.*(..))")
    public void viewRecordsPoinCut() {

    }

再配置切入点的时候,需要定义表达式,具体展开如下:

切入点表达式的格式如下:

execution([修饰符] [返回值类型] [类全路径] [方法名 ( [参数] )])

修饰符可以省略不写,不是必须要出现的。

返回值类型是不能省略不写的,根据你的方法来编写返回值,可以使用 * 代替。

-----------------------------注解方式实现AOP----------------------------

5.AOP注解方式入门程序

创建maven工程,导入坐标。编写接口,完成IOC的操作。步骤略。

编写切面类

给切面类添加注解 @Aspect,编写增强的方法,使用通知类型注解声明

5.1  配置xml扫描注解

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--开启注解扫描-->
   <context:component-scan base-package="com.qcby"/>
    <!--开启Aspect生成代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
</beans>

5.2 配置注解

package com.qcby;

import org.springframework.stereotype.Component;

@Component
public class User {

    public void add(){
//        int a= 10/0;
        System.out.println("add方法");
    }

}

5.3 给切面类添加注解 @Aspect,编写增强的方法,使用通知类型注解声明

package com.qcby;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class UserPorxy {
//    增强/通知  ---》前置通知
    @Before(value = "execution(public void com.qcby.User.add())")
    public void before(){
        System.out.println("切面类before先执行");
    }

//@Around(value = "execution(public void com.qcby.User.add())")
//    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//        System.out.println("这是在add方法执行前执行的(around)。。。。");
//
//        // 执行被增强的方法
//        proceedingJoinPoint.proceed();
//
//        System.out.println("这是在add方法执行后执行的(around)。。。。");
//    }
    // 无论切入点成功或者失败都会进程执行,类似于finally

//    @After(value = "execution(public void com.qcby.User.add())")
//    public void after(){
//        System.out.println("这是在add方法执行后执行的(after)。。。。");
//    }
    // 后置通知 : 和 after一样都是在方法执行之后执行,但是 after 无论切入点成功或者失败都会进程执行

//    @AfterReturning(value = "execution(public void com.qcby.User.add())")
//    public void afterReturning(){
//        System.out.println("afterReturning");
//    }

//   @AfterThrowing(value = "execution(public void com.qcby.User.add())")
//    public void afterThrowing(){
//        System.out.println("afterThrowing....");
//    }


}

5.4 测试类

package com.qcby.test;

import com.qcby.User;
import javafx.application.Application;
import jdk.nashorn.internal.ir.CallNode;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class DemoTest {
    @Test
    public void aopTest(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("SpringConfig.xml");
        User user = (User) ac.getBean("user");
        user.add();
    }

}

 5.5 结果呈现(五个分别测试,在测试异常的时候,需要在User.java中写个除0异常)

 

AOP实现机制
执着之道
10-18 586
附件中有本文的源代码和Pdf版。本文写的很长的原因,是不希望大家学习AOP时到处找资料,大家有时间可以按照本文动手实践下,相信会有非常大的收获的,有什么问题互相交流,有问必答! 1 AOP各种的实现     AOP就是面向切面编程,我们可以从几个层面来实现AOP。 在编译器修改源代码,在运行期字节码加载前修改字节码或字节码加载后动态创建代理类的字节码,以下是各种实现机制的比较。    ...
AOP如何实现实现原理
weixin_34224941的博客
11-23 5092
概述: 最近在开发中遇到了一个刚好可以用AOP实现的例子,就顺便研究了AOP实现原理,把学习到的东西进行一个总结。文章中用到的编程语言为kotlin,需要的可以在IDEA中直接转为java。 这篇文章将会按照如下目录展开: AOP简介 代码实现举例 AOP实现原理 部分源码解析 1. AOP简介 相信大家或多或少的了解过AOP,都知道它是面向切面编程,在网上搜索可以找到很多的解释。这里我用...
AOP应用
最新发布
weixin_45119534的博客
08-21 118
的方式实现的,由代理对象持有目标对象,在执行目标对象的目标方法的前后执行增强的代码
AOP实现代码
10-20
详细介绍了AOP的核心功能(拦截功能)在底层是如何实现的;介绍了两种实现AOP的动态代理:jdk动态代理和cglib动态代理,并详细描述了它们在代码层面的实现。附有必须的cglib.jar和asm.jar
AOP代码实现
氵胡少的博客
07-19 590
一、AOP代码实现 需求:在不改动业务逻辑类的基础上实现在原有代码之前之后分别加上新的功能及异常处理。 导包 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://m
spring aop实现代码
jcw的博客
09-14 1142
跟踪spring aop实现 aop核心概念介绍 什么是切面编程 在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面编程。 Aspect(切面) 什么是切面呢?切面这个词感觉还是挺抽象的,咱们可能知道点构成线,线构成面的数据话术。但是在编程语言中如何体现这个面呢?我的理解是“在程序中切面是由切入点和在切入点的表现的行为构成”。给个在spring中定义切面的例子如下...
Spring本质系列(2)AOP共3页.pdf.zip
10-30
9. **事务管理**:Spring AOP的一个常见应用是声明式事务管理,允许开发者在XML配置或注解中声明事务边界,由Spring自动管理事务的开始、提交、回滚。 10. **性能优化**:虽然AOP带来了很多便利,但过度使用可能会...
Spring核心-AOP(9)
03-15
最后,`实验报告参考文档.docx`和`实验9 Spring核心——AOP.pdf`可能是学习资源,详细解释了如何创建和配置Spring AOP,以及如何在实际项目中应用。文档可能包括步骤指导、代码示例和分析,帮助读者深入理解AOP的...
SpringAOP源码解析:连接点(Join Point)的概念与分类
本文旨在深入探讨SpringAOP框架中连接点(Join Point)的概念与分类,通过对连接点的原理、实现机制和应用场景进行剖析,帮助读者更好地理解AOP编程思想,并且能够在实际项目中灵活运用连接点进行业务逻辑的增强和...
AOP(面向切面编程)
StudyJava01的博客
12-08 1348
  我们在编写代码时,只想关注逻辑代码实现。但是我们的程序是要放到服务器上去跑的,我们在代码中如果只有业务逻辑代码。虽然代码简洁,但是万一程序在服务器跑的时候数据出现问题时,没办法快速定位到问题所在。但是将日志等非逻辑代码和我们的逻辑代码放在一起,会使得代码冗余,代码的可读性难度加大。   基于上面的问题,Spring为我们提供了一种解决方案,将业务功能逻辑代码和非功能代码分开来写,然后使用动态代理,将非功能性代码嵌入到功能代码中,这就是所谓的AOP(面向切面编程)。  1、我们要使用Aop对我们的bea
SpringAOP源码解析:AOP与事务管理(@Transactional)的联动原理
SpringAOP可以应用于不同的场景,常见的应用包括日志记录、性能监控、事务管理等。它能够帮助开发人员将关注点从业务逻辑中分离出来,提高代码的可维护性和可测试性。 ## 1.2 AOP的基本概念和原理 AOP是一种编程...
Spring源码之AOP实现
qq_38706652的博客
08-20 1456
Spring源码之AOP
Spring AOP代码实现:实例演示与注解全解
热门推荐
Marion的博客
04-10 1万+
1 理解AOP 1.1 什么是AOP AOP(Aspect Oriented Programming),面向切面思想,是Spring的三大核心思想之一(两外两个:IOC-控制反转、DI-依赖注入)。 那么AOP为何那么重要呢?在我们的程序中,经常存在一些系统性的需求,比如权限校验、日志记录、统计等,这些代码会散落穿插在各个业务逻辑中,非常冗余且不利于维护。例如下面这个示意图: 有多少业务操作,就要写多少重复的校验和日志记录代码,这显然是无法接受的。当然,用面向对象的思想,我们可以把这些重复的代码抽离出来,写
AOP实现
weixin_30376323的博客
06-28 49
AOP基于xml配置方式实现 Spring基于xml开发AOP     定义目标类(接口及实现类) 1 /** 2 * 目标类 3 */ 4 public interface UserService { 5 //业务方法 6 public void getById(); 7 public void add(); ...
SpringAOP(三)AOP代码实现
m0_52784892的博客
12-01 545
SpringAOP(三)AOP代码实现
SpringAOP-注解
qq_39736103的博客
06-24 586
1、导入aop模块;Spring AOP:(spring-aspects)   &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-aspects&lt;/artifactId&gt; &lt;version&gt;4.3.12.RELE...
实现一个简单的AOP
weixin_45648789的博客
04-29 859
文章目录一、AOP是什么?二、一个简单的AOP实现1.实现动态代理2.面向切面编程(这里是在添加开始和结束的时间)3.用一个工厂类将其进行初始化5.然后就是测试了6.配置文件 提示:以下是本篇文章正文内容,下面案例可供参考 一、AOP是什么? AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的
AOP实现方法
Y717310的博客
03-08 789
1、自定义类//自定义类System.out.println("=========执行方法前==========");System.out.println("=========执行方法后==========");2、配置文件信息,基于方法一修改-- 自定义前,ref要引用的类 -->--切入点 -->-- 通知-->3、测试实现代码同方法一,输出结果为:=========执行方法前==========增加了一各用户!=========执行方法后==========
AOP应用
Seven_0110的博客
09-07 221
JoinPoint 对象 JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象. 常用api: 方法名 功能 Signature getSignature(); 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息 Object[] getArgs(); 获取传入目标方法的参数对象 Object getTarget(); 获取被代理的对象 Obje
SSMAOP日志系统分析与实现
"SSMAOP日志.pdf 是一个关于日志管理的PDF文档,主要讨论了在Java Spring和MyBatis环境中如何实现AOP(面向切面编程)日志记录。文档提到了一个名为`sysLog`的日志表,包含了如主键、访问时间、用户名、IP地址、URL...
写文章

热门文章

  • 如何在idea中创建一个SpringBoot项目(超详细教学) 237407
  • maven项目报error in opening zip file错误解决办法 25775
  • Java实现Excel导入 24765
  • Java实现登录token令牌 19351
  • mybatis plus一对多查询(经典案例) 15634

分类专栏

  • java 18篇
  • 软件使用安装 3篇
  • 报错解决 6篇
  • Spring 8篇
  • 中间件 2篇
  • 数据库Mysql+redis 13篇
  • java项目 16篇
  • Java基础 10篇
  • tomcat 1篇
  • linux及云服务器 2篇
  • 经典算法题 1篇
  • leetcode+牛客刷题总结 9篇
  • 大作业 3篇
  • 代码 3篇
  • 大数据 7篇
  • C++ 3篇
  • HTML 2篇

最新评论

  • 如何在idea中创建一个SpringBoot项目(超详细教学)

    m0_74954442: 请问1.2那一步左边栏里没有Spring Initializr怎么办

  • 搭建ssm下增删改查框架(增删改查代码)

    2301_78874606: 资源在哪里

  • 如何在idea中创建一个SpringBoot项目(超详细教学)

    哥哥风华正茂: 大佬 你好, idea背景图片能分享下嘛表情包

  • 如何在idea中创建一个SpringBoot项目(超详细教学)

    楸叔: 为啥我没有爆红,运行完之后浏览器也找不到了表情包

  • 图片/视频上传(超简单教程)

    普通网友: 好文,细节很到位!【我也写了一些相关领域的文章,希望能够得到博主的指导,共同进步!】

大家在看

  • 【C++基础知识——GoogleTest框架的Setup方法】 547
  • C++ 注释详解
  • 跟着哪吒学Java——JavaSE
  • 粤港澳大湾区信息学创新大赛决赛(Goc小高组)题目全解析 469
  • IP地址管理的常见模式

最新文章

  • 图片/视频上传(超简单教程)
  • 一文完美实现Navicat15安装
  • Transactional注解规范使用
2024年3篇
2023年3篇
2022年77篇
2021年24篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叫我老伯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或 充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

深圳SEO优化公司优化小程序哪个网站好受欢迎高端网站设计优化建站php 网站 优化寿光网站优化服务东门互联网网站优化360网站怎样优化排名专业网站优化大概多少钱谷城网站优化公司百度网站优化效果怎么样武汉网站优化推广怎么样进行网站优化怎么做免加盟费的网站优化代理项目优化对网站网站优化难点怎么处理迪庆网站优化策划象山网站排名优化软件咸宁打拱网站优化乐昌网站免费优化金华网站优化公司哪家好网站优化策略大全邹平优化网站推广成都网站关键字如何优化网站优化定制开发行唐优化网站电话韶关专业网站优化咨询客服莱芜网站如何做优化青浦区谷歌网站优化方案定制怎么优化网站网页企业网站优化服务商家靠谱萝岗企业网站推广优化教程歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化