时间:2017-2-4 21:12

——AspectJ简介

1、AspectJ是一个基于Java语言的AOP框架。
2、Spring2.0以后新增了对AspectJ切点表达式的支持。
3、@AspectJ是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面。

4、新版本Spring框架,建议使用AspectJ方式来开发AOP。
5、使用AspectJ需要导入Spring AOP和AspectJ相关jar包:
    spring-aop-3.2.0.RELEASE.jar

    com.springsource.org.aopalliance-1.0.0.jar
 
    spring-aspects-3.2.0.RELEASE.jar
    com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
 

——通过配置启用@AspectJ切面

<?xml version="1.0" encoding="UTF-8"?>
    <!-- 开启AspectJ自动代理-->
    <!-- 底层基于AnnotationAwareAspectJAutoProxyCreator,如果存在注解,则自动生成代理 -->
    <aop:aspectj-autoproxy />

</beans>
 

——在通知中通过value属性定义切点

1、通过execution函数,可以定义切点的方法切入
2、语法:
    execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
3、例如
    1)匹配所有类public方法:execution(public * *(..))
    2)匹配指定包下所有类方法:execution(* cn.itcast.dao.*(..)) 不包含子包
    3)execution(* cn.itcast.dao..*(..))  ..*表示包、子孙包下所有类
    4)匹配指定类所有方法:execution(* cn.itcast.service.UserService.*(..))
    5)匹配实现特定接口所有类方法,包含子类:execution(* cn.itcast.dao.GenericDAO+.*(..))
    6)匹配所有save开头的方法:execution(* save*(..))
 

——基于注解的开发

1、导包
    *   AspectJ依赖AOP环境。
        >    spring-aop-3.2.0.RELEASE.jar

        >   com.springsource.org.aopalliance-1.0.0.jar
 

*   导入AspectJ的jar包
        >   spring-aspects-3.2.0.RELEASE.jar
        >   com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

2、编写目标类
    UserDao
    提供增删改查方法。

3、使用AspectJ添加注解

    /**
     * 切面类
     * 切点 + 增强的结合
     * @author WYC
     *
     */
    // 用来定义切面类
    @Aspect
    public class MyAspect {
        // 对UserDao类的add()方法应用前置增强
        @Before(value = "execution(* com.wyc.spring3.demo1.UserDao.add())")
        public void before(){
            System.out.println("前置增强");
        }
    }

4、创建applicationContext.xml
    1)引入AOP的约束
    2)打开标签:<aop:aspectj-autoproxy />
        表示自动生成代理,该标签底层基于AnnotationAwareAspectJAutoProxyCreator,如果存在注解,则自动生成代理。


       <!-- 开启AspectJ自动代理 -->
        <!-- 底层基于AnnotationAwareAspectJAutoProxyCreator,如果存在注解,则自动生成代理 -->
        <aop:aspectj-autoproxy />
 
 
        <!-- 配置目标类 -->
        <bean id="userDao" class="com.wyc.spring3.demo1.UserDao" />
 
        <!-- 配置切面类(增强类) -->
        <bean id="myAspect" class="com.wyc.spring3.demo1.MyAspect" />

5、示例代码

UserDao目标类:

public class UserDao {
    public void add() {
        System.out.println("添加用户");
    }
 
    public void update() {
        System.out.println("修改用户");
    }
 
    public void delete() {
        System.out.println("删除用户");
    }
 
    public void find() {
        System.out.println("查询用户");
    }
}

----------------------------------------------------------------------------------------------------------------------------

MyAspect切面类:

/**
 * 切面类
 * 切点 + 增强的结合
 * @author WYC
 *
 */
// 用来定义切面类
@Aspect
public class MyAspect {
    // 对UserDao类的add()方法应用前置增强
    @Before(value = "execution(* com.wyc.spring3.demo1.UserDao.add())")
    // JoinPoint表示切点方法的描述,打印结果为:execution(void com.wyc.spring3.demo1.UserDao.add())
    public void before(JoinPoint joinPoing){
        System.out.println("前置增强" + joinPoing);
    }
}

