AOP的概念和理解:使用画图的方式理解面向切面编程(AOP)
(1)创建目标接口以及实现类
package com.ssm.aop.service;
import com.ssm.aop.game.Role;
public interface RoleService {
public void printRole(Role role);
}
package com.ssm.aop.service.Impl;
import com.ssm.aop.game.Role;
import com.ssm.aop.service.RoleService;
import org.springframework.stereotype.Component;
@Component
public class RoleServiceImpl implements RoleService {
@Override
public void printRole(Role role) {
System.out.println("{id: "+role.getId()+", "
+"role_name:"+role.getRoleName()+", "
+"note:"+role.getNote()+"}");
}
}
(2)创建切面类(用于增强目标类的方法)
package com.ssm.aop.aspect;
import org.aspectj.lang.annotation.*;
@Aspect
public class RoleAspect {
@Before("execution(* com.ssm.aop.service.Impl.RoleServiceImpl.printRole(..))")
public void before()
{
System.out.println("before...");
}
@After("execution(* com.ssm.aop.service.Impl.RoleServiceImpl.printRole(..))")
public void after()
{
System.out.println("after...");
}
@AfterReturning("execution(* com.ssm.aop.service.Impl.RoleServiceImpl.printRole(..))")
public void afterReturning()
{
System.out.println("afterReturning...");
}
@AfterThrowing("execution(* com.ssm.aop.service.Impl.RoleServiceImpl.printRole(..))")
public void afterThrowing()
{
System.out.println("afterThrowing...");
}
}
在类的上方加上@Aspect注解,就表明该类为切面类
@Before:添加该注解的方法表明该方法在被代理对象的方法前调用,即前置通知
@After:添加该注解的方法表明该方法在被代理对象的方法后调用,即后置通知
@AfterReturning:添加该注解的方法表明该方法在被代理对象的方法正常返回后调用。
@AfterThrowing:添加该注解的方法表明该方法在被代理对象的方法抛出异常后调用。
方法上的注解括号中的配置项(定义了execution的正则表达式,来匹配对应的切点)用于定义切点:
表达式的语法:
execution([修饰符]返回值类型 包名.类名.方法名(参数))
- 访问的修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用*代表任意
- 包名与类名之间一个点.代表当前包下的类,两个点..表示当前包及其子包下的类
- 参数列表可以使用两个点..表示任意个数,任意类型的参数列表。
以下面的表达式为例:
execution(* com.ssm.aop.service.Impl.RoleServiceImpl.printRole(..))
execution:代表执行方法时会触发
*:代表任意返回类型的方法
com.ssm.aop.service.Impl.RoleServiceImpl:代表类的全限定名
printRole:被拦截的方法名称
(..):任意的参数
上述切面类中,切点的定义是重复的代码,比较麻烦,可以通过引入@Pointcut定义一个切点就可以避免该麻烦
在一个没有任何逻辑的方法上方定义@Pointcut注解,配置项为切点的表达式即可,在使用时只要在配置项配置没有逻辑的方法名即可。
改进后的代码如下
package com.ssm.aop.aspect;
import org.aspectj.lang.annotation.*;
@Aspect
public class RoleAspect {
@Pointcut("execution(* com.ssm.aop.service.Impl.RoleServiceImpl.printRole(..))")
public void print()
{
}
@Before("print()")
public void before()
{
System.out.println("before...");
}
@After("print()")
public void after()
{
System.out.println("after...");
}
@AfterReturning("print()")
public void afterReturning()
{
System.out.println("afterReturning...");
}
@AfterThrowing("print()")
public void afterThrowing()
{
System.out.println("afterThrowing...");
}
}
(3)配置
要使得上述的注解有效,要先启动AspectJ自动代理,这样Spring才会生成动态代理对象。
配置代码如下:
package com.ssm.aop.config;
import com.ssm.aop.aspect.RoleAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.ssm.aop")
public class AopConfig {
@Bean
public RoleAspect getRoleAspect()
{
return new RoleAspect();
}
}
@EnableAspectJAutoProxy
@Bean
public RoleAspect getRoleAspect()
{
return new RoleAspect();
}
该部分代码就代表了启动了AdpectJ框架的自动代理
另一种启用自动代理是通过XML配置的方式:
<?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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
">
<aop:aspectj-autoproxy/>
<bean id="roleAspect" class="com.ssm.aop.aspect.RoleAspect"/>
<bean id="roleService" class="com.ssm.aop.service.Impl.RoleServiceImpl"/>
</beans>
可以看到XML中将RoleAspect切面类和RoleServiceImpl目标类作为bean加入Ioc容器,相当于注解中的@Bean和@Component
<aop:aspectj-autoproxy>跟注解@EnableAspectJautoProxy作用相同,启用自动代理功能。
(4)测试
测试代码如下:
import com.ssm.aop.config.AopConfig;
import com.ssm.aop.game.Role;
import com.ssm.aop.service.RoleService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AopConfig.class);//使用config类配置
// ApplicationContext ctx=new ClassPathXmlApplicationContext("application-config.xml");//使用xml配置文件
RoleService roleService = (RoleService) ctx.getBean(RoleService.class);
Role role = new Role();
role.setId(1L);
role.setRoleName("role_name_1");
role.setNote("note_1");
roleService.printRole(role);
System.out.println("####################");
//测试异常通知
role = null;
roleService.printRole(role);
}
}
运行结果如下图;
在测试代码中特意创建了一个空对象来触发异常通知。
在没有错误的情况下,可以看到首先调用前置通知,然后调用printRole方法,再调用了成功返回的通知,最后调用后置通知。
在抛出异常的情况下,首先调用了前置通知,然后调用pringRole方法时出现异常,则调用异常通知输出afterThrowing,最后调用后置通知。
也就是说无论调用pringRole成功与否,都会调用后置通知。