- 浏览: 520961 次
- 性别:
- 来自: 惠州
-
文章分类
- 全部博客 (255)
- ant (1)
- springMVC (2)
- ajax (4)
- oracle (12)
- SSH (13)
- struts1 (2)
- Hibernate (14)
- spring (5)
- jstl (1)
- 连接池 (1)
- acegi (4)
- java (17)
- jquery (11)
- div+css (4)
- drupal (1)
- php (8)
- freemaker调模板生成静态页面 (1)
- xml (1)
- json (2)
- javascript (9)
- 正则表达式 (4)
- Ext (8)
- jdbc (1)
- sql server (2)
- perl (5)
- db4o (1)
- webservice (4)
- flex (13)
- it资讯 (1)
- joomla (0)
- 设计模式 (1)
- struts2 (4)
- s2sh (8)
- linux (3)
- ejb (2)
- android旅途 (24)
- android (36)
- C/C++ (16)
- mysql (1)
最新评论
-
fengyuxing168:
IBelyService bs = IBelyService. ...
为 Android 添加 Java 层服务也就是添加自定义的aidl服务到serviceManager 通过ServiceManager.getService取 -
dengzhangtao:
"由于ActivityManagerService是 ...
binder理解 -
yzyspy:
ActivityManagerService:startHom ...
Android的Launcher成为系统中第一个启动的,也是唯一的 -
Matchstick:
使用SELECT DISTINCT alias FROM Po ...
hibernate 一对多表查询时fetchMode.join 生成left outer join 出来数据重复问题 -
dlheart:
没看懂你什么意思啊,我遇到的问题是一对多,设了fetch = ...
hibernate 一对多表查询时fetchMode.join 生成left outer join 出来数据重复问题
Spring2.0简明手册(系列之三 AOP-1.x)
--------------------------------------------------------------------------------
2008-06-19 22:58:08 标签:Spring AOP [推送到技术圈]
版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://robert.blog.51cto.com/374512/83040
一、基本概念:
(1)目标对象(target)
就是被代理的对象,也就是具体的业务逻辑。比如OrderService
(2)切面 (Aspect)
交叉业务,也就是通用的业务逻辑,比如日志、事务。
(3)连接点 (Jointpoint)
切面可以插入的地点,主要有方法、属性
(4)切入点 (Pointcut)
指定哪些连接点可以应用切面/通知
(5)通知(Advice)
切面的具体实现
二、基本使用
1.代理
1)ProxyFactoryBean
Spring的AOP实现是基于代理的,而在Spring里创建一个AOP代理的基本方法就是
使用org.springframework.aop.framework.ProxyFactoryBean
ProxyFactoryBean的各个属性,参照Spring参考手册:7.5.2. JavaBean属性
这里的配置重点是目标与拦截器。
实际创建的代理类型,有两种:代理接口和代理类。
更详细的解释需要参照Spring参考手册:7.5.3. 基于JDK和CGLIB的代理。
下面我们看一下具体的配置。
(1)代理接口(JDK动态代理):
<bean id="person"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 需要被代理的接口 -->
<property name="proxyInterfaces">
<value>com.mycompany.Person</value>
</property>
<!-- 目标对象 -->
<property name="target">
<ref local="personTarget"/>
</property>
<!-- 需要被应用的通知或拦截器,这里的顺序是很重要的 -->
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
<value>debugInterceptor</value>
</list>
</property>
</bean>
目标对象也可以用匿名内部bean的方式使用:
<bean id="person" class="org.springframework.aop.framework.ProxyFactor Bean">
<property name="proxyInterfaces"><value>com.mycompany.Person</value></property>
<!-- Use inner bean, not local reference to target -->
<property name="target">
<bean class="com.mycompany.PersonImpl">
<property name="name"><value>Tony</value></property>
<property name="age"><value>51</value></property>
</bean>
</property>
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
<value>debugInterceptor</value>
</list>
</property>
</bean>
(2)代理类(CGLIB代理):
目标类不存在接口,只能使用CGLIB代理,
去掉proxyInterfaces属性
设置proxyTargetClass属性设为true或者忽略这个属性
目标类即使存在接口,也强制使用CGLIB代理:
设置proxyTargetClass属性设为true,甚至proxyInterfaces属性被设置的情况下仍然将实际使用基于CGLIB的代理。
“全局”advisor
通过在一个拦截器名后添加一个星号,所有bean名字与星号之前部分相匹配的Advice都将被加入到advisor链中。
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="service"/>
<property name="interceptorNames">
<list>
<value>globa *</value>
</list>
</property>
</bean>
<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
2)TransactionProxyFactoryBean
使用TransactionProxyFactoryBean时,我们通常都会用简化代理的方式进行配置。
你也许需要许多相似的代理定义,特别是定义事务性代理的时候。使用父子bean定义,以及内部bean定义,可以让代理定义大大得到极大的简化。
请参照Spring参考手册:7.6. 简化代理定义
2.Advisor
预先了解的概念:Advisor=Advice+Pointcut,所以这里的配置重点是如何把Advice和Pointcut集成到一起。
除了Introduction Advice(和DefaultIntroductionAdvisor一起使用),任何advisor都可以和任何Advice一起工作。
具体的配置示例如下:
1)NameMatchMethodPointcutAdvisor(内部使用NameMatchMethodPointcut)
<bean id="helloAdvisor"
class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="mappedName" value="*Newbie"/>
<property name="advice" ref="logBeforeAdvice"/>
</bean>
2)RegexpMethodPointcutAdvisor
在背后,如果使用J2SE 1.4或者以上版本,Spring将使用JdkRegexpMethodPointcut,在之前版本的虚拟机上,Spring将退回去使用Perl5RegexpMethodPointcut。
配置示例:
<bean id="regExpAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="pattern" value=".*Newbie"/>
<property name="advice" ref="logBeforeAdvice"/>
</bean>
3)DefaultPointcutAdvisor(使用外部注入的Pointcut)
<bean id="defaultAdvisor"
class="org.springframework.aop.support.DefaultPointcutAdvisor">
<constructor-arg ref="adviceBean"/>
<constructor-arg ref="poingcutBean"/>
</bean>
或
<bean id="defaultAdvisor"
class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="adviceBean"></property>
<property name="pointcut" ref="pointcutBean"></property>
</bean>
3.Advice
这里的重点是编程实现相应的接口。具体参照Spring参考手册,摘要如下:
Around Advice--实现MethodInterceptor接口--参照Spring参考手册:7.3.2.1. 拦截around通知
Before Advice --实现BeforeAdvice接口 --参照Spring参考手册:7.3.2.2. 前置通知
Throw Advice --实现ThrowsAdvice接口 --参照Spring参考手册:7.3.2.3. 异常通知
After Returning Advice--实现AfterReturningAdvice接口--参照Spring参考手册:7.3.2.4. 后置通知
Introduction Advice --实现IntroductionInterceptor接口,并使用DefaultIntroductionAdvisor织入Advice或者,继承DelegatingIntroductionInterceptor,并使用DefaultIntroductionAdvisor织入Advice。Spring 把引入通知(introduction advice)作为一种特殊的拦截通知进行处理。--参照Spring参考手册:7.3.2.5. 引入通知
4.切入点PointCut
孤立的PointCut没有什么用处,必须结合Advisor一起使用,PointCut和DefaultPointcutAdvisor的结合使用,可以参照:http://www.easyjf.com/bbs.ejf?cmd=appShow&id=4554752
1)静态切入点
(1)正则表达式切入点
包括Perl5RegexpMethodPointcut和JdkRegexpMethodPointcut
两者都是StaticMethodMatcherPointcut的子类,不限制类,只限制方法
Perl5RegexpMethodPointcut依赖Jakarta ORO进行正则表达式匹配,需要把 jakarta-oro-xx.jar 文件放到 classpath 上。
JdkRegexpMethodPointcut需要在 JDK1.4 及以上的环境运行,不需要额外的库
他们均有2个属性:
(1) pattern或patterns:前者表示单个正则表达式,后置表示多个正则表达式,支持<list>配置;
(2) ExcludedPattern或ExcludedPatterns:前者表示排除某个字符串,后者表示排除一组字符串,支持<list>配置;
配置示例:
<bean id="pointcutBean"
class="org.springframework.aop.support. JdkRegexpMethodPointcut">
<property name="pattern">
<value>.*business.*</value>
</property>
<property name="ExcludedPattern">
<value>business2</value>
</property>
</bean>
或
<bean id="pointcutBean"
class="org.springframework.aop.support. Perl5RegexpMethodPointcut">
<property name="pattern">
<value>.*business.*</value>
</property>
<property name="ExcludedPattern">
<value>business2</value>
</property>
</bean>
(2)NameMatchMethodPointcut
StaticMethodMatcherPointcut的子类,不限制类,只限制方法
NameMatchMethodPointcut只有一个属性mappedName或者mappedNames,前者表示映射单个字符串,后者表示映射一组字符串,支持<list>配置
<bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedNames">
<list>
<value>business*</value>
</list>
</property>
</bean>
(3)ExpressionPointcut接口
不是StaticMethodMatcherPointcut的子类,可以既限制类,又限制方法
在 Spring2 中,在 Pointcut 的基础上,引入了一个 ExpressionPointcut 接口用来通过切入点表达语言来描述切入点。
有了 ExpressionPointcut,我们可以使用下面更加简单的方式来描述切入点,
如 execution(* Component.business*(..))表示执行所有 Component 的业务方法(此处为 business 打头的方法)。
Spring2 提供了一个 ExpressionPointcut 的实现,即 AspectJExpressionPointcut,
该类的使用很简单,只需要做如下配置即可:
<bean id="pointcutBean"
class="org.springframework.aop.aspectj.AspectJExpressionPointcut">
<property name="expression"
value="execution(void spring.chapter3.proxy.Component.business*(..))">
</property>
</bean>
属性驱动的切入点
一个重要的静态切入点是元数据驱动(metadata-driven)切入点。这使用元数据参数:特别是源代码级别的元数据。
2)动态切入点
控制流切入点(ControlFlowPointcut)--既限制类,又限制方法
如果有这样的需求:我们对一个方法进行切入通知,但只有这个方法在一个特定方法中被调用的时候执行通知,我们可以使用ControlFlowPointCut流程切入点
以实例来说明(参考:http://blog.csdn.net/daryl715/archive/2007/08/14/1743311.aspx)
当BeanOne的方法被Test类的runfoo方法调用时,织入通知。
BeanOne.java
package study.springAop;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
public class BeanOne {
private Logger logger =
Logger.getLogger(this.getClass().getName());
public void foo(){
logger.log(Level.INFO, "executing==>foo-one");
}
public void bar(){
logger.log(Level.INFO, "executing==>bar-one");
}
}
SimpleAdvise.java
package study.springAop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
public class SimpleAdvise
implements MethodInterceptor {
private Logger logger =
Logger.getLogger(this.getClass().getName());
public Object invoke(MethodInvocation invocation) throws Throwable {
logger.log(Level.INFO, "before>>>");
Object retVal=invocation.proceed();
logger.log(Level.INFO, "after<<<");
return retVal;
}
}
Test.java
package study.springAop;
public class Test {
BeanOne beanOne = null;
public void setBeanOne(BeanOne beanOne) {
this.beanOne = beanOne;
}
public void runfoo() {
beanOne.foo();
beanOne.bar();
}
public void runbar() {
beanOne.bar();
beanOne.foo();
}
public void runfoo(BeanOne pBeanOne) {
pBeanOne.foo();
pBeanOne.bar();
}
public void runbar(BeanOne pBeanOne) {
pBeanOne.bar();
pBeanOne.foo();
}
}
beans-config.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-2.0.xsd">
<bean id="myTest" class="study.springAop.Test">
<property name="beanOne" ref="proxyOne"></property>
</bean>
<!--配置DefaultPointcutAdvisor-->
<bean id="myFlowControlPointcut"
class="org.springframework.aop.support.ControlFlowPointcut">
<constructor-arg value="study.springAop.Test"/>
<constructor-arg value="runfoo"/>
</bean>
<bean id="myAdvise" class="study.springAop.SimpleAdvise"/>
<bean id="myAdvisor"
class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="myAdvise"/>
<property name="pointcut" ref="myFlowControlPointcut"/>
</bean>
<!--配置代理-->
<bean id="beanOne" class="study.springAop.BeanOne"/>
<bean id="proxyOne"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 目标类没有实现任何接口,一个基于CGLIB的代理将被创建 -->
<property name="target" ref="beanOne"/>
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
</list>
</property>
</bean>
</beans>
log4j.properties
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] %-13c{1}:%L - %m%n
SpringAOPDemo.java
package study.springAop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.
support.ClassPathXmlApplicationContext;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
public class SpringAOPDemo {
private static Logger logger =
Logger.getLogger(SpringAOPDemo.class.getName());
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"beans-config.xml");
Test myTest = (Test) context.getBean("myTest");
BeanOne proxyOne = (BeanOne) context.getBean("proxyOne");
logger.log(Level.INFO, "\n the flowing doesn't work...");
// 直接调用
logger.log(Level.INFO, "不通过Test类调用BeanOne的方法...");
proxyOne.foo();
proxyOne.bar();
// 通过runbar方法调用
logger.log(Level.INFO, "通过Test类的runbar(BeanOne pBeanOne)方法调用BeanOne的方法...");
myTest.runbar(proxyOne);
logger.log(Level.INFO, "通过Test类的runbar()方法调用BeanOne的方法...");
myTest.runbar();
logger.log(Level.INFO, "\n the flowing works...");
// 通过runfoo方法调用
logger.log(Level.INFO, "通过Test类的runfoo(BeanOne pBeanOne)方法调用BeanOne的方法...");
myTest.runfoo(proxyOne);
logger.log(Level.INFO, "通过Test类的runfoo()方法调用BeanOne的方法...");
myTest.runfoo();
}
}
执行结果:
INFO [main] SpringAOPDemo:18 -
the flowing doesn't work...
INFO [main] SpringAOPDemo:20 - 不通过Test类调用BeanOne的方法...
INFO [main] BeanOne :10 - executing==>foo-one
INFO [main] BeanOne :13 - executing==>bar-one
INFO [main] SpringAOPDemo:24 - 通过Test类的runbar(BeanOne pBeanOne)方法调用BeanOne的方法...
INFO [main] BeanOne :13 - executing==>bar-one
INFO [main] BeanOne :10 - executing==>foo-one
INFO [main] SpringAOPDemo:26 - 通过Test类的runbar()方法调用BeanOne的方法...
INFO [main] BeanOne :13 - executing==>bar-one
INFO [main] BeanOne :10 - executing==>foo-one
INFO [main] SpringAOPDemo:28 -
the flowing works...
INFO [main] SpringAOPDemo:30 - 通过Test类的runfoo(BeanOne pBeanOne)方法调用BeanOne的方法...
INFO [main] SimpleAdvise :13 - before>>>
INFO [main] BeanOne :10 - executing==>foo-one
INFO [main] SimpleAdvise :15 - after<<<
INFO [main] SimpleAdvise :13 - before>>>
INFO [main] BeanOne :13 - executing==>bar-one
INFO [main] SimpleAdvise :15 - after<<<
INFO [main] SpringAOPDemo:32 - 通过Test类的runfoo()方法调用BeanOne的方法...
INFO [main] SimpleAdvise :13 - before>>>
INFO [main] BeanOne :10 - executing==>foo-one
INFO [main] SimpleAdvise :15 - after<<<
INFO [main] SimpleAdvise :13 - before>>>
INFO [main] BeanOne :13 - executing==>bar-one
INFO [main] SimpleAdvise :15 - after<<<
3)AspectJ切入点表达式语言
从2.0开始,Spring中使用的最重要的切入点类型是org.springframework.aop.aspectj.AspectJExpressionPointcut。而且,使用AspectJ切入点表达式通常会更简单一些。格式:execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
更详细的例子参考:6.2.3.4. 示例
三、高级使用
1.自动代理
推荐一篇文章: http://blog.csdn.net/gabriel80/archive/2008/05/13/2441814.aspx
1)BeanNameAutoProxyCreator
为名字匹配字符串或者通配符的bean自动创建AOP代理。
主要目的是把相同的配置一致地应用到多个对象,并且使用最少量的配置。
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames"><value>jdk*,onlyJdk</value></property>
<property name="interceptorNames">
<list>
<value>myInterceptor</value>
</list>
</property>
</bean>
2)DefaultAdvisorAutoProxyCreator
这个类的奇妙之处在于它实现了 BeanPostProcessor 接口。当 ApplicationContext 读入所有 Bean 的配置信息后,
DefaultAdvisorAutoProxyCreator 将扫描上下文,寻找所有的 Advisor 。
它将这些 Advisor 应用到所有符合 Advisor 切入点的 Bean 中。这个代理创建器只能与 Advisor 配合使用 。
通常自动代理的好处是它让调用者或者被依赖对象不能得到一个没有通知过的对象。
在下面例子中,在ApplicationContext上调用getBean("businessObject1")将返回一个AOP代理,而不是目标业务对象。
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
<property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>
<bean id="customAdvisor" class="com.mycompany.MyAdvisor"/>
<bean id="businessObject1" class="com.mycompany.BusinessObject1">
<!-- Properties omitted -->
</bean>
<bean id="businessObject2" class="com.mycompany.BusinessObject2"/>
DefaultAdvisorAutoProxyCreator支持过滤(通过使用一个命名约定让只有特定的advisor被评估,允许在同一个工厂里使用多个不同配置的AdvisorAutoProxyCreator)和排序。
关于过滤的说明:
It's possible to filter out advisors - for example, to use multiple post processors of this type in the same factory - by setting the usePrefix property to true, in which case only advisors beginning with the DefaultAdvisorAutoProxyCreator's bean name followed by a dot (like "aapc.") will be used. This default prefix can be changed from the bean name by setting the advisorBeanNamePrefix property. The separator (.) will also be used in this case.
3)AbstractAdvisorAutoProxyCreator
如果在某些情况下框架提供的DefaultAdvisorAutoProxyCreator不能满足你的需要,你可以通过继承AbstractAdvisorAutoProxyCreator这个类(DefaultAdvisorAutoProxyCreator的父类)来创建你自己的自动代理创建器。
4)使用元数据驱动的自动代理
参照:JPetStore示例应用程序的/attributes 目录
2.使用TargetSources
ProxyFactoryBean的targetSource属性,缺省的实现是对于每次调用代理将返回相同的目标。
如果我们想对目标实现 池化(pooling),热切换(hot swappable)等功能时,就需要配置ProxyFactoryBean的targetSource属性了。
<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetSource" ref="配置为适当的TargetSource"/>
<property name="interceptorNames" value="myInterceptor"/>
</bean>
如果我们想热切换一个AOP代理的目标--配置为HotSwappableTargetSource
<bean id="initialTarget" class="mycompany.OldTarget"/>
<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">
<constructor-arg ref="initialTarget"/>
</bean>
如果我们想池化一个AOP代理的目标--配置为CommonsPoolTargetSource
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject" scope="prototype">
... properties omitted
</bean>
<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPoolTargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
<property name="maxSize" value="25"/>
</bean>
如果我们想为每个进来的请求(即每个线程)创建一个对象--配置为ThreadLocalTargetSource
<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
</bean>
具体实例,可以参照:http://blog.csdn.net/peirenlei/archive/2007/04/12/1562087.aspx
--------------------------------------------------------------------------------
2008-06-19 22:58:08 标签:Spring AOP [推送到技术圈]
版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://robert.blog.51cto.com/374512/83040
一、基本概念:
(1)目标对象(target)
就是被代理的对象,也就是具体的业务逻辑。比如OrderService
(2)切面 (Aspect)
交叉业务,也就是通用的业务逻辑,比如日志、事务。
(3)连接点 (Jointpoint)
切面可以插入的地点,主要有方法、属性
(4)切入点 (Pointcut)
指定哪些连接点可以应用切面/通知
(5)通知(Advice)
切面的具体实现
二、基本使用
1.代理
1)ProxyFactoryBean
Spring的AOP实现是基于代理的,而在Spring里创建一个AOP代理的基本方法就是
使用org.springframework.aop.framework.ProxyFactoryBean
ProxyFactoryBean的各个属性,参照Spring参考手册:7.5.2. JavaBean属性
这里的配置重点是目标与拦截器。
实际创建的代理类型,有两种:代理接口和代理类。
更详细的解释需要参照Spring参考手册:7.5.3. 基于JDK和CGLIB的代理。
下面我们看一下具体的配置。
(1)代理接口(JDK动态代理):
<bean id="person"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 需要被代理的接口 -->
<property name="proxyInterfaces">
<value>com.mycompany.Person</value>
</property>
<!-- 目标对象 -->
<property name="target">
<ref local="personTarget"/>
</property>
<!-- 需要被应用的通知或拦截器,这里的顺序是很重要的 -->
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
<value>debugInterceptor</value>
</list>
</property>
</bean>
目标对象也可以用匿名内部bean的方式使用:
<bean id="person" class="org.springframework.aop.framework.ProxyFactor Bean">
<property name="proxyInterfaces"><value>com.mycompany.Person</value></property>
<!-- Use inner bean, not local reference to target -->
<property name="target">
<bean class="com.mycompany.PersonImpl">
<property name="name"><value>Tony</value></property>
<property name="age"><value>51</value></property>
</bean>
</property>
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
<value>debugInterceptor</value>
</list>
</property>
</bean>
(2)代理类(CGLIB代理):
目标类不存在接口,只能使用CGLIB代理,
去掉proxyInterfaces属性
设置proxyTargetClass属性设为true或者忽略这个属性
目标类即使存在接口,也强制使用CGLIB代理:
设置proxyTargetClass属性设为true,甚至proxyInterfaces属性被设置的情况下仍然将实际使用基于CGLIB的代理。
“全局”advisor
通过在一个拦截器名后添加一个星号,所有bean名字与星号之前部分相匹配的Advice都将被加入到advisor链中。
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="service"/>
<property name="interceptorNames">
<list>
<value>globa *</value>
</list>
</property>
</bean>
<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
2)TransactionProxyFactoryBean
使用TransactionProxyFactoryBean时,我们通常都会用简化代理的方式进行配置。
你也许需要许多相似的代理定义,特别是定义事务性代理的时候。使用父子bean定义,以及内部bean定义,可以让代理定义大大得到极大的简化。
请参照Spring参考手册:7.6. 简化代理定义
2.Advisor
预先了解的概念:Advisor=Advice+Pointcut,所以这里的配置重点是如何把Advice和Pointcut集成到一起。
除了Introduction Advice(和DefaultIntroductionAdvisor一起使用),任何advisor都可以和任何Advice一起工作。
具体的配置示例如下:
1)NameMatchMethodPointcutAdvisor(内部使用NameMatchMethodPointcut)
<bean id="helloAdvisor"
class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="mappedName" value="*Newbie"/>
<property name="advice" ref="logBeforeAdvice"/>
</bean>
2)RegexpMethodPointcutAdvisor
在背后,如果使用J2SE 1.4或者以上版本,Spring将使用JdkRegexpMethodPointcut,在之前版本的虚拟机上,Spring将退回去使用Perl5RegexpMethodPointcut。
配置示例:
<bean id="regExpAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="pattern" value=".*Newbie"/>
<property name="advice" ref="logBeforeAdvice"/>
</bean>
3)DefaultPointcutAdvisor(使用外部注入的Pointcut)
<bean id="defaultAdvisor"
class="org.springframework.aop.support.DefaultPointcutAdvisor">
<constructor-arg ref="adviceBean"/>
<constructor-arg ref="poingcutBean"/>
</bean>
或
<bean id="defaultAdvisor"
class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="adviceBean"></property>
<property name="pointcut" ref="pointcutBean"></property>
</bean>
3.Advice
这里的重点是编程实现相应的接口。具体参照Spring参考手册,摘要如下:
Around Advice--实现MethodInterceptor接口--参照Spring参考手册:7.3.2.1. 拦截around通知
Before Advice --实现BeforeAdvice接口 --参照Spring参考手册:7.3.2.2. 前置通知
Throw Advice --实现ThrowsAdvice接口 --参照Spring参考手册:7.3.2.3. 异常通知
After Returning Advice--实现AfterReturningAdvice接口--参照Spring参考手册:7.3.2.4. 后置通知
Introduction Advice --实现IntroductionInterceptor接口,并使用DefaultIntroductionAdvisor织入Advice或者,继承DelegatingIntroductionInterceptor,并使用DefaultIntroductionAdvisor织入Advice。Spring 把引入通知(introduction advice)作为一种特殊的拦截通知进行处理。--参照Spring参考手册:7.3.2.5. 引入通知
4.切入点PointCut
孤立的PointCut没有什么用处,必须结合Advisor一起使用,PointCut和DefaultPointcutAdvisor的结合使用,可以参照:http://www.easyjf.com/bbs.ejf?cmd=appShow&id=4554752
1)静态切入点
(1)正则表达式切入点
包括Perl5RegexpMethodPointcut和JdkRegexpMethodPointcut
两者都是StaticMethodMatcherPointcut的子类,不限制类,只限制方法
Perl5RegexpMethodPointcut依赖Jakarta ORO进行正则表达式匹配,需要把 jakarta-oro-xx.jar 文件放到 classpath 上。
JdkRegexpMethodPointcut需要在 JDK1.4 及以上的环境运行,不需要额外的库
他们均有2个属性:
(1) pattern或patterns:前者表示单个正则表达式,后置表示多个正则表达式,支持<list>配置;
(2) ExcludedPattern或ExcludedPatterns:前者表示排除某个字符串,后者表示排除一组字符串,支持<list>配置;
配置示例:
<bean id="pointcutBean"
class="org.springframework.aop.support. JdkRegexpMethodPointcut">
<property name="pattern">
<value>.*business.*</value>
</property>
<property name="ExcludedPattern">
<value>business2</value>
</property>
</bean>
或
<bean id="pointcutBean"
class="org.springframework.aop.support. Perl5RegexpMethodPointcut">
<property name="pattern">
<value>.*business.*</value>
</property>
<property name="ExcludedPattern">
<value>business2</value>
</property>
</bean>
(2)NameMatchMethodPointcut
StaticMethodMatcherPointcut的子类,不限制类,只限制方法
NameMatchMethodPointcut只有一个属性mappedName或者mappedNames,前者表示映射单个字符串,后者表示映射一组字符串,支持<list>配置
<bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedNames">
<list>
<value>business*</value>
</list>
</property>
</bean>
(3)ExpressionPointcut接口
不是StaticMethodMatcherPointcut的子类,可以既限制类,又限制方法
在 Spring2 中,在 Pointcut 的基础上,引入了一个 ExpressionPointcut 接口用来通过切入点表达语言来描述切入点。
有了 ExpressionPointcut,我们可以使用下面更加简单的方式来描述切入点,
如 execution(* Component.business*(..))表示执行所有 Component 的业务方法(此处为 business 打头的方法)。
Spring2 提供了一个 ExpressionPointcut 的实现,即 AspectJExpressionPointcut,
该类的使用很简单,只需要做如下配置即可:
<bean id="pointcutBean"
class="org.springframework.aop.aspectj.AspectJExpressionPointcut">
<property name="expression"
value="execution(void spring.chapter3.proxy.Component.business*(..))">
</property>
</bean>
属性驱动的切入点
一个重要的静态切入点是元数据驱动(metadata-driven)切入点。这使用元数据参数:特别是源代码级别的元数据。
2)动态切入点
控制流切入点(ControlFlowPointcut)--既限制类,又限制方法
如果有这样的需求:我们对一个方法进行切入通知,但只有这个方法在一个特定方法中被调用的时候执行通知,我们可以使用ControlFlowPointCut流程切入点
以实例来说明(参考:http://blog.csdn.net/daryl715/archive/2007/08/14/1743311.aspx)
当BeanOne的方法被Test类的runfoo方法调用时,织入通知。
BeanOne.java
package study.springAop;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
public class BeanOne {
private Logger logger =
Logger.getLogger(this.getClass().getName());
public void foo(){
logger.log(Level.INFO, "executing==>foo-one");
}
public void bar(){
logger.log(Level.INFO, "executing==>bar-one");
}
}
SimpleAdvise.java
package study.springAop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
public class SimpleAdvise
implements MethodInterceptor {
private Logger logger =
Logger.getLogger(this.getClass().getName());
public Object invoke(MethodInvocation invocation) throws Throwable {
logger.log(Level.INFO, "before>>>");
Object retVal=invocation.proceed();
logger.log(Level.INFO, "after<<<");
return retVal;
}
}
Test.java
package study.springAop;
public class Test {
BeanOne beanOne = null;
public void setBeanOne(BeanOne beanOne) {
this.beanOne = beanOne;
}
public void runfoo() {
beanOne.foo();
beanOne.bar();
}
public void runbar() {
beanOne.bar();
beanOne.foo();
}
public void runfoo(BeanOne pBeanOne) {
pBeanOne.foo();
pBeanOne.bar();
}
public void runbar(BeanOne pBeanOne) {
pBeanOne.bar();
pBeanOne.foo();
}
}
beans-config.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-2.0.xsd">
<bean id="myTest" class="study.springAop.Test">
<property name="beanOne" ref="proxyOne"></property>
</bean>
<!--配置DefaultPointcutAdvisor-->
<bean id="myFlowControlPointcut"
class="org.springframework.aop.support.ControlFlowPointcut">
<constructor-arg value="study.springAop.Test"/>
<constructor-arg value="runfoo"/>
</bean>
<bean id="myAdvise" class="study.springAop.SimpleAdvise"/>
<bean id="myAdvisor"
class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="myAdvise"/>
<property name="pointcut" ref="myFlowControlPointcut"/>
</bean>
<!--配置代理-->
<bean id="beanOne" class="study.springAop.BeanOne"/>
<bean id="proxyOne"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 目标类没有实现任何接口,一个基于CGLIB的代理将被创建 -->
<property name="target" ref="beanOne"/>
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
</list>
</property>
</bean>
</beans>
log4j.properties
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] %-13c{1}:%L - %m%n
SpringAOPDemo.java
package study.springAop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.
support.ClassPathXmlApplicationContext;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
public class SpringAOPDemo {
private static Logger logger =
Logger.getLogger(SpringAOPDemo.class.getName());
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"beans-config.xml");
Test myTest = (Test) context.getBean("myTest");
BeanOne proxyOne = (BeanOne) context.getBean("proxyOne");
logger.log(Level.INFO, "\n the flowing doesn't work...");
// 直接调用
logger.log(Level.INFO, "不通过Test类调用BeanOne的方法...");
proxyOne.foo();
proxyOne.bar();
// 通过runbar方法调用
logger.log(Level.INFO, "通过Test类的runbar(BeanOne pBeanOne)方法调用BeanOne的方法...");
myTest.runbar(proxyOne);
logger.log(Level.INFO, "通过Test类的runbar()方法调用BeanOne的方法...");
myTest.runbar();
logger.log(Level.INFO, "\n the flowing works...");
// 通过runfoo方法调用
logger.log(Level.INFO, "通过Test类的runfoo(BeanOne pBeanOne)方法调用BeanOne的方法...");
myTest.runfoo(proxyOne);
logger.log(Level.INFO, "通过Test类的runfoo()方法调用BeanOne的方法...");
myTest.runfoo();
}
}
执行结果:
INFO [main] SpringAOPDemo:18 -
the flowing doesn't work...
INFO [main] SpringAOPDemo:20 - 不通过Test类调用BeanOne的方法...
INFO [main] BeanOne :10 - executing==>foo-one
INFO [main] BeanOne :13 - executing==>bar-one
INFO [main] SpringAOPDemo:24 - 通过Test类的runbar(BeanOne pBeanOne)方法调用BeanOne的方法...
INFO [main] BeanOne :13 - executing==>bar-one
INFO [main] BeanOne :10 - executing==>foo-one
INFO [main] SpringAOPDemo:26 - 通过Test类的runbar()方法调用BeanOne的方法...
INFO [main] BeanOne :13 - executing==>bar-one
INFO [main] BeanOne :10 - executing==>foo-one
INFO [main] SpringAOPDemo:28 -
the flowing works...
INFO [main] SpringAOPDemo:30 - 通过Test类的runfoo(BeanOne pBeanOne)方法调用BeanOne的方法...
INFO [main] SimpleAdvise :13 - before>>>
INFO [main] BeanOne :10 - executing==>foo-one
INFO [main] SimpleAdvise :15 - after<<<
INFO [main] SimpleAdvise :13 - before>>>
INFO [main] BeanOne :13 - executing==>bar-one
INFO [main] SimpleAdvise :15 - after<<<
INFO [main] SpringAOPDemo:32 - 通过Test类的runfoo()方法调用BeanOne的方法...
INFO [main] SimpleAdvise :13 - before>>>
INFO [main] BeanOne :10 - executing==>foo-one
INFO [main] SimpleAdvise :15 - after<<<
INFO [main] SimpleAdvise :13 - before>>>
INFO [main] BeanOne :13 - executing==>bar-one
INFO [main] SimpleAdvise :15 - after<<<
3)AspectJ切入点表达式语言
从2.0开始,Spring中使用的最重要的切入点类型是org.springframework.aop.aspectj.AspectJExpressionPointcut。而且,使用AspectJ切入点表达式通常会更简单一些。格式:execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
更详细的例子参考:6.2.3.4. 示例
三、高级使用
1.自动代理
推荐一篇文章: http://blog.csdn.net/gabriel80/archive/2008/05/13/2441814.aspx
1)BeanNameAutoProxyCreator
为名字匹配字符串或者通配符的bean自动创建AOP代理。
主要目的是把相同的配置一致地应用到多个对象,并且使用最少量的配置。
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames"><value>jdk*,onlyJdk</value></property>
<property name="interceptorNames">
<list>
<value>myInterceptor</value>
</list>
</property>
</bean>
2)DefaultAdvisorAutoProxyCreator
这个类的奇妙之处在于它实现了 BeanPostProcessor 接口。当 ApplicationContext 读入所有 Bean 的配置信息后,
DefaultAdvisorAutoProxyCreator 将扫描上下文,寻找所有的 Advisor 。
它将这些 Advisor 应用到所有符合 Advisor 切入点的 Bean 中。这个代理创建器只能与 Advisor 配合使用 。
通常自动代理的好处是它让调用者或者被依赖对象不能得到一个没有通知过的对象。
在下面例子中,在ApplicationContext上调用getBean("businessObject1")将返回一个AOP代理,而不是目标业务对象。
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
<property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>
<bean id="customAdvisor" class="com.mycompany.MyAdvisor"/>
<bean id="businessObject1" class="com.mycompany.BusinessObject1">
<!-- Properties omitted -->
</bean>
<bean id="businessObject2" class="com.mycompany.BusinessObject2"/>
DefaultAdvisorAutoProxyCreator支持过滤(通过使用一个命名约定让只有特定的advisor被评估,允许在同一个工厂里使用多个不同配置的AdvisorAutoProxyCreator)和排序。
关于过滤的说明:
It's possible to filter out advisors - for example, to use multiple post processors of this type in the same factory - by setting the usePrefix property to true, in which case only advisors beginning with the DefaultAdvisorAutoProxyCreator's bean name followed by a dot (like "aapc.") will be used. This default prefix can be changed from the bean name by setting the advisorBeanNamePrefix property. The separator (.) will also be used in this case.
3)AbstractAdvisorAutoProxyCreator
如果在某些情况下框架提供的DefaultAdvisorAutoProxyCreator不能满足你的需要,你可以通过继承AbstractAdvisorAutoProxyCreator这个类(DefaultAdvisorAutoProxyCreator的父类)来创建你自己的自动代理创建器。
4)使用元数据驱动的自动代理
参照:JPetStore示例应用程序的/attributes 目录
2.使用TargetSources
ProxyFactoryBean的targetSource属性,缺省的实现是对于每次调用代理将返回相同的目标。
如果我们想对目标实现 池化(pooling),热切换(hot swappable)等功能时,就需要配置ProxyFactoryBean的targetSource属性了。
<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetSource" ref="配置为适当的TargetSource"/>
<property name="interceptorNames" value="myInterceptor"/>
</bean>
如果我们想热切换一个AOP代理的目标--配置为HotSwappableTargetSource
<bean id="initialTarget" class="mycompany.OldTarget"/>
<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">
<constructor-arg ref="initialTarget"/>
</bean>
如果我们想池化一个AOP代理的目标--配置为CommonsPoolTargetSource
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject" scope="prototype">
... properties omitted
</bean>
<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPoolTargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
<property name="maxSize" value="25"/>
</bean>
如果我们想为每个进来的请求(即每个线程)创建一个对象--配置为ThreadLocalTargetSource
<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
</bean>
具体实例,可以参照:http://blog.csdn.net/peirenlei/archive/2007/04/12/1562087.aspx
发表评论
-
spring ioc 原理 spring aop原理
2010-06-10 14:09 71471.关于spring ioc 这段 ... -
spring aop控制权限,想要细到method级别
2008-11-26 12:38 2600spring aop控制权限,想要细到method级别,但是a ... -
CGlib简单介绍
2008-09-28 16:30 187105-11 (转)CGlib简单介绍 原文:http://ww ... -
Spring+Hibernate+Acegi 的初次体验
2008-07-29 16:06 1234Spring+Hibernate+Acegi 的初次体验 一 ...
相关推荐
- **实现方式**:Spring框架通过代理模式实现AOP,主要有两种代理方式:JDK动态代理和CGLIB动态代理。前者基于接口,后者基于继承。 - **核心概念**:AOP涉及几个核心概念,包括切面(Aspect)、连接点(Join Point...
嵌入式八股文面试题库资料知识宝典-华为的面试试题.zip
训练导控系统设计.pdf
嵌入式八股文面试题库资料知识宝典-网络编程.zip
人脸转正GAN模型的高效压缩.pdf
少儿编程scratch项目源代码文件案例素材-几何冲刺 转瞬即逝.zip
少儿编程scratch项目源代码文件案例素材-鸡蛋.zip
嵌入式系统_USB设备枚举与HID通信_CH559单片机USB主机键盘鼠标复合设备控制_基于CH559单片机的USB主机模式设备枚举与键盘鼠标数据收发系统支持复合设备识别与HID
嵌入式八股文面试题库资料知识宝典-linux常见面试题.zip
面向智慧工地的压力机在线数据的预警应用开发.pdf
基于Unity3D的鱼类运动行为可视化研究.pdf
少儿编程scratch项目源代码文件案例素材-霍格沃茨魔法学校.zip
少儿编程scratch项目源代码文件案例素材-金币冲刺.zip
内容概要:本文深入探讨了HarmonyOS编译构建子系统的作用及其技术细节。作为鸿蒙操作系统背后的关键技术之一,编译构建子系统通过GN和Ninja工具实现了高效的源代码到机器代码的转换,确保了系统的稳定性和性能优化。该系统不仅支持多系统版本构建、芯片厂商定制,还具备强大的调试与维护能力。其高效编译速度、灵活性和可扩展性使其在华为设备和其他智能终端中发挥了重要作用。文章还比较了HarmonyOS编译构建子系统与安卓和iOS编译系统的异同,并展望了其未来的发展趋势和技术演进方向。; 适合人群:对操作系统底层技术感兴趣的开发者、工程师和技术爱好者。; 使用场景及目标:①了解HarmonyOS编译构建子系统的基本概念和工作原理;②掌握其在不同设备上的应用和优化策略;③对比HarmonyOS与安卓、iOS编译系统的差异;④探索其未来发展方向和技术演进路径。; 其他说明:本文详细介绍了HarmonyOS编译构建子系统的架构设计、核心功能和实际应用案例,强调了其在万物互联时代的重要性和潜力。阅读时建议重点关注编译构建子系统的独特优势及其对鸿蒙生态系统的深远影响。
嵌入式八股文面试题库资料知识宝典-奇虎360 2015校园招聘C++研发工程师笔试题.zip
嵌入式八股文面试题库资料知识宝典-腾讯2014校园招聘C语言笔试题(附答案).zip
双种群变异策略改进RWCE算法优化换热网络.pdf
内容概要:本文详细介绍了基于瞬时无功功率理论的三电平有源电力滤波器(APF)仿真研究。主要内容涵盖并联型APF的工作原理、三相三电平NPC结构、谐波检测方法(ipiq)、双闭环控制策略(电压外环+电流内环PI控制)以及SVPWM矢量调制技术。仿真结果显示,在APF投入前后,电网电流THD从21.9%降至3.77%,显著提高了电能质量。 适用人群:从事电力系统研究、电力电子技术开发的专业人士,尤其是对有源电力滤波器及其仿真感兴趣的工程师和技术人员。 使用场景及目标:适用于需要解决电力系统中谐波污染和无功补偿问题的研究项目。目标是通过仿真验证APF的有效性和可行性,优化电力系统的电能质量。 其他说明:文中提到的仿真模型涉及多个关键模块,如三相交流电压模块、非线性负载、信号采集模块、LC滤波器模块等,这些模块的设计和协同工作对于实现良好的谐波抑制和无功补偿至关重要。
内容概要:本文探讨了在工业自动化和物联网交汇背景下,构建OPC DA转MQTT网关软件的需求及其具体实现方法。文中详细介绍了如何利用Python编程语言及相关库(如OpenOPC用于读取OPC DA数据,paho-mqtt用于MQTT消息传递),完成从OPC DA数据解析、格式转换到最终通过MQTT协议发布数据的关键步骤。此外,还讨论了针对不良网络环境下数据传输优化措施以及后续测试验证过程。 适合人群:从事工业自动化系统集成、物联网项目开发的技术人员,特别是那些希望提升跨协议数据交换能力的专业人士。 使用场景及目标:适用于需要在不同通信协议间建立高效稳定的数据通道的应用场合,比如制造业生产线监控、远程设备管理等。主要目的是克服传统有线网络限制,实现在不稳定无线网络条件下仍能保持良好性能的数据传输。 其他说明:文中提供了具体的代码片段帮助理解整个流程,并强调了实际部署过程中可能遇到的问题及解决方案。
基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档~ 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档