----------------------------------------------------------------------------------------------------------------------------

XML配置文件:

<?xml version="1.0" encoding="UTF-8"?>
    xsi:schemaLocation="
 
    <!-- 开启AspectJ自动代理 -->
    <!-- 底层基于AnnotationAwareAspectJAutoProxyCreator,如果存在注解,则自动生成代理 -->
    <aop:aspectj-autoproxy />
 
    <!-- 配置目标类 -->
    <bean id="userDao" class="com.wyc.spring3.demo1.UserDao" />
 
    <!-- 配置增强类 -->
    <bean id="myAspect" class="com.wyc.spring3.demo1.MyAspect" />
 
</beans>

----------------------------------------------------------------------------------------------------------------------------

测试代码:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo1 {
 
    @Autowired
    @Qualifier("userDao")
    private UserDao userDao;
 
    @Test
    public void fun1(){
        userDao.add();
        userDao.delete();
        userDao.update();
        userDao.find();
    }
}

——AspectJ的通知类型

1、@Before 前置通知,相当于BeforeAdvice
    在目标方法之前执行,前置通知无法阻止目标对象执行。 
 
2、@AfterReturning 后置通知,相当于AfterReturningAdvice
    后置通知可以获得方法的返回值。 
 
3、@Around 环绕通知,相当于MethodInterceptor
    可以在方法执行前后执行增强方法,环绕通知可以阻止目标方法执行,可以得到目标方法的返回值。 
 
4、@AfterThrowing抛出通知,相当于ThrowAdvice
    当抛出异常时,会执行该通知。
 
5、@After 最终final通知,不管是否异常,该通知都会执行
 
6、@DeclareParents 引介通知,相当于IntroductionInterceptor (不要求掌握)

示例代码:

UserDao目标类:

public class UserDao {
    public void add() {
        System.out.println("添加用户");
    }
 
    public int update() {
        System.out.println("修改用户");
        return 123;
    }
 
    public void delete() {
        System.out.println("删除用户");
    }
 
    public void find() {
        System.out.println("查询用户");
        throw new RuntimeException("哈哈哈");
    }
}

----------------------------------------------------------------------------------------------------------------------------

MyAspect切面类:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
 
/**
 * 切面类
 * 切点 + 增强的结合
 * @author WYC
 *
 */
// 用来定义切面类
@Aspect
public class MyAspect {
    // 对UserDao类的add()方法应用前置增强
    @Before(value = "execution(* com.wyc.spring3.demo1.UserDao.add())")
    public void before(JoinPoint joinPoing){
        System.out.println("前置增强..." + joinPoing);
    }
 
 
    // 后置通知
    @AfterReturning(value="execution(* com.wyc.spring3.demo1.UserDao.update(..))", returning="returnVal")
    // 参数名必须和注解中的配置相同,returnVal会得到方法执行后的返回值
    public void afterReturing(Object returnVal){
        System.out.println("后置增强... " + returnVal);
    }
 
 
    // 环绕通知
    @Around(value = "execution(* com.wyc.spring3.demo1.UserDao.find(..))")
    // ProceedingJoinPoint,获得切点方法
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        System.out.println("环绕前增强...");
 
        // 调用目标方法,该方法会返回目标方法的返回值
        Object obj = proceedingJoinPoint.proceed();
        System.out.println("环绕后增强...");
        return obj;
    }
 
 
    // 异常通知,throwing="e"表示获取一场对象,e表示异常对象的名称
    @AfterThrowing(value="execution(* com.wyc.spring3.demo1.UserDao.find())", throwing="e")
    public void afterThrowing(Throwable e){
        // 显示异常信息
        System.out.println("抛出异常... " + e.getMessage());
    }
 
 
    // 最终通知
    @After(value="execution(* com.wyc.spring3.demo1.UserDao.*())")
    public void after(){
        System.out.println("最终通知....");
    }

}

----------------------------------------------------------------------------------------------------------------------------

XML配置文件:

