AOP底层实现动态代理

1、导入spring-aop包依赖

<!--aopV1-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<scope>test</scope>
</dependency>
<!--aopV2-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>

2、在配置类加入注解

@EnableAspectJAutoProxy//启用AOP切面(开启注解版的AOP功能)

3、@Aspect

在切面类上加入@Aspect表示这是一个切面

4、切面配置

spring官方文档(https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-introduction-defn)

  1. /*

  2. 标识这个方法是个前置通知, 切点表达式表示执行任意类的任意方法.

  3. 第一个 * 代表匹配任意修饰符及任意返回值,

  4. 第二个 * 代表任意类的对象,

  5. 第三个 * 代表任意方法,

  6. 参数列表中的 .. 匹配任意数量的参数

  7. */

  • The execution of any public method:

        execution(public * *(..))
  • The execution of any method with a name that begins with set:

        execution(* set*(..))
  • The execution of any method defined by the AccountService interface:

        execution(* com.xyz.service.AccountService.*(..))
  • The execution of any method defined in the service package:

        execution(* com.xyz.service.*.*(..))
  • The execution of any method defined in the service package or one of its sub-packages:

        execution(* com.xyz.service..*.*(..))
  • Any join point (method execution only in Spring AOP) within the service package:

        within(com.xyz.service.*)
  • Any join point (method execution only in Spring AOP) within the service package or one of its sub-packages:

        within(com.xyz.service..*)
  • Any join point (method execution only in Spring AOP) where the proxy implements the AccountService interface:

        this(com.xyz.service.AccountService)

@Before("execution(* com.xyz.myapp.dao.*.*(..))")

@AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")

指定返回参数

@AfterReturning(
pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}

制定异常(返回)

@Aspect
public class AfterThrowingExample { @AfterThrowing(
pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
}

获取输出参数

//先定义切点(Pointcut)
@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {} //使用切点
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
// ...
}

使用

@Configuration
@ComponentScan("com.test2")
@EnableAspectJAutoProxy
public class Config1 { public static void main(String[] args) {
AnnotationConfigApplicationContext content = new AnnotationConfigApplicationContext(Config1.class);
System.out.println("********BeanDefinition*********");
Arrays.stream(content.getBeanDefinitionNames()).forEach(val -> {
System.out.println(val);
});
System.out.println("********BeanDefinition*********");
System.out.println("############spring--容器启动完成############");
TestService testService = content.getBean(TestService.class);
testService.addUser();
} } //控制台输出
LogAspect--around-before
LogAspect--before
com.test2.bean.TestService执行addUser动作
LogAspect--afterReturning返回结果:添加成功
LogAspect--after
LogAspect--around-after返回值:添加成功

Aspect类定义

package com.test2.bean;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component; /**
* Created by user on 2021/1/18.
*/
@Component
@Aspect
public class LogAspect { @Pointcut("execution(* com.test2.bean.TestService.*(..))")
public void pointCut() {
} //JoinPoint必须是方法的第一个参数,否则spring会无法识别。具体查看spring源码分析
@Before("execution(public String com.test2.bean.TestService.addUser())")
public void before(JoinPoint point) {
System.out.println(point.getSignature().getName());//方法
System.out.println(point.getArgs());//参数
System.out.println("LogAspect--before");
} @After(value = "pointCut()")
public void after() {
System.out.println("LogAspect--after");
} @AfterReturning(value = "pointCut()", returning = "result")
public void afterReturning(Object result) {
System.out.println("LogAspect--afterReturning" + "返回结果:" + result);
} @AfterThrowing(value = "pointCut()", throwing = "exception")
public void afterThrowing(Exception exception) {
System.out.println("LogAspect--AfterThrowing" + exception.getMessage());
} @Around("pointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("LogAspect--around-before");
long beginTime = System.currentTimeMillis();
Object result = point.proceed();//实际方法执行
long time = System.currentTimeMillis() - beginTime;
System.out.println("LogAspect--around-after" + "返回值:" + result);
return result;
}
}
@Service
public class TestService { public String addUser() {
System.out.println(this.getClass().getName() + "执行addUser动作");
int i = 1/0;//手动抛出异常【如果手动抛出异常时,afterThrowing会被执行,且能够接受到具体的异常信息】
return "添加成功";
}
}

