zoukankan      html  css  js  c++  java
  • AOP面向切面编程

    一、AOP概述

    1. AOP简介

    AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程
    AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB的动态代理

    可以看之前写的动态代理,

    https://www.cnblogs.com/mengd/p/13429797.html

    1. jdk动态代理:使用jdk中的Proxy,Method,InvocaitonHanderl创建代理对象,jdk动态代理要求目标类必须实现接口
    2. cglib动态代理:第三方的工具库,创建代理对象,原理是继承。 通过继承目标类,创建子类,子类就是代理对象。 要求目标类不能是final的, 方法也不能是final的

    动态代理的作用:

    • 在目标类源代码不改变的情况下,增加功能
    • 减少代码的重复
    • 专注业务逻辑代码
    • 解耦合,让你的业务功能和日志分离,事务和非业务功能分离

    2. 如何理解AOP

    AOP(Aspect Orient Programming)面向切面编程

    • Aspect: 切面,给你的目标类增加的功能,就是切面,切面的特点: 一般都是非业务方法,独立使用的
    • Orient:面向
    • oop: 面向对象编程

    理解:

    1. 需要在分析项目功能时,找出切面
    2. 合理的安排切面的执行时间(在目标方法前, 还是目标方法后)
    3. 合理的安全切面执行的位置,在哪个类,哪个方法增加增强功能

    二、AOP编程术语

    1. 切面(Aspect)

    表示增强的功能, 就是一堆代码,完成某个一个功能,非业务功能

    常见的切面功能有日志, 事务, 统计信息, 参数检查, 权限验证

    2. 连接点(JoinPoint)

    连接业务方法和切面的位置,就某类中的业务方法

    3. 切入点(Pointcut)

    指多个连接点方法的集合,多个方法

    4. 目标对象(Target)

    给哪个类的方法增加功能, 这个类就是目标对象

    5. 通知(Advice)

    通知表示切面功能执行的时间

    一个切面有三个关键的要素:

    1. 切面的功能代码,切面是干什么
    2. 切面的执行位置,使用Pointcut表示切面执行的位置
    3. 切面的执行时间,使用Advice表示时间,在目标方法之前,还是目标方法之后

    三、Aspectj对AOP的实现

    aop是一个规范,是动态的一个规范化,一个标准

    aop的技术实现框架:

    1. spring:spring在内部实现了aop规范,能做aop的工作,我们项目开发中很少使用spring的aop实现。 因为spring的aop比较笨重
    2. aspectJ: 一个开源的专门做aop的框架。spring框架中集成了aspectj框架,通过spring就能使用aspectj的功能

    aspectJ框架实现aop有两种方式:

    • 使用xml的配置文件 : 配置全局事务
    • 使用注解,我们在项目中要做aop功能,一般都使用注解, aspectj有5个注解

    1. Aspectj的通知类型

    AspectJ 中常用的通知有五种类型

    • 前置通知
    • 后置通知
    • 环绕通知
    • 异常通知
    • 最终通知

    2. Aspectj的切入点表达式

    以上表达式共4个部分

    execution(访问权限 方法返回值 方法声明(参数) 异常类型)

    切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就
    是方法的签名。

    注意,表达式中黑色文字表示可省略部分,各部分间用空格分开

    在其中可以使用以下符号:

    常用的几个:

    execution(public * * (..))

    指定切入点的位置:任意的公共方法

    execution(* set*(..))

    指定切入点的位置:任何一个以set开始的方法

    execution(* com.xyz.service.*.*(..))

    指定切入点的位置:定义在service包里的任意类的任意方法

    execution(* com.xyz.service..*.*(..))

    指定切入点的位置:定义在service包或者子包里的任意类的任意方法

    .. 出现在类名中时,后面必须跟*,表示包、子包下的所有类

    execution(* *..service.*.*(..))

    指定所有包下的service子包下所有类中所有的方法为切入点

    3. Aspectj的开发环境

    1. maven依赖

    <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.11</version>
          <scope>test</scope>
        </dependency>
    
        <!--spring依赖-->
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>5.2.5.RELEASE</version>
        </dependency>
    
        <!--aspectj依赖-->
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-aspects</artifactId>
          <version>5.2.5.RELEASE</version>
        </dependency>
    

    2. 引入AOP约束

    在 AspectJ 实现 AOP 时,要引入 AOP 的约束。配置文件中使用的 AOP 约束中的标签,均是 AspectJ 框架使用的,而非 Spring 框架本身在实现 AOP 时使用的
    AspectJ 对于 AOP 的实现有注解和配置文件两种方式,常用是注解方式

    四、AspectJ基于注解的AOP实现

    1. 实现步骤

    1. 定义业务接口与实现类

    package com.md.b1;
    
    /**
     * @author MD
     * @create 2020-08-09 10:55
     */
    public interface SomeService {
        void doSome(String name , Integer age);
    }
    
    //-------------------------
    
    package com.md.b1;
    
    /**
     * @author MD
     * @create 2020-08-09 10:55
     */
    
    // 目标类
    public class SomeServiceImpl implements SomeService {
        @Override
        public void doSome(String name, Integer age) {
            // 给doSome方法增加一个功能,在执行之前输出时间
            System.out.println("目标方法doSome()");
        }
    }
    
    

    2. 定义切面类

    类中定义了若干普通方法,将作为不同的通知方法,用来增强功能

    注意点:

    @Aspect

    • 这个注解是aspectj框架中的注解
    • 作用:表示当前类是切面类
    • 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
    • 位置:类定义的上面

    定义方法,方法是实现切面功能的

    方法的要求:

    • 公共方法
    • 方法名称自定义
    • 方法没有返回值
    • 方法可以有或没有参数,如果有参数,参数不是自定义的,有几个参数类型可以使用
    package com.md.b1;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import java.util.Date;
    
    /**
     * @author MD
     * @create 2020-08-09 10:58
     */
    
    @Aspect
    public class MyAspect {
    
        // 前置通知,具体的在下变
       @Before(value = "execution(public void com.md.b1.SomeServiceImpl.doSome(String,Integer))")
        public void myBefore(){
            // 就是你切面要执行的功能代码
            System.out.println("前置通知,切面功能:在目标方法之前输出时间:"+new Date());
        }
    }
    
    

    3. 定义目标对象切面类对象

    还是在src/main/resources下建立applicationContext.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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!--把对象交给spring容器,由spring容器统一创建,管理对象-->
        
        <!--声明目标对象-->
        <bean id="someService" class="com.md.b1.SomeServiceImpl"/>
    
        <!--声明切面类对象-->
        <bean id="myAspect" class="com.md.b1.MyAspect"/>
    
    </beans>
    

    4. 注册AspectJ自动代理

    在上面文件的基础上添加

        <bean id="someService" class="com.md.b1.SomeServiceImpl"/>
    
        <bean id="myAspect" class="com.md.b1.MyAspect"/>
    
    
        <!--声明自动代理生成器:
            使用的是aspectj框架内部的功能,创建目标对象的代理对象
            创建代理对象是在内存中实现的,修改目标对象的内存中的结构,
            创建为代理对象。所以,目标对象就是被修改后的代理对象
    
            aspectj-autoproxy:会把spring容器中的所有目标对象,一次性都生成代理对象
        -->
    
        <aop:aspectj-autoproxy  />
    

    <aop:aspectj-autoproxy/>的底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的。
    从其类名就可看出,是基于 AspectJ 的注解适配自动代理生成器。
    其工作原理是,<aop:aspectj-autoproxy/>通过扫描找到@Aspect 定义的切面类,再由切
    面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点

    5. 测试类中的使用

    package com.md;
    
    import com.md.b1.SomeService;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import sun.security.provider.Sun;
    
    /**
     * @author MD
     * @create 2020-08-09 15:28
     */
    public class MyTest01 {
    
        @Test
        public void test01(){
            String config = "applicationContext.xml";
    
            ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    
            // 从容器中获取目标对象,此时的目标对象是经过了aspectj修改后的代理对象
            SomeService proxy = (SomeService) ac.getBean("someService");
    
            //com.sun.proxy.$Proxy8 jdk动态代理
            //System.out.println(proxy.getClass().getName());
    
            // 通过代理的对象执行方法,实现目标方法执行,增强了功能
            proxy.doSome("张三",19);
    
    //        前置通知,切面功能:在目标方法之前输出时间:Sun Aug 09 15:33:24 CST 2020
    //        目标方法doSome()
        }
    }
    
    

    2. @Before前置通知

    在目标方法执行之前执行

    被注解为前置通知的方法,可以包含一个 JoinPoint 类型参数。该类型的对象本身就是切入点表达式

    通过该参数,可获取切入点表达式、方法签名、目标对象等
    不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数。

    这个JoinPoint参数的值是由框架赋予, 必须是第一个位置的参数

    @Aspect
    public class MyAspect {
    
    //    @Before():前置通知注解
    //    属性:value 是切入点表达式,表示切面功能执行的位置
    //    位置:在方法的上面
    //    特点:
    //    1. 在目标方法之前执行
    //    2. 不会改变目标方法的执行结果
    //    3. 不会影响目标方法的执行
    
    
    
    //    @Before(value = "execution(public void com.md.b1.SomeServiceImpl.doSome(String,Integer))")
    //    public void myBefore(){
    //        // 就是你切面要执行的功能代码
    //        System.out.println("前置通知,切面功能:在目标方法之前输出时间:"+new Date());
    //    }
    
    
    //    @Before(value = "execution( * *..SomeServiceImpl.do*(..))")
    //    public void myBefore(){
    //        // 就是你切面要执行的功能代码
    //        System.out.println("前置通知,切面功能:在目标方法之前输出时间:"+new Date());
    //    }
    
    
        /**
         * 指定通知方法中的参数 : JoinPoint
         * JoinPoint:业务方法,要加入切面功能的业务方法
         *    作用是:可以在通知方法中获取方法执行时的信息, 例如方法名称,方法的实参。
         *    如果你的切面功能中需要用到方法的信息,就加入JoinPoint.
         *    这个JoinPoint参数的值是由框架赋予, 必须是第一个位置的参数
         */
        @Before(value = "execution(void *..SomeServiceImpl.doSome(String,Integer))")
        public void myBefore(JoinPoint jp){
            // 获取方法的完整定义
            System.out.println("方法的定义:"+jp.getSignature());
            System.out.println("方法的名称:"+jp.getSignature().getName());
    
            // 获取方法的实参
            Object[] args = jp.getArgs();
            for (Object arg:args){
                System.out.println("参数:"+arg);
            }
    
    
    //        方法的定义:void com.md.b1.SomeService.doSome(String,Integer)
    //        方法的名称:doSome
    //        参数:张三
    //        参数:19
    
            // 就是你切面要执行的功能代码
            System.out.println("前置通知,切面功能:在目标方法之前输出时间:"+new Date());
        }
    }
    
    

    3. @AfterReturning后置通知

    在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值

    该注解的 returning 属性就是用于指定接收方法返回值的变量名的

    所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型

    增加接口的方法

    public interface SomeService {
        void doSome(String name, Integer age);
        String doOther(String name , Integer age);
    }
    //--------------------------------------------------
    // 目标类
    public class SomeServiceImpl implements SomeService {
    
    
        @Override
        public void doSome(String name, Integer age) {
            // 给doSome方法增加一个功能,在执行之前输出时间
            System.out.println("目标方法doSome()");
        }
    
        @Override
        public String doOther(String name, Integer age) {
            System.out.println("目标方法doOther()");
            return "a";
        }
    }
    
    

    定义切面

    @Aspect
    public class MyAspect {
        /**
         * 后置通知定义方法,方法是实现切面功能的。
         * 方法的定义要求:
         * 1.公共方法 public
         * 2.方法没有返回值
         * 3.方法名称自定义
         * 4.方法有参数的,推荐是Object ,参数名自定义
         */
    
        /**
         * @AfterReturning:后置通知
         *    属性:1.value 切入点表达式
         *         2.returning 自定义的变量,表示目标方法的返回值的。
         *          自定义变量名必须和通知方法的形参名一样。
         *    位置:在方法定义的上面
         * 特点:
         *  1。在目标方法之后执行的。
         *  2. 能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能
         *      Object res = doOther();
         *  3. 可以修改这个返回值,但不影响最后的调用结果
         *
         *  后置通知的执行
         *    Object res = doOther();
         *    参数传递: 传值, 传引用
         *    myAfterReturing(res);
         *    System.out.println("res="+res)
         *
         */
    
        @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",returning = "res")
        public void myAfterReturing(Object res){
    
            if (res.equals("a")){
                // 你可以做一些功能
                System.out.println("登陆成功");
            }else{
                System.out.println("登陆失败");
            }
    
            
            // Object res:是目标方法执行后的返回值,可以根据返回值做切面功能处理
            System.out.println("后置通知:获取的返回值是:"+res);
    
            // 修改目标方法的返回值,是否影响最后的方法调用结果
            // 无影响,
            if (res != null){
                res = "hello Aspectj";
            }
        }
    }
    
    

    4. @Around环绕通知

    在目标方法执行之前之后执行。

    被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。

    接口 ProceedingJoinPoint 其有一个proceed()方法,用于执行目标方法。

    若目标方法有返回值,则该方法的返回值就是目标方法的返回值。

    最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行

    首先增加方法和实现

    public interface SomeService {
        void doSome(String name, Integer age);
        String doOther(String name, Integer age);
        String doFirst(String name,Integer age);
    }
    //-------------------------------------------
    // 目标类
    public class SomeServiceImpl implements SomeService {
    
        @Override
        public void doSome(String name, Integer age) {
    
            // 给doSome方法增加一个功能,在执行之前输出时间
            System.out.println("目标方法doSome()");
        }
    
    
        @Override
        public String doOther(String name, Integer age) {
            System.out.println("目标方法doOther()");
            return "a";
        }
    
        @Override
        public String doFirst(String name, Integer age) {
            System.out.println("目标方法doFirst()");
            return "doFirst";
        }
    
    }
    

    切面类

    @Aspect
    public class MyAspect {
        /**
         * 环绕通知方法的定义格式
         *  1.public
         *  2.必须有一个返回值,推荐使用Object
         *  3.方法名称自定义
         *  4.方法有参数,固定的参数 ProceedingJoinPoint
         */
    
        /**
         * @Around: 环绕通知
         *    属性:value 切入点表达式
         *    位置:在方法定义的上面
         * 特点:
         *   1.它是功能最强的通知
         *   2.在目标方法的前和后都能增强功能。
         *   3.控制目标方法是否被调用执行
         *   4.修改原来的目标方法的执行结果。 影响最后的调用结果
         *
         
         *  环绕通知,等同于jdk动态代理的,InvocationHandler接口
         *
         *  参数:  ProceedingJoinPoint 就等同于 Method
         *         作用:执行目标方法的
         *  返回值: 就是目标方法的执行结果,可以被修改。
         *
         *  环绕通知: 经常做事务, 在目标方法之前开启事务,执行目标方法, 在目标方法之后提交事务
         */
    
    //    @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
    //    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
    //
    //        Object result = null;
    //
    //
    //        System.out.println("环绕通知,在目标方法之前加入通知:现在时间:"+new Date());
    //
    //        // 1. 目标方法调用,等同于method.invoke(); 在这里等同于Object result = doFirst();
    //        result = pjp.proceed();
    //
    //        // 2. 在目标方法前后加入功能
    //        System.out.println("环绕通知,在目标方法之后提交事务");
    //
    //        // 3. 返回目标方法的执行结果
    //        return result;
    //
    //    }
    
    
    
    
        @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
        public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
    
    	// 可以获取到调用方法的参数
            String name = "";
            // 获取第一个参数的值
            Object args[] = pjp.getArgs();
            if (args!=null && args.length > 1){
                Object arg = args[0];
                name = (String) arg;
            }
    
    
            // 实现环绕通知
            Object result = null;
            System.out.println("环绕通知,在目标方法之前加入通知:现在时间:"+new Date());
    
    
            // 1. 目标方法调用,等同于method.invoke(); 在这里等同于Object result = doFirst();
            if ("张三".equals(name)){
                // 符合条件,调用目标方法
                result = pjp.proceed();
            }
    
    
            // 2. 在目标方法前后加入功能
            System.out.println("环绕通知,在目标方法之后提交事务");
    
    
            // 还可以修改目标方法的执行结果,影响方法最后的调用结果
            if (result != null){
                result = "修改了";
            }
    
    
            // 3. 返回目标方法的执行结果
            return result;
    
        }
    
    }
    
    

    测试:

    public class MyTest03 {
    
        @Test
        public void test01(){
            String config = "applicationContext.xml";
    
            ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    
            // 从容器中获取目标对象
            SomeService proxy = (SomeService) ac.getBean("someService");
    
    
    
            // 通过代理的对象执行方法,实现目标方法执行,增强了功能
            String str = proxy.doFirst("张三",20);
    
            System.out.println(str);
    
        }
    }
    
    //环绕通知,在目标方法之前加入通知:现在时间:Mon Aug 10 20:52:07 CST 2020
    //        目标方法doFirst()
    //        环绕通知,在目标方法之后提交事务
    //        修改了
    

    5. @AfterThrowing 异常通知

    在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象

    相当于try-catch中catch里面执行的

    增加业务方法

    public interface SomeService {
        void doSome(String name, Integer age);
        String doOther(String name, Integer age);
        String doFirst(String name, Integer age);
    
        void doSecond();
    }
    
    
    //实现类-----------------------------------------
    @Override
        public void doSecond() {
             System.out.println("执行业务方法doSecond()" + (10/0));
        }
    

    切面类:

    @Aspect
    public class MyAspect {
        /**
         * 异常通知方法的定义格式
         *  1.public
         *  2.没有返回值
         *  3.方法名称自定义
         *  4.方法有个一个Exception, 如果还有是JoinPoint,
         */
    
        /**
         * @AfterThrowing:异常通知
         *     属性:1. value 切入点表达式
         *          2. throwinng 自定义的变量,表示目标方法抛出的异常对象。
         *             变量名必须和方法的参数名一样
         * 特点:
         *   1. 在目标方法抛出异常时执行的
         *   2. 可以做异常的监控程序, 监控目标方法执行时是不是有异常。
         *      如果有异常,可以发送邮件,短信进行通知
         *
         *  执行就是:
         *   try{
         *       SomeServiceImpl.doSecond(..)
         *   }catch(Exception e){
         *       myAfterThrowing(e);
         *   }
         */
        @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",
                throwing = "ex")
        public void myAfterThrowing(Exception ex) {
            System.out.println("异常通知:方法发生异常时,执行:"+ex.getMessage());
            //发送邮件,短信,通知开发人员
        }
    }
    

    6. @After最终通知

    无论目标方法是否抛出异常,该增强均会被执行

    相当于try-catch-finally中finally里面执行的

    增加方法及实现

    public interface SomeService {
        void doSome(String name, Integer age);
        String doOther(String name, Integer age);
        String doFirst(String name, Integer age);
        void doSecond();
    
        void doThird();
    }
    
    //------------------------
      @Override
        public void doThird() {
            System.out.println("执行业务方法doThird()"+ (10/0));
        }
    

    切面类

    @Aspect
    public class MyAspect {
        /**
         * 最终通知方法的定义格式
         *  1.public
         *  2.没有返回值
         *  3.方法名称自定义
         *  4.方法没有参数,  如果还有是JoinPoint,
         */
    
        /**
         * @After :最终通知
         *    属性: value 切入点表达式
         *    位置: 在方法的上面
         * 特点:
         *  1.总是会执行
         *  2.在目标方法之后执行的
         *
         *  try{
         *      SomeServiceImpl.doThird(..)
         *  }catch(Exception e){
         *
         *  }finally{
         *      myAfter()
         *  }
         *
         */
        @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
        public  void  myAfter(){
            System.out.println("执行最终通知,总是会被执行的代码");
            //一般做资源清除工作的。
         }
    
    }
    
    

    7. @Pointcut定义切入点

    当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式

    其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法

    五、总结

    前面的概念有些绕,看了代码就比较清晰了,感觉和python的装饰器很像,只不过py的没有这么绕,就是为已经存在的对象添加额外的功能

    总的来说就是在一个方法前或一个方法后执行一些通用的方法,提高效率,把那些业务的方法写一块,那些非业务的方法或那些业务方法经常使用的方法写成切面类,使用方便还便于管理

    1. 使用aspectj框架实现aop

    使用aop:目的是给已经存在的一些类和方法增加额外的功能,前提是不改变原来类的代码

    1. 新建maven项目
    2. 加入依赖:spring依赖和aspectj依赖以及junit单元测试
    3. 创建目标类
      • 接口和它的实现类,要做的是给类中的方法增加功能
    4. 创建切面类:普通类
      • 在类的上面加入@Aspect
      • 在类中定义方法,这个方法就是切面要执行的功能代码
      • 在方法的上面加入aspectj中的通知注解。例如:@Before
      • 还需要指定切入点表达式,execution()
    5. 创建spring的配置文件,声明对象,把对象交给容器统一管理,声明对象可以使用注解或者<bean>
      • 声明目标对象
      • 声明切面类对象
      • 声明aspectj框架中的自动代理生成器标签,自动代理生成器:用来完成代理对象的自动创建功能
    6. 创建测试类
      • 从spring容器中获取目标对象(实际上就是代理对象),通过代理执行,实现aop的功能增强

    2. Review



    强制使用cglib代理

     目标类有接口,还想用cglib代理
            proxy-target-class="true" : 这句话就是告诉框架,要使用cglib动态代理
            <aop:aspectj-autoproxy proxy-target-class="true" />
    
  • 相关阅读:
    SQL: 从一个表随机读取一行或几行记录的问题
    Android: 创建一个AlertDialog对话框,必须按确定或取消按钮才能关闭对话框,禁止按[返回键]或[搜索键]关闭
    Asp: 解决脚本输出网页出现乱码情况
    Java: |(或运算) 与 多选判断
    ASP: Response 对象 错误 'ASP 0251 : 80004005' 解决办法
    Java: 在dos窗口输入密码,不要把密码直接显示出来,原来可以这么简单
    Android: 网络随时需要在3G和Wifi切换,网络程序需要注意
    Android: 待机时如何让程序继续运行 extends Service
    Android: View换切后,无法正常设置焦点或切换后TextView的虚拟键盘不弹出
    MySQL存储过程学习笔记
  • 原文地址:https://www.cnblogs.com/mengd/p/13485462.html
Copyright © 2011-2022 走看看