spring-AOP(二) 自动代理

spring-AOP(二) 自动代理,第1张

下图是AOP自动代理的流程图

spring中已经定义了创建代理的工厂类 ProxyFactory,通过 ProxyFactory 创建代理,必须要一个被代理对象和一个增强Advisor列表

spring的动态代理实质就是对象创建完毕之后,查找筛选能够应用于该对象上的Advisor列表,然后调用ProxyFactory创建代理对象返回。

Spring中定义了 AbstractAutoProxyCreator 类用于实现自动代理。

从继承图可以看出该类实现了 BeanPostProcessor 接口,覆写了postProcessAfterInitialization 方法。spring的bean初始化完成后会遍历注册的所有BeanPostProcessor实现类对象,调用其postProcessAfterInitialization方法,该方法可以返回一个新的对象覆盖原有对象,在此spring提供一个创建代理并覆盖被代理对象的机会

遍历容器中注册的所有BeanPostProcessor,调用postProcessAfterInitialization方法,如果返回一个新的对象会覆盖掉原始对象注册到spring容器中

上面分析了AbstractAutoProxyCreator是实现自动代理的关键,那么在spring中如何配置一个AbstractAutoProxyCreator的实现类对象呢,spring又是如何根据配置注册该对象的

在spring中可以通过两种配置,启动自动代理

这两中方法最终都是向spring容器中注册了一个AnnotationAwareAspectJAutoProxyCreator实例,这样在spring初始化完对象后就可以调用AnnotationAwareAspectJAutoProxyCreator的postProcessAfterInitialization方法了

上面分析了spring是在何时何处开始创建代理的,解下来分析AbstractAutoProxyCreator进行自动代理的总体实现

在postProcessAfterInitialization方法中判断 被代理对象不为空,调用wrapIfNecessary判断是否对目标类进行代理

createProxy方法中通过ProxyFactory设置AOP配置,如被代理对象和Advisor列表,调用getProxy创建代理对象。

至此AbstractAutoProxyCreator完成了自动创建代理的总体实现,在该抽象类中没有实现获取Adviso列表r的功能,交由各个子类去实现。

分析完spring自动代理的整体实现,接下来看下AbstractAutoProxyCreator的子类AbstractAdvisorAutoProxyCreator是如何进行查找 Advisor、筛选Advisor、排序Advisor的。

AbstractAdvisorAutoProxyCreator实现了getAdvicesAndAdvisorsForBean方法,用于获取应用于目标类的Advisor列表

在findEligibleAdvisors方法定义了查找 Advisor、筛选Advisor、排序Advisor三步 *** 作

在spring中可以通过注册Advisor的bean来实现对目标类的增强代理。spring会筛选出容器中所有Advisor类型的bean,用于对容器中的对象进行增强代理,查找的功能由AbstractAdvisorAutoProxyCreator类实现

将查找功能委托给advisorRetrievalHelper(BeanFactoryAdvisorRetrievalHelperAdapter)实现,该方法的功能就是获取Advisor类型的bean对象返回

spring除了支持配置或者手动注册 Advisor类型的bean之外,还支持通过 @Aspect、@Before、@After等AOP注解来声明Advisor。Advisor的查找解析由子类AnnotationAwareAspectJAutoProxyCreator实现

在AnnotationAwareAspectJAutoProxyCreator的findCandidateAdvisors方法中,第一步首先调父类的方法获取到容器中注册的所有Advisor,然后再委托aspectJAdvisorsBuilder解析注解获取Advisor,然后合并两个结果返回

BeanFactoryAspectJAdvisorsBuilderbuildAspectJAdvisors方法解析AOP注解封装为Advisor对象。

spring定义了一个AspectJAdvisorFactory接口用于解析AOP的注解,接口主要定义了两个功能

AspectJAdvisorFactory的实现类ReflectiveAspectJAdvisorFactory提供了具体实现

调用getAdvisor方法,获取通知方法上的Pointcut表达式,如果在方法上未解析到Pointcut,则跳过,然后根据通知方法和对应Pointcut封装返回一个Advisor的实现类InstantiationModelAwarePointcutAdvisorImpl对象

解析获取通知方法的Pointcut表达式,在该方法中会过滤掉不带Aop注解的非通知型方法,判断一个方法是不是通知就是判断该方法是否声明了切点

