概述
Spring
的两大核心:IoC
和AOP
,IoC
作为Spring
的根基,通过大量的扩展点让系统轻而易举的就可以实现良好的扩展性,而AOP
和IoC
结合在一起,类似于发生强大化学反应一样,将Spring
的功能性又提高了一个层次。Spring
中也有大量使用AOP
场景,比如@Configuration
、数据库事务、mybatis mapper
接口注入等等。
AOP
全称Aspect Oriented Programming
,即面向切面编程,其并非Spring
独有,作为一种对OOP
编程思想的补充,其也有自己的标准规范并有独立的组织进行维护。
【资料图】
根据织入时机的不同,AOP
又可以分为三类:
ApectJ
主要采用的就是编译时织入方式,这种一般使用特定的编译器方式实现;类加载时织入:这种一般都是依赖JVM Instruments
技术实现,Spring中也有对这种技术支持,具体可以了解下LoadTimeWeaver
;动态织入:动态代理方式实现AOP
就是动态织入场景,Spring
中实现AOP
最主要方式,根据动态代理方式不同,又可以分为:JDK动态代理
或CGLIB动态代理
。AOP
标准规范是由独立的组织机构进行维护,其涉及到的核心概念主要如下:
JoinPoint
):程序运行中的某个阶段点,比如方法的调用、异常的抛出、类初始化和对象实例化等。连接点是AOP
的核心概念,并且定义了在应用程序中可以使用AOP
插入其它逻辑的点;切点(Pointcut
):切点是基于规则定义如何查找连接点,其可以看成包含一系列连接点的组合,Spring
中对应的是Pointcut
接口,定义了哪些类的哪些方法需要织入增强;通知(Advice
):在连接点处需要织入的增强代码逻辑封装;切面(Aspect
):切面是Advice
和Pointcut
组合,对应Spring
中Advisor
;织入(Weaving
):织入是在适当的位置将切面插入到应用程序代码中的过程,就是上面说的编译时织入、类加载时织入和动态织入;目标对象(target
):AOP
代理增强的原生对象;基础API
Spring AOP
很多人不能很好的理解、使用,一方面是因为AOP
涉及的概念可能比较抽象,不容易理解;另外一方面你对Spring AOP
涉及到的一些基础API
不熟悉。下面我们就对Spring AOP
中最核心的一些API
,由底向上,由基础到高级方式一步步分析。
Enhancer
Spring AOP
主要使用的是动态代理方式实现,动态代理实现主要包括两种:jdk动态代理
和cglib动态代理
。jdk动态代理
方式比较熟悉,下面就来看下cglib动态代理
如何实现。
Spring
中提供了一个工具类:Enhancer
,Spring
中主要就是利用该工具类创建cglib动态代理
。下面我们通过一个案例看下其基本使用:
1、创建Callback
回调接口类,该接口中就可以实现增强逻辑:
public class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { try { before(method);//前置通知 Object ret = methodProxy.invokeSuper(obj, args);//目标方法执行 after(method, ret);//后置通知 return ret; } catch (Exception e) { exception();//异常通知 } finally { afterReturning();//方法返回通知 } return null; } //前置增强 private void before(Method method) { System.out.printf("before execute:%s\r\n", method.getName()); } //后置增强 private void after(Method method, Object ret) { System.out.printf("after execute:%s, ret:%s\r\n", method.getName(), ret); } //异常增强 private void exception() { System.out.println("execute failure"); } //after返回增强 private void afterReturning() { System.out.println("execute finish"); } }
2、编写测试:
//NoOp.INSTANCE:NoOp回调把对方法调用直接委派给这个方法在父类中的实现,即可理解为真实对象直接调用方法,没有任何增强private static final Callback[] CALLBACKS = new Callback[] { new MyMethodInterceptor(), NoOp.INSTANCE};public void test() { //创建Enhancer实例 Enhancer enhancer = new Enhancer(); //cglib是基于继承方式代理,superClass就是基于哪个类型父类进行增强,创建出来的对象就是该类型子类 enhancer.setSuperclass(UserServiceImpl.class); //CallbackFilter主要用于过滤不同Method使用不同的Callback enhancer.setCallbackFilter(new CallbackFilter() { @Override public int accept(Method method) { if (method.getDeclaringClass() == Object.class) { return 1;//使用Callback数组下标是1的 } return 0;//使用Callback数组下标是0的 } }); //设置Callback数组,Callback就是封装的增强逻辑 enhancer.setCallbacks(CALLBACKS); //创建代理对象 UserService proxyObj = (UserService) enhancer.create(); System.out.println(proxyObj.say("zhangsan"));}
通过上面enhancer.create()
这条语句,就可以为目标类创建一个cglib动态代理
,通过Callback
回调方式将各种增强逻辑织入到代理实例中。
还可以使用
Enhancer.createClass()
方法只创建出代理类型,然后自己通过反射方式创建对象。这时,需要注意:1、这时就不能使用setCallbacks()
设置Callback
数组,而是使用setCallbackTypes()
设置Callback
对应的类型;2、Enhancer.createClass()
执行完成后,再通过Enhancer.registerStaticCallbacks(clz, CALLBACKS)
方式设置Callback
数组;
enhancer.setInterfaces()
可用于设置生成的代理类必须实现的接口,比如你可以不设置superclass
,只设置interfaces
,这时也是可以正常创建出基于这个接口的动态代理实例,但是这时就要注意不能触发目标对象方法执行,如methodProxy.invokeSuper
执行会报错,如下:
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { try { before(method);//前置通知 //Object ret = methodProxy.invokeSuper(obj, args);//目标方法执行 after(method, ret);//后置通知 return ret; } catch (Exception e) { exception();//异常通知 } finally { afterReturning();//方法返回通知 } return null;}
基于接口创建的代理实例还是非常有用的,比如mybatis mapper
就是一个没有实现类的接口,但是在spring
中却可以依赖注入到service bean
中,其中就是利用到上面基于接口创建动态代理的思想,注入进来的其实就是基于接口的动态代理,然后调用接口中方法时就可以进行拦截,获取到具体调用方法签名信息以及参数信息,基于这些数据进行业务逻辑处理。
invoke和invokeSuper方法区别
Callback#intercept()
回调方法中执行methodProxy.invokeSuper()
和methodProxy.invoke()
是有很大区别的,而且看不到执行流程,所以这里涉及的逻辑非常绕。
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object ret = methodProxy.invokeSuper(obj, args); Object ret = methodProxy.invoke(delete, args);}
大致区别如下图:
客户端触发代理对象say
方法调用,首先进入代理对象中的同名方法,然后进入方法拦截对象MethodInterceptor
,这里会出现两种情况:
invokeSuper
方法,流程会重新走到代理对象中,代理对象这时会识别出是调用super
中同名方法,所以没有继续向下走,而是通过super.say()
方式调用目标对象中的方法。如果调用invoke
方法,代理对象内部其实包裹一个目标对象target
,这时它是直接通过target.say
方式调用目标对象。invokeSuper()
和invoke()
方法都可以调用到目标对象方法,但是它们之间存在的一个本质区别:上下文环境不一样;或者更直接说:目标对象中this
指向不一样。通过super.say()
方式调用的目标对象,this
指向的是代理对象;而通过target.say()
方式调用的,目标对象中this
指向的就是目标对象本身。这会导致什么差异呢?
假如目标对象类型如下定义,然后使用Enhancer
创建一个代理对象:
public class Target { public void a() { System.out.println(" a 方法"); b(); } public void b() { System.out.println(" b 方法"); }}
客户端触发代理对象a()
方法执行,如果拦截器中使用invoke
方式调用目标对象:直接调用目标对象a()
方法,这个方法中又会通过this.b()
调用方法b
,由于是目标对象本身内部调用,所以b()
方法不会被拦截的。
客户端触发代理对象a()
方法执行,如果拦截器中使用invokeSuper()
方式调用目标对象:这里是通过super.a()
方式调用目标对象中的a()
方法,然后a()
方法又会通过this.b()
调用方法b
,注意这时的this
不是目标对象本身,而是代理对象,因为代理对象继承目标对象,代理对象会有重名方法覆写了目标对象方法。所以,this.b()
实际上会触发代理对象中方法b
的执行,这时是会触发拦截器的。
所以,methodProxy.invokeSuper(obj, args)
这个obj
是代理对象;而methodProxy.invoke(obj, args)
这个入参obj
是目标对象。搞清楚这些基本理解清楚应该使用invoke
还是invokeSuper
。
ProxyFactory
Enhancer
只能用于创建cglib动态代理
,Spring
中还有一个更上层点的类ProxyFactory
,可以用于创建JDK动态代理
或cglib动态代理
。
public void test02() { ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTarget(new UserServiceImpl()); /* 调用ProxyFactory.addInterface()或setInterfaces()表示对接口进行代理,一般会使用jdk动态代理, 除非setOptimize(true)或setProxyTargetClass(true)表示使用cglib代理 cglib代理类名称格式大致为:ServiceImpl$$EnhancerBySpringCGLIB$$f2952b94 */ //setInterfaces设置这个,会基于接口代理,使用jdk动态代理方式 proxyFactory.setInterfaces(UserService.class); //proxyFactory.setOptimize(true); //proxyTargetClass=true:直接代理目标类,而不是接口,使用cglib proxyFactory.setProxyTargetClass(true); // 添加增强 proxyFactory.addAdvice(new MyBeforeAdvice02()); //内部使用了缓存,target不变时,getProxy()获取到的都是同一个 //只有target变化时,才会重新创建新的代理对象 Object proxy = proxyFactory.getProxy();}
ProxyFactory
类是AOP
底层实现中非常重要的一个类,另外AnnotationAwareAspectJAutoProxyCreator
、BeanNameAutoProxyCreator
、DefaultAdvisorAutoProxyCreator
等一些高级AOP
实现工具类都是通过在其父类AbstractAutoProxyCreator
中借助ProxyFactory
实现AOP
逻辑织入的。所以,理解ProxyFactory
的使用对理解Spring AOP
至关重要。
ProxyFactory
类控制代理的创建过程,其内部委托给DefaultAopProxyFactory
的一个实例,该实例又转而委托给Cglib2AopProxy
或JdkDynamicAopProxy
,用于创建基于cglib
代理还是jdk
代理,想了解这两种动态代理区别可以分析下这个类源码。
ProxyFactory
类addAdvice()
方法将传入的通知封装到DefaultPointcutAdvisor
(DefaultPointcutAdvisor
是PointcutAdvisor
的标准实现)的一个实例中,并使用默认包含所有方法的切入点对其进行配置。为更加灵活细粒度的控制在哪些连接点上拦截通知,可以使用addAdVisor()
方法添加一个带有切入点消息的Advisor
。
可以使用相同的ProxyFactory
实例来创建多个代理,每个代理都有不同的切面。为了帮助实现该过程,ProxyFactory
提供了removeAdvice()
和removeAdvisor()
方法,这些方法允许从ProxyFactory
中删除之前传入的任何通知或切面,同时可以使用boolean adviceIncluded(@Nullable Advice advice)
检查ProxyFactory
是否附有特定的通知对象。
Advice
ProxyFactory
类addAdvice()
和addAdvisor()
两个方法分别引入了两个重要的类:Advice
和Advisor
。首先,我们来看下Advice
这个接口类,其可以看成需要织入增强的代码逻辑封装。Advice
在Spring
中API
结构如下:
大致描述:
BeforeAdvice
:前置通知,该接口没有任何方法,平时主要使用其子类MethodBeforeAdvice
,因为Spring中只能在方法前后织入,对应注解@Before
;AfterAdvice
:后置通知,其存在两个子类AfterReturningAdvice
和ThrowsAdvice
,分别对应@AfterReturning
和@AfterThrowing
;MethodInterceptor
:可以实现环绕通知,对应注解@Around
;Advisor
在AOP
规范中有切面概念,在Spring
中大概对应就是Advisor
。Advisor
有两个子接口:PointcutAdvisor
和IntroductionAdvisor
:
其实真正使用比较多的是它的子类PointcutAdvisor
,该接口关键就是如下两个方法:
public interface PointcutAdvisor { Advice getAdvice(); Pointcut getPointcut();}
PointcutAdvisor
从接口定义大概就可以看出,其就是对Advice
和Pointcut
的封装,Advice
代表的是横切面需要织入的代码,而Pointcut
定义了如何去切的问题。从之前分析来看,Advice
也可以看出一种非常简单的切面,是对指定的类所有方法都进行切入,横切面太宽泛,灵活性不够,PointAdvisor
引入了Pointcut
后显然比Advice
更加灵活、强大。
PointcutAdvisor
主要有6个具体的实现类,分别介绍如下:
DefaultPointcutAdvisor
:最常用的切面类型,它可以通过任意Pointcut
和Advice
定义一个切面,一般可以通过扩展该类实现自定义的切面;NameMatchMethodPointcutAdvisor
:通过该类可以定义按方法名定义切点的切面;RegexpMethodPointcutAdvisor
:使用正则表达式模式定义切点,其内部通过JdkRegexpMethodPointcut
构造出正则表达式方法名切点;StaticMethodMatcherPointcutAdvisor
:静态方法匹配器切点定义的切面,默认情况下,匹配所有的目标类;AspecJExpressionPointcutAdvisor
:用于Aspecj
切点表达式定义切点的切面;AspecJPointcutAdvisor
:用于AspecJ
语法定义切点的切面;其实,这些Advisor
主要区别还是基于其内部封装的Pointcut
实现类体现的,在实际工作中这些类使用的可能不多,这里的核心在于Pointcut
如何定义切入点,所以实际开发中更多的可能会去定制Pointcut
实现类,然后使用DefaultPointcutAdvisor
将其包装成Advisor
使用。
下面通过RegexpMethodPointcutAdvisor
案例简单了解即可:
public void test1(){ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("aop.demo03"); UserServiceImpl target = new UserServiceImpl(); ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); proxyFactoryBean.setTarget(target); proxyFactoryBean.setProxyTargetClass(true); RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor(); //设置advisor的advice advisor.setAdvice(new MyBeforeAdvice02()); //设置advisor的pointcut,aop.demo03包下所有类中已say开头的方法才会织入 advisor.setPattern("aop.demo03..*.say*"); proxyFactoryBean.addAdvisor(advisor); proxyFactoryBean.setBeanFactory(context); Object obj = proxyFactoryBean.getObject(); System.out.println(obj.getClass().getName()); UserServiceImpl userService = (UserServiceImpl) obj; System.out.println(userService.say("haha"));}
RegexpMethodPointcutAdvisor
表示通过正则表达式进行切点描述的切面,它有一个pattern
属性用来指定增强要应用到哪些类的哪些方法,也可以通过patterns
属性指定多个表达式进行匹配。有一个advice
属性用来表示要应用的增强,这样就能表示一个完整的切面了。
Pointcut
Advisor
引入了一个核心接口Pointcut
,其描述了对哪些类的哪些方法进行切入。Pointcut
从其定义可以看出其由ClassFilter
和MethodMatcher
构成。ClassFilter
用于定位哪些类可以进行切入,然后再通过MethodMatcher
定位类上的哪些方法可以进行切入,这样Pointcut
就拥有了识别哪些类的哪些方法能被进行切入的能力。
public interface Pointcut { ClassFilter getClassFilter(); MethodMatcher getMethodMatcher();}
Pointcut
接口API
结构见下:
其中这里比较常用的AnnotationMatchingPointcut
基于注解进行切入,之前分析【Spring源码】- 09 扩展点之@Import注解一节就使用到该实现类,将标记有@MyAsync
注解的方法都进行增强就是利用这个实现类:
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(MyAsync.class);Advice advice = new AsyncAnnotationAdvice(executor);DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();advisor.setPointcut(pointcut);advisor.setAdvice(advice);ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.setTarget(bean);if(!this.isProxyTargetClass()){ proxyFactory.setInterfaces(bean.getClass().getInterfaces());}proxyFactory.addAdvisor(advisor);proxyFactory.copyFrom(this);return proxyFactory.getProxy();
ProxyFactoryBean
ProxyFactoryBean
和ProxyFactory
功能和使用其实差不多,底层逻辑也基本一致,ProxyFactoryBean
主要是融合了IOC
功能。一方面ProxyFactoryBean
类是FactoryBean
的一个实现,更加方便注入IoC
中,参照mybatis
与spring
整合一节,其中关键一步就是将扫描的BeanDefinition
中beanClass
由接口类替换成FactoryBean
类型;另一点就是切面可以使用IoC
容器bean
。
下面通过一个案例简单看下ProxyFactoryBean
使用:
package org.simon.ioc.demo1;import org.springframework.aop.MethodBeforeAdvice;import org.springframework.stereotype.Component;import java.lang.reflect.Method;@Componentpublic class MyBeforeAdvice implements MethodBeforeAdvice { public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { System.out.println("-----洗手-----"); }}
@Testpublic void test1(){ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("org.simon.ioc.demo1"); UserService target = new UserServiceImpl(); ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); proxyFactoryBean.setTarget(target); proxyFactoryBean.setInterceptorNames("myBeforeAdvice", "otherAdvice*"); proxyFactoryBean.setBeanFactory(context); Object obj = proxyFactoryBean.getObject(); System.out.println(obj.getClass().getName()); UserService userService = (UserService) obj; System.out.println(userService.say("haha"));}
其中关键一步在:proxyFactoryBean.setInterceptorNames("myBeforeAdvice", "otherAdvice*");
,可以使用IoC
中的beanName
,同时还支持通配符*
从IoC
中查找对应Bean
。
高级API
前面介绍的类、接口等都是Spring AOP
中一些底层API
,使用起来不太方便,感觉功能不太强大,不论是ProxyFactory
还是ProxyFactoryBean
创建织入切面的代理,每次只能硬编码一个具体的Bean
,假如我想将某个包路径下符合一定规则的类的特定方法都进行织入代理怎么办?
使用前面那些API
好像都不能实现这个需求,但是结合之前分析的Spring
扩展点,很容易想到可以结合BeanPostProcessor
扩展点实现这个需求,postProcessAfterInitialization()
这个方法回调时Bean
依赖注入、初始化等都已经完成,这时就可以在这个方法中过滤出符合一定条件的Bean
进行代理增强处理。
其实,在Spring
中基于这种思想,已经为我们提供了三个实现类:
BeanNameAutoProxyCreator
:基于beanName
进行织入增强,通过setBeanNames(String... beanNames)
将需要增强的beanName
设置进去;DefaultAdvisorAutoProxyCreator
:基于Advisor
匹配机制进行织入增强,它会对容器中所有的Advisor
进行扫描,自动将这些切面应用到匹配的Bean
中;AnnotationAwareAspectjAutoProxyCreator
:基于Bean
中AspectJ
注解方式进行织入增强,就是实现平时使用的@Aspect
、@Before
、@Around
等AspectJ
注解功能支持。下面我们就来通过DefaultAdvisorAutoProxyCreator
了解下使用场景:
1、定义一个目标类,后续就是基于该类进行增强:
@Componentpublic class UserServiceImpl{ public String say(String name){ System.out.println("执行:==UserService#say==="); return "hello,"+name; }}
2、定义一个配置类:
@Configuration@ComponentScan(basePackageClasses = AopConfig.class)public class AopConfig { @Bean public RegexpMethodPointcutAdvisor regexpMethodPointcutAdvisor(){ RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor(); advisor.setAdvice(new MyBeforeAdvice02()); //aop.demo03包下所有类中带有say开头方法进行增强 advisor.setPattern("aop.demo03..*.say*"); return advisor; } @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){ return new DefaultAdvisorAutoProxyCreator(); }}
这个类有两个关键,一个是向IoC
中注入Advisor
,之前分析过Advisor
包含两个功能:
Pointcut
定位哪些类的哪些方法需求切入;通过关联的Advice
指定切入增强逻辑;另一个关键就是注入DefaultAdvisorAutoProxyCreator
,这个就是一个Spring
内置的实现BeanPostProcessor
扩展类,其在postProcessAfterInitialization()
方法中对Bean
进行切入增强。
3、测试:
public void test1(){ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class); System.out.println(context.getBean(UserServiceImpl.class).getClass());}
context.getBean(UserServiceImpl.class)
从IoC
容器中获取的就是使用cglib
代理后的实例。
下面我们再来分析下AnnotationAwareAspectjAutoProxyCreator
,平时如果项目中需要开启AOP
功能,使用@EnableAspectJAutoProxy
注解方式开启,我们来看下该注解干了什么?
1、@EnableAspectJAutoProxy
注解使用@Import
注解将AspectJAutoProxyRegistrar
引入:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Import(AspectJAutoProxyRegistrar.class)public @interface EnableAspectJAutoProxy { boolean proxyTargetClass() default false; boolean exposeProxy() default false;}
2、AspectJAutoProxyRegistrar
是ImportBeanDefinitionRegistrar
接口实现类:
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { //注册一个SmartInstantiationAwareBeanPostProcessor类型的实现类:AnnotationAwareAspectJAutoProxyCreator 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); } } }}
其中最关键一句AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
,就是向IoC容器中注入AnnotationAwareAspectJAutoProxyCreator
。
这样我们就明白了@EnableAspectJAutoProxy
注解方式开启AOP
的本质就像向IoC
中注入AnnotationAwareAspectJAutoProxyCreator
,它利用BeanPostProcessor
扩展点功能实现织入增强逻辑。
总结
首先,对Spring AOP
底层一些最基础、最核心的API
的分析梳理,相信你会对Spring AOP
底层实现逻辑有了一个更加深入的理解。然后通过Spring AOP
提供的高级API
,理解了如何将IoC
和AOP
集成到一起实现强大功能,对Spring
中AOP
的整体实现思路也有了比较清晰的认识。
标签:
-
【报资讯】【Spring源码】- 10 Spring AOP核心API
Spring的两大核心:IoC和AOP,IoC作为Spring的根基,通过大量的扩展点让系统轻而易举的就可以实现良好的扩展性,而AOP和IoC结合在一起,类似于
-
湖南益阳南洞庭湿地恢复得怎么样了?_全球快播
上世纪80年代,湖南引进并种植欧美黑杨,发展经济的同时给湿地生态造成了一定影响。这几年,湖南益阳南洞庭在清理欧美黑杨之后,继续坚持清除
-
又一外资巨头申请公募牌照!
近日,又一全球资管巨头安联投资递交公募基金管理人资格审批申请。随着首家外商独资公募基金管理公司贝莱德基金的获批展业,公募
-
无聊的近义词是什么(无聊的近义词是什么最佳答案)|环球观焦点
无聊是一种注意力倾注的对象不符合自己的价值观时的心理体验。那么无聊的近义词是什么?下面
-
【焦点热闻】forge怎么用
1、我的世界forge怎么安装我的世界forge怎么用。2、我的世界forge是游戏必备工具之一,那么如何安装该工具呢
-
创志科技财务数据前后矛盾
3月27日,深交所上市审核委员会召开了2023年第15次上市委员会审议会议,审议结果显示,创志科技(江苏)股份有限公司(
-
天天微速讯:河北沧州一废弃冷库在拆除过程中发生火灾,11人死亡
3月27日14时30分许,河北省沧州市沧县崔尔庄镇东村一废弃冷库在拆除过程中发生火灾。消防、公安等单位全力扑救,至22时
-
世界快看:沈氏夫夫结婚是真的吗_沈氏夫夫造假
1、其实这个见仁见智。2、看微博的照片,两个人那么亲热,那旁边是有他人给拍的照片。3、就跟摆poss一样(我朋友老这样反
-
天天短讯!赛尔号谱尼第三封印怎么打_谱尼第3封印怎么打
1、药剂方面:必备加血药剂:+150的×99、+100的×99、+50的也×99;巅峰药剂:+200×5、+250的×5必备活力药剂:+20PP的×99、+1
-
黄河行丨全国每十个酒瓶,就有一个是山东郓城造
3月27日,山东省组织的“走文化廊道进经济园区看山东高质量发展”行进式主题采访活动——走黄河文化体验廊道来到郓城县。从二十世纪八十年...
-
唐山市丰南区教育考察团参访西安市高新第六小学 盛赞学校“成美教育”新理念
3月23日,由唐山市丰南区教育局总督学韩志刚带队,唐山市丰南区的校长、教研员和骨干教师组成的考察团到访西安市高新第六小学,进行了为期两天
-
从移动互联到游戏科技,技术助力千年文博业走在数字化最前沿
腾讯集团市场与公关部副总裁李航分享了腾讯助力文博数字化的多年探索与思考。
-
吉他左手按弦技巧视频_吉他左手按弦技巧_每日消息
1、首先拿琴的姿势非常重要。2、然后爬格子,刚开始练习必须越慢越好,确保能发出每一个音,手指要垂直指板,同时保证每根按弦
-
天天热头条丨河南:完善设施扩大合作
为加快推动交通区位优势向枢纽经济优势转变,近年来,河南先后印发实施了《河南省“十四五”现代物流业发展规划》《关于加快现代
-
世界滚动:欧预赛:荷兰3-0十人直布罗陀获首胜 曼城中卫双响 国米边卫2助
欧预赛:荷兰3-0十人直布罗陀获首胜曼城中卫双响国米边卫2助
-
今头条!包邮什么意思网络语_包邮什么意思
1、退货包邮是指商家在购买退货包运费服务后,对于在服务保障期间内正常销售且符合条件的订单,平台都会赠送相应的退货包运费服
-
突发!曝顶流男星刘宇宁离异有娃,男方回应孩子身份让人意外
3月27日一大早,娱记阳阳就曝出一个大瓜,而且这个瓜和最近其他瓜不同,没名没姓全靠吃瓜群众猜,这次的瓜不仅有名有姓,而且瓜主还是娱乐圈的
-
天天简讯:让更多“济南味道”香飘四方
全国各地争相布局预制菜赛道,使其成为推动当地经济发展的强力引擎。进入2023年,济南预制菜市场好戏不断:1月5日,济南第
-
观点:英镑兑人民币今日汇率 3月27日英镑汇率走势图
英镑兑人民币今日汇率3月27日英镑汇率走势图,英镑兑人民币今日汇率3月27日英镑汇率走势图?同南方财富网火车君来看看。1人民币0 1187英镑汇率
-
西门子燃气灶爆燃 使用不当还是质量问题?
西门子燃气灶爆燃使用不当还是质量问题?
-
吃白袜少年脚的文章_亲亲鱼为什么吃脚-全球短讯
1、亲鱼的学名是幸子鱼,是人工繁殖的。它只有2-4厘米长,能在18-43的水中生活。低于这个温度,它就会停止进食。对盐度
-
德州市人力资源和社会保障局
1、德州市人力资源和社会保障局是人民政府的职能部门。2、拟订全市人力资源和社会保障事业发展规划。3、并组织实施和监督
-
与你一起守护碧水|天天日报
日前,团经开区委联合人民社区新时代文明实践站、蓝天社工,组织青年志愿者开展环保志愿服务活动。活动中,志愿者们干劲十足。他们积极参与,
-
韭菜鸡蛋能隔夜吃吗_韭菜鸡蛋隔了一夜还能食用吗 全球即时
解答:1、韭菜鸡蛋能否隔夜食用,通常需要根据具体情况具体分析。韭菜炒鸡蛋如果隔夜没有变质,一般可以食用;如果有变质,一般
-
中国酒业协会理事长宋书玉:消化库存是白酒产业2023年首要任务
中国酒业协会理事长宋书玉:消化库存是白酒产业2023年首要任务;中国酒业协会理事长宋书玉今日表示,2023年年初,白酒消费水平反弹上扬态势明
-
快看点丨安徽省11个部门联合印发《绿色矿山管理办法(试行)》
安徽省自然资源厅、安徽省经济和信息化厅、财政厅等11个部门近日联合印发《绿色矿山管理办法(试行)》,就绿色矿山管理职责分工、建设与申报
-
世纪华通:公司游戏板块包括盛趣游戏、点点互动、天游软件、七酷网络等多家子公司_环球视讯
每经AI快讯,有投资者在投资者互动平台提问:公司主营业务为互联网游戏、汽车零部件制造和云数据三个板块,目前游戏板块也就是盛趣板块多少人
-
北向资金单日净卖出2.34亿元,减仓银行、计算机、建筑装饰 环球热头条
e公司数据统计显示,3月24日北向资金单日净卖出2 34亿元,其中沪股通净卖出17亿元,深股通净买入14 66亿元。北向资金成交金额1163 31亿元,占A股总
-
最新消息:葱香肉饼最正宗的做法_葱香肉饼
材料:糖2g,酱油10ml,水120ml,料酒10ml,香油15ml,生抽15ml。花生油50克,盐10克,蚝油100克
-
【短视频】生活提示:春季养生穿衣注意事项-环球聚看点
春季早晚温差较大,春季养生需注意些什么呢?1、每天坚持多喝水每天坚持多喝水,能够增强身体系统的循环速度,缓解紧张的身体机