}
@Override
public void saveAllUser(List users) {
for (User user : users) {
UserService proxyUserServiceImpl = (UserService) AopContext.currentProxy();
proxyUserServiceImpl.saveUser(user);
}
}
}
UserServiceImpl.java
@Component
//表明当前类是一个切面类
@Aspect
public class LogUtil {
//用于配置当前方法是一个前置通知
@Before(“execution(* com.example.service.impl.*.saveUser(…))”)
public void printLog(){
System.out.println(“打印日志”);
}
}
LogUtil.java
@Configuration
@ComponentScan(“com.example”)
//开启Spring注解aop配置的支持
@EnableAspectJAutoProxy(exposeProxy = true)
public class SpringConfiguration {
}
SpringConfiguration
4.2 用于配置切面的
4.2.1 @Aspect
4.2.1.1 作用
- 声明当前类是一个切面类。
4.2.1.2 属性
- value:默认我们的切面类应该为单例的。但是当切面类为一个多例类时,指定预处理的切入点表达式。用法是perthis(切入点表达式)。它支持指定切入点表达式,或者是用@Pointcut修饰的方法名称(要求全限定方法名)
4.2.1.3 基本使用
- 属性说明
@Component
//表明当前类是一个切面类
@Aspect(“perthis(execution(* com.example.service.impl..(…)))”)
@Scope(“prototype”)
public class LogUtil {
//用于配置当前方法是一个前置通知
@Before(“execution(* com.example.service.impl..(…))”)
public void printLog(){
System.out.println(“打印日志”);
}
}
LogUtil.java
-
对于多个切面的执行顺序问题
-
方法都是@Before注解时,默认先执行切面的类名排名更靠前的方法,也就是先执行EfficiencyUtils.java
@Component
//表明当前类是一个切面类
@Aspect
public class LogUtil {
//用于配置当前方法是一个前置通知
@Before(“execution(* com.example.service.impl..(…))”)
public void printLog(){
System.out.println(“打印日志”);
}
}
LogUtil.java
@Component
@Aspect
public class EfficiencyUtils {
private Long time;
@Before(“execution(* com.example.service.impl..(…))”)
public void before() {
time = System.currentTimeMillis();
System.out.println(“方法执行开始时间:” + time);
}
@After(“execution(* com.example.service.impl..(…))”)
public void after() {
System.out.println(“方法执行时间:” + (System.currentTimeMillis() - time) / 1000);
}
}
EfficiencyUtils.java
- 也可以在类上加@Order(数值) 自己配置执行顺序,数字越小越先执行
4.3 用于配置切入点表达式的
4.3.1 @Pointcut
4.3.1.1 作用
- 此注解是用于指定切入点表达式的。
4.3.1.2 属性
- value:用于指定切入点表达式。
4.3.1.3 基本使用
@Component
//表明当前类是一个切面类
@Aspect
public class LogUtil {
//用于定义通用的切入点表达式
@Pointcut(value = “execution(* com.example.service.impl..(…)) && args(user)”,argNames = “user”)
private void pointcut1(User user){
}
//用于配置当前方法是一个前置通知
@Before(value = “pointcut1(user)”,argNames = “user”)
public void printLog(User user){
System.out.println(“打印日志” + user);
}
}
LogUtil.java
4.4 用于配置通知的
4.4.1 @Before
4.4.1.1 作用
- 被此注解修饰的方法为前置通知。前置通知的执行时间点是在切入点方法执行之前。
4.4.1.2 属性
-
value:用于指定切入点表达式。可以是表达式,也可以是表达式的引用。
-
argNames:用于指定切入点表达式参数的名称。它要求和切入点表达式中的参数名称一致。通常不指定也可以获取切入点方法的参数内容。
4.4.2 @AfterReturning
4.4.2.1 作用
- 用于配置后置通知。后置通知的执行是在切入点方法正常执行之后执行。
需要注意的是,由于基于注解的配置时,spring创建通知方法的拦截器链时,后置
通知在最终通知之后,所以会先执行@After注解修饰的方法。
4.4.2.2 属性
-
value:用于指定切入点表达式,可以是表达式,也可以是表达式的引用。
-
pointcut:它的作用和value是一样的。
-
returning:指定切入点方法返回值的变量名称。它必须和切入点方法返回值名称一致。
-
argNames:用于指定切入点表达式参数的名称。它要求和切入点表达式中的参数名称一致。通常不指定也可以获取切入点方法的参数内容。
4.4.3 @AfterThrowing
4.4.3.1 作用
- 用于配置异常通知。
4.4.3.2 属性
-
value:用于指定切入点表达式,可以是表达式,也可以是表达式的引用。
-
pointcut:它的作用和value是一样的。
-
throwing:指定切入点方法执行产生异常时的异常对象变量名称。它必须和异常变量名称一致。
-
argNames:用于指定切入点表达式参数的名称。它要求和切入点表达式中的参数名称一致。通常不指定也可以获取切入点方法的参数内容。
4.4.4 @After
4.4.4.1 作用
- 用于指定最终通知。
4.4.4.2 属性
-
value:用于指定切入点表达式,可以是表达式,也可以是表达式的引用。
-
argNames:用于指定切入点表达式参数的名称。它要求和切入点表达式中的参数名称 一致。通常不指定也可以获取切入点方法的参数内容。
4.4.5 基本使用
@Component
//表明当前类是一个切面类
@Aspect
public class LogUtil {
//用于配置当前方法是一个前置通知
@Before(“execution(* com.example.service.impl..(…))”)
public void beforePrintLog(){
System.out.println(“前置通知打印日志”);
}
//用于配置当前方法是一个最终通知
@After(“execution(* com.example.service.impl..(…))”)
public void afterPrintLog(){
System.out.println(“最终通知打印日志”);
}
//用于配置当前方法是一个后置通知
@AfterReturning(“execution(* com.example.service.impl..(…))”)
public void afterReturningPrintLog(){
System.out.println(“后置通知日志”);
}
//用于配置当前方法是一个异常通知
@AfterThrowing(“execution(* com.example.service.impl..(…))”)
public void afterThrowingPrintLog(){
System.out.println(“异常通知打印日志”);
}
}
LogUtil.java
- 执行顺序
前置通知打印日志
保存用户User{id=1, username=‘zs’, password=‘123’, birthday=Sat Jul 24 16:02:21 CST 2021}
后置通知日志
最终通知打印日志
4.4.6 一个切面内相同类型通知的执行顺序
@Component
//表明当前类是一个切面类
@Aspect
public class LogUtil {
//用于配置当前方法是一个前置通知
@Before(“execution(* com.example.service.impl..(…))”)
public void before2PrintLog(){
System.out.println(“前置通知2打印日志”);
}
//用于配置当前方法是一个前置通知
@Before(“execution(* com.example.service.impl..(…))”)
public void before1PrintLog(){
System.out.println(“前置通知1打印日志”);
}
}
- 最终执行顺序
前置通知1打印日志
前置通知2打印日志
保存用户User{id=1, username=‘zs’, password=‘123’, birthday=Sat Jul 24 16:20:17 CST 2021}
-
一个切面内相同类型通知的执行顺序与声明顺序无关
-
一个切面内相同类型通知的执行顺序由方法名中每个字符的ASCII码顺序决定
-
个切面内相同类型通知的执行顺序不能用@Order进行控制
4.4.7 @Around
4.4.7.1 作用
- 用于指定环绕通知。
4.4.7.2 属性
-
value:用于指定切入点表达式,可以是表达式,也可以是表达式的引用。
-
argNames:用于指定切入点表达式参数的名称。它要求和切入点表达式中的参数名称一致。通常不指定也可以获取切入点方法的参数内容。
4.4.7.3 基本使用
public interface UserService {
@Description(“保存”)
void saveUser(User user);
@Description(“查找”)
User findById(String id);
@Description(“更新”)
void update(User user);
@Description(“删除”)
void delete(String id);
}
UserService.java
/**
- 系统日志的实体类
*/
public class SystemLog implements Serializable {
//主键
private String id;
//方法名称
private String method;
//方法说明
private String action;
//时间
private Date time;
//来访者名称
private String remoteIp;
@Override
public String toString() {
return “SystemLog{” +
“id=’” + id + ‘’’ +
“, method=’” + method + ‘’’ +
“, action=’” + action + ‘’’ +
“, time=” + time +
“, remoteIp=’” + remoteIp + ‘’’ +
‘}’;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public Date getTime() {
return time;
}
public void setTime(Date time) {
this.time = time;
}
public String getRemoteIp() {
return remoteIp;
}
public void setRemoteIp(String remoteIp) {
this.remoteIp = remoteIp;
}
}
SystemLog.java
@Component
//表明当前类是一个切面类
@Aspect
public class LogUtil {
/**
-
用于增强业务层方法,在其执行时记录系统日志
-
@return
*/
@Around(“execution(* com.example.service.impl..(…))”)
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue = null;
//创建系统日志对象
SystemLog log = new SystemLog();
try {
log.setId(UUID.randomUUID().toString());
log.setRemoteIp(“127.0.0.1”);
log.setTime(new Date());
//使用ProceedingJoinPoint中的获取签名方法
Signature signature = pjp.getSignature();
//判断当前的签名是否是方法签名
if(signature instanceof MethodSignature){
//把签名转换成方法签名
MethodSignature methodSignature = (MethodSignature) signature;
//获取当前执行的方法
Method method = methodSignature.getMethod();
log.setMethod(method.getName());
//判断当前方法上是否有@Description注解
boolean isAnnotated = method.isAnnotationPresent(Description.class);
if(isAnnotated){
//得到当前方法上的@Description
Description description = method.getAnnotation(Description.class);
//得到注解的value属性
String value = description.value();
log.setAction(value);
}
}
System.out.println(“环绕通知” + log);
Object[] args = pjp.getArgs();
//切入点方法执行
rtValue = pjp.proceed(args);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return rtValue;
}
}
LogUtil.java
4.5 用于扩展目标类的
4.5.1 @DeclareParents
4.5.1.1 作用
- 用于给被增强的类提供新的方法。(实现新的接口)
4.5.1.2 属性
-
value:指定目标类型的表达式。当在全限定类名后面跟上+时,表示当前类及其子类
-
defaultImpl:指定提供方法或者字段的默认实现类。
4.5.1.3 基本使用
@Component
//表明当前类是一个切面类
@Aspect
public class LogUtil {
//让目标类具备当前声明接口中的方法,动态代理
@DeclareParents(value = “com.example.service.UserService+”,defaultImpl = ValidateExtensionServiceImpl.class)
private ValidateExtensionService validateExtensionService;
//用于配置当前方法是一个前置通知
@Before(“execution(* com.example.service.impl..(…))”)
public void printLog(){
System.out.println(“打印日志”);
}
}
LogUtil.java
@Component
public class ValidateExtensionServiceImpl implements ValidateExtensionService {
@Override
public boolean checkUser(User user) {
//校验不通过
if(user.getUsername().contains(“zs”)){
return false;
}
return true;
}
}