最终封装返回的Advisor是InstantiationModelAwarePointcutAdvisorImpl类型,在该实现类的getAdvice方法会回调ReflectiveAspectJAdvisorFactory中的getAdvice方法,ReflectiveAspectJAdvisorFactory会根据通知方法上不同的注解,创建对应的Advice

回调ReflectiveAspectJAdvisorFactory中的getAdvice,策略创建对应的Advice实现类对象

获取到spring容器中所有的Advisor之后,再回到AbstractAdvisorAutoProxyCreator类中,接下来筛选能够应用到目标对象的Advisor。通过Advisor中的Pointcut的ClassFilter和MethodMatcher来对目标对象进行匹配。 在这个阶段的筛选,只要Advisor能应用到目标类型的任意一个方法上都会返回成功

接下来看AbstractAdvisorAutoProxyCreatorfindAdvisorsThatCanApply方法,将筛选的工作委托给AopUtils来实现

AopUtilsfindAdvisorsThatCanApply方法中遍历所有的Advisor,然后调用canApply方法判断是否符合,在当中会区分引介增强和切点增强,引介增强是类级别的,只需要根据切点的ClassFilter对目标类进行判断就行。

canApply方法中,通过Pointcut来进行匹配,引介增强IntroductionAdvisor直接根据其ClassFilter判断目标类型,PointcutAdvisor根据其中的Pointcut来进行筛选

Pointcut会首先使用ClassFilter对目标类型进行过滤。如果通过,再使用 MethodMatcher 校验 类中所有的方法,只要有一个方法匹配上,代表该Advisor符合条件。

在这里宁可多通过,也不要校验太严格,因为在代理类中具体方法执行的时候,还会再一次使用Pointcut进行校验。所以该方法中会获取目标类型的所有接口,判断接口中的方法,有一个符合也会返回true

当获取到目标类型上的所有Advisor后,还需要对Advisor进行排序。Advisor的顺序决定了通知方法的应用顺序。

Advisor的排序主要分为两种

接下来看下spring是怎么实现排序的

AbstractAdvisorAutoProxyCreatorsortAdvisors提供了基于Ordered的排序,由spring的AnnotationAwareOrderComparator统一处理Ordered实现类或者添加@Ordered注解类的排序

AspectJAwareAdvisorAutoProxyCreator覆写了父类的sortAdvisors方法,在基于Ordered排序基础上提供了同一切面下不同通知之间的排序,具体排序实现委托给了PartialOrder

至此完成Advisor的查找、筛选、排序。

@Controller:组合注解(组合了@Component注解),应用在Controller层(控制层)。

@Service:组合注解(组合了@Component注解),应用在Service层(业务逻辑层)。

@Reponsitory:组合注解(组合了@Component注解),应用在Dao层(数据访问层)。

@Component:表示类是一个“组件”,成为Spring管理的Bean。同时@Component还是一个元注解。

@Autowired:Spring提供的工具(由Spring的依赖注入工具BeanPostProcessor或BeanFactoryPostProcessor自动注入)。

@Resource:JSR-250提供的注解。

@Inject:JSR-330提供的注解。

@Configuration:声明当前类是一个配置类(相当于一个Spring配置文件)。

@ComponentScan:自动扫描指定包下所有使用@Service,@Component,@Controller,@Repository的类并注册。

@Bean:注解在方法上,声明当前方法的返回值为一个Bean。返回的Bean对应的类中可以定义init()方法和destroy()方法,然后在@Bean(initMethod=”init”, destroyMethod=”destroy”)定义,在构造之后执行init,在销毁之前执行destroy。

@Aspect:声明一个切面。

@After:后置建言(advice),在原方法前执行。

@Before:前置建言(advice),在原方法后执行。

@Around:环绕建言(advice),在原方法执行前执行,在原方法执行后再执行。

@PointCut:声明切点,即定义拦截规则,确定有哪些方法会被切入。

@Transactional:声明事务(一般默认配置即可满足要求,当然也可以自定义)。

@Cacheable:声明数据缓存。

@EnableAspectJAutoProxy:开启Spring对AspectJ的支持。

@Value:值得注入。经常与Sping EL表达式语言一起使用,注入普通字符,系统属性,表达式运算结果,其他Bean的属性,文件内容,网址请求内容,配置文件属性值等。