AOP原理


@EnableAspectJAutoProxy

//加入AOP后,才开始起作用

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
//给容器导入某个Bean
@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; }

导入组件(AspectJAutoProxyRegistrar)

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

}

给Spring容器导入Bean---AspectJAutoProxyRegistrar,由于实现了ImportBeanDefinitionRegistrar,也就是在Spring容器启动的时候会调用registerBeanDefinitions方法;

这个是invokeBeanFactoryPostProcessors的时候执行,这个可以给Spring容器直接导入Bean的定义;然后spring在IOC的时候,就会创建对应的bean

ImportBeanDefinitionRegistrar

org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator

关键方法执行

#Spring-AOP之AspectJ切面理解
+ 在配置类中加入@EnableAspectJAutoProxy注解支持
(1)问题,为什么加入了@EnableAspectJAutoProxy注解后,spring-AOP就起作用了?
(1)通过使用注解后,注解内部导入了org.springframework.context.annotation.AspectJAutoProxyRegistrar类【这个类起关键作用】
+ 注解@EnableAspectJAutoProxy定义
//org.springframework.context.annotation.AspectJAutoProxyRegistrar
@Import(AspectJAutoProxyRegistrar.class)//导入了AspectJAutoProxyRegistrar
public @interface EnableAspectJAutoProxy {}
+ AspectJAutoProxyRegistrar类干了什么事情?【@Import(AspectJAutoProxyRegistrar.class)//导入了AspectJAutoProxyRegistrar】
AspectJAutoProxyRegistrar类实现了接口 ImportBeanDefinitionRegistrar(org.springframework.context.annotation包)
+ 在spring容器初始化(refresh())的时候,会调用invokeBeanFactoryPostProcessors(beanFactory)方法
//Invoke factory processors registered as beans in the context【执行工厂后置处理器,注册一个bean在spring-上下文】
invokeBeanFactoryPostProcessors(beanFactory)
在这个方法里面就实现了对实现接口ImportBeanDefinitionRegistrar的处理【具体可以看另一篇文章针对ImportBeanDefinitionRegistrar合适处理的分析】
文章地址:https://blog.csdn.net/qqzhengwei/article/details/113102969
+ 上面执行完毕后,就会把 AspectJAutoProxyRegistrar 【完成AnnotationAwareAspectJAutoProxyCreator初始化new】
org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator的registerBeanDefinitions方法
+ AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry)//完成下面说的注册到spring容器中
beanName=org.springframework.aop.config.internalAutoProxyCreator 实例为 AnnotationAwareAspectJAutoProxyCreator,注册到spring-beanFactory中
+ 最终给spring容器注册了beanName=org.springframework.aop.config.internalAutoProxyCreator,实例为 AnnotationAwareAspectJAutoProxyCreator
+ 上面只是把AnnotationAwareAspectJAutoProxyCreator的beanDefinition注册到spring容器,但是这个时候bean还没有被实例化
+ AnnotationAwareAspectJAutoProxyCreator 在何时被初始化的呢?
【查看 AnnotationAwareAspectJAutoProxyCreator 类图结构】--看看他实现了什么接口,以及什么时候完成的初始化
1)、实现了 BeanFactoryAware 接口,也就是在 AnnotationAwareAspectJAutoProxyCreator spring创建这个bean的时候,会注册 beanFactory实例进来
这个时候就可以通过beanFactory放注册的bean
2)、实现了 BeanPostProcessor ,bean的后置处理器【这个都是在bean实例化的时候做的动作】
--postProcessBeforeInitialization 在bean初始化之前做的事情
--postProcessAfterInitialization 在bean实例化之后做的事情
+ org.springframework.context.support.AbstractApplicationContext的refresh()
+ registerBeanPostProcessors(beanFactory);//(注册bean的后置处理器,用来拦截bean的创建)Register bean processors that intercept bean creation.
+ PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this)
通过PostProcessorRegistrationDelegate去处理
//通过beanFactory在spring容器中把上面定义的AnnotationAwareAspectJAutoProxyCreator给找到
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
//通过beanFactory-根据beanName名称(org.springframework.aop.config.internalAutoProxyCreator)去容器中获取这个bean
//getBean,去spring容器获取bean,如果spring容器找不到,就去创建;(bean的实例化过程实现)
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class)
+ getBean的执行链路
org.springframework.beans.factory.support.AbstractBeanFactory类中的方法(为beanFactory的抽象类)
1)、getBean(String name, Class<T> requiredType)
2)、doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
3)、getSingleton(String beanName, ObjectFactory<?> singletonFactory)最终还是会执行下面createBean方法
3)、sharedInstance = getSingleton(beanName, () -> {return createBean(beanName, mbd, args))};之说核心方法
4)、createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
--org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
【这里执行了BeforeInstantiation】Object bean = resolveBeforeInstantiation(beanName, mbdToUse)
//Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
5)、doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
一、循环依赖问题解决(较早暴露bean)
二、createBeanInstance 通过无参构造函数实例化bean
三、populateBean 给bean的各种属性赋值
四、initializeBean 初始化bean
1)、invokeAwareMethods 调用实现了Aware接口的方法
2)、applyBeanPostProcessorsBeforeInitialization 执行前置处理器 postProcessBeforeInitialization
3)、invokeInitMethods 调用初始化方法:例如afterPropertiesSet实现InitializingBean接口
4)、postProcessAfterInitialization 执行后置处理器 postProcessAfterInitialization
【这个后置处理器就会调用 AnnotationAwareAspectJAutoProxyCreator 的postProcessBeforeInitialization和postProcessAfterInitialization方法】
+ 以上就完成了AnnotationAwareAspectJAutoProxyCreator的创建和注册到spring容器