<?xml version="1.0" encoding="UTF-8"?>
    xsi:schemaLocation="
 
    <!-- 开启AspectJ自动代理 -->
    <!-- 底层基于AnnotationAwareAspectJAutoProxyCreator,如果存在注解,则自动生成代理 -->
    <aop:aspectj-autoproxy />
 
    <!-- 配置目标类 -->
    <bean id="userDao" class="com.wyc.spring3.demo1.UserDao" />
    <!-- 配置增强类 -->
    <bean id="myAspect" class="com.wyc.spring3.demo1.MyAspect" />
 
</beans>

---------------------------------------------------------------------------------------------------------------------------- 

测试代码:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo1 {
 
    @Autowired
    @Qualifier("userDao")
    private UserDao userDao;
 
    @Test
    public void fun1(){
        userDao.add();
        userDao.delete();
        userDao.update();
        userDao.find();
    }
}

---------------------------------------------------------------------------------------------------------------------------- 

打印结果:

前置增强...execution(void com.wyc.spring3.demo1.UserDao.add())
添加用户
最终通知....
删除用户
最终通知....
修改用户
最终通知....
后置增强... 123
环绕前增强...
查询用户
最终通知....
抛出异常... 哈哈哈

——切点的注解

定义一个方法,添加注解表达式,然后在其他方法的注解中使用:类名.方法  即可。

示例代码:

/**
 * 切面类
 * 切点 + 增强的结合
 * @author WYC
 *
 */
// 用来定义切面类
@Aspect
public class MyAspect {
    // 对UserDao类的add()方法应用前置增强
    @Before(value = "execution(* com.wyc.spring3.demo1.UserDao.add())")
    public void before(JoinPoint joinPoing){
        System.out.println("前置增强..." + joinPoing);
    }
 
    // 后置通知
    @AfterReturning(value="execution(* com.wyc.spring3.demo1.UserDao.update(..))", returning="returnVal")
    // 参数名必须和注解中的配置相同
    public void afterReturing(Object returnVal){
        System.out.println("后置增强... " + returnVal);
    }
 
    // 环绕通知
    @Around(value = "MyAspect.myPointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        System.out.println("环绕前增强...");
 
        // 调用目标方法
        Object obj = proceedingJoinPoint.proceed();
        System.out.println("环绕后增强...");
        return obj;
    }
 
    // 异常通知
    @AfterThrowing(value="MyAspect.myPointcut()", throwing="e")
    public void afterThrowing(Throwable e){
        // 显示异常信息
        System.out.println("抛出异常... " + e.getMessage());
    }
 
    // 最终通知
    @After("MyAspect.myPointcut()")
    public void after(){
        System.out.println("最终通知....");
    }
 
    /*
     * 定义一个通用的表达式
     */
    @Pointcut("execution(* com.wyc.spring3.demo1.UserDao.find())")
    private void myPointcut(){
 
    }
}

——Advisor和Aspect的区别

1、Advisor:Spring传统意义上的切面,支持一个切点和一个通知的组合。
2、Aspect:可以支持多个切点和多个通知的组合。 

——基于XML的开发

1、编写被增强的类:

    *   ProductDao

2、定义切面

3、配置applicationContext.xml
    1)前置通知:
        代码:
            public void before(){
                System.out.println("前置通知...");
            }
        配置:

            <!-- 定义AOP配置 -->
            <aop:config>
                <!-- 定义切点 -->
                <!-- 前置通知 -->
                <!-- 给指定表达式起名 -->
                <aop:pointcut expression="execution(* com.wyc.spring3.demo2.ProductDao.add(..))" id="beforePointcut"/>
 
                <!-- 定义切面 -->
                <aop:aspect ref="myAspectXML">
                    <!-- 给切面类的指定方法添加表达式 -->
                    <!-- 前置通知 -->
                    <aop:before method="before" pointcut-ref="beforePointcut"/>
                </aop:aspect>
            </aop:config>

2)后置通知
        代码:
            public void afterReturning(Object returnVal){

                System.out.println("后置增强... " + returnVal);
            }

