spring AOP

一、AOP概述

1.AOP
全称是Aspect Oriented Programming即:面向切面编程。

简单的说它就是把程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

2.作用
在程序运行期间,不修改源码对已有方法进行增强。
①减少重复代码
②提高开发效率
③维护方便

AOP的实现方式
使用动态代理技术

二、动态代理

1.动态代理的特点
字节码随用随创建,随用随加载。
它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载。
装饰者模式就是静态代理的一种体现。

2.动态代理常用的两种方式

  • 基于接口的动态代理
    提供者:JDK官方的Proxy类。
    要求:被代理类最少实现一个接口。
    创建代理对象的方法:newProxyInstance(ClassLoader,Class[],InvocationHandler)
    参数的含义:
    ClassLoader:类加载器。和被代理对象使用相同的类加载器。一般都是固定写法
    Class[]:字节码数组。被代理类实现的接口。要求代理对象和被代理对象有相同的行为。一般都是固定写法。
    InvocationHandler:它是一个接口,就是用于提供增强代码的。一般都是写一个该接口的实现类。实现类可以是匿名内部类。
    含义:如何代理。这个参数的代码只能是谁用谁提供。
    策略模式:数据有,目标明确,达成目标的过程就是策略。

举一个演员和经纪公司的例子

定义一个艺人标准的接口IActor.java

package com.company.proxy;

/**
 * 艺人标准
 */
public interface IActor {

    public void basicAct(float money);

    public void dangerAct(float money);

}

再实现一个演员类Actor.java

package com.company.proxy;
/**
 * 一个演员
 */
public class Actor implements IActor{
    public void basicAct(float money){
        System.out.println("拿到钱,开始基本表演"+money);
    }

    public void dangerAct(float money){
        System.out.println("拿到钱,开始危险表演"+money);
    }
}

最后再来个经纪公司管理演员的出场费用,并使用动态代理
Client.java

package com.company.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 模拟一个经纪公司
 */
public class Client {
    public static void main(String[] args) {
        Actor actor = new Actor();
//没有经纪公司的时候,直接找演员。
//        actor.basicAct(100f);
//        actor.dangerAct(500f);
        //动态代理
        IActor proxyActor = (IActor) Proxy.newProxyInstance(actor.getClass().getClassLoader(),
                actor.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 执行被代理对象的任何方法都会经过该方法,该方法有拦截的功能
                     * 方法的参数
                     *      Object proxy:代理对象的引用。不一定每次都会有
                     *      Method method:当前执行的方法
                     *      Object[] args:当前执行方法所需的参数
                     * 返回值:
                     *      当前执行方式的返回值
                     */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object rtValue = null;
                //1.取出执行方法中的参数
                Float money = (Float) args[0];
                //2.判断当前执行的是什么方法
                //每个经纪公司对不同演出收费不一样,此处开始判断
                if ("basicAct".equals(method.getName())){
                    //基本演出
                    if (money > 10000){
                        //执行方法
                        //看上去剧组是给了8000,实际到演员手里只有4000
                      //这就是我们没有修改原来basicAct方法源码,对方法进行了增强
                        rtValue = method.invoke(actor,money/2);
                    }
                }
                if ("dangerAct".equals(method.getName())){
                    //危险演出
                    if (money > 50000){
                        //执行方法
                        rtValue = method.invoke(actor,money);
                    }
                }
                return rtValue;
            }
        });
        //剧组无法直接联系演员,而是由经纪公司找的演员
        proxyActor.basicAct(80000f);
        proxyActor.dangerAct(60000f);
    }
}
运行结果
  • 基于子类的动态代理
    提供者:第三方的CGLib,如果报asmxxxx异常,需要导入asm.jar。
    要求:被代理类不能用final修饰的类(最终类)。
    涉及的类:Enhancer
    创建代理对象的方法:create(Class,Callback);
    参数的含义:
    Class:被代理对象的字节码
    Callback:如何代理。它和InvocationHandler的作用一样。
    它也是一个接口,一般使用该接口的子接口MethodInterceptor,在使用是也是创建该接口的匿名内部类。

还是上述例子
不需要IActor.java的接口

更改Client.java

package com.company.proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 模拟一个经纪公司
 */
