Spring学习(9)-AOP之使用aop:config标签

1 导包

最开始还是贴出用到了哪些包(相比上一篇博文多了一个aspectjweaver.jar,java切面织入包)
在这里插入图片描述
下载地址:aspectjweaver-1.9.4.jar下载

2 AspectJ介绍

AspectJ 是一个基于 Java 语言的 AOP 框架,它扩展了 Java 语言。Spring 2.0 以后,新增了对 AspectJ 方式的支持,新版本的 Spring 框架,建议使用 AspectJ 方式开发 AOP。

使用 AspectJ 开发 AOP 通常有两种方式:

  • 基于 XML 的声明式。(支持Bean)
  • 基于 Annotation 的声明式。(支持注解)

为什么要使用这个AspectJ呢?

Spring aop为我们提供了前置通知MethodBeforeAdvice后置通知AfterReturningAdvice环绕通知MethodInterceptor(需要aopalliance-1.0.jar)等通知接口,我们只需要在写的通知类中实现想要的通知接口,在xml中使用代理工厂ProxyFactoryBean,就可以简单实现切面的织入。
那么对一个POJO(简单java类,即不实现通知接口)中的方法可以放到代理工厂作为切面类吗,是不行的。
如果我们需要一个POJO转换成一个切面,可以使用spring提供的aop标签来实现,而AspectJ便是为了让这个POJO具备和继承了通知父类一样的能力。

3 使用<aop:config>标签

用一个具体例子来说明如何使用

项目结构
在这里插入图片描述

3.1 首先需要在xml头部引入aop标签

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

3.2 定义一个切面类

可以看到这个类并无继承任何父类或实现接口,只有单纯的方法
我在其中定义了常用的五种通知方法:前置、环绕、后置、最终、异常通知;
这些切面方法如果想要进一步对切入点进行控制,就需要使用到Aspectj来作为方法参数,比如前置通知方法有参数JoinPoint jp,环绕通知需要参数ProceedingJoinPoint pjp等,具体解释在代码中。

package com.cheng.aop.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import java.util.Arrays;

//FirstAspect 切面类
public class FirstAspect {

    /**
     * 前置通知:数据校验,身份校验
     * @param jp 连接点参数Joinpoint,必须放在方法的第一位,可对切入点进行分析
     *           比如拿连接点的名字,连接点的所在类,连接点的参数
     */
	public void before(JoinPoint jp){
		System.out.println("1...前置通知,切入类"+jp.getTarget().getClass());
		System.out.println("1...前置通知,切入方法"+jp.getSignature().getName());
        System.out.println("1...前置通知,参数:"+ Arrays.toString(jp.getArgs()));
	}

    /**
     * 环绕通知:事务控制,权限控制,返回对象
     * @param pjp 对连接点的方法内容进行整体控制
     * @return 返回连接点的返回值
     * @throws Throwable 连接点有异常则抛出
     */
	public Object around(ProceedingJoinPoint pjp) throws Throwable{
		System.out.println("2...环绕前置, 在执行用辅助业务");
		Object obj = pjp.proceed(); //调用核心业务
        System.out.println("2...环绕返回对象:"+obj.toString());
		System.out.println("2...环绕后置, 在执行用辅助业务");
		return obj;

	}

    /**
     * 后置通知:日志记录,返回对象
     * @param jp 连接点的基本信息
     * @param result 获取连接点的返回对象
     */
	public void afterReturning(JoinPoint jp,Object result){
		System.out.println("3...后置通知,切入类"+jp.getTarget().getClass());
		System.out.println("3...后置通知,切入方法"+jp.getSignature().getName());
        System.out.println("3...后置通知,返回对象为:"+result);
	}

    /**
     * 最终通知:在方法最后执行
     * @param jp 连接点的基本信息
     */
    public void after(JoinPoint jp){
        System.out.println("4...最终通知,切入类"+jp.getTarget().getClass());
        System.out.println("4...最终通知,切入方法"+jp.getSignature().getName());
    }

    /**
     * 异常通知:异常处理,事务控制
     * 在目标方法抛出异常时执行的通知
     * @param jp 连接点的基本信息
     * @param e 连接点的异常信息
     */
	public void afterThrow(JoinPoint jp,Throwable e){
		System.out.println("5...异常通知,切入类"+jp.getTarget().getClass());
		System.out.println("5...异常通知,切入方法"+jp.getSignature().getName());
		System.out.println(e.getMessage());
	}
}

3.3 服务接口和实现类

package com.cheng.aop.service;

//用户服务接口
public interface UserService {
	//用户新增
	public void addUser(String name);
	
	//用户修改
	public String updateUser();
	
	//用户删除
	public String deleteUser();
		
	//用户查询
	public void queryUser();
}

package com.cheng.aop.serviceImpl;


import com.cheng.aop.service.UserService;

public class UserServiceImpl implements UserService {

	@Override
	public void addUser(String name) {
		System.out.println("新增用户:"+name);

	}

	@Override
	public String updateUser() {
		System.out.println("用户修改业务...");
		return "已修改";

	}

	@Override
	public String deleteUser() {
		System.out.println("用户删除业务...");
		return "已删除";
	}

