Spring核心API介绍

时间:2023-03-10 10:48:02 买帖  | 投诉/举报

篇首语:本文由小编为大家整理,主要介绍了Spring核心API介绍相关的知识,希望对你有一定的参考价值。

 

Spring核心API介绍

BeanFactory

org.springframework.beans.factory

BeanFactory是用于访问Spring Bean容器的根接口,典型的工厂模式,用于生产Bean的一个Bean工厂,其提供了生产Bean所需的最基本规则。

BeanFactory的所有方法:

 

BeanDefinition

功能

BeanDefinition是bean在spring中的描述,有了BeanDefinition我们就可以创建Bean,BeanDefinition是Bean在spring中的定义形态 接下来我们看看BeanDefinition的相关接口与类.

方法

  • String: getBeanClassName: 返回当前bean definition定义的类名
  • ConstructorArgumentValues: getConstructorArgumentValues:返回bean的构造函数参数
  • String[]: getDependsOn:返回当前bean所依赖的其他bean的名称
  • String: getFactoryBeanName: 返回factory bean的名称
  • String: getFactoryMethodName: 返回工厂方法的名称
  • BeanDefinition: getOriginatingBeanDefinition: 返回原始的BeanDefinition,如果不存在返回null
  • String: getParentName: 返回当前bean definition的父definition的名字
  • MutablePropertyValues: getPropertyValues: 返回一个用于新的bean实例上的属性值
  • String: getScope: 返回当前bean的目标范围
  • boolean: isAbstract: 当前bean是否是abstract,意味着不能被实例化
  • boolean: isLazyInit: bean是否是延迟初始化
  • boolean: isPrimary: bean是否为自动装配的主要候选bean
  • boolean: isPrototype: bean是否是多实例
  • boolean: isSingleton: bean是否是单例
  • void: setAutowiredCandidate(boolean): 设置bean是否对其他bean是自动装配的候选bean
  • void: setBeanClassName(String): 指定bean definition的类名
  • void: setDependsOn(String ...): 设置当前bean初始化所依赖的beans的名称
  • void: setFactoryBeanName(String): 如果factory bean的名称
  • void: setFactoryMethodName(String): 设置工厂的方法名
  • void: setLazyInit(boolean lazyInit): 设置是否延迟初始化
  • void: setParentName(String): 设置父definition的名称
  • void: setPrimary(boolean): 设置是否主要的候选bean
  • void: setScope(String): 设置bean的范围,如:单例,多实例

定义

  • BeanDefinition接口: 顶级基础接口,用来描述Bean,里面存放Bean元数据,比如Bean类名、scope、属性、构造函数参数列表、依赖的bean、是否是单例类、是否是懒加载等一些列信息。

向上

  • BeanMetadataElement接口:BeanDefinition元数据,返回该Bean的来源
  • AttributeAccessor接口:提供对BeanDefinition属性操作能力,

向下

  • AbstractBeanDefinition类:抽象类统一实现了BeanDefinition定义的一部分操作,可以说是定义了BeanDefinition很多默认的属性。 正是在AbstractBeanDefinition基础上, Spring衍生出了一些列BeaDefinition。

这里我们可以关注下重写的equals(),hashcode(), toString()方法

此外initMethodName属性,destroyMethodName 属性, 这两个属性bean的生命周期有关,此处只提一句,后续讲解。

接下来。我们看看从AbstractBeanDefinition上衍生出来的几个类

  • RootBeanDefinition: 代表一个xml,java Config来的BeanDefinition
  • ChildBeanDefinition: 从Spring2.5开始,ChildBeanDefinition已经不再使用,取而代之的是GenericBeanDefinition。
  • GenericBeanDefinition: spring2.5后注册bean首选的是GenericBeanDefinition。GenericBeanDefinition允许动态的设置父bean.GenericBeanDefinition可以作为RootBeanDefinition与ChildBeanDefinition的替代品。
  • AnnotatedBeanDefinition接口: 表示注解类型BeanDefinition。有两个重要的属性,AnnotationMetadata,MethodMetadata分别表示BeanDefinition的注解元信息和方法元信息 实现了此接口的BeanDefinition可以获取到注解元数据和方法元数据。
  • AnnotatedGenericBeanDefinition类: 表示@Configuration注解注释的BeanDefinition类
  • ScannedGenericBeanDefinition类: 表示@Component、@Service、@Controller等注解注释的Bean类

操作

动作也可分为两种: 一种是针对自身的操作: 自提提供给外部的可以操作其本身的动作 另一种是外部对BeanDefinition的操作

  • BeanDefinitionRegistry接口:具有增,查,删BeanDefinition的能力。一次只能注册一个BeanDefinition.

实现类SimpleBeanDefinitionRegistry,DefaultListableBeanFactory,GenericApplicationContext等 一般实现类里都都有一个 private final Map beanDefinitionMap = new ConcurrentHashMap()来存储BeanDefinition. 

  • BeanDefinitionReader接口: 既可以使用BeanDefinitionRegistry构造。也可以通过loadBeanDefinitions把配置加载为多个BeanDefinition并注册到BeanDefinitionRegistry中。 可以说是高效版本的BeanDefinitionRegistry. 实现类有 XmlBeanDefinitionReader从xml中读取BeanDefinition; PropertiesBeanDefinitionReader从Properties文件读取BeanDefinition
  • AnnotatedBeanDefinitionReader类 对带有注解的BeanDefinition进行注册
  • ClassPathBeanDefinitionScanner类: 可以扫描到@Component @Repository @Service @Controller 的BeanDefinition注册到容器中。

其他形态

  • BeanDefinitionHolder: BeanDefinition包装类。

Bean

Bean是我们需要的对象,是我们从spring内得到的结果,也就是对象实例

定义

从定义层面看.Bean其实就是我们需要的对象.

操作