AnnotationAwareAspectJAutoProxyCreator实现了接口BeanPostProcessor后置处理器,也就是以后创建任何Bean的都是都会调用

----》postProcessBeforeInitialization

---》postProcessAfterInitialization

方法,这样就实现了拦截处理。


InstantiationAwareBeanPostProcessor 后置处理器作用(使用契机)


==================================【以下是在实例化之前做的事情】 doCreateBean之前做的
+ interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor
InstantiationAwareBeanPostProcessor 也是一个BeanPostProcessor,只是是BeanPostProcessor的子接口
+ AnnotationAwareAspectJAutoProxyCreator ==> InstantiationAwareBeanPostProcessor【这个叫实例化之前的后置处理器】
=========
+ org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(beanFactory)
在spring容器初始化完成finishBeanFactoryInitialization
【AnnotationAwareAspectJAutoProxyCreator在所有bean创建之前都会有一个拦截的作用 因为他是一个 InstantiationAwareBeanPostProcessor 后置处理器】
【在容器AbstractApplicationContext.finishBeanFactoryInitialization(beanFactory)执行时产生的效果】--实例化bean容器里面所有的bean
+ 遍历容器中所有的bean,依次创建getBean(beanName)对象;beanFactory的beanDefinitionNames属性
+ [+ getBean的执行链路]查看上面的getBean执行链路
+ 创建bean实例
1)、先从缓存中看看是否存在bean实例;
如果能够获取到直接从缓存中获取(单例的bean),如果不存在的话,否则创建bean;
2)、createBean创建bean
【BeanPostProcessors 是在对象创建完成,初始化前后调用】
【InstantiationAwareBeanPostProcessor 是在对象,创建之前调用】
1)、Object bean = resolveBeforeInstantiation(beanName, mbdToUse)执行:创建bean前的,处理器
代码注释Give BeanPostProcessors a chance to return a proxy instead of the target bean instance
给BeanPostProcessors(后置处理器)一个机会,来返回一个代理对象,来替代真实的对象【代理就是在这里开始的】
+ 【这里执行了BeforeInstantiation】Object bean = resolveBeforeInstantiation(beanName, mbdToUse)
+ resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd)
//执行前置
bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
如果bp instanceof InstantiationAwareBeanPostProcessor 就执行后置处理器的postProcessBeforeInstantiation方法
if (bean != null) {
bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
执行后置--postProcessAfterInitialization
}
2)、 希望后置处理器在这里可以返回一个代理对象,如果能返回就使用代理对象bean,如果不能返回,就在创建bean;
Object beanInstance = doCreateBean(beanName, mbdToUse, args)
+ AnnotationAwareAspectJAutoProxyCreator.postProcessBeforeInstantiation方法又干了什么事情呢?
要获取代理bean这样获取
BeanFactory接口里面的常量String FACTORY_BEAN_PREFIX = "&";
BeanFactory.FACTORY_BEAN_PREFIX + beanName
1)、postProcessBeforeInstantiation
1)、判断当前bean是否已经增强
2)、在判断beanClass是否是基础类型的(Advice、Pointcut、Advisor、AopInfrastructureBean、@AspectJ)
3)、是否需要跳过shouldSkip()
1)、获取候选的增强器(切面里面的通知方法)【List<Advisor> candidateAdvisors增强器集合】
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
List<Advisor> candidateAdvisors = findCandidateAdvisors();
for (Advisor advisor : candidateAdvisors) {
if (advisor instanceof AspectJPointcutAdvisor &&
((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
return true;
}
}
return super.shouldSkip(beanClass, beanName);
}
每一个封装的通知方法的增强器是 InstantiationModelAwarePointcutAdvisor;
判断每一个增强器是否是 AspectJPointcutAdvisor 类型的;返回true
2)、永远返回false
  2)、创建对象 postProcessAfterInitialization