	@Override
	public void queryUser() {
		System.out.println("用户查询业务...");
		System.out.println(0/20);//抛出异常
	}

}

3.4 在xml中对切点和切面进行配置

  1. 目标对象(通知需要切入的类)和切面(通知类)注册bean
  2. 通过<aop:pointcut>配置切入点,使用expression中的execution()方法来实现对切入点的范围控制
  3. 通过<aop:aspect ref="切面id">来实现切面类中的切面方法需要织入到哪一个切入点中

最重要的地方就是第二步,使用expression中的execution()方法
这个方法是为了找到切面需要织入的那个地方,可以是单个/多个方法,可以是整个类的所有方法
它的具体写法要求如下:
execution(
     modifiers-pattern? —修饰符,比如public
     ret-type-pattern —标识方法的返回值
     declaring-type-pattern? —声明类型模式
     name-pattern/param-pattern —指定方法名/指定方法参数的路径
     throws-pattern? —抛出模式
     )
     
ret-type-pattern,name-pattern(param-pattern)是必须的.

  • ret-type-pattern:标识方法的返回值,需要使用全路径的类名如java.lang.String,也可以为*表示任何返回值;
  • name-pattern:指定方法名,*代表所有,例如set*,代表以set开头的所有方法.
  • param-pattern:指定方法参数(声明的类型):
    1. (..)代表所有参数
    2. (*)代表一个参数
    3. (*,String)代表第一个参数为任何值,第二个为String类型.

表达式例子如下:

  • 任意公共方法的执行:
        execution(public * *(..))
  • 任何一个以“set”开始的方法的执行:
        execution(* set*(..))
  • UserService接口的任意方法的执行:
        execution(* com.cheng.aop.service.UserService.*(..))
  • 定义在service包里的任意类和任意方法的执行:
        execution(* com.cheng.aop.service.*.*(..))
  • 定义在service包和所有子包里的任意类的任意方法的执行(比上面多了个点):
        execution(* com.cheng.aop.service..*.*(..))
  • 定义在pointcutexp包和所有子包里的UserService的的任意方法的执行:
        execution(* com.cheng.aop.service..UserService.*(..))

我想要的是:共5个切面方法,织入到4个切入点中,其中after和afterReturning通知织入到一个切入点中

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

    <!-- 配置目标对象 -->
    <bean id="userService" class="com.cheng.aop.serviceImpl.UserServiceImpl"/>

    <!-- 配置切面 -->
    <bean id="firstAspect" class="com.cheng.aop.aop.FirstAspect"/>

    <aop:config>
        <!-- 配置切入点 方法1 addUser-->
        <aop:pointcut
                expression="execution(* com.cheng.aop.service.UserService.addUser(..))"
                id="pc001"/>
        <!-- 配置切入点 方法2 updateUser-->
        <aop:pointcut
                expression="execution(* com.cheng.aop.service.UserService.updateUser(..))"
                id="pc002"/>
        <!-- 配置切入点 方法3 deleteUser-->
        <aop:pointcut
                expression="execution(* com.cheng.aop.service.UserService.deleteUser(..))"
                id="pc003"/>

        <!-- 配置切入点 方法4 queryUser-->
        <aop:pointcut
                expression="execution(* com.cheng.aop.service.UserService.queryUser(..))"
                id="pc004"/>

        <aop:pointcut id="pc005" expression="execution(public * *(..))"/>

        <!-- 配置切面 -->
        <aop:aspect ref="firstAspect">
            <!-- 前置通知 -->
            <aop:before method="before" pointcut-ref="pc001"/>
            <!-- 环绕通知 -->
            <aop:around method="around" pointcut-ref="pc002" />
            <!-- 后置通知 -->
            <aop:after-returning method="afterReturning" pointcut-ref="pc003" returning="result"/>
            <!-- 最终通知 都织入pc003(deleteUser)中,看看后置和最终谁最后执行 -->
            <aop:after method="after" pointcut-ref="pc003"/>
            <!-- 异常通知 -->
            <aop:after-throwing method="afterThrow" pointcut-ref="pc004" throwing="e"/>
        </aop:aspect>

    </aop:config>
</beans>

3.5 测试结果

package com.cheng.aop.test;

import com.cheng.aop.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AOPTest {

	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		UserService userService = (UserService)context.getBean("userService");
		userService.addUser("此成");
		//userService.updateUser();
		//userService.deleteUser();
		//userService.queryUser();
	}
}

1.addUser(“此成”)方法执行,它被织入了before()前置通知方法
在这里插入图片描述
2.updateUser()方法执行,它被织入了around()环绕通知方法
在这里插入图片描述
3.deleteUser()方法执行,它被织入了afterReturning()后置通知方法和after()最终通知方法
在这里插入图片描述
4.queryUser()方法执行,它被织入了afterThrow()异常通知方法
在这里插入图片描述

4 总结

通过使用<aop:config>标签来实现对POJO的切面化和织入过程
通过<aop:pointcut>配置切入点,使用expression中的execution()方法来实现对切入点的范围控制
通过<aop:aspect ref="切面id">来实现切面类中的切面方法需要织入到哪一个切入点中


文毕,如有助,赞之,乐乎
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值