1.@Aspect注解
(1) @Aspect注解用于声明一个切面类,我们可在该类中来自定义切面,早在Spring之前,AspectJ框架中就已经存在了这么一个注解,而Spring为了提供统一的注解风格,因此采用了和AspectJ框架相同的注解方式,这便是@Aspect注解的由来,换句话说,在Spring想做AOP框架之前,AspectJ AOP框架就已经很火了,而直接把AspectJ搬过来又不现实,因此,Spring想了一个折中的方案,即只使用AspectJ框架的声明,写法和定义方式(比如@Aspect注解),而底层由Spring自己实现,这样,就避免了我们程序员从AspectJ AOP切换到Spring AOP后,还要再去学一套新的写法了,也正因为如此,如果想要使用Spring AOP,就必须依赖aspectjweaver.jar包(不然谁来提供写法和定义方式),我们可以通过maven进行导入,如下
<!-- 添加对AspectJ框架的依赖 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
<!-- 除了上面的方式外,也可以直接使用spring-aspects依赖,它里面包含了对AspectJ的依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.framework.version}</version>
</dependency>
(2) 同时还需使用@EnableAspectJAutoProxy注解来开启Spring对于AspectJ注解的支持,如下
@Configuration
@EnableAspectJAutoProxy
public class Config {
}
如果是基于xml的配置,可通过如下标签进行开启
<aop:aspectj-autoproxy/>
2.自定义一个切面类
(1) 在基于注解的配置下,除了使用@Aspect注解外,还需要声明该切面是一个bean,否则,spring在扫描过程中是会忽略掉这个类的,如下
@Aspect
@Component
public class Logger {
}
(2) 对上面的例子,基于xml配置的写法如下
@Aspect
public class Logger {
}
<!-- xml配置文件中 -->
<beans ...>
<!-- 无论何种配置方式,不要忘了将切面类注册为spring的一个bean -->
<bean id="logger" class="cn.example.spring.boke.Logger"></bean>
</beans>
(3) 由@Aspect注解标注的类,称之为切面类,与普通的类一样,都有成员方法与成员变量,不同的是,切面类还可以包含连接点,通知,引介等与AOP有关的东西
(4) 切面不能再被增强,如果想拿一个切面来增强另一个切面,是不可能的,Spring会将切面类从自动代理(auto-proxying)中排除
3.自定义一个切入点
(1) Spring AOP中的切入点目前只可能是bean中的方法,而对于一个普通类中的方法,是不可能成为切入点的,在Spring中,声明一个切入点主要包括两个部分:一个切入点签名以及一个切入点表达式,如下
//如下定义了一个叫做anyExampleAMethod的切入点,这个切入点会匹配cn.example.spring.boke包下的ExampleA类中的任何方法
//其中,(1)就代表的是切入点表达式,(2)就代表的是切入点签名,注意,这个签名的返回值必须是void
@Pointcut("execution(* cn.example.spring.boke.ExampleA.*(..))") //(1)
public void anyExampleAMethod() {} //(2)
(2) Spring AOP的切入点表达式中,支持如下等切入点标识符
execution:最为常用,用于匹配某个包,某个类中的方法
within:进行类型匹配,用于匹配某个包下所有类的所有方法或某个指定类中的所有方法,如下
//指定了within的类型,这个切入点会匹配cn.example.spring.boke包下ExampleA类中的任何方法
@Pointcut("within(cn.example.spring.boke.ExampleA)")
public void withinDesignator(){}
//此前我们提到过,Spring AOP中的底层实现分为jdk动态代理和cglib动态代理,jdk动态代理基于接口,要求目标对象必须实现某个接口,而cglib动态代理基于继承,因此不同的实现方式下,导致Spring生成的代理对象的类型可能不同,这就是this标识符的基础
//首先定义一个接口
public interface Parent {
void register();
void sendEmail();
}
//让我们的ExampleA类,实现这个接口
@Component
public class ExampleA implements Parent{
public void register() {
}
public void sendEmail() {
}
}
//设置@EnableAspectJAutoProxy注解中的proxyTargetClass属性值为false,表示使用jdk动态代理,为true,表示使用cglib动态代理,默认值为false,不过我们这里显式的声明出来
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ComponentScan(basePackages = "cn.example.spring.boke")
public class Config {
}
//切面类,在其中声明一个this标识符,并指定类型为ExampleA
@Aspect
@Component
public class Logger {
/**
* this标识符,进行类型匹配,用于匹配代理对象的类型是否为指定类型
*/
@Pointcut(value = "this(cn.example.spring.boke.ExampleA)")
public void thisDesignator(){}
@Around(value = "thisDesignator()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println(new Date() + " 开始执行...");
joinPoint.proceed();
System.out.println(new Date() + " 结束执行...");
}
}
//执行如下打印方法,可见通知未被执行,原因就是因为我们使用了jdk动态代理,Spring为我们生成的代理对象继承自jdk中的Proxy类并实现了Parent接口,它不属于ExampleA类型,自然而然切入点匹配失败,我们的通知未被执行
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
ctx.getBean(Parent.class).register();
ctx.getBean(Parent.class).sendEmail();
//打印一下系统中代理对象的类型是否为ExampleA,结果为false
System.out.println(ctx.getBean(Parent.class) instanceof ExampleA);
//为了能进行匹配,我们可以将@EnableAspectJAutoProxy中的proxyTargetClass属性设置为true,使用cglib动态代理,这时再执行上面的打印方法,通知就会被执行了,原因就是因为使用了cglib动态代理后,Spring为我们生成的代理对象是继承自ExampleA,当然属于ExampleA类型,因此通知会被执行
@EnableAspectJAutoProxy(proxyTargetClass = true)
target:进行类型匹配,用于匹配目标对象的类型是否为指定类型,跟上面的this类似
args:进行方法参数匹配,用于匹配方法的参数类型是否为指定类型,如下
//ExampleA中的register方法的参数为String
@Component
public class ExampleA{
public void register(String name) {
}
public void sendEmail() {
}
}
@Aspect
@Component
public class Logger {
/**
* 指定了args参数的类型为String,因此只会与ExampleA中的register方法匹配
*/
@Pointcut(value = "args(java.lang.String)")
public void argsDesignator() {}
@Around(value = "argsDesignator()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println(new Date() + " 开始执行...");
joinPoint.proceed();
System.out.println(new Date() + " 结束执行...");
}
}
@target:用于匹配目标对象的类上有没有标注指定注解
@args:用于匹配方法的参数的所属类上有没有标注指定注解
@within:用于匹配某个类上有没有标注指定注解
@annotation:最常用,用于匹配某个方法上有没有标注指定注解
(3) Spring的AOP是基于代理实现的,因此,在目标对象中进行内部调用是不会被拦截的(即this指针会导致AOP失效问题),此外,对于jdk动态代理,只能拦截public方法,而对于cglib动态代理,会拦截public和protected方法(package-visible 方法在配置后也能被拦截)
(4) Spring AOP还提供了一个PCD bean,用于按照bean的名称进行切入,它是Spring AOP独有的,如下
//匹配所有beanName以A结尾的bean
@Pointcut("bean(*A)")
public void pcd() {}
4.组合切入点表达式
(1) 可以通过 &&,|| 和 !来组合切入点表达式,如下
//切入所有public方法
@Pointcut("execution(public * *(..))")
public void allPublicMethod() {}
//切入boke包下所有类中的所有方法
@Pointcut("within(cn.example.spring.boke.*)")
public void methodInBokePackage() {}
//使用 && 操作符,将上面两个切入点表达式组合起来,即切入boke包下所有类中的所有public方法
@Pointcut("allPublicMethod() && methodInBokePackage()")
public void allPublicMethodInBokePackage() {}
5.常见切入点表达式例子
(1)在实际工作中,我们的切入点表达式的通常形式为:execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?),其中ret-type-pattern表示一个方法的返回类型, 用 * 号可以代表任何类型; name-pattern表示方法名称,用 * 可以进行全部或部分名称匹配; param-pattern表示方法参数,其中用()代表无参方法,用(..)代表任何数量的参数(0个或多个),用()代表1个参数,(, String)代表有两个参数,第一个参数可以是任何类型,而第二个参数只能是String类型; 除此之外,其他带有 ? 的都是选填项,可不填写
(2)常见例子
//匹配任意public方法
execution(public * *(..))
//匹配任意名称以set开头的方法
execution(* set*(..))
//匹配com.xyz.service包下,AccountService类下的任意方法
execution(* com.xyz.service.AccountService.*(..))
//匹配com.xyz.service包下,任意类下的任意方法
execution(* com.xyz.service.*.*(..))
//匹配com.xyz.service包及其子包下,任意类下的任意方法
execution(* com.xyz.service..*.*(..))
//匹配com.xyz.service包下,任意类下的任意方法
within(com.xyz.service.*)
//匹配com.xyz.service包及其子包下,任意类下的任意方法
within(com.xyz.service..*)
//匹配代理对象的类型为AccountService的类下的任意方法
this(com.xyz.service.AccountService)
//匹配目标对象的类型为AccountService的类下的任意方法
target(com.xyz.service.AccountService)
//匹配方法参数只有一个且参数类型为Serializable的方法,注意它与execution(* *(java.io.Serializable))的一点区别:execution这个例子只能匹配参数类型为Serializable的方法,如果说某个方法的参数类型是Serializable的子类,是不会匹配的,而下面args这个例子可以匹配参数类型为Serializable或其子类的方法
args(java.io.Serializable)
//匹配标注了@Transactional注解的目标对象中的任意方法
@target(org.springframework.transaction.annotation.Transactional)
//匹配标注了@Transactional注解的类中的任意方法
@within(org.springframework.transaction.annotation.Transactional)
//匹配标注了@Transactional注解的任意方法
@annotation(org.springframework.transaction.annotation.Transactional)
//匹配方法的参数有且只有一个且该参数的所属类上标注了@Classified注解的任意方法
@args(com.xyz.security.Classified)
//匹配beanName为tradeService的bean中的任意方法
bean(tradeService)
//匹配所有以Service作为beanName结尾的bean中的任意方法
bean(*Service)
6.编写良好的pointcuts
(1)Spring将切入点标识符分为3大类,分别为:
7.声明一个通知
(1)在前面已经提及过通知,它是增强的逻辑,与切入点相关联,会在切入点执行前或执行后执行,在Spring中总共分为5大类,如下
@Aspect
@Component
public class Logger {
@Before(value = "execution(* cn.example.spring.boke.ExampleA.*(..))")
public void before() {
//...
}
}
//一个普通的bean ExampleA
@Component
public class ExampleA{
public String doSomething() {
return "finish";
}
}
//有时候,我们可能需要访问切入点执行后的返回值,那么我们可以使用@AfterReturning注解中的returning属性来指定返回值的名称,然后再给这个切面方法添加一个形参,这个形参类型即为切入点执行后的返回值类型(或其父类型,但不能完全不一致,否则切面会切入失败),形参名要与刚刚设置过的returning属性值一致,如下例
@Aspect
@Component
public class Logger {
@AfterReturning(value = "execution(* cn.example.spring.boke.ExampleA.*(..))", returning = "returnVal")
public void afterReturning(Object returnVal) {
System.out.println(new Date() + " 开始执行...");
System.out.println(returnVal);
System.out.println(new Date() + " 结束执行...");
}
}
//同样,我们有时候也期望访问切入点执行过程中抛出的异常,与返回通知一致,例子如下
@Aspect
@Component
public class Logger {
@AfterThrowing(value = "execution(* cn.example.spring.boke.ExampleA.*(..))", throwing = "throwable")
public void afterReturning(Throwable throwable) {
System.out.println(new Date() + " 开始执行...");
System.out.println(throwable);
System.out.println(new Date() + " 结束执行...");
}
}
@Aspect
@Component
public class Logger {
@After(value = "execution(* cn.example.spring.boke.ExampleA.*(..))")
public void afterReturning() {
//...
}
}
@Aspect
@Component
public class Logger {
//环绕通知方法可不声明形参,但如果要声明形参,第一个形参的类型必须是ProceedingJoinPoint类型,对ProceedingJoinPoint调用proceed方法后会导致切入点真正的执行,此外,proceed方法还有一个重载方法,我们可以对它传递一个Object[],那么当切入点执行时会以这个数组中的值作为方法参数值来执行
//我们可以调用一次,多次或根本不调用proceed方法
@Around(value = "execution(* cn.example.spring.boke.ExampleA.*(..))")
public void afterReturning(ProceedingJoinPoint joinPoint) {
Object returnVal = null;
try {
//...
returnVal = joinPoint.proceed();
//...
} catch (Throwable e) {
//...
e.printStackTrace();
}
return returnVal;
}
}
8.切入点信息获取
(1)有时候,我们期望获取到切入点相关信息,比如它的签名,形参等信息,Spring为我们提供了JoinPoint类型,用于获取相关信息,在前面的环绕通知的例子中,我们就已经使用了JoinPoint的子类型ProceedingJoinPoint,它添加了proceed方法,来显式的调用执行切入点
@Aspect
@Component
public class Logger {
//除了下面的例子外,使用JoinPoint,还可以获取到切入点的其他一些信息,可参考api文档
@Before(value = "execution(* cn.example.spring.boke.ExampleA.*(..))")
public void before(JoinPoint joinPoint) {
System.out.println("被拦截的类:" + joinPoint.getTarget().getClass().getName());
System.out.println("被拦截的方法:" + ((MethodSignature) joinPoint.getSignature()).getMethod().getName());
System.out.println("被拦截的方法参数:" + Arrays.toString(joinPoint.getArgs()));
}
}
9.通知执行顺序
(1)不同切面类中的通知,在默认情况下,按照所在切面类名的字典序排序,若其排序越高则优先级也就越高,如下
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = "cn.example.spring.boke")
public class Config { }
@Component
public class ExampleA{
public void doSomething() {
System.out.println("doSomething...");
}
}
//声明两个切面类TimerLogger和OperationLogger
@Aspect
@Component
public class TimerLogger {
@Before(value = "execution(* cn.example.spring.boke.ExampleA.*(..))")
public void a() {
System.out.println("timer...");
}
}
@Aspect
@Component
public class OperationLogger {
@Before(value = "execution(* cn.example.spring.boke.ExampleA.*(..))")
public void a() {
System.out.println("operation...");
}
}
//启动容器,观察打印结果
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
ctx.getBean(ExampleA.class).doSomething();
//打印结果如下,OperationLogger中的前置通知先执行,TimerLogger中的前置通知后执行,就是因为O的字典序列大于T,因此OperationLogger中的通知的优先级高于TimerLogger中的,而对于前置通知而言,优先级越高的越先执行,对于后置通知,优先级越高的越后执行
operation...
timer...
doSomething...
//我们可以将TimerLogger改为ATimerLogger,这样的话它里面的前置通知就会先执行了
@Aspect
@Component
public class ATimerLogger {
@Before(value = "execution(* cn.example.spring.boke.ExampleA.*(..))")
public void a() {
System.out.println("timer...");
}
}
(2)我们可以对切面类实现Ordered接口或添加@Order注解来显示的指定优先级,其中指定的值越小,优先级越高
//此时TimerLogger的优先级高于OperationLogger
@Aspect
@Component
@Order(1)
public class TimerLogger {
@Before(value = "execution(* cn.example.spring.boke.ExampleA.*(..))")
public void a() {
System.out.println("timer...");
}
}
@Aspect
@Component
@Order(2)
public class OperationLogger {
@Before(value = "execution(* cn.example.spring.boke.ExampleA.*(..))")
public void a() {
System.out.println("operation...");
}
}
(3)对于同个切面类中的相同类型的通知,其优先级只与通知方法名字典序的排序有关,排序越高,优先级越高,如下
//Logger切面类中定义了两个前置通知为aPrint和bPrint
@Aspect
@Component
public class Logger {
@Before(value = "execution(* cn.example.spring.boke.ExampleA.*(..))")
public void aPrint() {
System.out.println("a");
}
@Before(value = "execution(* cn.example.spring.boke.ExampleA.*(..))")
public void bPrint() {
System.out.println("b");
}
}
//启动容器,可见aPrint先于bPrint,这就是因为a的字典序高于b
a
b
doSomething...
//将aPrint改为caPrint,这时bPrint会先执行,因为此时它的字典序高
@Before(value = "execution(* cn.example.spring.boke.ExampleA.*(..))")
publicvoid caPrint() {
System.out.println("a");
}
//此外使用@Order注解,无法改变优先级,因为此时显式指定优先级的策略已经失效了,如下面这个例子还是按照之前默认的优先级进行执行
@Aspect
@Component
public class Logger {
@Before(value = "execution(* cn.example.spring.boke.ExampleA.*(..))")
@Order(Ordered.LOWEST_PRECEDENCE)
public void aPrint() {
System.out.println("a");
}
@Before(value = "execution(* cn.example.spring.boke.ExampleA.*(..))")
@Order(Ordered.HIGHEST_PRECEDENCE)
public void bPrint() {
System.out.println("b");
}
}
10.Introductions(引介)
(1)引介能够使指定的对象实现某些接口,并提供对这些接口的实现,以达到向对象中动态添加它所没有方法的目的,例子如下
//我们希望向ExampleA类中增加某些新的方法
@Component
public class ExampleA{ }
//声明一个接口,这个接口里的方法即为我们希望增加的新的方法
public interface Extention {
void doSomething();
}
//新方法的具体实现
public class ExtentionImpl implements Extention{
@Override
public void doSomething() {
System.out.println("doSomething...");
}
}
//定义一个切面
@Component
@Aspect
public class MyAspect {
//使用@DeclarePrents注解,声明被拦截的类有一个新的父类型,其中value指定拦截哪些类,在下面这个例子中指定拦截cn.example.spring.boke包下的所有类,同时指定它们的父类型均为Extention,具体的实现为ExtentionImpl
@DeclareParents(value = "cn.example.spring.boke.*", defaultImpl = ExtentionImpl.class)
public Extention extention;
}
//开启AOP
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = "cn.example.spring.boke")
public class Config { }
//启动容器,从容器中获取到exampleA并将其强制转换为Extention,这样我们就能使用向ExampleA中新添加的方法了
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
Extention exampleA = (Extention)ctx.getBean("exampleA");
exampleA.doSomething();
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
matlab打开matlab,用最简单的imread方法读取一个图像clcclearimg_h=imread('hua.jpg');返回一个数组(矩阵),往往是a*b*cunit8类型解释一下这个三维数组的意思,行数、数和层数,unit8:指数据类型,无符号八位整形,可理解为0~2^8的数三个层数分别代表RGB三个通道图像rgb最常用的是24-位实现方法,即RGB每个通道有256色阶(2^8)。基于这样的24-位RGB模型的色彩空间可以表现256×256×256≈1670万色当imshow传入了一个二维数组,它将以灰度方式绘制;可以把图像拆分为rgb三层,可以以灰度的方式观察它figure(1
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总
深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal
需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc
我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or
Ruby有一些不错的文档生成器,例如Yard、rDoc,甚至Glyph。问题是Sphinx可以做网站、PDF、epub、LaTex等。它在重组文本中完成所有这些事情。在Ruby世界中有替代方案吗?也许是程序的组合?如果我也能使用Markdown就更好了。 最佳答案 自1.0版以来,Sphinx有了“域”的概念,它是从Python和/或C以外的语言标记代码实体(如方法调用、对象、函数等)的方法。有一个rubydomain,所以你可以只使用Sphinx本身。您唯一会缺少的(我认为)是Sphinx使用autodoc从源代码自动创建文档
我正在寻找用于Rails的优质管理插件。似乎大多数现有的插件/gem(例如“restful_authentication”、“acts_as_authenticated”)都围绕着self注册等展开。但是,我正在寻找一种功能齐全的基于管理/管理角色的解决方案——但不是简单地附加到另一个非基于角色的解决方案。如果我找不到,我想我会自己动手......只是不想重新发明轮子。 最佳答案 RyanBates最近做了两个关于授权的railscast(注意身份验证和授权之间的区别;身份验证检查用户是否如她所说的那样,授权检查用户是否有权访问资源