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中对切点和切面进行配置
- 目标对象(通知需要切入的类)和切面(通知类)注册bean
- 通过
<aop:pointcut>
配置切入点,使用expression中的execution()方法来实现对切入点的范围控制 - 通过
<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:指定方法参数(声明的类型):
(..)
代表所有参数(*)
代表一个参数(*,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">
来实现切面类中的切面方法需要织入到哪一个切入点中
文毕,如有助,赞之,乐乎