@PropertySource:指定文件地址。提供了一种方便的、声明性的机制,用于向Spring的环境添加PropertySource。与@Configuration类一起使用。

@PostConstruct:标注在方法上,该方法在构造函数执行完成之后执行。

@PreDestroy:标注在方法上,该方法在对象销毁之前执行。

@Profile:表示当一个或多个指定的文件是活动的时,一个组件是有资格注册的。使用@Profile注解类或者方法,达到在不同情况下选择实例化不同的Bean。@Profile(“dev”)表示为dev时实例化。

@EnableAsync:开启异步任务支持。注解在配置类上。

@Async:注解在方法上表示这是一个异步方法,在类上表示这个类所有的方法都是异步方法。

@EnableScheduling:注解在配置类上,开启对计划任务的支持。

@Scheduled:注解在方法上,声明该方法是计划任务。支持多种类型的计划任务:cron,fixDelay,fixRate。

@Conditional:根据满足某一特定条件创建特定的Bean。

@Enable:通过简单的@Enable来开启一项功能的支持。所有@Enable注解都有一个@Import注解,@Import是用来导入配置类的,这也就意味着这些自动开启的实现其实是导入了一些自动配置的Bean。

@RunWith:这个是Junit的注解,SpringBoot集成了junit。一般在测试类里使用:@RunWith(SpringJUnit4ClassRunnerclass) — SpringJUnit4ClassRunner在JUnit环境下提供Sprng TestContext Framework的功能

@ContextConfiguration:用来加载配置ApplicationContext,其中classes属性用来加载配置类:@ContextConfiguration(classes = {TestConfigclass(自定义的一个配置类)})

@ActiveProfiles:用来声明活动的profile–@ActiveProfiles(“prod”(这个prod定义在配置类中))

@EnableWebMvc:用在配置类上,开启SpringMvc的Mvc的一些默认配置:如ViewResolver,MessageConverter等。同时在自己定制SpringMvc的相关配置时需要做到两点:1:配置类继承WebMvcConfigurerAdapter类,2:就是必须使用这个@EnableWebMvc注解。

@RequestMapping:用来映射web请求(访问路径和参数),处理类和方法的。可以注解在类和方法上,注解在方法上的@RequestMapping路径会继承注解在类上的路径。同时支持Serlvet的request和response作为参数,也支持对request和response的媒体类型进行配置。其中有value(路径),produces(定义返回的媒体类型和字符集),method(指定请求方式)等属性。

@ResponseBody:将返回值放在response体内。返回的是数据而不是页面

@RequestBody:允许request的参数在request体中,而不是在直接链接在地址的后面。此注解放置在参数前。

@PathVariable:放置在参数前,用来接受路径参数。

@RestController:组合注解,组合了@Controller和@ResponseBody,当我们只开发一个和页面交互数据的控制层的时候可以使用此注解。

@ControllerAdvice:用在类上,声明一个控制器建言,它也组合了@Component注解,会自动注册为Spring的Bean。

@ExceptionHandler:用在方法上定义全局处理,通过他的value属性可以过滤拦截的条件:@ExceptionHandler(value=Exceptionclass)–表示拦截所有的Exception。

@ModelAttribute:将键值对添加到全局,所有注解了@RequestMapping的方法可获得次键值对(就是在请求到达之前,往model里addAttribute一对name-value而已)。

@InitBinder:通过@InitBinder注解定制WebDataBinder(用在方法上,方法有一个WebDataBinder作为参数,用WebDataBinder在方法内定制数据绑定,例如可以忽略request传过来的参数Id等)。

@WebAppConfiguration:一般用在测试上,注解在类上,用来声明加载的ApplicationContext是一个WebApplicationContext。他的属性指定的是Web资源的位置,默认为src/main/webapp,我们可以修改为:@WebAppConfiguration(“src/main/resources”)。

@EnableAutoConfiguration:此注释自动载入应用程序所需的所有Bean——这依赖于Spring Boot在类路径中的查找。该注解组合了@Import注解,@Import注解导入了EnableAutoCofigurationImportSelector类,它使用SpringFactoriesLoaderloaderFactoryNames方法来扫描具有META-INF/springfactories文件的jar包。而springfactories里声明了有哪些自动配置。

