SpringFramework学习-(14)SpringAOP配置

前面几篇分别介绍了AOP是什么,AOP实现的内部原理,SpringAOP和AspectJ之间的关系。只有在初识AOP的末尾通过一个demo展示了SpringAOP,并未在SpringAOP的配置和实现上做过多的停留,本篇将介绍一下SpringAOP的配置和说明。

1.基本术语

1.通知(Advice)
通知定义了切面是什么以及何时使用。描述了切面要完成的工作和何时需要执行这个工作。

1.前置通知[Before advice]:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。使用注解@Before

2.正常返回通知[After returning advice]:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。 
使用注解@AfterReturning(value="execution(* com..*.*(..))",returning="result"),这里的result只是一个参数名,一定要有

3.异常返回通知[After throwing advice]:在连接点抛出异常后执行。 使用注解@AfterThrowing(value="execution(* com..*.*(..))",throwing="ex"),这里的ex也是一个参数名,要有

4.返回通知[After (finally) advice]:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。使用注解@After 

5.环绕通知[Around advice]:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。使用注解@Around

2.连接点(Joinpoint)
程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法被调用时、异常被抛出时等等。

3.切入点(Pointcut)
通知定义了切面要发生的“故事”和时间,那么切入点就定义了“故事”发生的地点,例如某个类或方法的名称,Spring中允许我们方便的用正则表达式来指定

4.切面(Aspect)
通知切入点共同组成了切面:时间、地点和要发生的“故事”。

5.引入(Introduction)
引入允许我们向现有的类添加新的方法和属性(Spring提供了一个方法注入的功能)

6.目标(Target)
即被通知的对象,如果没有AOP,那么它的逻辑将要交叉别的事务逻辑,有了AOP之后它可以只关注自己要做的事(AOP让他做爱做的事)

7.代理(proxy)
应用通知的对象,详细内容参见设计模式里面的代理模式

8.织入(Weaving)
切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:

1.编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器
2.类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码
3.运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理应该是使用了JDK的动态代理技术

2.SpringAOP实现

1.经典的基于代理的AOP
接口

package com.jd.aop4;

public interface EatAble {

    public void eat();
}

实现

package com.jd.aop4;

public class IamEat implements EatAble{

    public void eat() {
        System.out.println("我吃饭");
    }

}

代理

package com.jd.aop4;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;

public class EatHelper implements MethodBeforeAdvice,AfterReturningAdvice{

    public void afterReturning(Object arg0, Method arg1, Object[] arg2,Object arg3) throws Throwable {
        System.out.println("吃完饭漱口。。。");
    }

    public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
        System.out.println("吃饭前洗手。。。");
    }

}

配置文件

<?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-4.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">

    <bean id="eatHelper" class="com.jd.aop4.EatHelper"></bean>

    <bean id="iamEat" class="com.jd.aop4.IamEat"></bean>

    <bean id="eatPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
        <property name="pattern" value=".*eat"/>
    </bean>

    <bean id="eatHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
         <property name="advice" ref="eatHelper"/>
         <property name="pointcut" ref="eatPointcut"/>
    </bean>

    <bean id="myProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
         <property name="target" ref="iamEat"/>
         <property name="interceptorNames" value="eatHelperAdvisor" />
         <property name="proxyInterfaces" value="com.jd.aop4.EatAble" />
    </bean>

</beans>

测试

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class EatTest {

    public static void main(String[] args) {

        ApplicationContext ac = new ClassPathXmlApplicationContext(new String[]{"springaop4.xml"});

        EatAble eat = (EatAble)ac.getBean("myProxy");

        eat.eat();
    }
}

结果

吃饭前洗手。。。
我吃饭
吃完饭漱口。。。

2.@AspectJ注解驱动的切面

package com.jd.aop;

/**
 * 计算器接口
 */
public interface Calculator {

    public int add(int a,int b);

}
package com.jd.aop;

import org.springframework.stereotype.Component;

@Component
public class CalculatorImpl implements Calculator{

    public int add(int a, int b) {
        int result = a + b;
        return result;
    }

}
import java.util.Arrays;
import java.util.List;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 日志切面
 * 1)把该类放入到ioc容器@Component@Aspect,声明切面
 * 2)制定该切面在哪些方法之前执行@before
 * 3)指定切面的优先级,值越小优先级越高
 */
@Order(1)
@Aspect
@Component
public class LoggerAspect {

    @Before("execution(* com..*.*(..))")
    public void beforeMethod(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println(name+" invoke beforeMethod ..logger..,args:"+args);
    }

    @After("execution(* com..*.*(..))")
    public void afterMethod(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println(name+" invoke afterMethod ..logger..,args:"+args);
    }
}

这中方案是在每个方法上面都写一个表达式,也可以在类中定义一个切点方法,在其他方法上面引用该切点方法,如下:

/**
     * 如果每个方法上面都配置切点,重复性太多,所以定义一个切入点
     * 拦截的切入点方法,注解的在方法级别之上,但是不执行方法体,只表示切入点的入口
     */
    @Pointcut(value="execution(* com..*.*(..))")
    public void pointCut(){

    }

    //使用定义的切入点,不同包下需要添加包名和类型如:com.jd.aop2.LoggerAspectJAnnotation.pointCut()
    @Before("pointCut()")
    private void testPointCut(){
        System.out.println("pointCut....");
    }

配置文件

<?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-4.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">

    <!-- 配置包扫描 -->
    <context:component-scan base-package="com.jd.aop"></context:component-scan>

    <!-- 使AspectJ注解起作用 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

测试

public class AopTest {