配置:
            <!-- 定义AOP配置 -->

            <aop:config>
                <!-- 定义切点 -->
                <!-- 给指定表达式起名 -->
                <aop:pointcut expression="execution(* com.wyc.spring3.demo2.ProductDao.find(..))" id="afterReturning"/>

 
                <!-- 定义切面 -->
                <aop:aspect ref="myAspectXML">
                    <!-- 给切面类的指定方法添加表达式 -->
                    <!-- 后置通知 -->
                    <aop:after-returning method="afterReturning" pointcut-ref="afterReturning" returning="returnVal"/>
                </aop:aspect>
            </aop:config>

3)环绕通知
        代码:
            public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{

                System.out.println("环绕前增强...");
                Object result = proceedingJoinPoint.proceed();
                System.out.println(result);
                System.out.println("环绕后增强...");
                return result;
            }

配置:
            <!-- 定义AOP配置 -->

            <aop:config>
                <!-- 定义切点 -->
                <!-- 给指定表达式起名 -->
                <aop:pointcut expression="execution(* com.wyc.spring3.demo2.ProductDao.delete(..))" id="myPointcut"/>

 
                <!-- 定义切面 -->
                <aop:aspect ref="myAspectXML">
                    <!-- 给切面类的指定方法添加表达式 -->
                    <!-- 环绕通知 -->
                    <aop:around method="around" pointcut-ref="myPointcut"/>
                </aop:aspect>
            </aop:config>

4)异常通知
        代码:
            public void afterThrowing(Throwable e){

                System.out.println("异常通知..." + e.getMessage());
            }

配置:
            <!-- 定义AOP配置 -->

            <aop:config>
                <!-- 定义切点 -->
                <!-- 给指定表达式起名 -->
                <aop:pointcut expression="execution(* com.wyc.spring3.demo2.ProductDao.update(..))" id="afterThrowing"/>

 
                <!-- 定义切面 -->
                <aop:aspect ref="myAspectXML">
                    <!-- 异常通知 -->
                    <aop:after-throwing method="afterThrowing" pointcut-ref="afterThrowing" throwing="e"/>
                </aop:aspect>
            </aop:config>

5)最终通知
        代码:
            public void after(){

                System.out.println("最终通知...");
            }
 

配置:
            <aop:config>

                <!-- 定义切点 -->
                <!-- 给指定表达式起名 -->
                <aop:pointcut expression="execution(* com.wyc.spring3.demo2.ProductDao.update(..))" id="afterThrowing"/>

 
                <!-- 定义切面 -->
                <aop:aspect ref="myAspectXML">
                    <!-- 最终通知 -->
                    <aop:after method="after" pointcut-ref="afterThrowing"/>
                </aop:aspect>
            </aop:config>

4、示例代码

ProductDao目标类:

public class ProductDao {
    public void add() {
    System.out.println("添加商品");
}
 
public void update() {
    System.out.println("修改商品");
    // throw new RuntimeException("哈哈哈哈哈");
}
 
public Object delete() {
    System.out.println("删除商品");
    return "删除成功";
}
 
public int find() {
    System.out.println("查询商品");
    return 123;
}

------------------------------------------------------------------------------------------------------------------

MyAspectXML切面类:

import org.aspectj.lang.ProceedingJoinPoint;
 
/**
 * 切面类
 * 
 * @author WYC
 * 
 */
public class MyAspectXML {
    // 前置通知
    public void before() {
        System.out.println("前置增强...");
    }
 
    // 后置通知
    public void afterReturning(Object returnVal) {
        System.out.println("后置增强... " + returnVal);
    }
 
    // 环绕通知
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕前增强...");
        Object result = proceedingJoinPoint.proceed();
        System.out.println(result);
        System.out.println("环绕后增强...");
        return result;
    }
 
    // 异常通知
    public void afterThrowing(Throwable e) {
        System.out.println("异常通知..." + e.getMessage());
    }
 
    // 最终通知
    public void after() {
        System.out.println("最终通知...");
    }
}

------------------------------------------------------------------------------------------------------------------

