最开始还是贴出用到了哪些包(相比上一篇博文多了一个aspectjweaver.jar,java切面织入包)
下载地址:aspectjweaver-1.9.4.jar下载
AspectJ 是一个基于 Java 语言的 AOP 框架,它扩展了 Java 语言。Spring 2.0 以后,新增了对 AspectJ 方式的支持,新版本的 Spring 框架,建议使用 AspectJ 方式开发 AOP。
使用 AspectJ 开发 AOP 通常有两种方式:
为什么要使用这个AspectJ呢?
Spring aop为我们提供了前置通知MethodBeforeAdvice、后置通知AfterReturningAdvice、环绕通知MethodInterceptor(需要aopalliance-1.0.jar)等通知接口,我们只需要在写的通知类中实现想要的通知接口,在xml中使用代理工厂ProxyFactoryBean,就可以简单实现切面的织入。
那么对一个POJO(简单java类,即不实现通知接口)中的方法可以放到代理工厂作为切面类吗,是不行的。
如果我们需要一个POJO转换成一个切面,可以使用spring提供的aop标签来实现,而AspectJ便是为了让这个POJO具备和继承了通知父类一样的能力。
标签用一个具体例子来说明如何使用
<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>
可以看到这个类并无继承任何父类或实现接口,只有单纯的方法
我在其中定义了常用的五种通知方法:前置、环绕、后置、最终、异常通知;
这些切面方法如果想要进一步对切入点进行控制,就需要使用到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());
}
}
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);//抛出异常
}
}
配置切入点,使用expression中的execution()方法来实现对切入点的范围控制
来实现切面类中的切面方法需要织入到哪一个切入点中最重要的地方就是第二步,使用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)是必须的.
*
代表所有,例如set*
,代表以set开头的所有方法.(..)
代表所有参数(*)
代表一个参数(*,String)
代表第一个参数为任何值,第二个为String类型.表达式例子如下:
execution(public * *(..))
execution(* set*(..))
execution(* com.cheng.aop.service.UserService.*(..))
execution(* com.cheng.aop.service.*.*(..))
execution(* com.cheng.aop.service..*.*(..))
execution(* com.cheng.aop.service..UserService.*(..))
我想要的是:共5个切面方法,织入到4个切入点中,其中after和afterReturning通知织入到一个切入点中
<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>
<aop:pointcut
expression="execution(* com.cheng.aop.service.UserService.addUser(..))"
id="pc001"/>
<aop:pointcut
expression="execution(* com.cheng.aop.service.UserService.updateUser(..))"
id="pc002"/>
<aop:pointcut
expression="execution(* com.cheng.aop.service.UserService.deleteUser(..))"
id="pc003"/>
<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"/>
<aop:after method="after" pointcut-ref="pc003"/>
<aop:after-throwing method="afterThrow" pointcut-ref="pc004" throwing="e"/>
aop:aspect>
aop:config>
beans>
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()异常通知方法
通过使用
标签来实现对POJO的切面化和织入过程
通过
配置切入点,使用expression
中的execution()
方法来实现对切入点的范围控制
通过
来实现切面类中的切面方法需要织入到哪一个切入点中