切面_切面编程 - CSDN
精华内容
参与话题
  • 一、AOP切面编程 1、什么是AOP AOP是面向切面编程。全称:Aspect Oriented Programming 面向切面编程指的是:程序是运行期间,动态地将某段代码插入到原来方法代码的某些位置中。这就叫面向切面编程。 2、一个...

    个人博客网:https://wushaopei.github.io/    (你想要这里多有)

    一、AOP切面编程

    1、什么是AOP

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

    面向切面编程指的是:程序是运行期间动态地将某段代码插入到原来方法代码的某些位置中。这就叫面向切面编程。

    2、一个简单计算数功能加日记

    public class LogUtil {
    
    	public static void logBefore(String method, Object... args) {
    		System.out.println("方法是【" + method + "】,参数是:" + Arrays.asList(args));// 日记
    	}
    
    	public static void logAfterReturning(String method, Object result) {
    		System.out.println("方法是【" + method + "】,返回值是:" + result);// 日记
    	}
    
    }
    public interface Calculate {
    	// 加法
    	public int add(int num1, int num2);
    
    	// 加法
    	public int add(int num1, int num2, int num3);
    
    	// 除法
    	public int div(int num1, int num2);
    
    }
    
    
    实现类
    
    public class Calculator implements Calculate {
    
    	@Override
    	public int add(int num1, int num2) {
    		LogUtil.logBefore("add", num1, num2);
    		int result = num1 + num2;
    		LogUtil.logAfterReturning("add", result);
    		return result;
    	}
    
    	@Override
    	public int add(int num1, int num2, int num3) {
    		LogUtil.logBefore("add", num1, num2, num3);
    		int result = num1 + num2 + num3;
    		LogUtil.logAfterReturning("add", result);
    		return result;
    	}
    
    	@Override
    	public int div(int num1, int num2) {
    		LogUtil.logBefore("div", num1, num2);
    		int result = num1 / num2;
    		LogUtil.logAfterReturning("div", result);
    		return result;
    	}
    
    }

    测试:

    //告诉junit测试,我的Spring容器配置文件在哪里
    @ContextConfiguration(locations="classpath:applicationContext.xml")
    //使用Spring扩展的junit4运行器去运行测试
    @RunWith(SpringJUnit4ClassRunner.class)
    public class SpringTest {
    
    	@Autowired
    	Calculator calculate;
    	
    	@Test
    	public void test1() throws Exception {
    		calculate.add(100, 100);
    		System.out.println("=================================");
    		calculate.div(100, 0);
    	}
    	
    }

    结果:

    方法 前置通知logBefore 是【add】,参数是:[100, 100]
    目标方法  add(int num1, int num2) 正在执行
    方法 后置通知logAfter 是【add】,参数是:[100, 100]
    方法 返回通知logAfterReturning 是【add】,返回值是:200

    =================================
    方法 前置通知logBefore 是【div】,参数是:[100, 0]
    目标方法  div(int num1, int num2) 正在执行
    方法 后置通知logAfter 是【div】,参数是:[100, 0]
    方法 异常通知logAfterThrowing 是【div】,异常是:java.lang.ArithmeticException: / by zero

    十月 29, 2019 8:01:20 下午 org.springframework.context.support.GenericApplicationContext doClose
    信息: Closing org.springframework.context.support.GenericApplicationContext@42d3bd8b: startup date [Tue Oct 29 20:01:19 CST 2019]; root of context hierarchy

    实现业务原理分析:

    4、使用代理实现日记

    4.1、使用jdk动态代理统一日记

       增强功能:

    public class LogUtil {
    	/**
    	 * 前置通知
    	 * 
    	 * @param method
    	 * @param args
    	 */
    	public static void logBefore(String method, Object... args) {
    		System.out.println("方法 前置通知logBefore 是【" + method + "】,参数是:" + Arrays.asList(args));// 日记
    	}
    
    	/**
    	 * 后置通知
    	 * 
    	 * @param method
    	 * @param args
    	 */
    	public static void logAfter(String method, Object... args) {
    		System.out.println("方法 后置通知logAfter 是【" + method + "】,参数是:" + Arrays.asList(args));// 日记
    	}
    
    	/**
    	 * 返回通知 == 记录方法的返回值
    	 * 
    	 * @param method
    	 * @param result
    	 */
    	public static void logAfterReturning(String method, Object result) {
    		System.out.println("方法 返回通知logAfterReturning 是【" + method + "】,返回值是:" + result);// 日记
    	}
    
    	/**
    	 * 异常通知 == 记录方法的异常信息
    	 * 
    	 * @param method
    	 * @param result
    	 */
    	public static void logAfterThrowing(String method, Throwable exc) {
    		System.out.println("方法 异常通知logAfterThrowing 是【" + method + "】,异常是:" + exc);// 日记
    	}
    
    }

    代理类:

    public class JdkProxyFactory {
    
        public static Object createProxy(Object target) {
    	// 给目标对象创建代理
    	return Proxy.newProxyInstance(target.getClass().getClassLoader(),
    			target.getClass().getInterfaces(), new InvocationHandler() {
    		/**
    		 * 每次调用代理方法,就会调用invoke拦截方法,做一些增强的工作
    		 */
    		@Override
    		public Object invoke(Object proxy, Method method, Object[] args)
    						throws Throwable {
    			Object result = null;
    			try {
    			    try {
    				// 前置通知
    				LogUtil.logBefore(method.getName(), args);
    				// 调用目标方法
    				result = method.invoke(target, args);
    			        } finally {
    				// 后置通知
    				LogUtil.logAfter(method.getName(), args);
    			        }
    				// 返回通知
    				LogUtil.logAfterReturning(method.getName(), result);
    
    			    } catch (Throwable e) {
    				ogUtil.logAfterThrowing(method.getName(), e);
    				throw e;
    			    }
    
    				return result;
    			}
    		});
    	}
    }
    

    测试及结果:

            @Test
    	public void test1() throws Exception {
    		// 在计算器的每个方法中都需要记录下方法的参数。和方法的返回值。
    		// 接下来的需求是,给所有的类似Calculator这样的对象,的每个方法都需要统一加上类似的功能。
    		Calculate calculate = new Calculator();
    		//创建代理对象
    		Calculate proxy = (Calculate) JdkProxyFactory.createProxy(calculate);
    		
    		System.out.println(proxy.add(100, 200));// 前置,后置,返回
    		
    		System.out.println("=============华丽的分隔线=================");
    		System.out.println(proxy.div(200, 0));// 前置 ,后置,异常
    	}

    方法 前置通知logBefore 是【add】,参数是:[100, 200]
    目标方法  add(int num1, int num2) 正在执行
    方法 后置通知logAfter 是【add】,参数是:[100, 200]
    方法 返回通知logAfterReturning 是【add】,返回值是:300
    300
    =============华丽的分隔线=================
    方法 前置通知logBefore 是【div】,参数是:[200, 0]
    目标方法  div(int num1, int num2) 正在执行
    方法 后置通知logAfter 是【div】,参数是:[200, 0]
    方法 异常通知logAfterThrowing 是【div】,异常是:java.lang.reflect.InvocationTargetException

    优点:这种方式已经解决我们前面所有日记需要的问题。非常的灵活。而且可以方便的在后期进行维护和升级。

    缺点:当然使用jdk动态代理,需要有接口。如果没有接口。就无法使用jdk动态代理。

    4.2、使用cglib代理

    public class CglibProxyFactory {
    
        public static void main(String[] args) {
    
    	    Calculator calculate = new Calculator();
    		// 创建代理对象
    	    Calculator proxy = (Calculator) createProxy(calculate);
    
    	    System.out.println(proxy.add(100, 200));// 前置,后置,返回
    
    	    System.out.println("=============华丽的分隔线=================");
    	    System.out.println(proxy.div(200, 0));// 前置 ,后置,异常
    
        }
    
        public static Object createProxy(Object target) {
            // JDK动态代理,是根据 目标对象的接口去生成一个实现类。
    
    	// Cglib增强工具类
    	// Cglib动态代理。是根据目标对象本身,生成一个子类。
    	Enhancer enhancer = new Enhancer();
    	// 设置目标对象的类型
    	enhancer.setSuperclass(target.getClass());
    	// 设置用来拦截目标方法的接口
    	enhancer.setCallback(new MethodInterceptor() {
    		/**
    		 * intercept方法每次调用代理对象,都会执行intercept
    		 */
    		@Override
    		public Object intercept(Object proxy, Method method, Object[] args,
    					MethodProxy methodProxy) throws Throwable {
    			// 当前 目标方法返回值
    			Object result = null;
    			try {
    			    try {
    				// 前置通知
    				LogUtil.logBefore(method.getName(), args);
    				// 调用目标方法
    				result = methodProxy.invokeSuper(proxy, args);
    			    } finally {
    				// 后置通知
    				LogUtil.logAfter(method.getName(), args);
    			    }
    			// 返回通知
    			LogUtil.logAfterReturning(method.getName(), result);
    
    			} catch (Throwable e) {
    			    LogUtil.logAfterThrowing(method.getName(), e);
    			    throw e;
    			}
    
    		    return result;
    		}
    	});
    	// create生成代理对象
    	return enhancer.create();
        }
    }

    执行结果:

    方法 前置通知logBefore 是【add】,参数是:[100, 200]
    目标方法  add(int num1, int num2) 正在执行
    方法 后置通知logAfter 是【add】,参数是:[100, 200]
    方法 返回通知logAfterReturning 是【add】,返回值是:300
    300

    =============华丽的分隔线=================
    方法 前置通知logBefore 是【div】,参数是:[200, 0]
    目标方法  div(int num1, int num2) 正在执行
    方法 后置通知logAfter 是【div】,参数是:[200, 0]
    方法 异常通知logAfterThrowing 是【div】,异常是:java.lang.ArithmeticException: / by zero

    Exception in thread "main" java.lang.ArithmeticException: / by zero
        at com.atguigu.pojo.Calculator.div(Calculator.java:23)
        at com.atguigu.pojo.Calculator$$EnhancerByCGLIB$$cb948d28.CGLIB$div$2(<generated>)
        at com.atguigu.pojo.Calculator$$EnhancerByCGLIB$$cb948d28$$FastClassByCGLIB$$557a5f0b.invoke(<generated>)
        at net.sf.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:215)
        at com.atguigu.proxy.CglibProxyFactory$1.intercept(CglibProxyFactory.java:52)
        at com.atguigu.pojo.Calculator$$EnhancerByCGLIB$$cb948d28.div(<generated>)
        at com.atguigu.proxy.CglibProxyFactory.main(CglibProxyFactory.java:25)

    优点:在没有接口的情况下,同样可以实现代理的效果。

    缺点:同样需要自己编码实现代理全部过程。

    但是为了更好的整合Spring框架使用。所以我们需要学习一下Spring 的AOP 功能。也就是学习Spring提供的AOP功能。

    二、Spring框架的AOP功能

    1、AOP编程的专业术语

    通知(Advice)

    通知就是增强的代码。比如前置增强的代码。后置增强的代码。异常增强代码。这些就叫通知

    切面(Aspect)

    切面就是包含有通知代码的类叫切面。

     

    横切关注点

    横切关注点,就是我们可以添加增强代码的位置。比如前置位置,后置位置,异常位置。和返回值位置。这些都叫横切关注点。

    目标(Target)

    目标对象就是被关注的对象。或者被代理的对象。

    代理(Proxy)

    为了拦截目标对象方法,而被创建出来的那个对象,就叫做代理对象。

    连接点(Joinpoint)

    连接点指的是横切关注点和程序代码的连接,叫连接点。

    切入点(pointcut)

    切入点指的是用户真正处理的连接点,叫切入点。

    在Spring中切入点通过org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

    图解AOP专业术语:

    2、使用Spring实现AOP简单切面编程

    导包:

    com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
    commons-logging-1.1.3.jar
    spring-aop-4.3.18.RELEASE.jar
    spring-aspects-4.3.18.RELEASE.jar
    spring-beans-4.3.18.RELEASE.jar
    spring-context-4.3.18.RELEASE.jar
    spring-core-4.3.18.RELEASE.jar
    spring-expression-4.3.18.RELEASE.jar
    spring-test-4.3.18.RELEASE.jar

    切入点表达式,是告诉Spring窗口,当前这个通知对哪个方法感兴趣

    前置通知怎么描述@Before

    代码

    @Component
    public class Calculator implements Calculate {
    /**
     * @Aspect是告诉Spring我是切面类
     */
    @Aspect
    @Component
    public class LogUtil {
    	/**
    	 * 	@Before表示前置通知
    	 *     execution( public int com.atguigu.pojo.Calculator.add(int,
    	 *                    int) ) 是切入点表达式
    	 */
    	@Before("execution( public int com.webcode.pojo.Calculator.add(int, int) )")
    	public static void logBefore() {
    		System.out.println("方法 前置通知logBefore 是【】,参数是:" );// 日记
    	}

    配置信息:

            <!-- 
    		包扫描关心的是组件注解
    	 -->
    	<context:component-scan base-package="com.webcode"></context:component-scan>
    	<!-- 
    		启动自动代理,对@Aspect进行支持
    		关心的是AOP注解
    	 -->
    	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    测试代码:

    //告诉junit测试,我的Spring容器配置文件在哪里
    @ContextConfiguration(locations="classpath:applicationContext.xml")
    //使用Spring扩展的junit4运行器去运行测试
    @RunWith(SpringJUnit4ClassRunner.class)
    public class SpringTest {
    
    	@Autowired
    	Calculate calculate;
    	
    	@Test
    	public void test1() throws Exception {
    		calculate.add(100, 100);
    		System.out.println("=================================");
    		calculate.div(100, 100);
    	}
    	
    }

    3、Spring切面中的代理对象

    在Spring中,可以对有接口的对象和无接口的对象分别进行代理。在使用上有些细微的差别。

    1) 如果被代理的对象实现了接口。在获取对象的时候,必须要以接口来接收返回的对象。

    2) 如果被代理对象,如果没有实现接口。获取对象的时候使用对象类型本身

    4、Spring的切入点表达式

    @PointCut切入点表达式语法格式是: execution(访问权限 返回值类型 方法全限定名(参数类型列表))

    execution( public int com.webcode.pojo.Calculator.add(int, int) )

    访问权限 public

    返回值类型 int

    方法全限定名 包名+类包+方法名 ===>>>>com.webcode.pojo.Calculator.add

    (参数类型列表) (int, int)

    限定符:

    *表示任意的意思:

             1)匹配某全类名下,任意或多个方法。

               execution( public int com.webcode.pojo.Calculator.*(int, int) )

               对Calculator类的全部方法都匹配(参数类型必须是两个int)

             2)在Spring中只有public权限能拦截到,访问权限可以省略(访问权限不能写*)。

                  execution( int com.webcode.pojo.Calculator.*(int,int))execution( int com.webcode.pojo.Calculator.*(int,int))public 可以省略

                  execution( int com.webcode.pojo.Calculator.add(int, int) )

            3)匹配任意类型的返回值,可以使用 * 表示

                  execution( * com.webcode.pojo.Calculator.add(int, int) )

                  * 表示任意类型的返回值。

            4)匹配任意一层子包。

                  execution( int com.webcode.*.Calculator.add(int, int) )

                     上面这个*表示

                      com.webcode.所有子包,都可以匹配

           5)任意类型参数

                 execution( int com.webcode.pojo.Calculator.add(int, *) )

                      上面这个*表示任意参数类型

     

     ..:可以匹配多层路径,或任意多个任意类型参数

           1)任意层级的包

                 execution( int com.webcode..Calculator.add(int, int) )

                 上面的..表示 com.webcode下的所有包都匹配

           2)任意个任意类型的参数

                 execution( int com.webcode..Calculator.add(int, ..) )

                 上面的..表示任意个,任意类型的参数

    模糊匹配:

                  // 表示任意返回值,任意方法全限定符,任意参数

             execution(* *(..))

                 // 表示任意返回值,任意包名+任意方法名,任意参数

             execution(* *.*(..))

    精确匹配:

                execution(public int com.webcode.pojo.Calculator.add(int, int) )

                确定返回值必须为:int

                包名必须是:com.webcode.pojo

                类包必须是:Calculator

                方法名必须是:add

                参数类型必须是两个int (int, int)

    切入点表达式连接:&& 、||

                 // 表示需要同时满足两个表达式

            @Before("execution(public int com.webcode.aop.Calculator.add(int, int))"

                          + " && "

                          + "execution(public * com.webcode.aop.Calculator.add(..))")

                // 表示两个条件只需要满足一个,就会被匹配到

            @Before("execution(public int com.webcode.aop.Calculator.add(int, int))"

                         + " || "

                         + "execution(public * com.webcode.aop.Calculator.a*(int))")

    后面只对Service使用。

                     execution(public * com.webcode.service..*Service*.*(..))

    5、Spring通知的执行顺序

    Spring通知的执行顺序是:

            正常情况:

                       前置通知====>>>>目标方法====>>>>后置通知=====>>>>返回值之后

            异常情况:

                       前置通知====>>>>目标方法====>>>>后置通知=====>>>>抛异常通知

    @Aspect
    @Component
    public class LogUtil {
    	/**
    	 * 	@Before表示前置通知<br/>
    	 *  execution( public int com.webcode.pojo.Calculator.add(int, int) ) 是切入点表达式
    	 */
    	@Before("execution(public int com.webcode..Calculator.*(int, int) )")
    	public static void logBefore() {
    		System.out.println("方法 前置通知logBefore 是【】,参数是:" );// 日记
    	}
    
    	/**
    	 * 后置通知====@After
    	 * 
    	 */
    	@After("execution(public int com.webcode..Calculator.*(int, int) )")
    	public static void logAfter() {
    		System.out.println("方法 后置通知logAfter 是【】,参数是:" );// 日记
    	}
    
    	/**
    	 * 返回通知 == @AfterReturning
    	 * 
    	 */
    	@AfterReturning("execution(public int com.webcode..Calculator.*(int, int) )")
    	public static void logAfterReturning() {
    		System.out.println("方法 返回通知logAfterReturning 是【】,返回值是:");// 日记
    	}
    
    	/**
    	 * 异常通知 == 记录方法的异常信息
    	 */
    	@AfterThrowing("execution(public int com.webcode..Calculator.*(int, int) )")
    	public static void logAfterThrowing() {
    		System.out.println("方法 异常通知logAfterThrowing 是【】,异常是:");// 日记
    	}
    
    }
    

    正常情况:

    异常情况:

    6、获取连接点信息

    JoinPoint 是连接点的信息。

    只需要在通知方法的参数中,加入一个JoinPoint参数。就可以获取到拦截方法的信息。

    注意:是org.aspectj.lang.JoinPoint这个类。

            /**
    	 * @Before表示前置通知<br/>
    	 * 					execution( public int com.webcode.pojo.Calculator.add(int,
    	 *                    int) ) 是切入点表达式
    	 */
    	@Before("execution(public int com.webcode..Calculator.*(int, int) )")
    	public static void logBefore(JoinPoint jp) {
    		// jp.getSignature().getName() 获取方法名
    		// jp.getArgs() 获取目标方法的参数
    		System.out.println("方法 前置通知logBefore 是【" + jp.getSignature().getName() + "】,参数是:"
    				+ Arrays.asList(jp.getArgs()));// 日记
    	}

    打印结果:

    一个是正常的,一个是发生异常的

    7、获取拦截方法的返回值和抛的异常信息

    获取方法返回的值分为两个步骤:

          1、在返回值通知的方法中,追加一个参数 Object result

          2、然后在@AfterReturning注解中添加参数returning="参数名"

    	/**
    	 * 返回通知 == @AfterReturning 
    value属性是默认属性,表示切入点表达式<br/>
    	 * returning属性表示设置哪个参数用来接收返回值<br/>
    	 */
    	@AfterReturning(value = "execution(public int com.webcode..Calculator.*(int, int) )", returning = "result")
    	public static void logAfterReturning(JoinPoint jp, Object result) {
    		System.out.println(
    				"方法 返回通知logAfterReturning 是【" + jp.getSignature().getName() + "】,返回值是:" + result);// 日记
    	}

    获取方法抛出的异常分为两个步骤:

    1. 在异常通知的方法中,追加一个参数Throwable exception
    2. 然后在@AfterThrowing 注解中添加参数 throwing="参数名"
            /**
    	 * 异常通知 == 记录方法的异常信息
    	 * 	throwing="e"表示把目标方法中抛出的异常对象用参数e来接收
    	 */
    	@AfterThrowing(value="execution(public int com.webcode..Calculator.*(int, int) )",throwing="e")
    	public static void logAfterThrowing(JoinPoint jp,Throwable e) {
    		System.out.println("方法 异常通知logAfterThrowing 是【" + jp.getSignature().getName() + "】,异常是:" + e);// 日记
    	}
    

    图解方法抛异常:

    8、Spring的环绕通知

    1. 环绕通知使用@Around注解。
    2. 环绕通知如果和其他通知同时执行。环绕通知会优先于其他通知之前执行。
    3. 环绕通知一定要有返回值(环绕如果没有返回值。后面的其他通知就无法接收到目标方法执行的结果)。
    4. 在环绕通知中。如果拦截异常。一定要往外抛。否则其他的异常通知是无法捕获到异常的。
    /**
    	 * 环绕通知====@Around<br/>
    	 *  1 、环绕通知优先于普通通知先执行<br/>
    	 *  2、在环绕通知中,一定要有返回值。否则 普通的返回通知收不到返回值<br/>
    	 *  3、在环绕通知中,有异常一定要往外抛
    	 * @throws Throwable 
    	 */
    	@Around(value = "execution(public int com.webcode..Calculator.*(int, int) )")
    	public static Object around(ProceedingJoinPoint pjp) throws Throwable {
    		Object result = null;
    
    		try {
    		    try {
    			// 环绕的前置
    			System.out.println("环绕通知前置");
    
    			result = pjp.proceed();// 调用目标方法
    		    } finally {
    			// 后置通知
    			System.out.println("环绕通知后置");
    		    }
    		    // 环绕返回
    		    System.out.println("环绕返回通知 ,返回值:" + result);
    	        } catch (Throwable e) {
    		    // 环绕异常
    		    System.out.println("环绕异常通知, 异常==>>" + e);
    		    throw e;
    		}
    		return result;
    	}

    结果打印与分析:

    9、切入点表达式的复用

    1. 定义一个空方法,在方法上使用@Pointcut定义切入点表达式
            /**
    	 * 定义一个切入点
    	 */
    	@Pointcut("execution(public int com.webcode..Calculator.add(int, int) )")
    	public static void pointcut1() {}
    

    2、在需要使用切入点表达式的地方。使用 “方法名()” 代替 切入点表达式

            @Before("pointcut1()")
    	public static void logBefore(JoinPoint jp) {
    		// jp.getSignature().getName() 获取方法名
    		// jp.getArgs() 获取目标方法的参数
    		System.out.println("方法 前置通知logBefore 是【" + jp.getSignature().getName() + "】,参数是:"
    				+ Arrays.asList(jp.getArgs()));// 日记
    	}

    10、多个切面的执行顺序

    当我们有多个切面,多个通知的时候:

    1. 通知的执行顺序默认是由切面类的字母先后顺序决定。
    2. 在切面类上使用@Order注解决定通知执行的顺序(值越小,越先执行)

    Order的值,越小越优先。

    也就是上面两个切面类,LogUtil比A先执行。

    执行结果

     

    11、如何基于xml配置aop程序

    1、拷贝前面注解形式的aop工程

    2、去掉前面使用的所有注解

    applicationContext.xml配置文件内容:

            <!-- 配置目标对象 -->
    	<bean id="calculator" class="com.webcode.pojo.Calculator" />
    	<!-- 配置切面 -->
    	<bean id="logUtil" class="com.webcode.util.LogUtil" />
    	<!-- 
    		手动配置代理
    	 -->
    	<aop:config>
    		<!-- 
    			使用哪个类做切面
    		 -->
    		<aop:aspect ref="logUtil">
    			<!-- 定义一个可复用的切入点表达式 -->
    			<aop:pointcut expression="execution(public int com.webcode..Calculator.*(int, int) )" 
    				id="pointcut1"/>
    			<!-- 
    				aop:before配置前置通知
    					method="logBefore"	哪个方法是前置通知
    					pointcut 给当前前置通知定义一个自己的切入点表达式
    			 -->
    			<aop:before method="logBefore" 
    				pointcut="execution(public int com.webcode..Calculator.add(int, int) )"/>
    			<!-- aop:after后置通知
    					pointcut-ref="pointcut1"	表示引用定义的切入点表达式pointcut1
    			-->
    			<aop:after method="logAfter" pointcut-ref="pointcut1"/>
    			<!-- 配置返回通知 -->
    			<aop:after-returning method="logAfterReturning" 
    				pointcut-ref="pointcut1" returning="result"/>
    			<!-- 异常通知 -->
    			<aop:after-throwing method="logAfterThrowing"
    				pointcut-ref="pointcut1" throwing="e"
    			/>
    		</aop:aspect>
    	</aop:config>

    展开全文
  • 什么是基于注解的切面实现

    千次阅读 2017-12-05 19:51:06
    什么是基于注解的切面实现我们使用切面来非侵入式操作程序方法,常用的场景如日志记录、权限判断等。 下面我实现权限判断的切面。分析: 要实现基于注解的切面,我们要定义“注解”,定义切面,定义权限验证,定义...

    什么是基于注解的切面实现

    我们使用切面来非侵入式操作程序方法,常用的场景如日志记录、权限判断等。
    下面我实现权限判断的切面。

    分析:
    要实现基于注解的切面,我们要定义“注解”,定义切面,定义权限验证,定义权限返回。

    1. 定义注解:PermissionCheck.java
    @Target({ElementType.TYPE, ElementType.METHOD}) // 注解类型, 级别
    @Retention(RetentionPolicy.RUNTIME) // 运行时注解
    public @interface PermissionCheck {
    
        String value() default "";
    
    }
    
    1. 定义权限校验方法,这里定义 AuthService 和它的实现。
    public interface AuthService {
    
        boolean checkAccess();
    }
    
    
    @Service
    @Transactional(readOnly = true, rollbackFor = Exception.class)
    public class AuthServiceImpl implements AuthService {
    
    
        @Override
        public boolean checkAccess() {
            return true;
        }
    }
    

    根据需要可改写 checkAccess 方法。

    1. 定义一个advice 来处理校验结果:
    @ControllerAdvice
    public class PermissionAdvice {
    
        @ExceptionHandler(value = PermissionCheckException.class)
        @ResponseStatus(HttpStatus.OK)
        @ResponseBody
        public String dealWithPermissionCheckException(PermissionCheckException exception) {
            System.out.println(exception.getMessage());
            return exception.getMessage();
        }
    
    
        public String dealWithPermissionCheckException() {
    
            return null;
        }
    }
    
    
    1. 接下来就是组合进切面了
    @Aspect // 切面标识
    @Component // 交给spring容器管理
    public class PermissionAspect {
    
        @Autowired
        private AuthService authService;
    
        /**
         * 选取切入点为自定义注解
         */
        @Pointcut("@annotation(com.honeywen.credit.annotation.PermissionCheck)")
        public void permissionCheck(){}
    
    
        @Before(value = "permissionCheck()")
        public void before(JoinPoint joinPoint) throws NoSuchMethodException {
            // 获取连接点的方法签名对象
            Signature signature = joinPoint.getSignature();
            if (!(signature instanceof MethodSignature)) {
                throw new PermissionCheckException("user permission check failed , stop the request!");
    
            }
            MethodSignature methodSignature = (MethodSignature) signature;
            Object target = joinPoint.getTarget();
            // 获取到当前执行的方法
            Method method = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
            // 获取方法的注解
            PermissionCheck annotation = method.getAnnotation(PermissionCheck.class);
            System.out.println(annotation);
            System.out.println("我是在执行业务逻辑之前");
            // 权限检查
            authService.checkAccess();
    
    
    
        }
    
        @After(value = "permissionCheck()")
        public void after(JoinPoint joinPoint) {
            System.out.println("我是在执行业务逻辑之后");
    
        }
    
    
    
    
    }
    

    总结:
    主要过程在定义切面, 然后就是切面在哪里执行:

        @Pointcut("@annotation(com.honeywen.credit.annotation.PermissionCheck)")
        public void permissionCheck(){}

    这里声明的是标注 @PermissionCheck 注解的方法执行。

    展开全文
  • Spring基础:切面

    千次阅读 2018-03-24 21:53:11
    正题在开始文章前,有几个问题需要思考一下:切面到底是神马切面的构成切面有哪些实现类切面有哪些类型1. 切面到底是神马在 AOP 一文中提到什么是 AOP? AOP 可以使用 “面向切面编程”的译法。那切面就是把非业务...

    前言

    在之前的文章中总结了 AOP 在 Spring 中的作用及地位,在分析 AOP 时涉及到切面的内容,这一节详细的分析切面的知识点。

    正题

    在开始文章前,有几个问题需要思考一下:

    • 切面到底是神马
    • 切面的构成
    • 切面有哪些实现类
    • 切面有哪些类型

    1. 切面到底是神马

    在 AOP 一文中提到什么是 AOP? AOP 可以使用 “面向切面编程”的译法。那切面就是把非业务逻辑相关的代码抽取出来定位到具体的连接点上的一种实现方式。

    2. 切面的构成

    切面由切点和增强(引介)组成,它既包括横切逻辑的定义,也包括连接点的定义。

    2.1 切点的表示

    Spring 通过 org.springframework.aop.Pointcut 接口描述切点,Pointcut 由 ClassFilter 和 MethodMatcher 而构成,它通过 ClassFilter 定位到某些特定类上,通过 MethodMatcher 定位到某些特定方法上,这样 Pointcut 就拥有了描述某些类的某些特定方法的能力。

    可以看到 ClassFilter 只定义了一个方法 matches(Class clazz),其参数代表一个被检测类,该方法判别被检测的类是否匹配过滤条件。

    Spring 支持两种方法匹配器:静态方法匹配器和动态方法匹配器。所谓静态方法匹配器,仅对方法名签名(包括方法名和入参类型及顺序)进行匹配;而动态方法匹配器会在运行期检查方法入参的值。静态匹配仅会判别一次,而动态匹配因为每次调用方法的入参都可能不一样,所以每次调用方法都必须判断,因此,动态匹配对性能的影响很大。一般情况下,动态匹配不常使用。方法匹配器的类型由 isRuntime() 方法的返回值决定,返回 false 表示是静态方法匹配器,返回 true 表示是动态方法匹配器。

    Spring提供了6种类型的切点,下面分别针对它们的用途进行介绍。

    • 静态方法切点:org.springframework.aop.support.StaticMehtodMatcherPointcut 是静态方法切点的抽象基类,默认情况下它匹配所有的类。StaticMethodMatcherPointcut包括两个主要的子类,分别是NameMatchMethodPointcut 和 AbstractRegexpMethodPointcut,前者提供简单字符串匹配方法签名,而后者使用正则表达式匹配方法签名。
    • 动态方法切点:org.springframework.aop.support.DynamicMethodMatcherPointcut 是动态方法切点的抽象基类,默认情况下它匹配所有的类。
    • 注解切点:org.springframework.aop.support.annotation.AnnotationMatchingPointcut 实现类表示注解切点。使用 AnnotationMatchingPointcut 支持在 Bean 中直接通过 Java 5.0 注解标签定义的切点。
    • 流程切点:org.springframework.aop.support.ControlFlowPointcut 实现类表示控制流程切点。ControlFlowPointcut 是一种特殊的切点,它根据程序执行堆栈的信息查看目标方法是否由某一个方法直接或间接发起调用,以此判断是否为匹配的连接点。

    3. 切面有哪些实现类

    由于增强既包括横切代码,又包括部分连接点信息(方法前、方法后主方位信息),所以可以仅通过增强类生成一个切面。但切面仅代表目标类连接点的部分信息(类和方法的定位),所以仅有切点无法制作出一个切面,必须结合增强才能制作出切面。Spring 使用 org.springframework.aop.Advisor 接口表示切面的概念,一个切面同时包含横切代码和连接点信息。切面可以分为 3 类:一般切面、切点切面和引介切面,可以通过 Spring 所定义的切面接口清楚地了解切面的分类。

    • Advisor:代表一般切面,仅包含一个 Advice。因为 Advice 包含了横切代码和连接点信息,所以 Advice 本身就是一个简单的切面,只不过它代表的横切的连接点是所有目标类的所有方法,因为这个横切面太广泛,所以一般不会直接使用。
    • PointcutAdvisor:代表具体切点的切面,包含 Advice和 Pointcut 两个类,这样就可以通过类、方法名及方法方位等信息灵活地定义切面的连接点,提供更具适用性的切面。
    • IntroductionAdvisor:代表引介切面,引介切面是对应引介增强的特殊切面,它应用于类层面上,所以引介切点使用 ClassFilter 进行定义。

    下面再来看一下 PointcutAdvisor 的主要实现类体系:

    PointcutAdvisor 主要有 6 个具体的实现类,分别介绍如下:

    • DefaultPointcutAdvisor:最常用的切面类型,它可以通过任意 Pointcut 和 Advice 定义一个切面,唯一不支持的是引介切面类型,一般可以通过扩展该类实现自定义的切面。
    • NameMatchMethodPointcutAdvisor:通过该类可以定义按方法名定义切点的切面。
    • RegexpMethodPointcutAdvisor:对于按正则表达式匹配方法名进行切点定义的切面,可以通过扩展该实现类进行操作。RegexpMethodPointcutAdvisor允许用户以正则表达式模式串定义方法匹配的切点,其内部通过 JDKRegexpMehtodPointcut 构造出正则表达式方法名切点。
    • StaticMethodMatcherPointcutAdvisor:静态方法匹配器切点定义的切面,默认情况下匹配所有的目标类。
    • AspectJDKExpressionPointcutAdvisor:用于 AspectJ 切点表达式定义切点的切面。
    • AspectJPointcutAdvisor:用于 AspectJ 语法定义切点的切面。

    这些 Advisor 的实现类都可以在Pointcut中找到对应物,实际上,它们都是通过扩展对应的 Pointcut 实现类并实现 PointcutAdvisor 接口进行定义的。

    4. 切面有哪些类型


    4.1 静态普通方法名匹配切面

    StaticMethodMatcherPointcutAdvisor 代表一个静态方法匹配切面,它通过 StaticMethodMatcherPointcut 来定义切点,并通过类过滤和方法名来匹配所定义的切点。

    public class Waiter {
        public void greetTo(String name) {
            System.out.println("Waiter green to " + name + "...");
        }
    
        public void serveTo(String name) {
            System.out.println("Waiter serving " + name + "...");
        }
    }
    
    public class Seller {
        public void greetTo(String name) {
            System.out.println("Seller green to " + name + "...");
        }
    }

    Waiter和Seller拥有相同的greetTo方法,希望通过 StaticMethodMatcherPointcutAdvisor 定义一个切面,在 Waiter##greetTo 方法调用前织入一个增强。

    public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor {
        public boolean matches(Method method, Class<?> aClass) { //①
            return "greetTo".equals(method.getName());
        }
    
        @Override
        public ClassFilter getClassFilter() { //②
            return new ClassFilter() {
                public boolean matches(Class<?> aClass) {
                    return Waiter.class.isAssignableFrom(aClass);
                }
            };
        }
    }

    StaticMethodMatcherPointcutAdvisor 抽象类唯一需要定义的是 matches 方法。在默认情况下,该切面匹配所有类,这里通过覆盖 getClassFilter( )方法,让它仅匹配 Waiter 类及其子类。

    当然,Advisor 还需要一个增强类的配合。接下类定义一个前置增强:

    public class GreetingBeforeAdvice implements MethodBeforeAdvice {
        public void before(Method method, Object[] objects, Object o) throws Throwable {
            String clientName = (String) objects[0];
            System.out.println("how are you! Mr." + clientName);
        }
    }

    下面使用 Spring 配置来定义切面:

    <bean id="greetingAdvice" class="com.smart.Advice.GreetingBeforeAdvice"/>
    <bean id="greetingAdvisor" class="com.smart.Advisor.GreetingAdvisor"
          p:advice-ref="greetingAdvice"/>①
    
    <bean id="waiterTarget" class="com.smart.Advice.Waiter"/>
    <bean id="sellerTarget" class="com.smart.Advice.Seller"/>
    
    <bean id="parent" abstract="true"
          class="org.springframework.aop.framework.ProxyFactoryBean"
          p:interceptorNames="greetingAdvisor"
          p:proxyTargetClass="true"/>
    
    <bean id="waiter" parent="parent" p:target-ref="waiterTarget"/>
    <bean id="seller" parent="parent" p:target-ref="sellerTarget"/>
    在 ① 处将 greetingAdvice 增强装配到 greetingAdvisor 切面中。StaitcMatcherPointcutAdvisor 除具有 advice 属性外,还可以定义另外两个属性。
    • classFilter:类匹配过滤器,在 GreetingAdvisor 中用编码的方式设定了 classFilter。
    • order:切面织入时的顺序,该属性用于定义 Ordered 接口表示的顺序。

    由于需要分别为 waiter 和 seller 两个 Bean 定义代理器,且两者有很多公共的配置信息,所以使用一个父 <bean> 简化配置,通过引用父 <bean> 轻松地定义两个织入切面的代理。

    运行结果:

    how are you! Mr.John
    Waiter green to John...
    
    Seller green to John...
    4.2 静态正则表达式方法匹配切面

    在 StaticMethodMatcherPointcutAdvisor 中,仅能通过方法名定义切点,这种描述方式不够灵活。假设目标类中有多个方法。且它们都满足一定的命名规范,使用正则表达式进行匹配描述就要灵活多了。RegexpMethodPointcutAdvisor 是正则表达式方法匹配的切面实现类,该类已经是功能齐备的实现类,一般情况下无须扩展该类。

    <bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
          p:advice-ref="greetingAdvice">
        <property name="patterns">
            <list>
                <value>.*greetTo.*</value>②
            </list>
        </property>
    </bean>
    
    <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
          p:target-ref="waiterTarget"
          p:interceptorNames="regexpAdvisor"
          p:proxyTargetClass="true">
    </bean>

    在 ② 处定义了一个匹配模式串“.*greet.*”,该模式匹配 Waiter.greetTo() 方法。值得注意的是,匹配模式串的是目标方法的全限定名,即带类名的方法名。

    运行结果:

    how are you! Mr.John
    Waiter green to John...
    
    Waiter serving John...

    可见 Waiter.greetTo() 方法被织入了切面,而 Waiter.serveTo() 方法没有被织入切面。除了使用 patterns 和 advice 属性外,还有另外两个属性,分别介绍如下:

    • pattern:如果只有一个匹配模式,则可以使用该属性进行配置。patterns属性用于定义多个匹配模式串,这些匹配模式串之间是“或”的关系。
    • order:切面在织入时对应的顺序。

    只要程序的类包具有良好的命名规范,就可以使用简单的正则表达式描述出目标方法。由于需要使用全限定名来定义方法名,所以不但方法名需要具有良好的规范性,包名也需要具体良好的规范性。对包名、类名、方法名按其功用进行规范命名并不是一件坏事,相反,规范命名可以增强程序的可读性和团队开发的协作性,降低沟通成本,是值得实践和提倡的编程方法。

    4.3 动态切面

    DynamicMethodMatcherPointcut 是一个抽象类,它将 isRuntime( )标识为 final 且返回 true,这样其子类就一定是一个动态切点。该抽象类默认匹配所有的类和方法,因此需要通过扩展该类编写符合要求的动态切点。

    public class GreetingDynamicPointcut extends DynamicMethodMatcherPointcut {
        private static List<String> specialClientList = new ArrayList<String>();
        static {
            specialClientList.add("John");
            specialClientList.add("Tom");
        }
    
        @Override
        public ClassFilter getClassFilter() {
            return new ClassFilter() {
                public boolean matches(Class<?> aClass) {
                    System.out.println("调用getClassFilter()对" + aClass.getName() + "做静态检查.");
                    return Waiter.class.isAssignableFrom(aClass);
                }
            };
        }
    
        /**
         * 对方法进行静态切点检查
         */
        @Override
        public boolean matches(Method method, Class<?> targetClass) {
            System.out.println("调用matches(method, class)" + targetClass.getName() + "." + method.getName() + "做静态检查.");
            return "greetTo".equals(method.getName());
        }
    
        /**
         * 对方法进行动态切点检查
         */
        public boolean matches(Method method, Class<?> aClass, Object[] objects) {
            System.out.println("调用matches(method, class)" + aClass.getName() + "." + method.getName() + "做动态检查.");
            String clientName = (String) objects[0];
            return specialClientList.contains(clientName);
        }
    }

    GreetingDynamicPointcut 类既有用于静态切点检查的方法,又有用于动态检查的方法。由于动态切点检查会对性能造成很大的影响,所以应当尽量避免在运行时每次都对目标类的各个方法进行动态检查。Spring 采用这样的机制:在创建代理时对目标类的每个连接点使用静态切点检查,如果仅通过静态切点检查就可以知道连接点是不匹配的,则在运行时就不再进行动态检查;如果静态切点检查是匹配的,则在运行时才进行动态切点检查。在动态切点类中定义静态切点检查的方法可以避免不必要的动态检查操作,从而极大地提高运行效率。

    在编写好动态切点后,就可以着手在Spring配置文件中装配出一个动态切面,代码如下:

    <bean id="dynamicAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="pointcut">
            <bean class="com.smart.Advisor.GreetingDynamicPointcut"/>①
        </property>
        <property name="advice">
            <bean class="com.smart.Advice.GreetingBeforeAdvice"/>②
        </property>
    </bean>
    
    <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
          p:target-ref="waiterTarget"
          p:interceptorNames="dynamicAdvisor"
          p:proxyTargetClass="true">
    </bean>

    动态切面的配置和静态切面的配置没什么区别。使用 DefaultPointcutAdvisor 定义切面,在 ① 处使用内部 Bean 方式注入动态切点 GreetingDynamicPointcut,在 ② 处注入增强。此外,DefaultPointcutAdvisor 还有一个 order 属性,用于定义切面的织入顺序。

    接下来定义一个测试类:

    public class BeanFactoryTest {
    
        @Test
        public void getBean() throws IOException {
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            Resource[] resource = resolver.getResources("classpath:com/smart/bean1.xml");
    
            DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
            XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
            reader.loadBeanDefinitions(resource);
            Waiter waiter = (Waiter) factory.getBean("waiter");
            waiter.greetTo("John");
            waiter.greetTo("Pitty");
            waiter.serveTo("John");
        }
    }

    运行结果:

    调用getClassFilter()对com.smart.Advice.NaiveWaiter做静态检查.
    调用matches(method, class)com.smart.Advice.NaiveWaiter.serveTo做静态检查.
    调用getClassFilter()对com.smart.Advice.NaiveWaiter做静态检查.
    调用matches(method, class)com.smart.Advice.NaiveWaiter.greetTo做静态检查.
    调用getClassFilter()对com.smart.Advice.NaiveWaiter做静态检查.
    调用matches(method, class)com.smart.Advice.NaiveWaiter.toString做静态检查.
    调用getClassFilter()对com.smart.Advice.NaiveWaiter做静态检查.
    调用matches(method, class)com.smart.Advice.NaiveWaiter.clone做静态检查.
    调用getClassFilter()对com.smart.Advice.NaiveWaiter做静态检查.
    
    调用matches(method, class)com.smart.Advice.NaiveWaiter.greetTo做静态检查.
    调用matches(method, class)com.smart.Advice.NaiveWaiter.greetTo做动态检查.
    how are you! Mr.John
    Waiter green to John...
    
    调用getClassFilter()对com.smart.Advice.NaiveWaiter做静态检查.
    调用matches(method, class)com.smart.Advice.NaiveWaiter.serveTo做静态检查.
    Waiter serving John...
    
    调用matches(method, class)com.smart.Advice.NaiveWaiter.greetTo做动态检查.
    Waiter green to Pitty...
    Waiter serving Pitty...

    通过以上输出信息,对照 DynamicMethodMatcherPointcut 切点类,可以很容易发现,Spring 会在创建代理织入切面时,对目标类的所有方法进行静态切点检查;在生成织入切面的代理对象后,第一次调用代理类的每一个方法时都会进行一次静态切点检查,如果本次检查就能从候选者列表中将该方法排除,则以后对该方法的调用就不再执行静态切点检查;对于那些在静态切点检查时匹配的方法,在后续调用该方法时,将执行动态切点检查。

    如果将 getClassFilter() 方法和 matches(Method, Class) 方法注释掉,重新运行代码结果如下:

    调用matches(method, class)com.smart.Advice.NaiveWaiter.greetTo做动态检查.
    how are you! Mr.John
    Waiter green to John...
    调用matches(method, class)com.smart.Advice.NaiveWaiter.serveTo做动态检查.
    how are you! Mr.John
    Waiter serving John...
    调用matches(method, class)com.smart.Advice.NaiveWaiter.greetTo做动态检查.
    Waiter green to Pitty...
    调用matches(method, class)com.smart.Advice.NaiveWaiter.serveTo做动态检查.
    Waiter serving Pitty...

    可以发现,每次调用代理对象的任何一个方法,都会执行动态切点检查,这将导致很大的性能问题。所以,在定义动态切点时,切勿忘记同时覆盖 getClassFilter() 方法和 matches(Method, Class) 方法,通过静态切点检查排除大部分方法。

    动态代理的“动态”是相对于那些编译期生成代理 Class 文件和类加载期生成代理 Class 文件而言的。动态代理是运行时动态产生的代理。在 Spring 中,不管是静态切面还是动态切面,都是通过动态代理技术实现的。所谓静态切面,是指在生成代理对象时就确定了增强是否需要织入目标类的连接点上;而动态切面是指必须在运行期根据方法入参的值来判断增强是否需要织入目标类的连接点上。

    4.4 流程切面

    Spring 的流程切面由 DefaultPointcutAdvisor 和 ControlFlowPointcut 实现。流程切点代表由某个方法直接或间接发起调用的其他方法。来看下面的实例,假设通过一个 WaiterDelegate 类代理 Waiter 所有的方法,代码如下:

    public class WaiterDelegate {
        private Waiter waiter;
    
        public void service(String clientName) {
            waiter.greetTo(clientName);
            waiter.serveTo(clientName);
        }
    
        public void setWaiter(Waiter waiter) {
            this.waiter = waiter;
        }
    }

    如果希望所有由 WaiterDelegate#service() 方法发起调用的其他方法都织入 GreetingBeforeAdvice 增强,就必须使用流程切面来完成目标。下面使用 DefaultPointcutAdvisor 配置一个流程切面来完成这一需求:

    <bean id="waiterTarget" class="com.smart.Advice.NaiveWaiter"/>
    <bean id="greetingAdvice" class="com.smart.Advice.GreetingBeforeAdvice"/>
    <bean id="controlFlowPointcut" class="org.springframework.aop.support.ControlFlowPointcut">
        <constructor-arg type="java.lang.Class" value="com.smart.Advisor.WaiterDelegate"/>
        <constructor-arg type="java.lang.String" value="service"/>
    </bean>
    
    <bean id="controllFlowAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
          p:pointcut-ref="controlFlowPointcut"
          p:advice-ref="greetingAdvice"/>
    
    <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
          p:target-ref="waiterTarget"
          p:interceptorNames="controllFlowAdvisor"
          p:proxyTargetClass="true">
    </bean>

    ControlFlowPointcut 有两个构造函数,分别是 ControlFlowPointcut(Class clazz) 和 ControlFlowPointcut(Class clazz, String methodName)。第一个构造函数指定一个类作为流程切点;而第二个构造函数指定一个类和一个方法作为流程切点。在这里,指定 WaiterDelegate#service() 方法作为切点,表示所有通过该方法直接或间接发起的调用匹配切点。

    PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    Resource[] resource = resolver.getResources("classpath:com/smart/bean1.xml");
    
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
    reader.loadBeanDefinitions(resource);
    Waiter waiter = (Waiter) factory.getBean("waiter");
    waiter.greetTo("John");
    
    WaiterDelegate wd = new WaiterDelegate();
    wd.setWaiter(waiter);
    wd.service("John");

    运行上面的代码,在控制台输出一下信息:

    Waiter green to John...①
    ②
    how are you! Mr.John
    Waiter green to John...
    how are you! Mr.John
    Waiter serving John...

    ① 处的信息直接调用 greetTo() 方法的输出,此时增强没有起作用;② 处通过 WaiterDelegate#service() 调用 Waiter 的 greetTo() 和 serveTo() 方法输出,这时发现 Waiter 的两个方法都织入了增强。

    流程切面和动态切面从某种程度上说可以算是一类切面,因为二者都需要在运行期判断动态环境。对于流程切面来说,代理对象在每次调用目标类方法时,都需要判断方法调用堆栈中是否有满足流程切点要求的方法。因此,和动态切面一样,流程切面对性能的影响也很大。

    4.5 引介切面

    引介切面是引介增强的封装器,通过引介切面,可以更容易地为现有对象添加任何接口的实现。下图是引介增强的类继承关系图:


    IntroductionAdvisor 接口同时继承 Advisor 和 IntroductionInfo 接口,IntroductionInfo 接口描述了目标类需要实现的新接口。IntroductionAdvisor 和 PointcutAdvisor 接口不同,它仅有一个类过滤器 ClassFilter 而没有 MethodMatcher,这是因为引介切面的切点是类级别的,而 Pointcut 的切点是方法级别的。

    IntroductionAdvisor 有两个实现类,分别是 DefaultIntroductionAdvisor 和 DeclareParentsAdvisor,前者是引介切面最常用的实现类,后者用于实现使用 AspectJDK 语言的 DeclareParent 注解表示的引介切面。

    下面通过 DefaultIntroductionAdvisor 来增加引介增强配置切面,会发现这种方式比前面的方式更简洁、更清晰。

    <bean id="introductionAdvisor" class="org.springframework.aop.support.DefaultIntroductionAdvisor">
        <constructor-arg>
            <bean class="com.smart.Advice.ControllablePerformanceMonitor"/>
        </constructor-arg>
    </bean>
    
    <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
          p:target-ref="waiterTarget"
          p:interceptorNames="introductionAdvisor"
          p:proxyTargetClass="true"/>
    虽然引介切面和其他切面由很多的不同,但却可以采用相似的Spring配置方式配置引介切面。
    展开全文
  • AOP中的概念通知、切点、切面

    千次阅读 2018-10-09 16:01:57
    AOP中的概念通知、切点、切面*通知、增强处理(Advice)*通知、增强处理(Advice) 就是你想要的功能,也就是上说的安全、事物、日子等。你给先定义好,然后再想用的地方用一下。包含Aspect的一段处理代码连接点...

    AOP中的概念通知、切点、切面

    通知、增强处理(Advice)


    通知、增强处理(Advice) 就是你想要的功能,也就是上说的安全、事物、日子等。你给先定义好,然后再想用的地方用一下。包含Aspect的一段处理代码

    连接点(JoinPoint)


    连接点(JoinPoint) 这个就更好解释了,就是spring允许你是通知(Advice)的地方,那可就真多了,基本每个方法的钱、后(两者都有也行),或抛出异常是时都可以是连接点,spring只支持方法连接点。其他如AspectJ还可以让你在构造器或属性注入时都行,不过那不是咱们关注的,只要记住,和方法有关的前前后后都是连接点。

    切入点(Pointcut)


    切入点(Pointcut) 上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有十几个连接点了对吧,但是你并不想在所有方法附件都使用通知(使用叫织入,下面再说),你只是想让其中几个,在调用这几个方法之前、之后或者抛出异常时干点什么,那么就用切入点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。

    切面(Aspect)


    切面(Aspect) 切面是通知和切入点的结合。现在发现了吧,没连接点什么事,链接点就是为了让你好理解切点搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的befor,after,around等就能知道),二切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。


    引入(introduction)

    引入(introduction) 允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗


    目标(target)

    目标(target)
    引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咋们织入切面。二自己专注于业务本身的逻辑。


    代理(proxy)

    代理(proxy) 怎么实现整套AOP机制的,都是通过代理,这个一会儿给细说。


    织入(weaving)

    织入(weaving) 把切面应用到目标对象来创建新的代理对象的过程。

    目标对象 – 项目原始的Java组件。
    
    AOP代理  – 由AOP框架生成java对象。
    
    AOP代理方法 = advice + 目标对象的方法。
    
    

    example :

    <!-- 通知 -->
    	<tx:advice id="txAdvice" transaction-manager="transactionManager">
    		<tx:attributes>
    			<!-- 传播行为 -->
    			<tx:method name="save*" propagation="REQUIRED" />
    			<tx:method name="insert*" propagation="REQUIRED" />
    			<tx:method name="delete*" propagation="REQUIRED" />
    			<tx:method name="update*" propagation="REQUIRED" />
    			<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
    			<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
    			<tx:method name="query*" propagation="SUPPORTS" read-only="true" />
    		</tx:attributes>
    	</tx:advice>
    
    	<!-- 切面 -->
    	<aop:config>
    		<aop:advisor advice-ref="txAdvice"[具体的增强逻辑,此处是事物,上面已经定义好了]
    			pointcut="execution(* cn.itcast.ssm.service.*.*(..))" [增强逻辑的具体作用位置]/>
    	</aop:config>
    
    展开全文
  • Spring 创建切面

    千次阅读 2018-09-04 16:28:36
    3、切面类型 4、静态普通方法名匹配切面 5、静态正则表达式方法匹配切面 6、动态切面 7、流程切面 8、复合切点切面 9、总结 1、概述 在前面介绍各类增强时,大家可能没有注意到一个问题:增强被织入到目标类...
  • Spring AOP 之 多切面

    千次阅读 2018-08-17 16:20:50
    接着上文:... 多切面 相对来说,只是定义多个切面类,同时配置类中设置一下, demo如下: 首先定义一个接口: package com.cmb.multi; public interface MultiAsp { public void test(); } ...
  • 面向切面编程(AOP)的理解

    万次阅读 多人点赞 2014-02-08 19:40:47
    回家上网不方便,回头补上!祝大家过年好!
  • spring 面向切面编程

    2020-10-07 14:38:54
    基于注解的面向注解的切面编程(环绕方式) 后端springboot切面2019/06/25 第一阶段:目标:进行拦截特定方法 1.什么是面向切面编程呢? 根据本人从实际项目中里理解,面向切面编程实质上是将已存在的逻辑进行二...
  • Spring boot AOP(切面)使用方法和解析

    万次阅读 多人点赞 2018-07-28 11:53:21
    最近开始实习,终于有时间写博客了。因为需要使用springboot,大三学Oracle数据库的时候使用过springboot,之前没怎么用java写网页后台,发现网上很多的博客要不就是复制粘贴,要不就是存在默认知识现象(就是跳过了...
  • spring中切面获取切点的注解参数值

    千次阅读 2019-06-11 14:09:47
    自定义注解: @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Oauth { String key() default ...切面获取注解key的值 @Around(value ...
  • Spring AOP 之 通知、连接点、切点、切面

    万次阅读 多人点赞 2016-05-05 13:11:37
    2:面向切面的基本原理什么是面向切面编程 横切关注点:影响应用多处的功能(安全、事务、日志) 切面: 横切关注点被模块化为特殊的类,这些类称为切面 优点: 每个关注点现在都集中于一处,而不是分散到多处...
  • 在Spring AOP切面中启用新事务

    万次阅读 多人点赞 2019-07-31 19:16:01
    在工作中有一个在切面中需要记录一下操作日志的需求,而且要求这些操作日志要存入数据库,并且无论业务层有什么异常,日志照常记录,那就不能沿用业务层的事务,而是需要新启一个事务了。 sping的声明式事务就是靠...
  • AOP(Aspect-Oriented Programming)指一种程序设计范型,该范型以一种称为切面(aspect)的语言构造为基础,切面是一种新的模块化机制,用来描述分散在对象、类或方法中的横切关注点(crosscutting concern...
  • Spring-AOP 切点/切面类型和创建切面

    万次阅读 2017-08-21 07:08:25
    概述 静态方法匹配器 动态方法匹配器 六种切点类型 静态方法切点StaticMethodMatcherPointcut 动态方法切点DynamicMethodMatcher 注解切点AnnotationMatchingPointcut 表达式切点...三种切面类型一般切面Ad
  • 在spring的aop中获取自定义注解的参数值,即在切面中获取annotation的参数值
  • AOP【面向切面编程】

    万次阅读 多人点赞 2019-05-27 20:40:08
    AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming),面向对象编程的补充和完善。  面向切面编程是面向对象中的一种方式而已。在代码执行过程中,动态嵌入其他代码...
  • 2:面向切面的基本原理 什么是面向切面编程 横切关注点:影响应用多处的功能(安全、事务、日志) 切面: 横切关注点被模块化为特殊的类,这些类称为切面 优点: 每个关注点现在都集中...
  • spring之AOP切面不生效

    千次阅读 2018-03-23 10:23:06
    aop-aspectJ的切面不生效,请注意:只有 Spring 生成的对象才有 AOP 功能,因为 Spring 生成的代理对象才有 AOP 功能。切入的类和被切入的类必须是被spring管理的(springIOC),如果是自己new 出来的,切入...
  • 检查下springboot的启动类是否开启扫描 @SpringBootApplication @ComponentScan(basePackages = {"...}) 另外springboot默认开启的EnableAspectJAutoProxy为true 如果不放心可以增加: ...@...
  • 在传统的编写业务逻辑处理代码时,我们通常会习惯性地做几件事情:日志记录、事务控制及权限控制等,然后才是编写核心的业务逻辑处理代码。当代码编写完成回头再看时,不禁发现,扬扬洒洒上百行代码中,真正用于...
1 2 3 4 5 ... 20
收藏数 174,014
精华内容 69,605
关键字:

切面