spring(四)SpringAOP

一、概念

SpringAOP的底层实现就是对动态代理进行了封装,封装后我们只需要对关注的部分进行编码,并通过配置的方式完成指定目标方法的增强。

相关概念:

  • Target(目标对象):要被增强的对象,一般指业务逻辑类的对象。
  • Proxy(代理):一个类被AOP织入增强以后,就产生了一个结果代理类。
  • Aspect(切面):表示增强的功能,就是一些代码完成的某些功能,即非业务功能。是切入点和通知的结合。
  • Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在Spring中,这些点是指方法(一般是类中业务方法),Spring只支持方法类型的连接点。
  • Pointcut(切入点):指的是声明的一个或多个连接点的集合,通过切入点指定一组方法。被标记为final的方法是不能作为连接点或切入点的,因为它不能被修改,也不能增强。
  • Advice(通知/增强):拦截到Joinpoint之后需要做的事情就是通知。通知定义了增强代码切入到目标代码的时间点,即目标执行方法之前还是之后。通知类型不同,切入时间不同。 通知类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
    切入点定义切入位置,通知定义切入时间。
  • Weaving(织入):指把增强应用到目标对象来创建新的代理对象的过程。Spring采用动态代理方式织入,而AspectJ采用编译期织入和类装载期织入。

切面的三个关键因素:

  • 切面的功能
  • 切面的执行位置(pointCut)
  • 切面的执行时间(advice)

二、AspectJ对AOP的实现

对于AOP的编程思想,很多框架都进行了实现,Spring就是其中之一,可以完成面向切面编程。AspectJ也实现了AOP功能,且其实现方式更为简洁还支持注解式开发。所以,Spring又将AspectJ对AOP的实现引入到自己的框架中。

AspectJ是一个优秀的面向切面的框架,它扩展了Java语言,提供了强大的切面实现。

2.1、AspectJ的通知类型

AspectJ支持5种通知类型:

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

2.2、AspectJ切入点表达式

AspectJ定义了专门的表达式用于指定切入点。
表达式原型:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

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

说明:

表达式描述
modifiers-pattern访问权限类型
ret-type-pattern返回值类型
declaring-type-pattern包名类名
name-pattern(param-pattern)方法名(参数类型和参数个数)
throws-pattern抛出异常类型
可选部分

切入点表达式要匹配的对象就是目标方法的方法名。所以,execution表达式中就是方法的签名。表达式各部分间用空格分隔,在其中可以使用以下符号:

符号含义
*0-多个任意字符
用在方法参数中,表示任意个参数;用在包后,表示当前及其子包路径
+用在类名后,表示当前及其子类;用在接口后,表示当前接口及其实现

示例:

// 指定切点为:定义在service包中任意类 任意方法
execution(* com.kkb.service.*.*(..)) 

// 指定切点为:定义在service包或者子包中任意类 任意方法。 ..出现在类名中,后面必须跟*,表示包、子包下所有类。
execution(* com.kkb.service..*.*(..)) 

// 指定切点为:IUserService若为接口,则表示接口中任意方法及其实现类中任意方法;若为类,则表示该类及其子类中任意方法
execution(* com.kkb.service.IUserService+.*(..)) 

三、注解方式实现AOP

开发阶段:关心核心代码和AOP代码

运行阶段:spring框架会在运行时将核心业务和AOP代码通过动态代理方式编织在一起

代理方式选择:如果实现了接口就选择JDK动态代理;没有就选择CGLIB动态代理。

3.1、新建maven项目添加pom依赖

 <dependencies>
        <!--   单元测试    -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!--   spring依赖    -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.13.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.13.RELEASE</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!--   编译插件    -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

3.2、添加配置文件application.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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">

    <!--  包扫描  -->
    <context:component-scan base-package="com.jsonliu.test.service,com.jsonliu.test.config" />
    <!--  开启aop注解使用  -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
<!--
   aop:aspectj-autoproxy 底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的,是基于AspectJ的注解适配自动代理生成器。
   工作原理:aop:aspectj-autoproxy通过扫描找到@Aspect定义的切面,再由切面根据切点找到目标类的目标方法,再由通知类型找到切入的时间点。
 -->

</beans>

3.3、添加接口

public interface IService {

    void add(int id,String name);

    boolean update(int num);

}

3.4、添加实现类

@Service
public class TeamService implements IService{
    @Override
    public void add(int id, String name) {
        System.out.println("TeamService-------------add------------------");
    }

    @Override
    public boolean update(int num) {
        System.out.println("TeamService-------------update------------------");
        // 模拟异常
        int k=num/0;
        if(num>999) return true;
        return false;
    }
}


@Service("userService")
public class UserService implements IService{
    @Override
    public void add(int id, String name) {
        System.out.println("UserService-------------add------------------");
    }

    @Override
    public boolean update(int num) {
        System.out.println("UserService-------------update------------------");
        if(num<999) return true;
        return false;
    }
}

3.5、添加切面

@Aspect
@Component
public class MyAspect {

    /**
     * 当多个通知增强方法使用相同的execution切入点表达式,编写维护较为麻烦。
     * AspectJ提供了@Pointcut注解,用于定义execution切入点表达式
     * 用法:将@Pointcut注解在一个方法上,以后所有的execution的value值都可以使用该方法名作为切入点。
     * 代表的就是@Pointcut定义的切入点
     * 这个使用@Pointcut的注解方法一般使用private标识的方法,即没有实际作用的方法
     */
    @Pointcut("execution(* com.jsonliu.test.service..*.*(..))")
    private void pointCut(){

    }

