提示:以下是本篇文章正文内容,下面案例可供参考
一、AOP 简介
AOP,Aspect Oriented Programming,面向切面编程,是对面向对象编程OOP的升华。OOP是纵向对一个 事物的抽象,一个对象包括静态的属性信息,包括动态的方法信息等。而AOP是横向的对不同事物的抽象,属 性与属性、方法与方法、对象与对象都可以组成一个切面,而用这种思维去设计编程的方式叫做面向切面编程
二、基于XML
1、快速构建
1)导入AOP相关坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springDemo06-xmlAOP</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
</project>
2)准备目标类、通知类
//通知类
public class MyAdvice {
public void beforeAdvice() {
System.out.println("通知方法执行....");
}
}
//目标类
public class UserServiceImpl implements UserService{
public void save() {
System.out.println("save方法执行了....");
}
}
3)配置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"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<bean id="userService" class="com.jh.service.UserServiceImpl"/>
<bean id="myAdvice" class="com.jh.advice.MyAdvice"/>
<!-- AOP 配置 -->
<aop:config>
<!--配置切面表达式,目的是指定哪些方法被增强-->
<aop:pointcut id="myPointcut" expression="execution(* com.jh.service.*.*( ))"/>
<!--切面=切点+通知-->
<aop:aspect ref="myAdvice">
<!--指定通知方法是beforeAdvice-->
<aop:before method="beforeAdvice" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
</beans>
2、切点表达式
1)配置方式
第一种在快速构建中已经体现,使用<aop:pointcut>配置切点
第二种将切点直接在通知中配置,例如:
<!-- AOP 配置 -->
<aop:config>
<!--切面=切点+通知-->
<aop:aspect ref="myAdvice">
<!--指定前置通知方法是beforeAdvice-->
<!--切点直接配置-->
<aop:before method="beforeAdvice" pointcut="execution(* com.jh.service.*.*( ))"/>
<!--指定后置通知方法是afterAdvice-->
<aop:after-returning method="afterAdvice" pointcut="execution(* com.jh.service.*.*( ))"/>
</aop:aspect>
</aop:config>
2)切点表达式语法
execution( [访问修饰符] 返回值类型 包名.类名.方法名(参数) )
⚫ 访问修饰符可以省略不写;
⚫ 返回值类型、某一级包名、类名、方法名 可以使用 * 表示任意;
⚫ 包名与类名之间使用单点 . 表示该包下的类,使用双点 .. 表示该包及其子包下的类;
⚫ 参数列表可以使用两个点 .. 表示任意参数。
例:
//表示访问修饰符为public、无返回值、在com.jh.aop包下的User类的无参方法show execution(public void com.jh.aop.User.show())
//表述com.jh.aop包下的User类的任意方法
execution(* com.jh.aop.User.*(..))
//表示com.jh.aop包下的任意类的任意方法
execution(* com.jh.aop.*.*(..))
//表示com.jh.aop包及其子包下的任意类的任意方法
execution(* com.jh.aop..*.*(..))
//表示任意包中的任意类的任意方法
execution(* *..*.*(..))
3、通知类型
1)AspectJ通知五种类型
通知类型 | 配置标签 | 执行方式 |
前置通知 | < aop:before > | 在目标方法执行之前执行 |
后置通知 | < aop:after-returning > | 在目标方法执行之后执行,目标方法异常时,不会执行 |
环绕通知 | < aop:around > | 在目标方法执行的前后执行,目标方法异常时,环绕后方法不会执行 |
异常通知 | < aop:after-throwing > | 只会在目标方法抛出异常时执行 |
最终通知 | < aop:after > | 不管目标方法是否有异常,最终都会执行 |
2.)代码场景
<!-- AOP 配置 -->
<aop:config>
<!--配置切面表达式,目的是指定哪些方法被增强-->
<aop:pointcut id="myPointcut" expression="execution(* com.jh.service.*.*( ))"/>
<!--切面通知-->
<aop:aspect ref="myAdvice">
<!--指定前置通知方法是before-->
<aop:before method="before" pointcut-ref="myPointcut"/>
<!--指定环绕通知方法是around-->
<aop:around method="around" pointcut-ref="myPointcut"/>
<!--指定后置通知方法是afterReturning-->
<aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
<!--指定异常通知方法是afterThrowing-->
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>
<!--指定最终通知方法是after-->
<aop:after method="after" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
public class MyAdvice {
/**
* 前置通知
*/
public void before() {
System.out.println("前置通知....");
}
/**
* 环绕通知
* @param joinPoint
* @throws Throwable
*/
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
//环绕前
System.out.println("环绕前通知");
//目标方法
joinPoint.proceed();
///环绕后
System.out.println("环绕后通知");
}
/**
* 后置通知
*/
public void afterReturning() {
System.out.println("后置通知...");
}
/**
* 异常通知
*/
public void afterThrowing(){
System.out.println("目标方法抛出异常了,后置通知和环绕后通知不在执行");
}
/**
* 最终通知
*/
public void after(){
System.out.println("不管目标方法有无异常,我都会执行");
}
}
4、通知方法参数
参数对象 | 作用 |
JoinPoint | 任何通知都可使用,可以获得当前目标对象、目标方法参数等信息 |
ProceedingJoinPoint | JoinPoint子类对象,主要是在环绕通知中执行proceed(),进而执行目标方法 |
Throwable | 异常对象,使用在异常通知中,需要在配置文件中指出异常对象名称 |
1)JoinPoint对象
此处以后置通知为例对save方法增强:
public class App {
public static void main(String[] args) {
//加载配置文件
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//加载bean
UserService userService = (UserService) applicationContext.getBean("userService");
userService.save("success");
}
}
public class UserServiceImpl implements UserService{
public String save(String name) {
return name;
}
}
/**
* 后置通知
*/
public void afterReturning(JoinPoint joinPoint) {
//获得目标方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("参数:"+args[0]);
//获得目标对象
System.out.println("目标对象"+joinPoint.getTarget());
//获得精确的切点表达式信息
System.out.println("切点表达式信息"+joinPoint.getStaticPart());
System.out.println("后置通知执行....");
}
控制台:
2)ProceedingJoinPoint对象
与JoinPoint差异不大,主要在环绕通知中使用
/**
* 环绕通知
*
* @param joinPoint
* @throws Throwable
*/
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println(joinPoint.getArgs());//获得目标方法的参数
System.out.println(joinPoint.getTarget());//获得目标对象
System.out.println(joinPoint.getStaticPart());//获得精确的切点表达式信息
Object result = joinPoint.proceed();//执行目标方法
return result;//返回目标方法返回值
}
3)Throwable对象
注意:throwing="throwable"不要忘了配置
<!-- AOP 配置 -->
<aop:config>
<!--配置切面表达式,目的是指定哪些方法被增强-->
<aop:pointcut id="myPointcut" expression="execution(* com.jh.service.*.*(*))"/>
<!--切面=切点+通知-->
<aop:aspect ref="myAdvice">
<!--异常通知方法,如果有入参Throwable对象需要配置-->
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" throwing="throwable"/>
</aop:aspect>
</aop:config>
public class UserServiceImpl implements UserService{
public String save(String name) {
//此处会产生运算异常
int i = 1/0;
return name;
}
}
/**
* 异常通知
*/
public void afterThrowing(JoinPoint joinPoint,Throwable throwable) {
//获得异常信息
System.out.println("异常对象是:"+throwable+"异常信息是:"+throwable.getMessage());
System.out.println("目标方法抛出异常了,后置通知和环绕后通知不在执行");
}
控制台
5、扩展点Advisor
AOP的另一种配置方式,该方式需要通知类实现Advice的子功能接口
package org.aopalliance.aop;
/**
* Tag interface for Advice. Implementations can be any type
* of advice, such as Interceptors.
*
* @author Rod Johnson
* @version $Id: Advice.java,v 1.1 2004/03/19 17:02:16 johnsonr Exp $
*/
public interface Advice {
}
1)Advice接口继承关系
2)代码场景
可以看到在xml中没有像AspectJ通知那样标明各个通知类型,只配置了切点,这是因为通知类实现了Advice子接口,Spring框架自动能够识别
<bean id="myAdvice2" class="com.jh.advice.MyAdvice2"/>
<aop:config>
<!--配置切面表达式,目的是指定哪些方法被增强-->
<aop:pointcut id="myPointcut" expression="execution(* com.jh.service.*.*(*))"/>
<aop:advisor advice-ref="myAdvice2" pointcut-ref="myPointcut"/>
</aop:config>
/**
* 实现Advice子接口的自定义通知类
*/
public class MyAdvice2 implements MethodBeforeAdvice, AfterReturningAdvice , MethodInterceptor {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("相当于前置通知....");
}
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("相当于前置通知....");
}
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("相当于环绕前....");
//目标方法
//invocation.getThis() 目标方法对象
//invocation.getArguments() 目标方法参数
Object invoke = invocation.getMethod().invoke(invocation.getThis(), invocation.getArguments());
System.out.println("相当于环绕=后....");
return null;
}
}
3)与AspectJ的差异
⚫ 配置语法不同
AspectJ通知需要标明通知类型,Advisor不需要,因为实现了Advice子接口
⚫ 通知类的定义要求不同
Advisor需要的通知类需要实现Advice的子功能接口,aspect 不需要通知类实现任何接口,在配置的时候指定哪些方法属于哪种通知类型即可
⚫ 配置的切面数量不同
一个Advisor只能配置一个固定通知和一个切点表达式,一个aspect可以配置多个通知和多个切点表达式任意组合
⚫ 使用场景
- 如果通知类型多、允许随意搭配情况下可以使用aspect进行配置
- 如果通知类型单一、且通知类中通知方法一次性都会使用到的情况下可以使用advisor进行配置
- 在通知类型已经固定,不用人为指定通知类型时,可以使用advisor进行配置,例如Spring事务控制的配置
- 实际开发中,自定义aop功能的配置大多使用aspect的配置方式
6、xml方式AOP原理剖析
xml方式配置AOP时,由于引入了AOP的命名空间,我们先去找aop标签解析器,在spring-aop包下的META-INF文件夹下找spring.handlers文件
spring.handlers中配置的是:
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
打开AopNamespaceHandler类:
public class AopNamespaceHandler extends NamespaceHandlerSupport {
/**
* Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the
* '{@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}'
* and '{@code scoped-proxy}' tags.
*/
@Override
public void init() {
// In 2.0 XSD as well as in 2.5+ XSDs
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
// Only in 2.0 XSD: moved to context namespace in 2.5+
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
}
Handler的init方法中注册了config标签对应的解析器
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
从类ConfigBeanDefinitionParser中进去主要看parse方法
在进行源码剖析后,发现最终会注册一个AspectJAwareAdvisorAutoProxyCreator 进入到Spring容器中
AspectJAwareAdvisorAutoProxyCreator类的继承关系:
AspectJAwareAdvisorAutoProxyCreator其实就是BeanPostProcessor(bean后处理器)的实现类
AspectJAwareAdvisorAutoProxyCreator 的上上级父类AbstractAutoProxyCreator中的 postProcessAfterInitialization方法中(其实就是BeanPostProcessor的实现方法),进行判断是否需要增强,需要的话就返回一个Proxy对象
//参数bean:为目标对象
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
//如果需要被增强,则wrapIfNecessary方法最终返回的就是一个Proxy对象
return this.wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
查看wrapIfNecessary方法源码找到最终通过proxyFactory.getProxy(getProxyClassLoader())
方法获取一个代理对象,代理对象的产生方式就是下图中的两种
AopProxy接口有两个实现类,如上图,这两种 都是动态生成代理对象的方式,一种就是基于JDK的,一种是基于Cglib的
三、基于注解
1、注解方式AOP
1)启用AspectJ自动代理
Spring的AOP提供了注解方式配置,使用相应的注解替代之前的xml配置
注意:使用注解方式配置AOP,Spring核心配置文件中需要配置aspectj的自动代理
<aop:aspectj-autoproxy/>
如果使用配置类的话,使用注解@EnableAspectJAutoProxy配置aspectj的自动代理
@Configuration
@ComponentScan("com.jh")
@EnableAspectJAutoProxy //第三步
public class ApplicationContextConfig {
}
2)代码场景
通知类:
@Component
@Aspect //此注解表示该类是通知类
public class MyAdvice {
//切点表达式抽取
@Pointcut("execution(* com.jh.*.*.*(..))")
public void pointcut(){}
//前置通知
@Before("pointcut()")
public void before(JoinPoint joinPoint){}
}
五种注解方式通知类型:
@Component
@Aspect //此注解表示该类是通知类
public class MyAdvice {
//切点表达式抽取
//@Pointcut("execution(* com.jh.*.*.*(..))")
//public void pointcut(){}
//切点表达式直接配置在通知方法中
//前置通知
@Before("execution(* com.jh.*.*.*(..))")
public void before(JoinPoint joinPoint){}
//后置通知
@AfterReturning("execution(* com.jh.*.*.*(..))")
public void AfterReturning(JoinPoint joinPoint){}
//环绕通知
@Around("execution(* com.jh.*.*.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {}
//异常通知
@AfterThrowing("execution(* com.jh.*.*.*(..))")
public void AfterThrowing(JoinPoint joinPoint){}
//最终通知
@After("execution(* com.jh.*.*.*(..))")
public void After(JoinPoint joinPoint){}
}
2、注解方式AOP原理剖析
通过注解@EnableAspectJAutoProxy追踪源码
@Configuration
@ComponentScan("com.jh")
@EnableAspectJAutoProxy
public class SpringConfig {
}
@Import(AspectJAutoProxyRegistrar.class),Import注解导入了解析类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
/**
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
* to standard Java interface-based proxies. The default is {@code false}.
*/
boolean proxyTargetClass() default false;
/**
* Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
* for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
* Off by default, i.e. no guarantees that {@code AopContext} access will work.
* @since 4.3.1
*/
boolean exposeProxy() default false;
}
注册AnnotationAwareAspectJAutoProxyCreator类到Spring容器
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
/**
* Register, escalate, and configure the AspectJ auto proxy creator based on the value
* of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
* {@code @Configuration} class.
*/
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//去注册AnnotationAwareAspectJAutoProxyCreator类到Spring容器
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
}
查看AnnotationAwareAspectJAutoProxyCreator类的继承关系可知,它也是
BeanPostProcessor(bean后处理器)的实现类,那么基本上就与xml方式的底层逻辑大同小异了
四、两种动态代理方式
方式 | 使用条件 |
JDK动态代理 | 目标类有接口,是基于接口动态生成实现类的代理对象 |
CGLib动态代理 | 目标类无接口且不能使用final修饰,是基于被代理对象动态生成子对象为代理对象 |
1、JDK动态代理
//todo
2、CGLib动态代理
1)编写目标对象
/**
* 通知类
*/
public class MyAdvice {
public void before(){
System.out.println("前置方法....");
}
public void after(){
System.out.println("后置方法....");
}
}
/**
* 目标类
*/
public class UserServiceImpl {
public void show(){
System.out.println("show方法执行....");
}
}
2)编写Cglib代码
public class MyCglibProxy {
public static void main(String[] args) {
//增强器对象
Enhancer enhancer = new Enhancer();
//设置增强器
//设置父类
enhancer.setSuperclass(UserServiceImpl.class);
//增强器设置回调
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, java.lang.reflect.Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//目标对象
UserServiceImpl userService = new UserServiceImpl();
//通知对象
MyAdvice myAdvice = new MyAdvice();
//增强方法
myAdvice.before();
Object result = method.invoke(userService, objects);
myAdvice.after();
return result;
}
});
//创建代理对象
UserServiceImpl targetProxy = (UserServiceImpl) enhancer.create();
//测试
targetProxy.show();
}
}
End