public class Client {
    public static void main(String[] args) {
        Actor actor = new Actor();
        //动态代理

       Actor cglibActor = (Actor) Enhancer.create(actor.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                /**
                 * 执行被代理对象的任何方法都会经过该方法。它和基于接口动态代理的invoke方法的作用一样
                 * 方法的参数:
                 *      前面三个和invoke方法的参数含义和作用一样。
                 *      MethodProxy methodProxy:当前执行方法的代理对象,一般不用
                 */
                Object rtValue = null;
                //1.取出执行方法中的参数
                Float money = (Float) objects[0];
                //2.判断当前执行的是什么方法
                //每个经纪公司对不同演出收费不一样,此处开始判断
                if ("basicAct".equals(method.getName())){
                    //基本演出
                    if (money > 10000){
                        //执行方法
                        //看上去剧组是给了8000,实际到演员手里只有4000
                        //这就是我们没有修改原来basicAct方法源码,对方法进行了增强
                        rtValue = method.invoke(actor,money/2);
                    }
                }
                if ("dangerAct".equals(method.getName())){
                    //危险演出
                    if (money > 50000){
                        //执行方法
                        rtValue = method.invoke(actor,money);
                    }
                }
                return rtValue;
            }
        });
            cglibActor.basicAct(50000);
            cglibActor.dangerAct(100000);
    }
}
运行结果

3.动态代理的应用——连接池原理(c3p0)

导入的jar包要mysql-connector-java-5.0.8-bin.jar因为这个包里的Connection是个类,而5.1.7的版本里Connection是个接口,无法强转。

没有动态代理之前

关闭一个连接就少一个,启用动态代理,让用完的连接还回池中

项目目录结构

项目目录结构

dbconfig.properties

#数据库连接的配置

#数据库驱动
DRIVER=com.mysql.jdbc.Driver

#连接字符串
URL=jdbc:mysql://localhost:3306/goods

#数据库用户名
USER=root

#数据库密码
PASSWORD=root

JDBCUtil.java

package com.edu.util;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ResourceBundle;

/**
 * 数据库操作相关的工具类
 * @author zhy
 *
 */
public class JDBCUtil {

    //使用ResourceBundle读取资源文件
    private static ResourceBundle bundle = ResourceBundle.getBundle("dbconfig");
    private static String driver;
    private static String url;
    private static String user;
    private static String password;

    //使用静态代码块进行赋值
    static{
        driver = bundle.getString("DRIVER");
        url = bundle.getString("URL");
        user = bundle.getString("USER");
        password = bundle.getString("PASSWORD");
    }