@SpingBootApplication:SpringBoot的核心注解,主要目的是开启自动配置。它也是一个组合注解,主要组合了@Configurer,@EnableAutoConfiguration(核心)和@ComponentScan。可以通过@SpringBootApplication(exclude={想要关闭的自动配置的类名class})来关闭特定的自动配置。

@ImportResource:虽然Spring提倡零配置,但是还是提供了对xml文件的支持,这个注解就是用来加载xml配置的。例:@ImportResource({“classpath

@ConfigurationProperties:将properties属性与一个Bean及其属性相关联,从而实现类型安全的配置。例:@ConfigurationProperties(prefix=”authot”, locations={“classpath

@ConditionalOnBean:条件注解。当容器里有指定Bean的条件下。

@ConditionalOnClass:条件注解。当类路径下有指定的类的条件下。

@ConditionalOnExpression:条件注解。基于SpEL表达式作为判断条件。

@ConditionalOnJava:条件注解。基于JVM版本作为判断条件。

@ConditionalOnJndi:条件注解。在JNDI存在的条件下查找指定的位置。

@ConditionalOnMissingBean:条件注解。当容器里没有指定Bean的情况下。

@ConditionalOnMissingClass:条件注解。当类路径下没有指定的类的情况下。

@ConditionalOnNotWebApplication:条件注解。当前项目不是web项目的条件下。

@ConditionalOnResource:条件注解。类路径是否有指定的值。

@ConditionalOnSingleCandidate:条件注解。当指定Bean在容器中只有一个,后者虽然有多个但是指定首选的Bean。

@ConditionalOnWebApplication:条件注解。当前项目是web项目的情况下。

@EnableConfigurationProperties:注解在类上,声明开启属性注入,使用@Autowired注入。例:@EnableConfigurationProperties(>

文章参考

springboot aop的execution 表达式详解

Aspectj切入点语法定义

在使用spring框架配置AOP的时候,不管是通过XML配置文件还是注解的方式都需要定义pointcut"切入点"

例如定义切入点表达式 execution ( comsampleserviceimpl ())

execution()是最常用的切点函数,其语法如下所示:

整个表达式可以分为五个部分:

1、execution(): 表达式主体。

2、第一个 号:表示返回类型, 号表示所有的类型。

3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,comsampleserviceimpl包、子孙包下所有类的方法。

4、第二个 号:表示类名, 号表示所有的类。

5、 ():最后这个星号表示方法名, 号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

AspectJ的Execution表达式

execution()

execution()是最常用的切点函数,其语法如下所示:

execution(<修饰符模式> <返回类型模式> <方法名模式>(<参数模式>) <异常模式>) 除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。与其直接讲解该方法的使用规则,还不如通过一个个具体的例子进行理解。下面,我们给出各种使用execution()函数实例。

1)通过方法签名定义切点

execution(public ())l

匹配所有目标类的public方法,但不匹配SmartSeller和protected void showGoods()方法。第一个 代表返回类型,第二个 代表方法名,而代表任意入参的方法;

execution( To())l

匹配目标类所有以To为后缀的方法。它匹配NaiveWaiter和NaughtyWaiter的greetTo()和serveTo()方法。第一个 代表返回类型,而 To代表任意以To为后缀的方法;

2)通过类定义切点

execution( combaobaotaoWaiter())l

匹配Waiter接口的所有方法,它匹配NaiveWaiter和NaughtyWaiter类的greetTo()和serveTo()方法。第一个 代表返回任意类型,combaobaotaoWaiter 代表Waiter接口中的所有方法;

execution( combaobaotaoWaiter+())l

匹 配Waiter接口及其所有实现类的方法,它不但匹配NaiveWaiter和NaughtyWaiter类的greetTo()和serveTo()这 两个Waiter接口定义的方法,同时还匹配NaiveWaiter#smile()和NaughtyWaiter#joke()这两个不在Waiter 接口中定义的方法。

3)通过类包定义切点

在类名模式串中,“ ”表示包下的所有类,而“ ”表示包、子孙包下的所有类。

execution( combaobaotao())l

匹配combaobaotao包下所有类的所有方法;

execution( combaobaotao())l

匹 配combaobaotao包、子孙包下所有类的所有方法,如combaobaotaodao,combaobaotaoservier以及 combaobaotaodaouser包下的所有类的所有方法都匹配。“”出现在类名中时,后面必须跟“”,表示包、子孙包下的所有类;

execution( com Daofind())l

匹配包名前缀为com的任何包下类名后缀为Dao的方法,方法名必须以find为前缀。如combaobaotaoUserDao#findByUserId()、combaobaotaodaoForumDao#findById()的方法都匹配切点。

4)通过方法入参定义切点

切点表达式中方法入参部分比较复杂,可以使用“ ”和“ ”通配符,其中“ ”表示任意类型的参数,而“”表示任意类型参数且参数个数不限。

execution( joke(String,int)))l

