Spring-AOP核心
前置-代理设计
我们先介绍相关的几种代理设计方法
1、静态代理
// 原始业务接口
public interface UserService {
void login(String username,String password);
}
// 业务实现类
public class UserServiceImpl implements UserService {
@Override
public void login(String username, String password) {
System.out.println("用户名:"+username+" 密码:"+password);
}
}
// 代理类
public class UserServiceStaticProxy implements UserService {
private UserService userService=new UserServiceImpl();
@Override
public void login(String username, String password) {
System.out.println("-----log-----");
userService.login(username,password);
}
}
2、JDK的动态代理
public class JDKProxy {
public static UserService getProxy(UserService userService) {
//获取原始类对象的类加载器
ClassLoader loader = userService.getClass().getClassLoader();
//被原始类对象的接口
Class<?>[] interfaces = userService.getClass().getInterfaces();
//方法执行器,执行原始类的目标方法
InvocationHandler handler = new InvocationHandler() {
/**
*
* @param proxy 代理对象,jdk使用的
* @param method 原始类要执行的目标方法
* @param args 方法调用传入的参数值
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("-----method before advice log------");
Object result = method.invoke(userService, args);
System.out.println("-----method after advice log------");
return result;
}
};
// Proxy.newProxyInstance() 通过目标类实现的接口创建代理类
Object proxyInstance = Proxy.newProxyInstance(loader, interfaces, handler);
return (UserService) proxyInstance;
}
}
3、CGlib的动态代理
//通过继承被代理类创建代理类
public class CGlibProxy {
public static UserService getProxy(UserService userService) {
// 创建Enhancer
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(userService.getClass().getClassLoader());
// 设置父类(目标类)
enhancer.setSuperclass(userService.getClass());
// 设置回调,执行原始类的目标方法
enhancer.setCallback(new MethodInterceptor() {
////相当于 InvocationHandler的invoke()方法
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("-----method before advice log------");
Object result = method.invoke(userService, args);
System.out.println("-----method after advice log------");
return result;
}
});
//通过Enhancer对象创建代理
Object proxyInstance = enhancer.create();
return (UserService) proxyInstance;
}
}
4、Spring动态代理
先配置一下依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
1)、实现MethodBeforeAdvice接口
public class Before implements MethodBeforeAdvice {
/**
* 作用:给主业务方法添加辅助功能,会在业务方法之前执行
*
* @param method 业务方法,比如:login()
* @param args 业务方法中的参数列表,比如:login()方法中的username和password
* @param target 业务类的对象,比如:UserServiceImpl
* @throws Throwable 抛出异常
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("-----method before advice log------");
}
}
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--创建业务类的对象和辅助功能类的对象-->
<bean id="userService" class="com.ty.service.UserServiceImpl"></bean>
<bean id="before" class="com.ty.proxy.Before"></bean>
<!--aop:config标签:配置aop动态代理
aop:pointcut标签:定义切入点,就是辅助功能的定义位置,expression="execution(* *(..))":所有方法都添加辅助功能
aop:advisor标签:将辅助功能类的对象和切入点组装在一起
-->
<aop:config>
<aop:pointcut id="mypoint" expression="execution(* *(..))"/>
<aop:advisor advice-ref="before" pointcut-ref="mypoint"></aop:advisor>
</aop:config>
</beans>
2)、实现MethodInterceptor接口
MethodBeforeAdvice和MethodInterceptor不同点就是:前者只能业务方法之前执行,而后者业务方法之前、之后、前后都能执行
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class Around implements MethodInterceptor {
/**
* @param invocation 封装了业务方法,invocation.proceed():表示执行的业务方法
* @return 业务方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("-----method before advice log------");
Object returnValue = invocation.proceed();
System.out.println("-----method after advice log------");
try {
int num = 6 / 0;
} catch (Exception e) {
e.printStackTrace();
System.out.println("-----method exception advice log------");
}
System.out.println("finally advice log--------------");
return returnValue;
}
}
下面我们完整写一个日志类:LogUtil.java
public class LogUtil {
//JoinPoint对象用来获取方法名和参数列表等信息
public static void before(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println(name + " 前置通知,参数是:" + Arrays.asList(args));
}
public void after(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println(name + " method after advice log 后置通知,参数是:" + Arrays.asList(args));
}
public void Exception(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
System.out.println(name + " method exception advice log 异常通知------");
}
public void Finally(JoinPoint joinPoint, Object result) {
String name = joinPoint.getSignature().getName();
System.out.println(name + " method afterReturn advice log 后置返回通知,方法执行完成");
}
}
<bean id="userService" class="com.ty.service.UserServiceImpl"></bean>
<bean id="logUtil" class="com.ty.util.LogUtil"></bean>
<bean id="logUtil2" class="com.ty.util.LogUtil2"></bean>
<aop:config>
<!--我们可以单独定义一个pointcut,下面使用的时候直接引用就好-->
<aop:pointcut id="myPoint" expression="execution(* com.ty.service.UserServiceImpl.*(..))"/>
<aop:aspect ref="logUtil" >
<aop:before method="before" pointcut-ref="myPoint"></aop:before>
<aop:after method="after" pointcut-ref="myPoint"></aop:after>
<aop:after-throwing method="Exception" pointcut-ref="myPoint"></aop:after-throwing>
<aop:after-returning method="afterReturn" pointcut-ref="myPoint"></aop:after-returning>
</aop:aspect>
</aop:config>
后置返回通知如果有结果值或者异常信息返回,可以在参数列表添加一个对应的参数
public void Exception(JoinPoint joinPoint,Throwable exception) {
String name = joinPoint.getSignature().getName();
System.out.println(name + " method exception advice log 异常通知------");
}
public void Finally(JoinPoint joinPoint, Object result) {
String name = joinPoint.getSignature().getName();
System.out.println(name + " method afterReturn advice log 后置返回通知,方法执行完成");
}
而对应的配置文件就变成这样
<!--标签中的throwing和returning属性的值与参数名相同-->
<aop:after-throwing method="Exception" pointcut-ref="myPoint" throwing="exception"></aop:after-throwing>
<aop:after-returning method="Finally" pointcut-ref="myPoint" returning="result"></aop:after-returning>
注解方式
@Component
//此注解=配置方式的<aop:config>标签
@Aspect
//此注解表示如果有多个辅助功能类,指定哪个先执行
@Order
public class LogUtil {
@Pointcut("execution(* com.ty.service.UserServiceImpl.*(..))")
public void myPoint() {
}
@Before(value = "myPoint()")
public static void before(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println(name + " method before advice log 前置通知,参数是:" + Arrays.asList(args));
}
@After(value = "myPoint()")
public void after(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println(name + " method after advice log 后置通知,参数是:" + Arrays.asList(args));
}
@AfterThrowing(value = "myPoint()", throwing = "exception")
public void Exception(JoinPoint joinPoint, Throwable exception) {
String name = joinPoint.getSignature().getName();
System.out.println(name + " method exception advice log 异常通知:" + exception);
}
@AfterReturning(value = "myPoint()", returning = "result")
public void Finally(JoinPoint joinPoint, Object result) {
String name = joinPoint.getSignature().getName();
System.out.println(name + " method afterReturn advice log 后置返回通知,方法执行完成");
}
@Around(value = "myPoint()")
public Object around(ProceedingJoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
Object result = null;
try {
System.out.println("环绕前置通知:" + name + "方法开始,参数是" + Arrays.asList(args));
//利用反射调用目标方法,相当于method.invoke()
result = joinPoint.proceed(args);
System.out.println("环绕返回通知:" + name + "方法返回,返回值是" + result);
} catch (Throwable throwable) {
System.out.println("环绕异常通知" + name + "方法出现异常,异常信息是:" + throwable);
} finally {
System.out.println("环绕后置通知" + name + 方法结束");
}
return result;
}
配置文件
<context:component-scan base-package="com.ty"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
5、Spring工厂返回代理对象
public class ProxyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("-----method before advice log------");
return method.invoke(bean, args);
}
};
return Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), handler);
}
}
<bean id="userService" class="com.ty.service.UserServiceImpl"></bean>
<bean id="postProcessor" class="com.ty.proxy.ProxyBeanPostProcessor"></bean>
这就是运用了postProcessAfterInitialization后置处理器方法,创建的是userService对象,但是经过一步步的处理到后置处理器这里进行加工,看到创建了代理对象,所以最后就返回了一个代理对象
AOP
AOP的概念
前面我们写过Spring动态代理的代码,我们没具体介绍,Spring动态代理对应的就可以看成AOP,AOP是一种编程思想模型,
对应的就是学过的OOP
AOP:Aspect Oriented Programming 面向切面编程
OOP:Object Oriented Programming 面向对象编程
面向切面编程:基于OOP基础之上新的编程思想,OOP面向的主要对象是类,而AOP面向的主要对象是切面,在处理日志、安全管理、
事务管理等方面有非常重要的作用。AOP是Spring中重要的核心点,虽然IOC容器没有依赖AOP,但是AOP提供了非常强大的功能,用来
对IOC做补充。通俗点说的话就是在程序运行期间,将某段代码动态切入到指定方法的指定位置进行运行的这种编程方式。
AOP的核心概念及术语
简单的AOP代码就是上面的Spring动态代理代码,就不重复写了,我们介绍几个专业术语,先把上面的代码用图表示
切面(Aspect): 指关注点模块化,这个关注点可能会横切多个对象。事务管理是企业级Java应用中有关横切关注点的例子。 在Spring AOP中,切面可以使用通用类基于模式的方式(schema-based approach)或者在普通类中以
@Aspect
注解(@AspectJ 注解方式)来实现。连接点(Join point): 在程序执行过程中某个特定的点,例如某个方法调用的时间点或者处理异常的时间点。在Spring AOP中,一个连接点总是代表一个方法的执行。
通知(Advice): 在切面的某个特定的连接点上执行的动作。通知有多种类型,包括“around”, “before” and “after”等等。通知的类型将在后面的章节进行讨论。 许多AOP框架,包括Spring在内,都是以拦截器做通知模型的,并维护着一个以连接点为中心的拦截器链。
切点(Pointcut): 匹配连接点的断言。通知和切点表达式相关联,并在满足这个切点的连接点上运行(例如,当执行某个特定名称的方法时)。切点表达式如何和连接点匹配是AOP的核心:Spring默认使用AspectJ切点语义。
引入(Introduction): 声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被通知的对象上。例如,可以使用引入来使bean实现
IsModified
接口, 以便简化缓存机制(在AspectJ社区,引入也被称为内部类型声明(inter))。目标对象(Target object): 被一个或者多个切面所通知的对象。也被称作被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,那么这个对象永远是一个被代理(proxied)的对象。
AOP代理(AOP proxy):AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。
织入(Weaving): 把切面连接到其它的应用程序类型或者对象上,并创建一个被被通知的对象的过程。这个过程可以在编译时(例如使用AspectJ编译器)、类加载时或运行时中完成。 Spring和其他纯Java AOP框架一样,是在运行时完成织入的。
切入点详解
之前写过的* *(..) 代表所有方法,分别表示:修饰符 返回值类型 方法全限定名对应的参数列表类型
上面的切入点表达式匹配粒度太粗,还有相对精准的表达方法
1)、类切入点表达式
<!--类的所有方法都添加辅助功能,
*表示任意修饰符和返回值类型
..表示匹配任意类型参数-->
<aop:pointcut id="mypoint" expression="execution(* com.ty.service.UserServiceImpl.*(..))"/>
<!--添加访问修饰符-->
<aop:pointcut id="mypoint" expression="execution(public * com.ty.service.UserServiceImpl.login(String,String))"/>
<!--类的login方法都添加辅助功能-->
<aop:pointcut id="mypoint" expression="execution(* com.ty.service.UserServiceImpl.login(..))"/>
或者:
<aop:pointcut id="mypoint" expression="execution(* com.ty.service.UserServiceImpl.login(String,String))"/>
或者:这里的参数*也是代表所有参数类型
<aop:pointcut id="mypoint" expression="execution(* com.ty.service.UserServiceImpl.login(String,*))"/>
<!--详细版本-->
<aop:pointcut id="mypoint" expression="execution(public void com.ty.service.UserServiceImpl.login(String,String))"/>
2)、包切入点表达式
<!-- com.ty.service包下类的所有login方法都添加辅助功能
注意:只能匹配一层路径,如果项目路径下有多层目录,那么*只能匹配一层路径
-->
<aop:pointcut id="mypoint" expression="execution(* com.ty.service.*.login(..))"/>
<!-- 匹配多级包-->
<aop:pointcut id="mypoint" expression="execution(* com.ty..*.login(..))"/>
切入点函数
<aop:pointcut id="mypoint" expression="execution(* com.ty..*.login(..))"/>
<!-- args()用于匹配方法参数,比如
execution(* *(String,String) = args(String,String))
-->
<aop:pointcut id="mypoint" expression="args(String,String))"/>
<!-- within():用于类和包的切入点
expression="execution(* *..UserServiceImpl.*(..)) = within(*..UserServiceImpl)"
execution(* com.ty..*.*(..)) = within(com.ty..*)
-->
<aop:pointcut id="mypoint" expression="within(*..UserServiceImpl)"/>
<aop:pointcut id="mypoint" expression="within(com.ty..*)"/>
<!-- @annotation():为具有特殊注解的方法加入额外功能 -->
<aop:pointcut id="mypoint" expression="@annotation(com.ty.proxy.Log)"/>
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
}
public class UserServiceImpl implements UserService {
@Log()
@Override
public void login(String username, String password) {
System.out.println("登录用户!\t用户名:" + username + " 密码:" + password);
}
切入点函数间的逻辑表示
<!--and或者&& :同时满足表达式-->
<aop:pointcut id="mypoint" expression="execution(* login(..)) and args(String,String)"/>
<!--or或者|| :满足任意表达式即可-->
<aop:pointcut id="mypoint" expression="execution(public void register()) or execution(* *.login(..))"/>
或者:
<aop:pointcut id="mypoint" expression="execution(public void register()) || execution(* *.login(..))"/>
<!--!:只要不是这个位置即可-->
<aop:pointcut id="mypoint" expression="! execution(* login(..))"/>
AOP的通知类型
前置通知(Before advice): 在连接点之前运行但无法阻止执行流程进入连接点的通知(除非它引发异常)。
后置返回通知(After returning advice):在连接点正常完成后执行的通知(例如,当方法没有抛出任何异常并正常返回时)。
后置异常通知(After throwing advice): 在方法抛出异常退出时执行的通知。
后置通知(总会执行)(After (finally) advice): 当连接点退出的时候执行的通知(无论是正常返回还是异常退出)。
环绕通知(Around Advice):环绕连接点的通知,例如方法调用。这是最强大的一种通知类型,。环绕通知可以在方法调用前后完成自定义的行为。它可以选择是否继续执行连接点或直接返回自定义的返回值又或抛出异常将执行结束。
通知方法的执行顺序
之前的代码也没说过通知的执行顺序,我们集体说一下
1、正常执行:@Before--->@After--->@AfterReturning
2、异常执行:@Before--->@After--->@AfterThrowing
3、环绕通知:环绕前置-->普通前置-->目标方法执行-->环绕正常结束/出现异常-->环绕后置-->普通后置-->普通返回或异常。
AOP的应用场景
- 日志管理
- 权限认证
- 安全检查
- 事务控制