我们来看看Bean在spring有哪些操作相关的接口或类。

  • SingletonBeanRegistry接口:与BeanDefinition的注册相应的。Bean的操作也有一个类似的接口来操作Bean.SingletonBeanRegistry接口提供了对Bean的注册,获取,存在性判断等功能。
  • InitializingBean:对于实现 InitializingBean的Bean,它将执行 afterPropertiesSet(); 在所有的 bean 属性被设置之后。
  • InstantiationStrategy:提供 Bean实例化的策略接口,
  • DisposableBean:对于 实现了DisposableBean的Bean ,它将运行 destroy()在 Spring 容器释放该 bean 之后
  • FactoryBean: 生产Bean的Bean.

其他形态

  • BeanWrapper: 对Bean的包装.BeanWrapper可以看作是一个从 BeanDefinition 到 Bean 过程中间的产物,可以称为”低级 bean“,在一般情况下,我们不会在实际项目中用到它。BeanWrapper 是 Spring 框架中重要的组件类,它就相当于一个代理类,Spring 委托 BeanWrapper 完成 Bean 属性的填充工作。在 bean 实例被 InstantiatioonStrategy 创建出来后,Spring 容器会将 Bean 实例通过 BeanWrapper 包裹起来,是通过 BeanWrapper.setWrappedInstance() 完成的

总结:

BeanDefinition是物料,Bean是成品,理解BeanDefinition与Bean的关系是理解spring的基础。

 

AOP

Pointcut家族

它是Spring AOP对切点的一个顶层首相,非常的重要。

首先得看看这个顶级接口抽象的图谱:

这里面有一个非常重要得子接口:ExpressionPointcut,它是用于解析String类型的切点表达式的接口(这也是我们使用得最最最多的)

Pointcut接口分析

**主要负责对系统的相应的Joinpoint进行捕捉,对系统中所有的对象进行Joinpoint所定义的规则进行匹配。**提供了一个TruePointcut实例,当Pointcut为TruePointcut类型时,则会忽略所有的匹配条件,永远返回true

显然可以看出,这个接口和ClassFilterMethodMatcher有关系

public interface Pointcut {ClassFilter getClassFilter();MethodMatcher getMethodMatcher();/** * Canonical Pointcut instance that always matches. * 意思是:用于匹配上的一个实例(意思是永远返回true嘛) */Pointcut TRUE = TruePointcut.INSTANCE;}

 

ClassFilter与MethodMatcher分别用于在不同的级别上限定Joinpoint的匹配范围,满足不同粒度的匹配

ClassFilter限定在类级别上,MethodMatcher限定在方法级别上

SpringAop主要支持在方法级别上的匹配,所以对类级别的匹配支持相对简单一些

ClassFilter

@FunctionalInterfacepublic interface ClassFilter {// true表示能够匹配。那就会进行织入的操作boolean matches(Class<?> clazz);// 常量 会匹配所有的类   TrueClassFilter不是public得class,所以只是Spring内部自己使用的ClassFilter TRUE = TrueClassFilter.INSTANCE;}

Spring给他的实现类也比较多,如下:

RootClassFilter

public class RootClassFilter implements ClassFilter, Serializable {private Class<?> clazz;public RootClassFilter(Class<?> clazz) {this.clazz = clazz;}// 显然,传进来的candidate必须是clazz的子类才行@Overridepublic boolean matches(Class<?> candidate) {return clazz.isAssignableFrom(candidate);}}

AnnotationClassFilter

public class AnnotationClassFilter implements ClassFilter {...public AnnotationClassFilter(Class<? extends Annotation> annotationType) {// 默认情况下checkInherited给的false:不去看它继承过来的注解this(annotationType, false);}// checkInherited true:表示继承过来得注解也算public AnnotationClassFilter(Class<? extends Annotation> annotationType, boolean checkInherited) {Assert.notNull(annotationType, "Annotation type must not be null");this.annotationType = annotationType;this.checkInherited = checkInherited;}...@Overridepublic boolean matches(Class<?> clazz) {return (this.checkInherited ?// 继承的注解也会找出来(AnnotationUtils.findAnnotation(clazz, this.annotationType) != null) :// 只会看自己本类的注解clazz.isAnnotationPresent(this.annotationType));}}

AspectJExpressionPointcut

它既是个Pointcut,它也是个ClassFilter,下面会详细分析本类

MethodMatcher

public interface MethodMatcher {// 这个称为静态匹配:在匹配条件不是太严格时使用,可以满足大部分场景的使用boolean matches(Method method, @Nullable Class<?> targetClass);// 这个称为动态匹配(运行时匹配): 它是严格的匹配。在运行时动态的对参数的类型进行匹配boolean matches(Method method, @Nullable Class<?> targetClass, Object... args);//两个方法的分界线就是boolean isRuntime()方法,步骤如下// 1、先调用静态匹配,若返回true。此时就会继续去检查isRuntime()的返回值// 2、若isRuntime()还返回true,那就继续调用动态匹配// (若静态匹配都匹配上,动态匹配那铁定更匹配不上得~~~~)// 是否需要执行动态匹配boolean isRuntime();MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;}

应用场景:比如需要统计用户登录次数时,那么登录传入的参数就是可以忽略的,则静态匹配就足够了

但是若要在登陆时对用户账号执行特殊的操作**(如赋予特殊的操作权限)**,就需要对参数进行一个类似于检验的操作,因此需要动态匹配

它有两个非常重要的抽象实现:StaticMethodMatcherDynamicMethodMatcher

StaticMethodMatcher 静态匹配

public abstract class StaticMethodMatcher implements MethodMatcher {// 永远返回false表示只会去静态匹配@Overridepublic final boolean isRuntime() {return false;}// 三参数matches抛出异常,使其不被调用@Overridepublic final boolean matches(Method method, @Nullable Class<?> targetClass, Object... args) {// should never be invoked because isRuntime() returns falsethrow new UnsupportedOperationException("Illegal MethodMatcher usage");}}

作用:它表示不会考虑具体 方法参数。因为不用每次都检查参数,那么对于同样的类型的方法匹配结果,就可以在框架内部缓存以提高性能。比如常用的实现类:AnnotationMethodMatcher

DynamicMethodMatcher 动态匹配

public abstract class DynamicMethodMatcher implements MethodMatcher {// 永远返回true@Overridepublic final boolean isRuntime() {return true;}// 永远返回true,去匹配动态匹配的方法即可@Overridepublic boolean matches(Method method, @Nullable Class<?> targetClass) {return true;}}

说明:因为每次都要对方法参数进行检查,无法对匹配结果进行缓存,所以,匹配效率相对 StatisMethodMatcher 来说要差,但匹配度更高。(实际使用得其实较少)

JdkRegexpMethodPointcut:基于正则的Pointcut

Spring官方为我们提供了一个基于正则表达式来匹配方法名的Pointcut,JdkRegexpMethodPointcut

它提供了最重要的4个属性(patternsexcludedPatterns来自于父类AbstractRegexpMethodPointcut):

这里昂个属性来自于父类,相对来说就是比较简单的匹配signatureString(方法的全路径名称)

