SSM-Spirng-面向切面编程-使用@AspectJ注解开发SpringAoP
使用@AspectJ注解的方式已经成为了主流。
选择切点
spring是方法级别的AOP框架,主要是以某个类的某个方法作为切入点,用动态代理的理论就是,要拦截哪个方法植入对应的AOP通知
//创建一个接口
public interface RoleService {
public void printRole(Role role);
}
//实现类
public class RoleServiceImpl implements RoleService {
public void printRole(Role role) {
System.out.println(role.getId()+role.getRoleName()+role.getNote());
}
}
创建上面代码主要是想通过printRole方法作为AOP切点,那么动态代理需要为类RoleServiceImpl生成代理对象,然后拦截printRole方法,生成各种AOP通知方法
创建切面
选着好切点就可以创建切面了,在动态代理中,他如同一个拦截器,在Spring使用注解@AspectJ注解一个类,spring IOC就会认为它是一个切面
@Aspect
public class RoleAspect {
@Before("execution(* aop02.service.impl.RoleServiceImpl.printRole(..))")
public void before(){
System.out.println("before...");
}
@After("execution(* aop02.service.impl.RoleServiceImpl.printRole(..))")
public void after(){
System.out.println("after..");
}
@AfterReturning("execution(* aop02.service.impl.RoleServiceImpl.printRole(..))")
public void afterReturning(){
System.out.println("afterReturning...");
}
@AfterThrowing("execution(* aop02.service.impl.RoleServiceImpl.printRole(..))")
public void afterThrowing(){
System.out.println("afterThrowing...");
}
}
上面代码并没有环绕通知,注解表如下:
注解 | 通知 | 备注 |
---|---|---|
@Before | 在被代理对象的方法前先调用 | 前置通知 |
@Around | 将被代理对象的方法封装起来,并用环绕通知取代它 | 环绕通知,它将覆盖原有的方法,但是允许你通过反射调用原有方法 |
@After | 在被代理对象的方法后调用 | 后置通知 |
@AfterReturning | 在被代理对象的方法正常返回后调用 | 返回通知,要求被代理对象的方法执行过程中没有发生异常 |
@After Throwing | 在被代理对象的方法抛出异常后调用 | 异常通知,要求被代理对象的方法执行过程中产生异常 |
连接点
在注解中定义execution的正则表达式,Spring通过这个正则表达式判断是否需要拦截你的方法,如:
execution(* aop02.service.impl.RoleServiceImpl.printRole(..))
分析:
- execution:代表执行方法的时候会触发
- *:代表任意返回类型的方法
- aop02.service.impl.RoleServiceImpl:代表类的全限定名
- printRole:被拦截的方法
- (…):任意的参数
还可以配置如下内容
AspectJ指示器 | 描述 |
---|---|
arg() | 限制连接点匹配参数为指定类型的方法 |
@args() | 限制而连接点匹配参数为指定注解的执行方法 |
execution | 用于匹配连接点的常用方法,这是最常用的匹配,可以通过类似上面的正则表达式进行匹配 |
this() | 限制连接点匹配AOP代理的Bean,引用为指定类型的类 |
target | 限制连接点匹配被代理对象为指定的类型 |
@target() | 限制连接点匹配特定的执行对象,这些对象要符合指定的注解类型 |
within() | 限制连接点指定匹配的包 |
@within() | 限制连接点匹配指定的类型 |
@annotation | 限制匹配带有指定注解的连接点 |
spring只支持上面表格的指示器,要是使用了其他会抛出异常
另外还可以使用 && || !代表 and or not
@Pointcut可以解决正则表达式需要重复书写多次的麻烦
测试AOP(*测试出问题,以后回来在解决,怀疑是AopConfig类)
对Spring的Bean进行配置,采用注解方式:
//注解的方式
@Configuration
//启动AspectJ框架的自动代理
@EnableAspectJAutoProxy
@ComponentScan("aop02")
public class AopConfig {
//生成一个切面实例
@Bean
public RoleAspect getRoleAspect(){
return new RoleAspect();
}
}
XML方式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/beans/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启自动代理功能-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<bean id="roleAspect" class="aop02.aspect.RoleAspect"></bean>
<bean id="roleService" class="aop02.service.impl.RoleServiceImpl"></bean>
</beans>
测试
package aop02.main;
import aop02.config.AopConfig;
import aop02.pojo.Role;
import aop02.service.RoleService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("spring06.xml");
//ApplicationContext context=new AnnotationConfigApplicationContext(AopConfig.class);
RoleService roleService=(RoleService)context.getBean(RoleService.class);
Role role=new Role();
role.setId(1L);
role.setRoleName("roleName01");
role.setNote("note01");
roleService.printRole(role);
System.out.println("************");
role=null;
roleService.printRole(role);
}
}
环绕通知
是SpringAop中最强大的功能,可以同时实现前置通知和后置通知,保留了调度被代理对象原有的方法和功能。
@Around("print()")
public void around(ProceedingJoinPoint jp){
System.out.println("around before...");
try{
jp.proceed();
}catch(Throwable e){
e.printStackTrace ();
}
System.out.println("around after...");
}
通过@Aroun注解加入了切面的环绕通知
织入
是生成代理对象的过程,规则:类存在接口,Spring将提供JDK动态代理,从而织入各个通知;当不存在接口,Spring将提供CGLEB来生成对象
给通知传递参数
修改切点为一个多参数方法
public void printRole(Role role,int sort){
System.out.println(role.getId+role.getRoleName+role.getNote);
System.out.println(sort);
}
存在两个参数,一个角色,一个整形排序参数,把这个方法作为切点,也就是使用切面拦截这个方法,以前置为例:
@Before("execution(* aop02.service.impl.RoleServiceImpl.printRole(..))"+"&& args(role,sort)")
public void before(Role role,int sort){
System.out.println("before...");
}
上面,在连接的定义上加入参数,就可以获取动态代理。
### 引入
如果希望通过引入其他类的方法来得到更好的实现,可以引入其他方法
如printRole方法要求,角色为空时不打印,那么引入一个新的检测器对其检测,定义一个接口:
public interface RoleVerifier {
public boolean verify(Role role);
}
该方法检测role是否为空
创建实现类
public class RoleVerifierImpl implements RoleVerifier {
public boolean verify(Role role) {
return role != null;
}
}
在切面类RoleAspect 加入一个新属性
@DeclareParents(value = "aop02.service.impl.RoleServiceImpl+",defaultImpl = RoleVerifierImpl.class)
public RoleVerifier roleVerifier;
注解@DeclareParents 使用:
- value = “aop02.service.impl.RoleServiceImpl+”:代表对RoleServiceImpl类进行增强,也就是引入一个新接口
- defaultImpl:代表其默认的实现类,上面是RoleVerifierImplra
然后调用测试代码
ApplicationContext context=new AnnotationConfigApplicationContext(AopConfig.class);
RoleService roleService=(RoleService)context.getBean(RoleService.class);
RoleVerifier roleVerifier= (RoleVerifier) roleService;
Role role=new Role();
role.setId(1L);
role.setRoleName("roleName01");
role.setNote("note01");
if(roleVerifier.verify(role)){
roleService.printRole(role);
}
使用强化后的roleService转换为RoleVerifier接口对象,然后就可以使用verify方法 RoleVerifer 调用的方法 verify ,显然它就是通过 RoleVerifierlmpl 来实现的
原理:SpringAOP依赖于动态代理实现,生成动态代理对象是通过类型下面的代理代码
//生成代理对象,并绑定代理方法
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().get!nterfaces(), _this );
SpringAOP让代理对象挂到RoleService和RoleVerifier两个接口下,把对应的Bean通过强制转换,如果RoleServicelmpl没有接口,它会使用CGLIB动态代理,使用增强者类也会有一个interfaces的属性,允许代理对象挂到对应的多个接口下,然后就可以按JDK动态代理那样使得对象可以在多个接口之间相互转换