return wrapIfNecessary(bean, beanName, cacheKey); //包装如果需要的情况下
1)、获取当前bean的所有增强器(通知方法) Object[] specificInterceptors
1、找到候选的所有的增强器(找哪些通知方法是需要切入当前bean方法的)
2、获取到能在bean使用的增强器。
3、给增强器排序使用@Order(3)数值越小,优先级越高。越优先执行
2)、保存当前bean在advisedBeans中;
3)、如果当前bean需要增强,创建当前bean的代理对象;
1)、获取所有增强器(通知方法)
2)、保存到proxyFactory
3)、创建代理对象:Spring自动决定
JdkDynamicAopProxy(config);jdk动态代理;
ObjenesisCglibAopProxy(config);cglib的动态代理;
4)、给容器中返回当前组件使用cglib增强了的代理对象;
5)、以后容器中获取到的就是这个组件的代理对象,执行目标方法的时候,代理对象就会执行通知方法的流程;
3)、目标方法执行 ;
容器中保存了组件的代理对象(cglib增强后的对象),这个对象里面保存了详细信息(比如增强器,目标对象,xxx);
1)、CglibAopProxy.intercept();拦截目标方法的执行
2)、根据ProxyFactory对象获取将要执行的目标方法拦截器链;
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
1)、List<Object> interceptorList保存所有拦截器
一个默认的ExposeInvocationInterceptor 和 配置的增强器;
2)、遍历所有的增强器,将其转为Interceptor;
registry.getInterceptors(advisor);
3)、将增强器转为List<MethodInterceptor>;
如果是MethodInterceptor,直接加入到集合中
如果不是,使用AdvisorAdapter将增强器转为MethodInterceptor;
转换完成返回MethodInterceptor数组;
3)、如果没有拦截器链,直接执行目标方法;
  retVal = methodProxy.invoke(target, argsToUse)
拦截器链(每一个通知方法又被包装为方法拦截器,利用MethodInterceptor机制)
4)、如果有拦截器链,把需要执行的目标对象,目标方法,
拦截器链等信息传入创建一个 CglibMethodInvocation 对象,
并调用 Object retVal = mi.proceed();
5)、拦截器链的触发过程;
new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed()
            相当于执行了父类的org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()方法
            1)、如果没有拦截器执行执行目标方法,或者拦截器的索引和拦截器数组-1大小一样(指定到了最后一个拦截器)执行目标方法;
            2)、链式获取每一个拦截器,拦截器执行invoke方法,每一个拦截器等待下一个拦截器执行完成返回以后再来执行;
                拦截器链的机制,保证通知方法与目标方法的执行顺序;
        public Object proceed() throws Throwable {
            //currentInterceptorIndex初始化是-1,interceptorsAndDynamicMethodMatchers拦截器链集合
            // We start with an index of -1 and increment early.
            if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
                return invokeJoinpoint();
            }
            //一个一个拦截器链的取
            Object interceptorOrInterceptionAdvice =
                    this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
            if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
                // Evaluate dynamic method matcher here: static part will already have
                // been evaluated and found to match.
                InterceptorAndDynamicMethodMatcher dm =
                        (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
                Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
                if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
                    return dm.interceptor.invoke(this);
                }
                else {
                    // Dynamic matching failed.
                    // Skip this interceptor and invoke the next in the chain.
                    return proceed();
                }
            }
            else {
                // It's an interceptor, so we just invoke it: The pointcut will have
                // been evaluated statically before this object was constructed.
                return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
            }
        }