  • String[] patterns:匹配的正则表达式。如find.*表示所有方法名以find开始的方法
  • String[] excludedPatterns:排除的正则表达式们

下面两个是子类,也就是JdkRegexpMethodPointcut自己提供的属性

  • Pattern[] compiledPatterns:相当于把正则字符串,Pattern.compile()成正则对象
  • Pattern[] compiledExclusionPatterns:同上

都是数组,正则表达式都可以多个哟~~

需要注意的是,这两组含义相同,请不要同时跨组使用,没有意义,没必要深究。

这种切点表达式,在早期Spring中的使用较多,一般这么使用:

<!-- 自己书写的日志切面 --><bean id="logBeforeAdvice" class="com.fsx.aop.LogBeforeAdvice" /><!-- 使用JDK的正则切点~~~~~~ --><bean id="regexPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">    <property name="patterns">         <list>             <value>find.*</value><!-- 拦截所有方法名以find开始的方法 -->         </list>    </property></bean><!-- 切面+切点  组合成一个增强器即可~~~~~~ --><aop:config>     <aop:advisor advice-ref="logBeforeAdvice" pointcut-ref="regexPointcut"/> </aop:config>

其实Spring为我们提供了一个简便的Advisor定义,可以方便的让我们同时指定一个JdkRegexpMethodPointcut和其需要对应的Advice,它就是RegexpMethodPointcutAdvisor,这样配置起来非常的方便

<bean id="logBeforeAdvice" class="com.fsx.aop.LogBeforeAdvice" />    <bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">        <property name="advice" ref="logBeforeAdvice"/>        <property name="pattern" value="find.*"/>    </bean>

这个举例事基于XML的,之前我们都是这么来用的。那么现在用Java代码的方式也实现一遍(不需要Spring容器):