    private ApplicationContext ac = null;
    private Calculator calculator = null;
    {
        ac = new ClassPathXmlApplicationContext("springaop.xml");
        calculator = ac.getBean(Calculator.class);
    }

    @Test
    public void testAop(){
        System.out.println("计算开始");
        int result = calculator.add(4, 2);
        System.out.println("计算结束");
//      System.out.println("==>result:"+result);

    }
}

结果

计算开始
add invoke beforeMethod ..logger..,args:[4, 2]
计算结果:6
add invoke afterMethod ..logger..,args:[4, 2]
计算结束

3.注入式AspectJ切面

package com.jd.aop5;

public interface Computer {

    int add(int a,int b);

}
package com.jd.aop5;

public class ComputerImpl implements Computer{

    public int add(int a, int b) {
        System.out.println("计算结果:"+a+b);
        return a+b;
    }

}
package com.jd.aop5;

public class LoggerAspectJXml {

    public void myBeforeMethod(){
        System.out.println("myBeforeMethod ....");
    }

    public void myAfterMethod(){
        System.out.println("myAfterMethod ....");
    }
}
<?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-4.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">

    <bean id="computer" class="com.jd.aop5.ComputerImpl"></bean>

    <bean id="loggerAspectJXml" class="com.jd.aop5.LoggerAspectJXml"></bean>

    <!-- 配置Aop -->
    <aop:config>

        <!-- 配置切点表达式 -->
        <aop:pointcut expression="execution(* com..*.*(..))" id="mypointcut"/>

        <!-- 配置切面及通知 -->
        <aop:aspect ref="loggerAspectJXml" order="1">
            <aop:before method="myBeforeMethod" pointcut-ref="mypointcut"/>
            <aop:after method="myAfterMethod" pointcut-ref="mypointcut"/>
        </aop:aspect>

        <!-- 配置其他切面及通知 -->

    </aop:config>

</beans>

测试

//测试通过xml配置文件的形式
public static void main(String[] args) {
    ApplicationContext ac = new ClassPathXmlApplicationContext("springaop5.xml");
    Computer computer = (Computer)ac.getBean("computer");
    int add = computer.add(1, 2);
    System.out.println(add);
}

myBeforeMethod ….
计算结果:12
myAfterMethod ….
3

3.切点函数

切点函数可以定位到准确的横切逻辑位置,在前面的示例中我们只使用过execution(* com.zhangguo.Spring052.aop02.Math.*(..)),execution就是一个切点函数,但该函数只什么方法一级,如果我们要织入的范围是类或某个注解则execution就不那么好用了,其实一共有9个切点函数,有不同的针对性。

@AspectJ使用AspectJ专门的切点表达式描述切面,Spring所支持的AspectJ表达式可分为四类:
方法切点函数:通过描述目标类方法信息定义连接点。
方法参数切点函数:通过描述目标类方法入参信息定义连接点。
目标类切点函数:通过描述目标类类型信息定义连接点。
代理类切点函数:通过描述代理类信息定义连接点。

常见的AspectJ表达式函数:
execution():满足匹配模式字符串的所有目标类方法的连接点
@annotation():任何标注了指定注解的目标方法链接点
args():目标类方法运行时参数的类型指定连接点
@args():目标类方法参数中是否有指定特定注解的连接点
within():匹配指定的包的所有连接点
target():匹配指定目标类的所有方法
@within():匹配目标对象拥有指定注解的类的所有方法
@target():匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
this():匹配当前AOP代理对象类型的所有执行方法

最常用的是:execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)切点函数,可以满足多数需求。

1)execution(* com...(..))表达式

表示切点的位置,这里一步一步的演进看到表达式该怎么写:

指定具体的某一个方法切入

@Before("execution(public int com.jd.aop.CalculatorImpl.add(int, int))");

可以将方法名用*代替

@Before("execution(public int com.jd.aop.CalculatorImpl.*(int, int))");

可以用..代替参数

@Before("execution(public int com.jd.aop.CalculatorImpl.*(..))");

可以将public int修饰符和返回值类型用*代替

@Before("execution(* com.jd.aop.CalculatorImpl.*(..))");

可以用*和.代替包名

@Before("execution(* com..*.*(..))")    
@Before("execution(* *..*.*(..))")

2)切点函数within

//within切点函数
//com.zhangguo.Spring052.aop03包下所有类的所有方法被切入
@After("within(com.jd.aop3.*)")
public void after(JoinPoint jp){
    System.out.println("最后通知");
}

3)this切点函数

//this切点函数
//实现了IAm接口的代理对象的任意连接点
@After("this(com.jd.aop3.IAm)")
public void after(JoinPoint jp){
    System.out.println("最终通知");
}

4)args切点函数

//args切点函数
//要求方法有两个int类型的参考才会被织入横切逻辑
@After("args(int,int)")
public void after(JoinPoint jp){
    System.out.println("最终通知");
}

5)@annotation切点函数
先自定义一个可以注解在方法上的注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnno {
}
//@annotation切点函数
//要求方法必须被注解com.zhangguo.Spring052.aop03.MyAnno才会被织入横切逻辑
@After("@annotation(com.jd.aop3.MyAnno)")
public void after(JoinPoint jp){
    System.out.println("最终通知");
}
@Component("strUtil")
public class StrUtil {
    @MyAnno
    public void show(){
        System.out.println("Hello StrUtil!");
    }
}

通过这篇,SpringAOP常用的一些配置方式和参数的配置基本够用了。
推荐一篇文章:https://www.cnblogs.com/best/p/5736422.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值