# 总结
1)、 @EnableAspectJAutoProxy 开启AOP功能
2)、 @EnableAspectJAutoProxy 会给容器中注册一个组件 AnnotationAwareAspectJAutoProxyCreator
3)、AnnotationAwareAspectJAutoProxyCreator是一个后置处理器;
4)、容器的创建流程:
1)、registerBeanPostProcessors()注册后置处理器;创建AnnotationAwareAspectJAutoProxyCreator对象
2)、finishBeanFactoryInitialization()初始化剩下的单实例bean
1)、创建业务逻辑组件和切面组件
2)、AnnotationAwareAspectJAutoProxyCreator拦截组件的创建过程
3)、组件创建完之后,判断组件是否需要增强
是:切面的通知方法,包装成增强器(Advisor);给业务逻辑组件创建一个代理对象(cglib);
5)、执行目标方法:
1)、代理对象执行目标方法
2)、CglibAopProxy.intercept();
1)、得到目标方法的拦截器链(增强器包装成拦截器MethodInterceptor)
2)、利用拦截器的链式机制,依次进入每一个拦截器进行执行;
3)、效果:
正常执行:前置通知-》目标方法-》后置通知-》返回通知
出现异常:前置通知-》目标方法-》后置通知-》异常通知
  • 拦截器链执行过程

拦截器返回通过

  • 拦截器链

  • 增强器 InstantiationModelAwarePointcutAdvisor接口的的实例InstantiationModelAwarePointcutAdvisorImpl

动态代理创建

org.springframework.aop.framework.DefaultAopProxyFactory创建代理
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
//如果实现了接口或者已经是代理类
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);//JDK动态代理
}
return new ObjenesisCglibAopProxy(config);//Cglib动态代理
}
else {
return new JdkDynamicAopProxy(config);
}
}
	AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
this();
register(annotatedClasses);
refresh();
}

//实例化剩下的不是懒加载的 单实例Bean
finishBeanFactoryInitialization(beanFactory);

beanFactory.preInstantiateSingletons();

// ? 构造器执行,创建一个Bean
getBean(beanName);

doGetBean

getSingleton