applicationContext.xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
    xsi:schemaLocation="
 
    <!-- 定义目标类 -->
    <bean id="productDao" class="com.wyc.spring3.demo2.ProductDao" />
 
    <!-- 定义切面类 -->
    <bean id="myAspectXML" class="com.wyc.spring3.demo2.MyAspectXML" />
 
    <!-- 定义AOP配置 -->
    <aop:config>
        <!-- 定义切点 -->
        <!-- 前置通知 -->
        <!-- 给指定表达式起名 -->
        <aop:pointcut expression="execution(* com.wyc.spring3.demo2.ProductDao.add(..))" id="beforePointcut"/>
        <aop:pointcut expression="execution(* com.wyc.spring3.demo2.ProductDao.*(..))" id="afterReturning"/>
        <aop:pointcut expression="execution(* com.wyc.spring3.demo2.ProductDao.delete(..))" id="myPointcut"/>
        <aop:pointcut expression="execution(* com.wyc.spring3.demo2.ProductDao.update(..))" id="afterThrowing"/>
 
        <!-- 定义切面 -->
        <aop:aspect ref="myAspectXML">
            <!-- 给切面类的指定方法添加表达式 -->
            <!-- 前置通知 -->
            <aop:before method="before" pointcut-ref="beforePointcut"/>
 
            <!-- 后置通知 -->
            <aop:after-returning method="afterReturning" pointcut-ref="afterReturning" returning="returnVal"/>
 
            <!-- 环绕通知 -->
            <aop:around method="around" pointcut-ref="myPointcut"/>
 
            <!-- 异常通知 -->
            <aop:after-throwing method="afterThrowing" pointcut-ref="afterThrowing" throwing="e"/>
 
            <!-- 最终通知 -->
            <aop:after method="after" pointcut-ref="afterThrowing"/>
        </aop:aspect>
    </aop:config>
 
</beans>

------------------------------------------------------------------------------------------------------------------

测试代码:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class SpringDemo2 {
  @Autowired
  @Qualifier("productDao")
  private ProductDao productDao;
 
  @Test
  public void fun1() {
    productDao.add();
    productDao.update();
    productDao.delete();
    productDao.find();
  }
}

——总结

为了简化开发,引入了AspectJ的支持。

1、基于注解开发
    开启XML中支持AOP的配置。

    @Aspect:切面注解

    @Before:前置通知
    @Around:环绕通知
    @AfterReturing:后置通知
    @AfterThrowing:抛出异常通知
    @After:最终通知

    @Pointcut:切点注解
        可以简化开发,不需要每一个注解都写表达式。

2、基于XML开发
    1)引入AOP的名称空间。
    2)<aop-config>
            <!-- 定义切点 -->
            <aop:pointcut id="" expression="" />

            <!-- 定义切面,并引入切点 -->
            <aop:aspect ref="">
                <aop:brfore />
            </aop:aspect>
          </aop-config>

Spring之AspectJ的更多相关文章

  1. Spring AOP + AspectJ annotation example

    In this tutorial, we show you how to integrate AspectJ annotation with Spring AOP framework. In simp ...

  2. Spring AOP + AspectJ Annotation Example---reference

    In this tutorial, we show you how to integrate AspectJ annotation with Spring AOP framework. In simp ...

  3. Spring 使用AspectJ的三种方式

    Spring 使用AspectJ 的三种方式 一,使用JavaConfig 二,使用注解隐式配置 三,使用XML 配置 背景知识: 注意 使用AspectJ 的 时候 要导入相应的Jar 包 嗯 昨天 ...

  4. Spring学习之旅(八)Spring 基于AspectJ注解配置的AOP编程工作原理初探

    由小编的上篇博文可以一窥基于AspectJ注解配置的AOP编程实现. 本文一下未贴出的相关代码示例请关注小编的上篇博文<Spring学习之旅(七)基于XML配置与基于AspectJ注解配置的AO ...

  5. 学习 Spring (十七) Spring 对 AspectJ 的支持 (完结)

    Spring入门篇 学习笔记 @AspectJ 的风格类似纯 java 注解的普通 java 类 Spring 可以使用 AspectJ 来做切入点解析 AOP 的运行时仍旧是纯的 Spring AO ...

  6. 关于 Spring AOP (AspectJ) 该知晓的一切

    关联文章: 关于Spring IOC (DI-依赖注入)你需要知道的一切 关于 Spring AOP (AspectJ) 你该知晓的一切 本篇是年后第一篇博文,由于博主用了不少时间在构思这篇博文,加上 ...

  7. Spring学习(十八)----- Spring AOP+AspectJ注解实例

    我们将向你展示如何将AspectJ注解集成到Spring AOP框架.在这个Spring AOP+ AspectJ 示例中,让您轻松实现拦截方法. 常见AspectJ的注解: @Before – 方法 ...

  8. Spring结合AspectJ的研究

    本文阐述以下内容:1.AspectJ是什么及使用方式2.Spring AOP和AspectJ的区别3.Spring结合AspectJ的使用方法和原理4.Spring注解方式使用AspectJ遇到的问题 ...

  9. 10 Spring框架 AOP (三) Spring对AspectJ的整合

    上两节我们讲了Spring对AOP的实现,但是在我们的开发中我们不太使用Spring自身的对AOP的实现,而是使用AspectJ,AspectJ是一个面向切面的框架,它扩展了Java语言.Aspect ...

  10. Spring整合AspectJ的AOP

    学而时习之,不亦说乎!                              --<论语> 看这一篇之前最好先看前面关于AOP的两篇. http://www.cnblogs.com/z ...

