spring深入学习(四)-----spring aop

时间:2023-12-11 08:55:44

AOP概述

aop其实就是面向切面编程,举个例子,比如项目中有n个方法是对外提供http服务的,那么如果我需要对这些http服务进行响应时间的监控,按照传统的方式就是每个方法中添加相应的逻辑,但是这些逻辑是重复的,我无非是需要记录请求的时间以及响应时间,另外可能需要加上请求入参以及响应出参。这时候就可以把这些http服务看成切面,通过aop的方式在方法前和方法后去做点什么操作。

aop的实现者有很多,包括AspectJ、Spring AOP等等,当然我们重点就放在Spring aop上了。

在spring中,aop可以由jdk动态代理或cglib实现,这地方的示例可以看下本人之前的文章了动态代理

jdk动态代理跟cglib有个重要的区别就是jdk动态代理的类必须要实现了某个接口,但是cglib则没这个要求。

针对cglib,举例如下:

spring深入学习(四)-----spring aop

spring深入学习(四)-----spring aop

spring aop简单示例

业务代码

@Component("knight")
public class BraveKnight {
public void saying(){
System.out.println("我是骑士..(切点方法)");
}
}
package com.cjh.aop2;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component; /**
* 注解方式声明aop
* 1.用@Aspect注解将类声明为切面(如果用@Component("")注解注释为一个bean对象,那么就要在spring配置文件中开启注解扫描,<context:component-scan base-package="com.cjh.aop2"/>
* 否则要在spring配置文件中声明一个bean对象)
* 2.在切面需要实现相应方法的前面加上相应的注释,也就是通知类型。
* 3.此处有环绕通知,环绕通知方法一定要有ProceedingJoinPoint类型的参数传入,然后执行对应的proceed()方法,环绕才能实现。
*/
@Component("annotationTest")
@Aspect
public class AnnotationTest {
//定义切点
@Pointcut("execution(* *.saying(..))")
public void sayings(){}
/**
* 前置通知(注解中的sayings()方法,其实就是上面定义pointcut切点注解所修饰的方法名,那只是个代理对象,不需要写具体方法,
* 相当于xml声明切面的id名,如下,相当于id="embark",用于供其他通知类型引用)
* <aop:config>
<aop:aspect ref="mistrel">
<!-- 定义切点 -->
<aop:pointcut expression="execution(* *.saying(..))" id="embark"/>
<!-- 声明前置通知 (在切点方法被执行前调用) -->
<aop:before method="beforSay" pointcut-ref="embark"/>
<!-- 声明后置通知 (在切点方法被执行后调用) -->
<aop:after method="afterSay" pointcut-ref="embark"/>
</aop:aspect>
</aop:config>
*/
@Before("sayings()")
public void sayHello(){
System.out.println("注解类型前置通知");
}
//后置通知
@After("sayings()")
public void sayGoodbey(){
System.out.println("注解类型后置通知");
}
//环绕通知。注意要有ProceedingJoinPoint参数传入。
@Around("sayings()")
public void sayAround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("注解类型环绕通知..环绕前");
pjp.proceed();//执行方法
System.out.println("注解类型环绕通知..环绕后");
}
}
<?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: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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.cjh.aop2"/>
<!-- 开启aop注解方式,此步骤不能少,这样java类中的aop注解才会生效 -->
<aop:aspectj-autoproxy/>
</beans>

测试:

package com.cjh.aop2;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("com/cjh/aop2/beans.xml");
BraveKnight br = (BraveKnight) ac.getBean("knight");
br.saying();
}
}

自动创建代理

1、BeanNameAutoProxyCreator

通过名字就可以看出来是干嘛的,可以根据bean的名字来创建自动代理,示例如下:

业务类:

public interface UserService {
void print();
}
public class UserServiceImpl implements UserService {
public void print(){
System.out.println(getClass()+"#print");
}
}

拦截器:

public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println(getClass()+"调用方法前");
Object ret=invocation.proceed();
System.out.println(getClass()+"调用方法后");
return ret;
}
}

配置类:

@Configuration
public class AppConfig {
//要创建代理的目标Bean
@Bean
public UserService userService(){
return new UserServiceImpl();
}
//创建Advice或Advisor
@Bean
public Advice myMethodInterceptor(){
return new MyMethodInterceptor();
}
//使用BeanNameAutoProxyCreator来创建代理
@Bean
public BeanNameAutoProxyCreator beanNameAutoProxyCreator(){
BeanNameAutoProxyCreator beanNameAutoProxyCreator=new BeanNameAutoProxyCreator();
//设置要创建代理的那些Bean的名字
beanNameAutoProxyCreator.setBeanNames("userSer*");
//设置拦截链名字(这些拦截器是有先后顺序的)
beanNameAutoProxyCreator.setInterceptorNames("myMethodInterceptor");
return beanNameAutoProxyCreator;
}
}

测试:

public class Main {
public static void main(String[] args) {
ApplicationContext applicationContext=new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService= applicationContext.getBean(UserService.class);
userService.print();
}
}

自定义注解+BeanNameAutoProxyCreator

基于上面说的,对一些http接口做个响应时间监控,先以一个接口为例吧!

注解(PerformanceMonitor):

