时间: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. MapReduce学习总结之简介

    执行步骤:1)准备Map处理的输入数据 2)Mapper处理 3)Shuffle 4)Reduce处理 5)结果输出 三.mapreduce核心概念: 1)split:交由MapReduce作业来处理 ...

  2. linux下系统时间和时钟时间

    linux中有关系统时间.时钟时间的命令: 1显示系统时间的命令 ># date 2显示时钟时间的命令 ># clock或hwclock 3系统时间与互联网同步的命令 ># ntpd ...

  3. .net core工具组件系列之Redis—— 第一篇:Windows环境配置Redis(5.x以上版本)以及部署为Windows服务

    Cygwin工具编译Redis Redis6.x版本是未编译版本(官方很调皮,所以没办法,咱只好帮他们编译一下了),所以咱们先下载一个Cygwin,用它来对Redis进行编译. Cygwin下载地址: ...

  4. vue(23)Vuex的5个核心概念

    Vuex的核心概念 Vuex有5个核心概念,分别是State,Getters,mutations,Actions,Modules. State   Vuex使用单一状态树,也就是说,用一个对象包含了所 ...

  5. 从redis数据库取数据存放到本地mysql数据库

    redis数据库属于非关系型数据库,数据存放在内存堆栈中,效率比较高. 其存储数据是以json格式字符串存储字典的,而类似的关系型数据库无法实现这种数据的存储. 在爬取数据时,将数据暂存到redis中 ...

  6. 图像旋转的FPGA实现(一)

    继续图像处理专题,这次写的是图像旋转.若要说小分辨率的图像旋转倒也简单,直接将原始图像存储在BRAM中,然后按照旋转后的位置关系取出便是.但是对于高分辨的图像(720P及以上)就必须得用DDR3或者D ...

  7. Python脚本:批量将.doc文件转化为.docx文件

    将.doc转换为.docx文件有几种常用的方法: Microsoft Word 和 WPS 自带.doc转换.docx功能,但只能一个文件一个文件转换,批量转换要会员 在线网页 Office-Conv ...

  8. DC-7靶机

    仅供个人娱乐 靶机信息 下载地址:http://www.five86.com/downloads/DC-7.zip 一.主机扫描 二.信息收集 或者 python3 cmseek.py --url h ...

  9. 将几张图片合并为一张图片,返回byte数组

    需求:通过url数组下载图片,再竖直合成一张新的图片,具体java代码如下 1 /** 2 * 竖直合并图片 3 * 4 * @param urls 5 * @return 6 */ 7 public ...

  10. 未知高度-纯css实现水平垂直居中

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...