doCreateBean{
//将需要Autowired 的属性方法执行一遍
populateBean(beanName, mbd, instanceWrapper); initializeBean(beanName, exposedObject, mbd){
// aware 方法执行,给实现了xxAware的方法设置值
invokeAwareMethods(beanName, bean);
applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName){
beanProcessor.postProcessBeforeInitialization(result, beanName){
invokeAwareInterfaces(bean){
((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
}
}
}
// 初始化方法,执行init
invokeInitMethods(beanName, wrappedBean, mbd);
applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
}

Spring基于注解开发的注解使用之AOP(部分源代码分析)的更多相关文章

  1. Spring基于Java的JSR-250注解

    以下内容引用自http://wiki.jikexueyuan.com/project/spring/annotation-based-configuration/spring-jsr250-annot ...

  2. Hibernate注解开发、注解创建索引

    1.注解的目的 简化繁琐的ORM映射文件(*.hbm)的配置 2.JPA和hibernate的关系 JPA:java persistence API,JPA注解是JavaEE的标准和规范. 两者的关系 ...

  3. 手把手教你基于C#开发WinCC语音报警插件「附源代码」

    写在前面 众所周知,WinCC本身是可以利用C脚本或者VBS脚本来做语音报警,但是这种方式的本质是调用已存在的音频文件,想要实现实时播报报警信息是不行的,灵活性还不够,本文主要介绍基于C#/.NET开 ...

  4. Spring注解开发系列Ⅵ --- AOP&事务

    注解开发 --- AOP AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,横向重复,纵向抽取.详细的AO ...

  5. Annotation(三)——Spring注解开发

    Spring框架的核心功能IoC(Inversion of Control),也就是通过Spring容器进行对象的管理,以及对象之间组合关系的映射.通常情况下我们会在xml配置文件中进行action, ...

  6. Spring 注解开发

    目录 注解开发简介 常用注解 启用注解功能 bean 定义:@Component.@Controller.@Service.@Repository bean 的引用类型属性注入:@Autowired. ...

  7. Spring-05 使用注解开发

    Spring-05 使用注解开发 使用注解开发 1.项目准备 在spring4之后,想要使用注解形式,必须得要引入aop的包5 <!-- https://mvnrepository.com/ar ...

  8. spring-aop-事务-注解开发-代理

    1.spring + mybatis: Aop流程: 提前定义好几个用于Aop的类 前置通知:新建MyBeForeAdvice类 实现 MethodBeforeAdvice,并实现其方法 后置通知:新 ...

  9. Spring的bean管理(注解)

    前端时间总是用配置文件  内容太多 下面认识一下注解 注解是什么? 1代码里面的特殊标记,使用注解可以完成功能 2注解写法@XXX 3使用注解可以少些很多配置文件 Spring注解开发准备 注解创建准 ...

随机推荐

  1. SpringBoot执行原理

    目录 [Toc] 一.执行原理: 每个Spring Boot项目都有一个主程序启动类,在主程序启动类中有一个启动项目的main()方法, 在该方法中通过执行SpringApplication.run( ...

  2. Atlas 2.1.0 实践(1)—— 编译Atlas

    为什么要做数据治理? 业务繁多,数据繁多,业务数据不断迭代.人员流动,文档不全,逻辑不清楚,对于数据很难直观理解,后期很难维护. 在大数据研发中,原始数据就有着非常多的数据库,数据表. 而经过数据的聚 ...

  3. jQuery中live()使用报错,TypeError: $(...).live is not a function

    原博文 https://blog.csdn.net/sdfdyubo/article/details/59536781 使用 原写法 /*为选项卡绑定右键*/ $(".tabs li&quo ...

  4. Listary效率快捷键

    快捷键 打开搜索框快捷键: 双击Ctrl键 上一个项目:向上键 下一个项目:向下键/Tap 打开动作:Enter 属性窗口:Ctrl+O (查询)关键字 作用范围:搜索框 使用方法:命令+空格+关键字 ...

  5. springmvc 统一处理异常

    1.自定义统一异常处理器 自定义Exception实现 HandlerExceptionResolver接口或继承AbstractHandlerExceptionResolver类 1.实现接口Han ...

  6. [leetcode]罗马数字和阿拉伯数字相互转换

    罗马转阿拉伯 public int romanToInt(String s) { /* 从左到右依次根据哈希表进行加法 如果是"CM"900这种情况就要执行+M和-C处理 */ i ...

  7. ESP8288-01S/ESP8288-01使用经验总结

    如图:ESP8288-01S/ESP8288-01的区别 超链接 ESP8288-01S/ESP8288-01是乐鑫公司推出的Wi-Fi模块,因为价格便宜迅速占领了市场,它可以做AT指令开发,也可以做 ...

  8. 我是这样理解EventLoop的

    我是这样理解EventLoop的 一.前言   众所周知,在使用javascript时,经常需要考虑程序中存在异步的情况,如果对异步考虑不周,很容易在开发中出现技术错误和业务错误.作为一名合格的jav ...

  9. Linux目录的慨念

    1.路径 对文件进行访问时,要用到"路径"(Path)的慨念. 顾名思义,路径是指从树型目录中的某个目录层次到某个文件的一条道路. 此路径的主要构成是目录名称,中间用"/ ...

  10. SalesForce学习——权限管理

    Salesforce 对于权限的管理是非常严谨的并且支持不同维度的权限控制.常用的有简档.权限集.角色层级机构.组织范围. 简档:Profiles是Salesforce为每个用户指定的标准配置文件,在 ...