随机推荐

  1. 【剑指offer】52. 两个链表的第一个公共节点

    剑指 Offer 52. 两个链表的第一个公共节点 知识点:链表: 题目描述 输入两个链表,找出它们的第一个公共节点. 如下面的两个链表: 示例 示例1: 输入:intersectVal = 8, l ...

  2. awk对某个字段分割处理

    工作中遇到要根据文件中某个字段分割成多行文本的处理,想到用awk处理,这里记录下: 问题: 原文件:假设一共2个字段,用"|"分割,其中第二个字段用"#"分割, ...

  3. 微信小程序云开发-数据库-商品列表数据显示N条数据

    一.wxml文件 在wxml文件中,写页面和点击事件,添加绑定事件limitGoods  二.js文件 在js文件中写limitGoods(),使用.limit(3)表示只显示3条数据

  4. 高德纳/Donald Ervin Knuth

    丸了丸了这位就是我人生的第一位爱豆了owo 感觉他的经历,气质都是我期望的类型呀. 即使没有人家的智商和绝顶天赋,也不断向彼努力吧. 从小喜欢音乐,会多种乐器(管风琴) 其实长得人高马大,高中校篮球队 ...

  5. Java文件I/O简单介绍

    目录 一.File类 1.1 构造方法 1.2 常用方法 1.3 例子 二.基础I/O:字节流.字符流 2.1 字节流 2.1.1 字节输出流 OutputStream 2.1.2 FileOutpu ...

  6. Jupyter Kernel Error 解决办法

    首先在pycharm的terminal中输入 python -m ipykernel install --user 之后会如下图显示: 这就是kernel的安装路径. 然后输入: jupyter ke ...

  7. netty系列之:netty中的Channel详解

    目录 简介 Channel详解 异步IO和ChannelFuture Channel的层级结构 释放资源 事件处理 总结 简介 Channel是连接ByteBuf和Event的桥梁,netty中的Ch ...

  8. 什么是CIDR?

    英文缩写: CIDR (Classless InterDomain Routing) 中文译名: 无类别域间路由选择 IP地址有"类"的概念,/8掩码是A类,/16掩码是B类,/2 ...

  9. 浅谈Java迭代器

    迭代器Iterator 概述: 迭代器(Iterator):它不是一个容器,它是一种用于访问容器的方法,可用于迭代 List.Set和Map等容器. 迭代:一个一个的往外拿. 作用:帮我们遍历或者拿到 ...

  10. 超详细 Java 15 新功能介绍

    点赞再看,动力无限.微信搜「程序猿阿朗 」,认认真真写文章. 本文 Github.com/niumoo/JavaNotes 和 未读代码博客 已经收录,有很多知识点和系列文章. Java 15 在 2 ...