    /**
     * 前置通知
     * @param jp
     */
    @Before("pointCut()")
    public void before(JoinPoint jp) {
        System.out.println("前置通知before:目标方法执行之前调用的通知");
        String name = jp.getSignature().getName();
        System.out.println("拦截的方法名称:" + name);
        Object[] args = jp.getArgs();
        System.out.println("拦截的方法个数:" + args.length);
        for (Object arg : args) {
            System.out.println("拦截的参数:" + arg);
        }
    }

    /**
     * 后置通知
     * value:切入点表达式,returning:返回的结果,可以在后置通知中修改
     * @param result
     * @return
     */
    @AfterReturning(value = "pointCut()",returning = "result")
    public Object afterReturn(Object result){
        System.out.println("后置通知afterReturn:目标方法执行之后被调用的通知,result="+result);
        return result==null?"":result.toString();
    }

    /**
     * 环绕通知
     * ProceedingJoinPoint中的proceed()方法表示目标方法被执行
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around(value = "pointCut()")
    public Object around(ProceedingJoinPoint pjp)throws Throwable {
        System.out.println("环绕通知--目标方法执行之前");
        Object proceed = pjp.proceed();
        System.out.println("环绕通知--目标方法执行之后");
        return proceed;
    }

    /**
     * 异常通知
     * value:切入点表达式,ex:异常信息
     * @param jp
     * @param ex
     */
    @AfterThrowing(value = "pointCut()",throwing = "ex")
    public void exception(JoinPoint jp,Throwable ex){
        //一般用于记录异常发生的时间、位置、原因
        System.out.println("异常通知exception:目标方法执行出现异常时才会调用的通知,否则不会执行");
        System.out.println(jp.getSignature().getName()+"方法出现异常,异常的信息:"+ex.getMessage());
    }

    /**
     * 最终通知
     */
    @After("pointCut()")
    public void myFinally(){
        System.out.println("最终通知after:无论是否出现异常都是最后执行的通知");
    }

}

3.6、添加测试类

    @Test
    public void test1(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
        TeamService teamService = (TeamService) applicationContext.getBean("teamService");
        System.out.println("-----------------------------add---------------------------------");
        teamService.add(1,"阿森纳");
        System.out.println("-----------------------------update---------------------------------");
        teamService.update(9999);

        System.out.println("-----------------------------UserService---------------------------------");
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.add(22,"曼联");

    }

运行结果:

在这里插入图片描述

四、XML方式实现AOP

4.1、修改Aspect

@Aspect    //aspectj 框架的注解 标识当前类是一个切面
@Component  //切面对象的创建权限依然交给spring容器
public class XmlAspect { 

    /**
     * 前置通知
     * @param jp
     */
    public void before(JoinPoint jp) {
        System.out.println("前置通知before:目标方法执行之前调用的通知");
        String name = jp.getSignature().getName();
        System.out.println("拦截的方法名称:" + name);
        Object[] args = jp.getArgs();
        System.out.println("拦截的方法个数:" + args.length);
        for (Object arg : args) {
            System.out.println("拦截的参数:" + arg);
        }
    }

    /**
     * 后置通知
     * @param result
     * @return
     */
    public Object afterReturn(Object result){
        System.out.println("后置通知afterReturn:目标方法执行之后被调用的通知,result="+result);
        return result==null?"":result.toString();
    }

    /**
     * 环绕通知
     * @param pjp
     * @return
     * @throws Throwable
     */
    public Object around(ProceedingJoinPoint pjp)throws Throwable {
        System.out.println("环绕通知--目标方法执行之前");
        Object proceed = pjp.proceed();
        System.out.println("环绕通知--目标方法执行之后");
        return proceed;
    }

    /**
     * 异常通知
     * value:切入点表达式,ex:异常信息
     * @param jp
     * @param ex
     */
    public void exception(JoinPoint jp,Throwable ex){
        //一般用于记录异常发生的时间、位置、原因
        System.out.println("异常通知exception:目标方法执行出现异常时才会调用的通知,否则不会执行");
        System.out.println(jp.getSignature().getName()+"方法出现异常,异常的信息:"+ex.getMessage());
    }

    /**
     * 最终通知
     */
    public void myFinally(){
        System.out.println("最终通知after:无论是否出现异常都是最后执行的通知");
    }

}

4.2、添加XML

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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">

    <!--  包扫描  -->
    <context:component-scan base-package="com.jsonliu.test.service,com.jsonliu.test.config" />
    <!--  开启aop注解使用  -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
<!--
   aop:aspectj-autoproxy 底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的,是基于AspectJ的注解适配自动代理生成器。
   工作原理:aop:aspectj-autoproxy通过扫描找到@Aspect定义的切面,再由切面根据切点找到目标类的目标方法,再由通知类型找到切入的时间点。
 -->
    <aop:config>
        <aop:pointcut id="pt1" expression="execution(* com.jsonliu.test.service..*.update*(..))"/>
        <aop:pointcut id="pt2" expression="execution(* com.jsonliu.test.service..*.add*(..))"/>
        <aop:aspect ref="xmlAspect">
            <aop:before method="before" pointcut="execution(* com.jsonliu.test.service..*.*(..))"/>
            <aop:after-returning method="afterReturn" pointcut-ref="pt1" returning="result"/>
            <aop:after-throwing method="exception" pointcut-ref="pt1" throwing="ex"/>
            <aop:after method="myFinally" pointcut-ref="pt2" />
            <aop:around method="around" pointcut-ref="pt1" />
        </aop:aspect>
    </aop:config>

</beans>

4.3、添加测试类

    @Test
    public void test2(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("xmlAop.xml");
        TeamService teamService = (TeamService) applicationContext.getBean("teamService");
        System.out.println("-----------------------------add---------------------------------");
        teamService.add(1,"阿森纳");
        System.out.println("-----------------------------update---------------------------------");
        teamService.update(9999); 
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笑谈子云亭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值