package com.ty.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; //代表此注解是运行过程中生效
@Retention(RetentionPolicy.RUNTIME)
//此注解作用于方法上
@Target(ElementType.METHOD)
public @interface PerformanceMonitor {
String value() default "";
}

AnnotationController:

package com.ty.controller;

import com.ty.annotation.PerformanceMonitor;
import org.springframework.stereotype.Controller; @Controller
public class AnnotationController { @PerformanceMonitor
public String testServer() {
System.out.println("do someThing");
return "test";
}
}

BeanNameAutoProxyConfig:

package com.ty.configuration;

import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class BeanNameAutoProxyConfig { @Bean
public BeanNameAutoProxyCreator create() {
BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
//为所有的controller创建自动动态代理
beanNameAutoProxyCreator.setBeanNames("*Controller");
//代理的具体的业务逻辑交给bean-----performanceInterceptor
beanNameAutoProxyCreator.setInterceptorNames("performanceInterceptor");
return beanNameAutoProxyCreator;
}
}

PerformanceInterceptor:

package com.ty.interceptor;
import com.ty.annotation.PerformanceMonitor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.stereotype.Service; import java.lang.reflect.Method; @Service
public class PerformanceInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object result = null;
//判断被代理类的方法上是否使用了@PerformanceMonitor注解
PerformanceMonitor performanceMonitor = methodInvocation.getMethod().getAnnotation(PerformanceMonitor.class);
if(performanceMonitor != null) {
//具体的代理逻辑,实现aop的效果
System.out.println("----------before----" + methodInvocation.getMethod().getName());
result = methodInvocation.proceed();
System.out.println("----------after----" + methodInvocation.getMethod().getName() + ";该笔调用耗时:" + (System.currentTimeMillis() - t1) + "ms");
        }else {
//不使用该注解,则走原方法的调用逻辑
result = methodInvocation.proceed();
} return result;
}
}

applicationContext.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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--自动扫描包配置-->
<context:component-scan base-package="com.ty"/> <bean id="student" class="com.ty.beans.Student">
<!-- property代表的是set方法注入-->
<property name="age" value="27"></property>
<property name="name" value="马云"></property>
</bean> <bean id="school" class="com.ty.beans.School">
<!-- constructor代表的是构造器注入-->
<constructor-arg ref="student"></constructor-arg>
</bean>
</beans>

测试类:

package com.ty.beans;

import com.ty.controller.AnnotationController;
import javafx.application.Application;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @ContextConfiguration(locations = { "classpath:applicationContext.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public class IOCTest { @Test
public void testAnnotation() {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
AnnotationController annotationController = (AnnotationController) context.getBean("annotationController");
annotationController.testServer();
}
}

运行结果:

----------before----testServer
do someThing
----------after----testServer;该笔调用耗时:8ms

这样也就实现了性能监控的目的,并且以后项目中所有的controller的接口,只要需要这个功能,加个注解即可。

@AspectJ

基于@AspectJ实现aop是相对简单的方式,上面也有类似的案例,下面详细的说说这个玩意。

aop主要包括切面切入点增强方法等核心组成部分。

1、切面

//用来声明这是一个AspectJ
@Aspect
//在此处声明一个Component 是因为Spring扫描注解并不能识别AspectJ 因此在此处声明,不必在applicationContext.xml配置bean标签了
@Component
public class ServiceLog {
。。。
}

2、切入点

@Pointcut("execution(* com.sample.service.impl..*.*(..))")
public void pc() {}

a、execution()是最常用的切点函数,整个表达式可以分为五个部分,其语法如下所示:

  • execution(): 表达式主体。
  • 第一个*号:表示返回类型,*号表示所有的类型。
  • 包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
  • 第二个*号:表示类名,*号表示所有的类。
  • *(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

b、@annotation

另外切入点中的execution()也可以用@Annotation代替,如下:

@AfterReturning("@annotation(com.ty.annotation.PerformanceMonitor)")
public void needTestFun() {
System.out.println("可以对注明@PerformanceMonitor的方法进行增强");
}

对于所有注明@PerformanceMonitor的方法进行增强。

或者还有一种用法,可以方便拿到注解上的信息

@Around(value = "@annotation(apiOperation)")
public Object logApiCallInfo(ProceedingJoinPoint joinPoint, ApiOperation apiOperation) {
//TODO 1、这里方便拿到注解的信息。不过注意@annotation(apiOperation)名称要与参数名称一致
String apiCode = apiOperation.value(); //TODO 调用业务接口
result = joinPoint.proceed(); //TODO 获取接口请求参数,并拼装成json,格式化输出
Object[] args = joinPoint.getArgs();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] parameterNames = methodSignature.getParameterNames();
JSONObject jsonObject = new JSONObject();
if(parameterNames != null && parameterNames.length > 0 && args != null && args.length > 0) {
for(int i = 0; i < parameterNames.length; i++) {
jsonObject.put(parameterNames[i], args[i]);
}
//标准化输出
jsonObject.toJsonString();
}
}

3、增强方法

//环绕通知注解,pc()则是上面的pointcut切入点
@Around("pc()")
//环绕通知会多ProceedingJoinPoint这个参数
public Object log(ProceedingJoinPoint pjp) throws Throwable {
。。。
}
  • @Before
  • @AfterReturning
  • @Around
  • @AfterThrowing
  • @After

4、applicationContext.xml

<aop:aspectj-autoproxy/>