    public static void main(String[] args) {        ProxyFactory factory = new ProxyFactory(new Person());        //声明一个aspectj切点,一张切面        JdkRegexpMethodPointcut cut = new JdkRegexpMethodPointcut();        //cut.setPattern("com.fsx.maintest.Person.run"); //它会拦截Person类下所有run的方法(无法精确到方法签名)        //cut.setPattern(".*run.*");//.号匹配除"\\r\\n"之外的任何单个字符。*号代表零次或多次匹配前面的字符或子表达式  所以它拦截任意包下任意类的run方法        cut.setPatterns(new String[]{".*run.*", ".*say.*"}); //可以配置多个正则表达  式...  sayHi方法也会被拦截         // 声明一个通知(此处使用环绕通知 MethodInterceptor )        Advice advice = (MethodInterceptor) invocation -> {            System.out.println("============>放行前拦截...");            Object obj = invocation.proceed();            System.out.println("============>放行后拦截...");            return obj;        };        //切面=切点+通知        // 它还有个构造函数:DefaultPointcutAdvisor(Advice advice); 用的切面就是Pointcut.TRUE,所以如果你要指定切面,请使用自己指定的构造函数        // Pointcut.TRUE:表示啥都返回true,也就是说这个切面作用于所有的方法上/所有的方法        // addAdvice();方法最终内部都是被包装成一个 `DefaultPointcutAdvisor`,且使用的是Pointcut.TRUE切面,因此需要注意这些区别  相当于new DefaultPointcutAdvisor(Pointcut.TRUE,advice);        Advisor advisor = new DefaultPointcutAdvisor(cut, advice);        factory.addAdvisor(advisor);        Person p = (Person) factory.getProxy();        // 执行方法        p.run();        p.run(10);        p.say();        p.sayHi("Jack");        p.say("Tom", 666);    }}class Person {    public int run() {        System.out.println("我在run...");        return 0;    }    public void run(int i) {        System.out.println("我在run...<" + i + ">");    }    public void say() {        System.out.println("我在say...");    }    public void sayHi(String name) {        System.out.println("Hi," + name + ",你好");    }    public int say(String name, int i) {        System.out.println(name + "----" + i);        return 0;    }}输出:============>放行前拦截...我在run...============>放行后拦截...============>放行前拦截...我在run...<10>============>放行后拦截...============>放行前拦截...我在say...============>放行后拦截...============>放行前拦截...Hi,Jack,你好============>放行后拦截...============>放行前拦截...Tom----666============>放行后拦截...

最后需要注意的是:RegexpMethodPointcutAdvisor没有提供不匹配的正则表达式注入方法,即没有excludedPatterns注入,如果需要该功能请还是使用JdkRegexpMethodPointcut

AspectJExpressionPointcut详细使用和分析

AspectJExpressionPointcut如字面,它和Expression有关,而这个切点表达式,最终还是依赖于AspectJ的jar包去解析的~~~~ Spring在使用@Aspect注解时,会大量的用到它

AspectJExpressionPointcut实现的切点比JdkRegexpMethodPointcut实现切点的好处就是,在设置切点的时候可以用切点语言来更加精确的表示拦截哪个方法。(可以精确到返回参数,参数类型,方法名,当然,也可以模糊匹配)

纯Java方式Demo:

下面我先用一个纯Java的方式的例子,先体验一把:

    public static void main(String[] args) {        //String pointcutExpression = "execution( int com.fsx.maintest.Person.run() )"; // 会拦截Person.run()方法        //String pointcutExpression = "args()"; // 所有没有入参的方法会被拦截。  比如:run()会拦截,但是run(int i)不会被拦截        // ... AspectJExpressionPointcut支持的表达式 一共有11种(也就是Spring全部支持的切点表达式类型)        String pointcutExpression = "@annotation(org.springframework.test.context.transaction.AfterTransaction)"; // 拦截上方法标有@AfterTransaction此注解的任意方法们        // =============================================================        ProxyFactory factory = new ProxyFactory(new Person());        //声明一个aspectj切点,一张切面        AspectJExpressionPointcut cut = new AspectJExpressionPointcut();        cut.setExpression(pointcutExpression); // 设置切点表达式        // 声明一个通知(此处使用环绕通知 MethodInterceptor )        Advice advice = (MethodInterceptor) invocation -> {            System.out.println("============>放行前拦截...");            Object obj = invocation.proceed();            System.out.println("============>放行后拦截...");            return obj;        };        //切面=切点+通知        // 它还有个构造函数:DefaultPointcutAdvisor(Advice advice); 用的切面就是Pointcut.TRUE,所以如果你要指定切面,请使用自己指定的构造函数        // Pointcut.TRUE:表示啥都返回true,也就是说这个切面作用于所有的方法上/所有的方法        // addAdvice();方法最终内部都是被包装成一个 `DefaultPointcutAdvisor`,且使用的是Pointcut.TRUE切面,因此需要注意这些区别  相当于new DefaultPointcutAdvisor(Pointcut.TRUE,advice);        Advisor advisor = new DefaultPointcutAdvisor(cut, advice);        factory.addAdvisor(advisor);        Person p = (Person) factory.getProxy();        // 执行方法        p.run();        p.run(10);        p.say();        p.sayHi("Jack");        p.say("Tom", 666);    }}class Person {    @AfterTransaction    public int run() {        System.out.println("我在run...");        return 0;    }    public void run(int i) {        System.out.println("我在run...<" + i + ">");    }    public void say() {        System.out.println("我在say...");    }    public void sayHi(String name) {        System.out.println("Hi," + name + ",你好");    }    public int say(String name, int i) {        System.out.println(name + "----" + i);        return 0;    }}

如上面的图,其实Spring也我们提供了AspectJExpressionPointcutAdvisor来专门处理基于AspectJ的通知+切点的

XML方式

public class Person {    @AfterTransaction    public int run() {        System.out.println("我在run...");        return 0;    }    public void run(int i) {        System.out.println("我在run...<" + i + ">");    }    public void say() {        System.out.println("我在say...");    }    public void sayHi(String name) {        System.out.println("Hi," + name + ",你好");    }    public int say(String name, int i) {        System.out.println(name + "----" + i);        return 0;    }}// 相当于准备了一个Advicepublic class MyMethodInteceptor implements MethodInterceptor {    @Override    public Object invoke(MethodInvocation invocation) throws Throwable {        System.out.println("============>放行前拦截...");        Object obj = invocation.proceed();        System.out.println("============>放行后拦截...");        return obj;    }}

书写Spring的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"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">    <!-- 作为示例,这是需要被切入的目标类 -->    <bean class="com.fsx.bean.Person"/>    <!-- 切面=切点+通知 (采用面向切点语言进行配置切面)   此处为了便捷 直接使用 AspectJExpressionPointcutAdvisor -->    <bean id="advisor" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">        <property name="expression"                  value="@annotation(org.springframework.test.context.transaction.AfterTransaction)"></property>        <!-- 一个Advisor里面对应一个advice~~~ -->        <property name="advice">            <bean class="com.fsx.aop.MyMethodInteceptor"/>        </property>    </bean></beans>

把该xml配置文件导入Config配置类,让它生效

@Configuration@ImportResource(locations = "classpath:spring.xml")public class RootConfig { ... }

启动Spring容器(采用JUnit测试):

@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = {RootConfig.class})public class TestSpringBean {    @Autowired    private Person p;    @Test    public void test1() {        System.out.println(p.getClass()); //class com.fsx.bean.Person$$EnhancerBySpringCGLIB$$cba1d735        p.run();        p.run(10);        p.say();        p.sayHi("Jack");        p.say("Tom", 666);    }}输出:class com.fsx.bean.Person$$EnhancerBySpringCGLIB$$cba1d735 // 说明它是CGLIB的代理============>放行前拦截...我在run...============>放行后拦截...我在run...<10>我在say...Hi,Jack,你好Tom----666

备注:此xml的配置方式其实是最原始的Spring AOP的使用。并没有使用到<aop:config>、<aop:pointcut>这种标签注解,关于使用它们的xml配置方式,此处不做过多介绍了~~~因为也比较简单~

AspectJExpressionPointcut源码分析

// 很容易发现,自己即是ClassFilter,也是MethodMatcher   // 它是子接口:ExpressionPointcut的实现类public class AspectJExpressionPointcut extends AbstractExpressionPointcutimplements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {...private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();// 从此处可以看出,Spring支持的AspectJ的切点语言表达式一共有10中(加上后面的自己的Bean方式一共11种)// AspectJ框架本省支持的非常非常多,详解枚举类:org.aspectj.weaver.tools.PointcutPrimitivestatic {SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);}...// 它持有BeanFactory 的引用,但是是可以为null的,也就是说它脱离容器也能够正常work@Nullableprivate BeanFactory beanFactory;...// PointcutExpression是org.aspectj.weaver.tools.PointcutExpression是AspectJ的类// 它最终通过一系列操作,由org.aspectj.weaver.tools.PointcutParser#parsePointcutExpression从字符串表达式解析出来@Nullableprivate transient PointcutExpression pointcutExpression;...// 由此可见,我们不仅仅可议写&& ||  !这种。也支持 and or not这种哦~~~private String replaceBooleanOperators(String pcExpr) {String result = StringUtils.replace(pcExpr, " and ", " && ");result = StringUtils.replace(result, " or ", " || ");result = StringUtils.replace(result, " not ", " ! ");return result;}...// 这是ClassFilter 匹配类。借助的PointcutExpression#couldMatchJoinPointsInType 去匹配public boolean matches(Class<?> targetClass) { ... }// MethodMatcher 匹配方法,借助的PointcutExpression和ShadowMatch去匹配的public boolean matches(Method method, @Nullable Class<?> targetClass, boolean hasIntroductions) { ... }@Overridepublic boolean isRuntime() {//mayNeedDynamicTest 相当于由AspectJ框架去判断的(是否有动态内容)return obtainPointcutExpression().mayNeedDynamicTest();}...// 初始化一个Pointcut的解析器。我们发现最后一行,新注册了一个BeanPointcutDesignatorHandler  它是准们处理Spring自己支持的bean() 的切点表达式的private PointcutParser initializePointcutParser(@Nullable ClassLoader classLoader) {PointcutParser parser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(SUPPORTED_PRIMITIVES, classLoader);parser.registerPointcutDesignatorHandler(new BeanPointcutDesignatorHandler());return parser;}// 真正的解析,依赖于Spring自己实现的这个内部类(主要是ContextBasedMatcher 这个类,就会使用到BeanFactory了)private class BeanPointcutDesignatorHandler implements PointcutDesignatorHandler {private static final String BEAN_DESIGNATOR_NAME = "bean";@Overridepublic String getDesignatorName() {return BEAN_DESIGNATOR_NAME;}// ContextBasedMatcher由Spring自己实现,对容器内Bean的匹配@Overridepublic ContextBasedMatcher parse(String expression) {return new BeanContextMatcher(expression);}}}

NameMatchMethodPointcut

如果创建切入点时候,我们往往只需要方法名字匹配,无需理会方法的签名和返回类型,这种情况下,我们可以使用 NameMatchMethodPointCut方法名字匹配切入点。(这种功能最弱,但显然效率是最高的)

public class Main {    public static void main(String[] args) {        ProxyFactory factory = new ProxyFactory(new Person());        // 声明一个通知(此处使用环绕通知 MethodInterceptor )        Advice advice = (MethodInterceptor) invocation -> {            System.out.println("============>放行前拦截...");            Object obj = invocation.proceed();            System.out.println("============>放行后拦截...");            return obj;        };        声明一个aspectj切点,一张切面        //NameMatchMethodPointcut cut = new NameMatchMethodPointcut();        //cut.setMappedName("run"); //会匹配所有的方法名为run的方法         切点+通知        //Advisor advisor = new DefaultPointcutAdvisor(cut, advice);        NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();        advisor.setMappedName("run");        advisor.setAdvice(advice);        factory.addAdvisor(advisor);        Person p = (Person) factory.getProxy();        // 执行方法        p.run();        p.run(10);        p.say();        p.sayHi("Jack");        p.say("Tom", 666);    }}输出:============>放行前拦截...我在run...============>放行后拦截...============>放行前拦截...我在run...<10>============>放行后拦截...我在say...Hi,Jack,你好Tom----666

其它Pointcut

上面已经介绍了Spring中使用得比较多的Pointcut,接下来简单的讲述一下稍微偏门些的Pointcut。

从顶部的pointcut的继承图中可以看出,有很多实现类。

ControlFlowPointCut:流程切入点

如果有这样的特殊需求:我们对一个方法进行切入通知,但只有这个方法在一个特定方法中被调用的时候执行通知(即存在流程上行的依赖关系),我们可以使用ControlFlowPointCut流程切入点

public class Main {    public static void main(String[] args) {        ProxyFactory factory = new ProxyFactory(new Person());        // 声明一个通知(此处使用环绕通知 MethodInterceptor )        Advice advice = (MethodInterceptor) invocation -> {            System.out.println("============>放行前拦截...");            Object obj = invocation.proceed();            System.out.println("============>放行后拦截...");            return obj;        };        声明一个aspectj切点,一张切面        // 含义:Main类里面,方法名为funabc执行时,内部调用的任何代理的方法都会被拦截~~~ 它控制的是整个流程        ControlFlowPointcut cut = new ControlFlowPointcut(Main.class, "funabc");        // 切点+通知        Advisor advisor = new DefaultPointcutAdvisor(cut, advice);        factory.addAdvisor(advisor);        Person p = (Person) factory.getProxy();        // 执行方法        p.run();        p.run(10);        p.say();        p.sayHi("Jack");        p.say("Tom", 666);        // 此处调用Main类,方法名为funabc的方法。内部代理对象的方法就都会被拦截上了        funabc(p);    }    private static void funabc(Person person) {        person.run();        person.say();    }}输出:我在run...我在run...<10>我在say...Hi,Jack,你好Tom----666============>放行前拦截...我在run...============>放行后拦截...============>放行前拦截...我在say...============>放行后拦截...

使用流程切入点有时候可以解决不少问题,但值得注意的是:

使用流程切入点在jdk1.4中比其他切入点要慢5倍,在1.3上则要慢10倍,追求高性能的要慎重使用

ComposablePointcut 组合切入点

从上面的例子中,每次我们只能定义一个切入点(切点表达式)。有的时候,一个切点可能难以描述目标连接点的信息,而是需要同时满足两个切入点才行,那么ComposablePointcut就派上了用场(aspectJ里面的&& ||等其实也能达到类似的效果)。

但是更好的方式是使用Spring提供的ComposalbePointcut把两个切点组合起来,通过切点的复合运行算表示,ComposalbePointcut可以将多个切点以并集或者交集的方式组合起来,提供切点之间复合运算的功能。

先看一个Demo:

public class Main {    public static void main(String[] args) {        ProxyFactory factory = new ProxyFactory(new Person());        // 声明一个通知(此处使用环绕通知 MethodInterceptor )        Advice advice = (MethodInterceptor) invocation -> {            System.out.println("============>放行前拦截...");            Object obj = invocation.proceed();            System.out.println("============>放行后拦截...");            return obj;        };        // 先创建一个流程切入点        ControlFlowPointcut controlFlowPointcut = new ControlFlowPointcut(Main.class, "funabc");        // 再创建一个方法名切入点        NameMatchMethodPointcut nameMatchMethodPointcut = new NameMatchMethodPointcut();        nameMatchMethodPointcut.addMethodName("say");        // 创建一个复合切点 把上面两者并且进来        ComposablePointcut cut = new ComposablePointcut();        cut.intersection((Pointcut) controlFlowPointcut).intersection((Pointcut)nameMatchMethodPointcut);        // 切点+通知(注意:此处放的是复合切面)        Advisor advisor = new DefaultPointcutAdvisor(cut, advice);        factory.addAdvisor(advisor);        Person p = (Person) factory.getProxy();        // 执行方法        p.run();        p.run(10);        p.say();        p.sayHi("Jack");        p.say("Tom", 666);        funabc(p);    }    private static void funabc(Person person) {        person.run();        person.say();    }}输出:我在run...我在run...<10>我在say...Hi,Jack,你好Tom----666我在run...============>放行前拦截...我在say...============>放行后拦截...

从结果中和上面对比我们能看出,两个切入点有并且的效果。(只有say方法被拦截了,run方法并没有被拦截)

ComposablePointcut 源码分析

public class ComposablePointcut implements Pointcut, Serializable {// 它持有ClassFilter 和 MethodMatcher ,最终通过它去组合匹配private ClassFilter classFilter;private MethodMatcher methodMatcher;// 构造函数一个共5个// 匹配所有类所有方法的复合切点public ComposablePointcut() {this.classFilter = ClassFilter.TRUE;this.methodMatcher = MethodMatcher.TRUE;}// 匹配特定切点的复合切点(相当于把这个节点包装了一下而已)public ComposablePointcut(Pointcut pointcut) {Assert.notNull(pointcut, "Pointcut must not be null");this.classFilter = pointcut.getClassFilter();this.methodMatcher = pointcut.getMethodMatcher();}// 匹配特定类**所有方法**的复合切点public ComposablePointcut(ClassFilter classFilter) {Assert.notNull(classFilter, "ClassFilter must not be null");this.classFilter = classFilter;this.methodMatcher = MethodMatcher.TRUE;}// 匹配**所有类**特定方法的复合切点public ComposablePointcut(MethodMatcher methodMatcher) {Assert.notNull(methodMatcher, "MethodMatcher must not be null");this.classFilter = ClassFilter.TRUE;this.methodMatcher = methodMatcher;}// 匹配特定类特定方法的复合切点(这个是最为强大的)public ComposablePointcut(ClassFilter classFilter, MethodMatcher methodMatcher) {Assert.notNull(classFilter, "ClassFilter must not be null");Assert.notNull(methodMatcher, "MethodMatcher must not be null");this.classFilter = classFilter;this.methodMatcher = methodMatcher;}// 匹配特定类特定方法的复合切点(这个是最为强大的)public ComposablePointcut union(ClassFilter other) {this.classFilter = ClassFilters.union(this.classFilter, other);return this;}// ==========3个并集(union) / 3个交集(intersection) 运算的方法========public ComposablePointcut intersection(ClassFilter other) {this.classFilter = ClassFilters.intersection(this.classFilter, other);return this;}public ComposablePointcut union(MethodMatcher other) {this.methodMatcher = MethodMatchers.union(this.methodMatcher, other);return this;}public ComposablePointcut intersection(MethodMatcher other) {this.methodMatcher = MethodMatchers.intersection(this.methodMatcher, other);return this;}public ComposablePointcut union(Pointcut other) {this.methodMatcher = MethodMatchers.union(this.methodMatcher, this.classFilter, other.getMethodMatcher(), other.getClassFilter());this.classFilter = ClassFilters.union(this.classFilter, other.getClassFilter());return this;}public ComposablePointcut intersection(Pointcut other) {this.classFilter = ClassFilters.intersection(this.classFilter, other.getClassFilter());this.methodMatcher = MethodMatchers.intersection(this.methodMatcher, other.getMethodMatcher());return this;}...}

ComposablePointcut没有提供直接对两个切点类型并集交集的运算的方法。若需要,请参照org.springframework.aop.support.Pointcuts这个工具类里面有对两个Pointcut进行并集、交集的操作(后面再介绍)

AnnotationMatchingPointcut 注解切入点

根据对象是否有指定类型的注解来匹配Pointcut

有两种注解,类级别注解和方法级别注解

//仅指定类级别的注解, 标注了 ClassLevelAnnotation 注解的类中的**所有方法**执行的时候,将全部匹配。  AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class);  // === 还可以使用静态方法创建 pointcut 实例  AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);  //仅指定方法级别的注解,标注了 MethodLeavelAnnotaion 注解的**方法(忽略类匹配)都将匹配**  AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(MethodLevelAnnotation.class);  ==========这个是同时想限定:===============//同时限定类级别和方法级别的注解,只有标注了 ClassLevelAnnotation 的类中 ***同时***标注了 MethodLevelAnnotation 的方法才会匹配  AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class, MethodLevelAnnotation.class);  

Demo:略

总结

其实,这些基础的知识也是为了去更好的理解Spring的自动代理创建器铺路

 

Advisor介绍

Advisor是Spring AOP的顶层抽象,用来管理AdvicePointcutPointcutAdvisor和切点有关,但IntroductionAdvisor和切点无关

注意:Advice是aopalliance对通知(增强器)的顶层抽象,请注意区分~~

Pointcut是Spring AOP对切点的抽象。切点的实现方式有多种,其中一种就是AspectJ

public interface Advisor {//@since 5.0 Spring5以后才有的  空通知  一般当作默认值Advice EMPTY_ADVICE = new Advice() {};// 该Advisor 持有的通知器Advice getAdvice();// 这个有点意思:Spring所有的实现类都是return true(官方说暂时还没有应用到)// 注意:生成的Advisor是单例还是多例不由isPerInstance()的返回结果决定,而由自己在定义bean的时候控制// 理解:和类共享(per-class)或基于实例(per-instance)相关  类共享:类比静态变量   实例共享:类比实例变量boolean isPerInstance();}

它的继承体系主要有如下两个:PointcutAdvisorIntroductionAdvisor

IntroductionAdvisor与PointcutAdvisor最本质上的区别就是,IntroductionAdvisor只能应用于类级别的拦截,只能使用Introduction型的Advice。

而不能像PointcutAdvisor那样,可以使用任何类型的Pointcut,以及几乎任何类型的Advice

PointcutAdvisor:和切点有关的Advisor

顾名思义,它和Pointcu有关。

其实我们已经介绍了好几个PointcutAdvisor。比如:RegexpMethodPointcutAdvisorNameMatchMethodPointcutAdvisor,它哥俩都位于org.springframework.aop.support此包。当然还要介绍过AspectJExpressionPointcutAdvisor

PointcutAdvisor它的实现类非常的多:

public interface PointcutAdvisor extends Advisor {Pointcut getPointcut();}

AbstractPointcutAdvisor:抽象实现

// 实现了 Ordered接口public abstract class AbstractPointcutAdvisor implements PointcutAdvisor, Ordered, Serializable {// 调用者可以手动来指定Orderpublic void setOrder(int order) {this.order = order;}@Overridepublic int getOrder() {if (this.order != null) {return this.order;}// 若调用者没有指定Order,那就拿advice的order为准(若有),否则LOWEST_PRECEDENCE表示最后执行Advice advice = getAdvice();if (advice instanceof Ordered) {return ((Ordered) advice).getOrder();}return Ordered.LOWEST_PRECEDENCE;}// Spring还没有使用该属性 永远返回true了@Overridepublic boolean isPerInstance() {return true;}...}

AbstractGenericPointcutAdvisor 一般的、通用的PointcutAdvisor

public abstract class AbstractGenericPointcutAdvisor extends AbstractPointcutAdvisor {private Advice advice = EMPTY_ADVICE;public void setAdvice(Advice advice) {this.advice = advice;}@Overridepublic Advice getAdvice() {return this.advice;}...}

DefaultPointcutAdvisor 通用的,最强大的Advisor

它是Spring提供的通用的,也被认为是最强大的Advisor。它可以把任意的两个Advice和Pointcut放在一起:

public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {private Pointcut pointcut = Pointcut.TRUE;public DefaultPointcutAdvisor() {}// 若没有指定advice,默认Pointcut.TRUE,也就是说会匹配所有的方法的执行public DefaultPointcutAdvisor(Advice advice) {this(Pointcut.TRUE, advice);}// 显然,这个构造函数式非常强大的~~public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {this.pointcut = pointcut;setAdvice(advice);}}

AbstractBeanFactoryPointcutAdvisor:和bean工厂有关的PointcutAdvisor

从命名也能看出来,它和BeanFactory有关。

// 实现了BeanFactoryAware接口,若在Bean容器里注册可议注入BeanFactory~~~从而访问里面的实例public abstract class AbstractBeanFactoryPointcutAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {// 我们发现这两个都是@Nullable,所以他们脱离容器使用也是可以的@Nullableprivate String adviceBeanName;@Nullableprivate BeanFactory beanFactory;@Nullableprivate transient volatile Advice advice;public void setAdviceBeanName(@Nullable String adviceBeanName) {this.adviceBeanName = adviceBeanName;}@Overridepublic void setBeanFactory(BeanFactory beanFactory) {this.beanFactory = beanFactory;// 若在Spring环境下,会给AdviceMonitor重新赋值为:getSingletonMutex()resetAdviceMonitor();}// 此处加锁public void setAdvice(Advice advice) {synchronized (this.adviceMonitor) {this.advice = advice;}}// 这是它最重要的方法,获取增强器@Overridepublic Advice getAdvice() {Advice advice = this.advice;// 非Spring环境一般手动set进来,所以就直接返回吧if (advice != null) {return advice;}// 显然进来Spring容器环境了,bean工厂和beanName都是不能为null的Assert.state(this.adviceBeanName != null, "'adviceBeanName' must be specified");Assert.state(this.beanFactory != null, "BeanFactory must be set to resolve 'adviceBeanName'");// 若bean是单例的  那就没什么好说的  直接去工厂里拿出来就完事了(Advice.class)  有可能返回null哦if (this.beanFactory.isSingleton(this.adviceBeanName)) {advice = this.beanFactory.getBean(this.adviceBeanName, Advice.class);this.advice = advice;return advice;}// 若是多例的,就加锁  然后调用getBean()给他生成一个新的实例即可else {synchronized (this.adviceMonitor) {//这步赋值和判断不能省~~~确保万无一失advice = this.advice;if (advice == null) {advice = this.beanFactory.getBean(this.adviceBeanName, Advice.class);this.advice = advice;}return advice;}}}}

DefaultBeanFactoryPointcutAdvisor:通用的BeanFactory的Advisor

这个是和Bean工厂关联的,通用的PointcutAdvisor

public class DefaultBeanFactoryPointcutAdvisor extends AbstractBeanFactoryPointcutAdvisor {private Pointcut pointcut = Pointcut.TRUE;// 若传进来为null,还是选择 Pointcut.TRUE 匹配所有public void setPointcut(@Nullable Pointcut pointcut) {this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);}@Overridepublic Pointcut getPointcut() {return this.pointcut;}}

在Spring事务相关里,你会看到这个类

位于org.springframework.aop.support包内

BeanFactoryCacheOperationSourceAdvisor:和Cache有关

Spring Cache的@Cachable等注解的拦截,就是采用了它。该类位于:org.springframework.cache.interceptor,显然它和cache相关了。Jar包属于:Spring-context.jar

// @since 3.1  毕竟Spring的整个org.springframework.cache.Cache体系都是从这里开始的。(@Cacheable...等等)public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {// 显然它最重要的是持有这个引用(Cache章节详细介绍了它)@Nullableprivate CacheOperationSource cacheOperationSource;// Pointcut使用的是CacheOperationSourcePointcutprivate final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() {@Override@Nullableprotected CacheOperationSource getCacheOperationSource() {return cacheOperationSource;}};public void setCacheOperationSource(CacheOperationSource cacheOperationSource) {this.cacheOperationSource = cacheOperationSource;}public void setClassFilter(ClassFilter classFilter) {this.pointcut.setClassFilter(classFilter);}@Overridepublic Pointcut getPointcut() {return this.pointcut;}}

AsyncAnnotationAdvisor:和@Async有关

位于包为:org.springframework.scheduling.annotation,所属jar包为spring-context.jar

public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {// 处理异步发生的异常的====private AsyncUncaughtExceptionHandler exceptionHandler;private Advice advice;private Pointcut pointcut;// 构造函数们public AsyncAnnotationAdvisor() {this(null, null);}// executor:可以自己指定异步任务的执行器// exceptionHandler:异步异常的处理器public AsyncAnnotationAdvisor(@Nullable Executor executor, @Nullable AsyncUncaughtExceptionHandler exceptionHandler) {Set<Class<? extends Annotation>> asyncAnnotationTypes = new LinkedHashSet<>(2);asyncAnnotationTypes.add(Async.class);// 支持EJB的注解:@Asynchronoustry {asyncAnnotationTypes.add((Class<? extends Annotation>)ClassUtils.forName("javax.ejb.Asynchronous", AsyncAnnotationAdvisor.class.getClassLoader()));} catch (ClassNotFoundException ex) {// If EJB 3.1 API not present, simply ignore.}if (exceptionHandler != null) {this.exceptionHandler = exceptionHandler;} else {// SimpleAsyncUncaughtExceptionHandler:只是一个简单的logger.error的输入打印this.exceptionHandler = new SimpleAsyncUncaughtExceptionHandler();}// buildAdvice: new AnnotationAsyncExecutionInterceptor(executor, exceptionHandler)   它是个MethodInterceptor  环绕通知器this.advice = buildAdvice(executor, this.exceptionHandler);// 把asyncAnnotationTypes交给buildPointcut,它最终是个ComposablePointcut,会把这两种注解都支持。union起来 或者的关系this.pointcut = buildPointcut(asyncAnnotationTypes);}public void setTaskExecutor(Executor executor) {this.advice = buildAdvice(executor, this.exceptionHandler);}...}

AbstractAspectJAdvice的实现类如下:这5个实现类完完整整的对应着我们AspectJ的那5个注解。


AspectJPointcutAdvisor

显然是和AspectJ相关的,使用得很是广泛。注意它和AspectJExpressionPointcutAdvisor的区别。有名字也能看出来,AspectJExpressionPointcutAdvisor和表达式语言的切点相关的,而AspectJPointcutAdvisor是无关的。它哥俩都位于包org.springframework.aop.aspectj里。

public class AspectJPointcutAdvisor implements PointcutAdvisor, Ordered {// AbstractAspectJAdvice通知:它的子类看下面截图,就非常清楚了private final AbstractAspectJAdvice advice;// 可以接受任意的Pointcut,可谓非常的通用(当然也包含切点表达式啦)private final Pointcut pointcut;@Nullableprivate Integer order;// 只有这一个构造函数,包装一个advicepublic AspectJPointcutAdvisor(AbstractAspectJAdvice advice) {Assert.notNull(advice, "Advice must not be null");this.advice = advice;// 然后pointcut根据advice直接给生成了一个。这是AbstractAspectJAdvice#buildSafePointcut的方法this.pointcut = advice.buildSafePointcut();}}

InstantiationModelAwarePointcutAdvisor

它是PointcutAdvisor的一个子接口。

// 由SpringAOP顾问包装AspectJ实现的接口 可能具有延迟初始化策略的方面。// 例如,一个PerThis实例化模型意味着对建议的初始化太慢public interface InstantiationModelAwarePointcutAdvisor extends PointcutAdvisor {// 该Advisor是否需要懒加载boolean isLazy();// 判断此Advisor它所拥有的Advice是否已经初始化了boolean isAdviceInstantiated();}

它的唯一实现类:InstantiationModelAwarePointcutAdvisorImpl

// 默认的访问权限,显然是Spring内部自己用的class InstantiationModelAwarePointcutAdvisorImplimplements InstantiationModelAwarePointcutAdvisor, AspectJPrecedenceInformation, Serializable {private static final Advice EMPTY_ADVICE = new Advice() {};// 和AspectJExpressionprivate final AspectJExpressionPointcut declaredPointcut;..// 通知方法private transient Method aspectJAdviceMethod;

以上是关于Spring核心API介绍的主要内容,如果未能解决你的问题,请参考以下文章