aop术语:
1、连接点(Joinpoint): 需要增强的具体位置比如某一个方法调用前,调用后,异常后
2、切点(pointcut): 用于定位连接点。
3、增强(advice):是植入连接点的一段代码
4、目标对象(target):连接点所在的类的实例
5、引介(introduction):可以为类添加属性和方法
6、织入(weaving):将增强添加到具体的目标对象的连接点上。(编译器、类装载期、运行期)
7、代理(Proxy):被增强后的新对象,这个对象融合了原类和增强逻辑的代理类。
8、切面(Aspect): 由切点和增强组成
代理基础:
1、jdk只支持有接口的代理,这个是jdk动态代理的短板。
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author liangjunhui * @date Created in 2020-08-19 9:37 */ public class JDKProxy<T> implements InvocationHandler { private T userService; /** * * @param proxy 代理后的对象 * @param method 连接点对应的方法 * @param args 方法参数 * @return 方法返回结果 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("方法前增强"); method.invoke(userService,args); System.out.println("方法后增强"); return null; } public T getProxy(T userService){ this.userService = userService; return (T)Proxy.newProxyInstance(userService.getClass().getClassLoader(),userService.getClass().getInterfaces(),this); } public static void main(String[] args) { JDKProxy<UserServiceImpl> jdkProxy = new JDKProxy(); UserServiceImpl userService = new UserServiceImpl(); UserService proxy = jdkProxy.getProxy(userService); proxy.test("sdfds"); } }
2、cglib代理,可以支持对无接口对象的代理
import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * @author liangjunhui * @date Created in 2020-08-19 9:53 */ public class CglibProxy<T> implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public T getUserService(Class<T> cls){ enhancer.setSuperclass(cls); enhancer.setCallback(this); return (T)enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("方法前增强"); methodProxy.invokeSuper(o,objects); System.out.println("方法后增强"); return null; } public static void main(String[] args) { CglibProxy<UserServiceImpl> cglibProxy = new CglibProxy(); UserServiceImpl userService = cglibProxy.getUserService(UserServiceImpl.class); userService.test("sed"); } }
增强:
/** * 注意这些增强只是植入了方法的执行位置,这个植入是无差别的,全部的方法都会植入 * @author liangjunhui * @date Created in 2020-08-19 10:17 */ @Configuration public class AdviceConfig { @Bean public ProxyFactoryBean userServiceProxy(){ ProxyFactoryBean factoryBean = new ProxyFactoryBean(); // 引介增强需要设置接口名 // 并强制使用cglib代理,要不然会报ClassCastException factoryBean.setInterfaces(Flag.class); factoryBean.setProxyTargetClass(true); factoryBean.setTargetName("userService"); factoryBean.setInterceptorNames("afterAdvice","beforeAdvice","aroundAdive","throwingAdvice","introductionAdvice"); return factoryBean; } @Bean public MethodBeforeAdvice beforeAdvice(){ // 创建一个前置增强 return (method, args, target) -> System.out.println("前置增强"); } @Bean public AfterReturningAdvice afterAdvice(){ // 后置通知这个是方法结束的时候做的出发,如果方法执行未完成则不会触发 return (returnValue, method, args, target) -> System.out.println("后置增强"); } @Bean public UserService userService(){ return new UserService(); } @Bean public MethodInterceptor aroundAdive(){ return invocation -> { System.out.println("环绕通知前"); // 执行目标方法 invocation.proceed(); System.out.println("环绕通知后"); return null; }; } @Bean public ThrowsAdvice throwingAdvice(){ /** * 这个接口没有定义任何方法,使用该接口,方法名必须是 * afterThrowing,参数3个选填,一个必填(Exception)。 */ return new ThrowsAdvice(){ public void afterThrowing(Method method, Object[] args, Object target, Exception ex){ System.out.println("方法发生异常"); } }; } @Bean public IntroductionAdvice introductionAdvice(){ return new IntroductionAdvice(); } public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AdviceConfig.class); UserService userServiceProxy = (UserService)context.getBean("userServiceProxy"); userServiceProxy.test("sdf"); System.out.println("=========================触发引介逻辑==================================="); ((Flag) userServiceProxy).flag(true); userServiceProxy.test("引介"); System.out.println("=========================有异常出现==================================="); userServiceProxy.test(null); } } class UserService { public void test(String name){ System.out.println(name+name.length()); } } interface Flag { Boolean flag(Boolean b); } class IntroductionAdvice extends DelegatingIntroductionInterceptor implements Flag{ private ThreadLocal<Boolean> threadLocal = new ThreadLocal<>(); @Override public Object invoke(MethodInvocation mi) throws Throwable { if(threadLocal.get()!=null && threadLocal.get()){ System.out.println("运行期特殊处理"); } return super.invoke(mi); } @Override public Boolean flag(Boolean b) { threadLocal.set(b); return b; } }
切点pointcut
接口定义:
ClassFilter getClassFilter(); MethodMatcher getMethodMatcher();
连接点方法匹配
1、静态:仅对方法签名做匹配(方法名和方法参数及顺序),这种只会判别一次
2、动态:可以判断方法运行时的入参,这种需要每一次做判别
切点类型(spring提供了六种)
1、静态方法切点{@link StaticMethodMatcherPointcut}
2、动态方法切点{@link DynamicMethodMatcherPointcut}
3、注解式切点 {@link AnnotationMatchingPointcut}
4、表达式切点 {@link ExpressionPointcut} 这个是为了支持Aspectj切点表达式语法定义的接口
5、流程切丁 {@link ControlFlowPointcut} 这个是根据堆栈信息判断目标方法是否由一个方法直接或间接调用,以此判断是否为连接点
6、复合切点 {@link ComposablePointcut} 可以定义多个切点,每个切点都是返回这个类的对象,因此这个类可以链式调用方法
切面(切点和增强):定义切点和增强,然后通过ProxyFactoryBean配置一个代理对象。这个是一个工厂bean,会为容器注入两个bean,一个是工厂bean本身,一个是工厂bean#getObject()方法获取的对象。创建代理对象是org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy这个方法决定的
@Configuration public class AdvisorConfig { private MethodBeforeAdvice beforeAdvice = (method, args, target) -> System.out.println("前置增强"); // 静态切面2 正则匹配方法名 @Bean public RegexpMethodPointcutAdvisor regexpMethodPointcutAdvisor(){ RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor(); advisor.setAdvice(beforeAdvice); advisor.setPattern(".*tes.*"); return advisor; } @Bean public ProxyFactoryBean regexpMethodPointcutAdvisorProxy(){ ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); proxyFactoryBean.setTargetName("a"); proxyFactoryBean.setInterceptorNames("regexpMethodPointcutAdvisor"); return proxyFactoryBean; } // 动态切面 是由 DefaultPointcutAdvisor 和 DynamicMethodPointcut两个支持的 @Bean public DefaultPointcutAdvisor dynamicMethodPointcutAdvisor(){ DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(); advisor.setPointcut(new MyDynamicMethodMatcherPointcut()); advisor.setAdvice(beforeAdvice); return advisor; } // 动态代理 @Bean public ProxyFactoryBean dynamicMethodPointcutAdvisorProxy(){ ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); proxyFactoryBean.setTargetName("a"); proxyFactoryBean.setInterceptorNames("dynamicMethodPointcutAdvisor"); return proxyFactoryBean; } }
注意:上面是手动创建一个代理,但是这样太过于麻烦(如果有多个切面的,就需手动要配多个),一般不这样做,大多时候都是通过自动代理来实现业务功能,这里知识介绍一下怎么创建一个代理对象。更多请直接参考源码