    /**
     * 获取连接
     * @return
     */
    public static Connection getConnection(){
        Connection conn = null;
        try {
            Class.forName(driver);
            conn = DriverManager.getConnection(url, user, password);
            return conn;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    /**
     * 释放资源
     * @param rs
     * @param st
     * @param conn
     */
    public static void release(ResultSet rs,Statement st,Connection conn){
        if(rs != null){
            try{
                rs.close();
            }catch(Exception e){
                e.printStackTrace();
            }
        }
        if(st != null){
            try{
                st.close();
            }catch(Exception e){
                e.printStackTrace();
            }
        }
        if(conn != null){
            try{
                conn.close();
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
}

自定义连接池MyDataSource.java

package com.edu.dataSource;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.edu.util.JDBCUtil;

/**
 * 自定义连接池
 * @author zhy
 *
 */
public class MyDataSource {
    //定义一个池,用于存放连接
    private static List<Connection> pool = Collections.synchronizedList(new ArrayList<Connection>());//把ArrayList转成线程安全的

    //使用静态代码块给池中加入连接
    static{
        for(int i=0;i<10;i++){
            Connection conn = JDBCUtil.getConnection();
            pool.add(conn);
        }
    }

    /**
     * 获取一个连接
     * @return
     */
    public static Connection getConnection(){
        final Connection conn =  pool.remove(0);
        //创建代理对象
        Connection proxyConn = (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(),
                conn.getClass().getInterfaces(),
                new InvocationHandler() {

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object rtValue = null;
                        //1.判断当前方法是不是close方法
                        if("close".equals(method.getName())){
                            //不能直接关闭
                            pool.add(conn);//还回池中
                        }else{
                            rtValue = method.invoke(conn, args);
                        }
                        return rtValue;
                    }
                });
        return proxyConn;
    }
    /**
     * 获取池中的连接数
     * @return
     */
    public static int getPoolSize(){
        return pool.size();
    }
}

测试类

package com.edu.test;

import com.edu.dataSource.MyDataSource;
import java.sql.Connection;

public class Main {

    public static void main(String[] args) throws Exception {
        int size = MyDataSource.getPoolSize();
        System.out.println("使用连接之前"+size);
        for (int i = 0;i < 8 ;i++) {
            Connection conn = MyDataSource.getConnection();
            System.out.println(conn);
            conn.close();
        }
        int size1 = MyDataSource.getPoolSize();
        System.out.println("使用连接之后"+size1);
        for (int i = 0;i < 8 ;i++) {
            Connection conn = MyDataSource.getConnection();
            System.out.println(conn);
            conn.close();
        }
    }
}
运行结果

三、Spring中的AOP

1.AOP相关术语

  • Joinpoint(连接点)
    业务层的接口方法,一端:公共代码(增强的代码);另一端就是业务,究竟要干什么事。这个连接点就是将业务和增强的代码结合起来。
  • Pointcut(切入点):
    被增强的就是切入点,没被增强的就是连接点。
    连接点不一定是切入点;切入点一定是连接点。
  • Advice(通知/增强):
    增强的代码在哪个类,哪个类就是通知。
    通知类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
  • Introduction(引介):
    引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field。
  • Target(目标对象):
    代理的目标对象。
  • Weaving(织入):
    是指把增强应用到目标对象来创建新的代理对象的过程。
    spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
  • Proxy(代理):
    一个类被AOP织入增强后,就产生一个结果代理类。
  • Aspect(切面):
    是切入点和通知(引介)的结合。

2.基于XML的AOP环境搭建

所需jar包

所需jar包

目录结构

目录结构

需要加强的类:

package com.edu.utils;

public class Logger {
    /**
     * 记录日志的操作
     * 计划让其在业务核心方法(切入点方法)执行之前执行
     */
    /**
     * 前置通知
     */
    public void beforePrintLog(){
        System.out.println("Logger中的beforePrintLog方法开始记录日志了。。。。");
    }

    /**
     * 后置通知
     */
    public void afterReturningPrintLog(){
        System.out.println("Logger中的afterReturningPrintLog方法开始记录日志了。。。。");
    }

    /**
     * 异常通知
     */
    public void afterThrowingPrintLog(){
        System.out.println("Logger中的afterThrowingPrintLog方法开始记录日志了。。。。");
    }


    /**
     * 最终通知
     */
    public void afterPrintLog(){
        System.out.println("Logger中的afterPrintLog方法开始记录日志了。。。。");
    }
}

配置bean.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
                   http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 配置Service -->
    <bean id="customerService" class="com.edu.service.impl.CustomerImpl"></bean>
    <!-- 基于XML的aop配置 -->
    <!-- 1.把通知类交给spring管理 -->
    <bean id="Logger" class="com.edu.utils.Logger"></bean>
    <!-- 2.导入aop名称空间,并且使用aop:config开始aop的配置 -->
    <aop:config>
        <!-- 定义通用的切入点表达式,如果写在aop:aspct标签外部,则表示所有切面可用 -->
        <aop:pointcut expression="execution(* com.edu.service.impl.*.*(..))" id="pt1"/>
        <!-- 3.使用aop:aspect配置切面,id属性用于给切面提供一个唯一标识。ref属性:用于通知bean的id -->
        <aop:aspect id="logAdvice" ref="Logger">
            <!-- 4.配置通知的类型,指定增强的方法什么时候执行。
                    method属性:用于指定的方法名称,
                    pointcut:用于指定切入点表达式
             -->
            <!-- 切入点表达式:
                    关键字:execution(表达式)
                    表达式写法:
                        访问修饰符 返回值 报名.报名...类名.方法名(参数列表)
                    全匹配方法:
                        public void com.edu.service.impl.CustomerImpl.saveCustomer()
                    访问修饰符可以省略
                        void com.edu.service.impl.CustomerServiceImpl.saveCustomer()
                    返回值可以使用*号,表示任意返回值
                        * com.edu.service.impl.CustomerServiceImpl.saveCustomer()
                    包名可以使用*号,表示任意包,但是有几级包,需要写几个*
                        * *.*.*.*.CustomerServiceImpl.saveCustomer()
                    使用..来表示当前包,及其子包
                        * com..CustomerServiceImpl.saveCustomer()
                    类名可以使用*号,表示任意类
                        * com..*.saveCustomer()
                    方法名可以使用*号,表示任意方法
                        * com..*.*()
                    参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
                        * com..*.*(*)
                    参数列表可以使用..表示有无参数均可,有参数可以是任意类型
                        * com..*.*(..)
                    全通配方式:
                        * *..*.*(..)
             -->

            <!-- 配置前置通知: 永远在切入点方法执行之前执行 -->
            <aop:before method="beforePrintLog" pointcut-ref="pt1"/>
            <!-- 配置后置通知: 切入点方法正常执行之后执行 -->
            <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"/>
            <!-- 配置异常通知: 切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个 -->
            <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"/>
            <!-- 配置最终通知:无论切入点方法是否正常执行,它都会在其后面执行 -->
            <aop:after method="afterPrintLog" pointcut-ref="pt1"/>

            <!-- 定义通用的切入点表达式:如果只写在了aop:aspect标签内部,则表达式只有当前切面可用-->
            <!--<aop:pointcut id="pt1" expression="execution(* com.edu.service.impl.*.*(..))"/>-->

        </aop:aspect>

    </aop:config>
</beans>

ui界面:

package com.edu.ui;

import com.edu.service.ICustomerService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Client {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        ICustomerService cs = (ICustomerService) applicationContext.getBean("customerService");
        cs.saveCustomer();
    }
}
运行结果

环绕通知

/**
     * 环绕通知
     *  它是spring框架为我们提供的一种可以在代码中手动控制增强部分什么时候执行的方式。
     * 问题:
     *  当我们配置了环绕通知之后,增强的代码执行了,业务核心方法没有执行。
     * 分析:
     *  通过动态代理我们知道在invoke方法中,有明确调用业务核心方法:method.invoke()。
     *  我们配置的环绕通知中,没有明确调用业务核心方法。
     * 解决:
     *  spring框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数
     *  在环绕通知执行时,spring框架会为我们提供该接口的实现类对象,我们直接使用就行。
     *  该接口中有一个方法proceed(),此方法就相当于method.invoke()
     */
    public void aroundPringLog(ProceedingJoinPoint pjp){
        try {
            System.out.println("前置通知:Logger类的aroundPringLog方法记录日志");
            pjp.proceed();
            System.out.println("后置通知:Logger类的aroundPringLog方法记录日志");
        } catch (Throwable e) {
            System.out.println("异常通知:Logger类的aroundPringLog方法记录日志");
            e.printStackTrace();
        }finally{
            System.out.println("最终通知:Logger类的aroundPringLog方法记录日志");
        }
    }

bean.xml

<aop:around method="aroundPrintLog" pointcut-ref="pt1"/>

3.基于注解的AOP环境搭建
上述案例
加强的类:

package com.edu.utils;

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;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 一个用于记录日志的类
 *
 */
@Component("logger")
@Aspect//配置了切面
public class Logger {

    @Pointcut("execution(* com.edu.service.impl.*.*(..))")
    private void pt1(){}
    
    /**
     * 前置通知
     */
    //@Before("pt1()")
    public void beforePrintLog(){
        System.out.println("前置:Logger中的beforePrintLog方法开始记录日志了。。。。");
    }
    
    /**
     * 后置通知
     */
    //@AfterReturning("pt1()")
    public void afterReturningPrintLog(){
        System.out.println("后置:Logger中的afterReturningPrintLog方法开始记录日志了。。。。");
    }
    
    /**
     * 异常通知
     */
    //@AfterThrowing("pt1()")
    public void afterThrowingPrintLog(){
        System.out.println("异常:Logger中的afterThrowingPrintLog方法开始记录日志了。。。。");
    }
    /**
     * 最终通知
     */
    //@After("pt1()")
    public void afterPrintLog(){
        System.out.println("最终:Logger中的afterPrintLog方法开始记录日志了。。。。");
    }
    
    
    
    /**
     * 环绕通知
     */
    @Around("pt1()")
    public Object aroundPrintLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try {
            System.out.println("Logger中的aroundPrintLog方法开始记录日志了。。。。前置");
            rtValue = pjp.proceed();
            System.out.println("Logger中的aroundPrintLog方法开始记录日志了。。。。后置");
        } catch (Throwable e) {
            System.out.println("Logger中的aroundPrintLog方法开始记录日志了。。。。异常");
            e.printStackTrace();
        }finally{
            System.out.println("Logger中的aroundPrintLog方法开始记录日志了。。。。最终");
        }
        
        return rtValue;
    }   
}

bean.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">
    
    <!-- 配置spring创建容器时要扫描的包 -->
    <context:component-scan base-package="com.edu"></context:component-scan>

    <!-- 开启spring对注解AOP的支持-->
    <aop:aspectj-autoproxy />
</beans>

纯注解

package config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfiguration {

}

Client.java

package com.itheima.ui;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.itheima.service.ICustomerService;

import config.SpringConfiguration;

public class Client {

    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        ICustomerService cs = (ICustomerService) ac.getBean("customerService");
        cs.saveCustomer();
        
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 前言 只有光头才能变强 上一篇已经讲解了Spring IOC知识点一网打尽!,这篇主要是讲解Spring的AOP模...
    Java3y阅读 6,878评论 8 181
  • **** AOP 面向切面编程 底层原理 代理!!! 今天AOP课程1、 Spring 传统 AOP2、 Spri...
    luweicheng24阅读 1,351评论 0 1
  • SpringAOP的使用Demo 通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 S...
    独念白阅读 457评论 0 5
  • 0.前言 本文主要想阐述的问题如下:什么动态代理(AOP)以及如何用JDK的Proxy和InvocationHan...
    SYFHEHE阅读 2,253评论 1 7
  • 1. Spring AOP介绍 AOP(Aspect-Oriented Programming),面向切面的编程,...
    郭寻抚阅读 1,657评论 1 11