匹 配joke(String,int)方法,且joke()方法的第一个入参是String,第二个入参是int。它匹配 NaughtyWaiter#joke(String,int)方法。如果方法中的入参类型是javalang包下的类,可以直接使用类名,否则必须使用全限定类名,如joke(javautilList,int);

execution( joke(String,)))l

匹 配目标类中的joke()方法,该方法第一个入参为String,第二个入参可以是任意类型,如joke(String s1,String s2)和joke(String s1,double d2)都匹配,但joke(String s1,double d2,String s3)则不匹配;

execution( joke(String,)))l

匹配目标类中的joke()方法,该方法第 一个入参为String,后面可以有任意个入参且入参类型不限,如joke(String s1)、joke(String s1,String s2)和joke(String s1,double d2,String s3)都匹配。

execution( joke(Object+)))l

匹 配目标类中的joke()方法,方法拥有一个入参,且入参是Object类型或该类的子类。它匹配joke(String s1)和joke(Client c)。如果我们定义的切点是execution( joke(Object)),则只匹配joke(Object object)而不匹配joke(String cc)或joke(Client c)。

args()和@args()

args()函数的入参是类名,@args()函数的入参必须是注解类的类名。虽然args()允许在类名后使用+通配符后缀,但该通配符在此处没有意义:添加和不添加效果都一样。

1)args()

该函数接受一个类名,表示目标类方法入参对象按类型匹配于指定类时,切点匹配,如下面的例子:

args(combaobaotaoWaiter)

表 示运行时入参是Waiter类型的方法,它和execution( (combaobaotaoWaiter))区别在于后者是针对类方法的签名而言的,而前者则针对运行时的入参类型而言。如 args(combaobaotaoWaiter)既匹配于addWaiter(Waiter waiter),也匹配于addNaiveWaiter(NaiveWaiter naiveWaiter),而execution( (combaobaotaoWaiter))只匹配addWaiter(Waiter waiter)方法;实际上,args(combaobaotaoWaiter)等价于execution( (combaobaotaoWaiter+)),当然也等价于args(combaobaotaoWaiter+)。

2)@args()

该函数接受一个注解类的类名,当方法的运行时入参对象标注发指定的注解时,方法匹配切点。这个切点函数的匹配规则不太容易理解,我们通过以下示意图对此进行详细讲解:

2点为线,3点为面,所谓切面差不多就这个意思,如:

comkingadminctrl

comkingbookctrl;

comkinglogctrl;

所有模块的Controller都是在ctrl包里,那么要拦截做处理,那么拦截点时什么呢?

那应该这么写:

@Pointcut("execution( comkingctrl())")

ctrl就是切入点,

那这样写来干嘛呢?——因为这种特殊模块可能有特别处理,那么可以切入,拦截并干点啥?能干啥呢?——可以获取到即将要调的方法,也可以获取传过去的参数,也可以获取到处理后的数据,这就是切

设(x-3)^2+(y-4)^2=5圆心为M,直线PM斜率:KPM=(4-3)/(3-0)=1/3

设N:x^2+y^2-4x+ky+4=0圆心为N,直线PN斜率:KPN=(-k/2-3)/(2-0)=-(k+6)/4

显然直线AB垂直于PM,直线CD垂直于PN

要使AB垂直于CD

则PM垂直于PN

则KPMKPN=-1

则(1/3)[-(k+6)/4]=-1

=> k=6

以上就是关于spring-AOP(二) 自动代理全部的内容,包括:spring-AOP(二) 自动代理、Spring 常用注解大全、springboot aop的execution 表达式详解等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/9406919.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-28
下一篇2023-04-28

发表评论

登录后才能评论

评论列表(0条)

    保存