-
1.Spring 框架优缺点? -
2.Spring 框架中都用到了哪些设计模式? -
3.Spring 中有哪些核心模块? -
4.说一下你理解的 IOC 是什么? -
5.Spring 中的 IOC 容器有哪些?有什么区别? -
6.BeanFactory 和 FactoryBean 又有什么区别? -
7.说说 AOP 是什么? -
8.动态代理和静态代理有什么区别? -
9.JDK 动态代理和 CGLIB 代理有什么区别? -
10.AOP 核心概念有哪些? -
11.Spring通知类型有哪些? -
12.Spring中 Bean 的生命周期是怎样的? -
13.Spring Bean 作用域有哪些? -
14.为什么 Spring 中的Bean默认为单例? -
15.Spring 如何解决循环依赖? -
16.为什么要使用三级缓存,二级缓存不能解决吗? -
17.使用@Autowired注解自动装配的过程是怎样的? -
18.Spring 的不同事务传播行为有哪些? -
19.Spring 支持的事务管理类型有哪些? -
20.你更倾向用哪种事务管理类型? -
21.Spring常用注解有哪些? -
22.Spring @Resource和@Autowired有什么区别? -
23.Spring5 新特性有哪些?
1.Spring 框架优缺点?
「优点」
-
控制反转(IOC):把对象的控制权交给了 spring,由 spring 容器进行管理。 -
AOP编程的支持:Spring提供面向切面编程,可以方便实现对程序进行权限拦截、运行监控等功能。 -
声明式事务的支持:只需要通过配置就可以完成对事务的管理,而无需手动编程。 -
方便程序的测试:Spring对Junit4支持,可以通过注解方便的测试Spring程序。 -
方便集成各种优秀框架:Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。 -
降低JavaEE API的使用难度:Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。
「缺点」
-
Spring依赖反射,反射影响性能 -
使用门槛升高,入门Spring需要较长时间
2.Spring 框架中都用到了哪些设计模式?
Spring 框架中使用到了大量的设计模式,下面列举了比较有代表性的:
单例模式
:Spring 中的 Bean 默认情况下都是单例的。无需多说。
工厂模式
:工厂模式主要是通过 BeanFactory 和 ApplicationContext 来生产 Bean 对象。
代理模式
:最常见的 AOP 的实现方式就是通过代理来实现,Spring主要是使用 JDK 动态代理和 CGLIB 代理。
模板方法模式
:主要是一些对数据库操作的类用到,比如 JdbcTemplate、JpaTemplate,因为查询数据库的建立连接、执行查询、关闭连接几个过程,非常适用于模板方法。
观察者模式
:Spring 事件驱动模型就是观察者模式
适配器模式
: Spring AOP 的增强或通知(Advice)使用到了适配器模式
3.Spring 中有哪些核心模块?
Spring 总共大约有 20 个模块, 由 1300 多个不同的文件构成。而这些组件被分别整合在核心容器(Core Container) 、 AOP和设备支持、数据访问与集成 、 Web、 消息 、 Test等 6 个模块中。以下是 Spring 5 的模块结构图:
-
spring core
:提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。 -
spring beans
:提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean。 -
spring context
:构建于 core 封装包基础上的 context 封装包,提供了一种框架式的对象访问方法。 -
spring jdbc
:提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBC。 -
spring aop
:提供了面向切面的编程实现,让你可以自定义拦截器、切点等。 -
spring Web
:提供了针对 Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。 -
spring test
:主要为测试提供支持的,支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试。
4.说一下你理解的 IOC 是什么?
首先 IOC 是一个「容器」,是用来装载对象的,它的核心思想就是「「控制反转」」
那么究竟「什么是控制反转」?
控制反转就是说,「把对象的控制权交给了 spring,由 spring 容器进行管理」,我们不进行任何操作
那么为「什么需要控制反转」?
我们想象一下,没有控制反转的时候,我们需要「自己去创建对象,配置对象」,还要「人工去处理对象与对象之间的各种复杂的依赖关系」,当一个工程的量起来之后,这种关系的维护是非常令人头痛的,所以就有了控制反转这个概念,将对象的创建、配置等一系列操作交给 spring 去管理,我们在使用的时候只要去取就好了。
「Spring IoC 的实现机制」
Spring 中的 IoC 的实现原理就是工厂模式加反射机制。
5.Spring 中的 IOC 容器有哪些?有什么区别?
spring 主要提供了两种 IOC 容器,一种是 「BeanFactory」,还有一种是 「ApplicationContext」
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
它们的区别就在于:
「依赖关系」
BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。
ApplicationContext:接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
-
继承MessageSource,因此支持国际化。 -
统一的资源文件访问方式。 -
提供在监听器中注册bean的事件。 -
同时加载多个配置文件。 -
载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
「加载方式」
BeanFactory:采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
ApplicationContext:它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
「注册方式」
BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。
6.BeanFactory 和 FactoryBean 又有什么区别?
这两个名称看去很像,但其实是完全不同的两个产物。
「BeanFactory 是 IOC 容器」,是用来承载对象的。它和ApplicationContext可以理解为同一产物。
「FactoryBean 是一个接口」,为 Bean 提供了更加灵活的方式,通过代理一个Bean对象,对方法前后做一些操作。
7.说说 AOP 是什么?
AOP 意为:「面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术」。
AOP 是 「OOP(面向对象编程) 的延续」,OOP主要是为了实现编程的重用性、灵活性和扩展性。它的几个特征分别是继承、封装、多态和抽象。OOP重点体现在编程架构,强调的是类之间的层次关系。
AOP作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。
「AOP 实现主要分为两类:」
-
「静态 AOP 实现」, AOP 框架「在编译阶段」对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ
-
「动态 AOP 实现」, AOP 框架「在运行阶段」对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP
spring 中 AOP 的实现是「通过动态代理实现的」,如果是实现了接口就会使用 JDK 动态代理,否则就使用 CGLIB 代理。
8.动态代理和静态代理有什么区别?
「静态代理」
-
由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了 -
静态代理通常只代理一个类 -
静态代理事先知道要代理的是什么
「动态代理」
-
在程序运行时,运用反射机制动态创建而成 -
动态代理是代理一个接口下的多个实现类 -
动态代理不知道要代理什么东西,只有在运行时才知道
9.JDK 动态代理和 CGLIB 代理有什么区别?
JDK 动态代理时业务类「必须要实现某个接口」,它是「基于反射的机制实现的」,生成一个实现同样接口的一个代理类,然后通过重写方法的方式,实现对代码的增强。
CGLIB 动态代理是使用字节码处理框架 ASM,其原理是通过字节码技术为一个类「创建子类,然后重写父类的方法」,实现对代码的增强。
10.AOP 核心概念有哪些?
-
切面(aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象,比如说事务处理和日志处理可以理解为两个切面。 -
切入点(pointcut):对连接点进行拦截的定义 -
连接点(joinpoint):被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器 -
通知(advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类 -
目标对象:代理的目标对象 -
织入(weave):将切面应用到目标对象并导致代理对象创建的过程 -
引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
简单来讲,整个 aspect 可以描述为: 「满足 pointcut 规则的 joinpoint 会被添加相应的 advice 操作」。我们来看下边这个例子。
@Component
@Aspect // 切面
public class LogAspect {
private final static Logger LOGGER = LoggerFactory.getLogger(LogAspect.class.getName());
// 切入点,表达式是指com.remcarpediem.test.aop.service
// 包下的所有类的所有方法
@Pointcut("execution(* com.remcarpediem.test.aop.service..*(..))")
public void aspect() {}
// 通知,在符合aspect切入点的方法前插入如下代码,并且将连接点作为参数传递
@Before("aspect()")
public void log(JoinPoint joinPoint) { //连接点作为参数传入
if (LOGGER.isInfoEnabled()) {
// 获得类名,方法名,参数和参数名称。
Signature signature = joinPoint.getSignature();
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] argumentNames = methodSignature.getParameterNames();
}
}
11.Spring通知类型有哪些?
在AOP术语中,切面的工作被称为通知,实际上是程序执行时要通过SpringAOP框架触发的代码段。Spring切面可以应用5种类型的通知:
-
前置通知(Before):在目标方法被调用之前调用通知功能; -
后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么; -
返回通知(After-returning ):在目标方法成功执行之后调用通知; -
异常通知(After-throwing):在目标方法抛出异常后调用通知; -
环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
12.Spring中 Bean 的生命周期是怎样的?
SpringBean 生命周期大致分为4个阶段:
-
1.「实例化」,实例化该 Bean 对象
-
2.「填充属性」,给该 Bean 赋值
-
3.「初始化」
-
如果实现了 Aware 接口,会通过其接口获取容器资源 -
如果实现了 BeanPostProcessor 接口,则会回调该接口的前置和后置处理增强 -
如果配置了 init-method 属性,会自动调用其配置的初始化方法 -
4.「销毁」
-
如果实现了 DisposableBean 接口,则会回调该接口的 destroy 方法 -
如果配置了 destroy-method 方法,则会执行 destroy-method 配置的方法
13.Spring Bean 作用域有哪些?
Spring 3 中为 Bean 定义了 5 种作用域,分别为 singleton(单例)、prototype(原型)、request、session 和 global session,5 种作用域说明如下:
-
「singleton」: 单例模式。Spring IoC 容器中只会存在一个共享的 Bean 实例,无论有多少个Bean 引用它,始终指向同一对象。该模式在多线程下是不安全的。
-
「prototype」: 原型模式每次使用时创建。每次通过 Spring 容器获取 prototype 定义的 bean 时,容器都将创建一个新的 Bean 实例,每个 Bean 实例都有自己的属性和状态。
-
「Request」:一次 request 一个实例。在一次 Http 请求中,容器会返回该 Bean 的同一实例。而对不同的 Http 请求则会产生新的 Bean,而且该 bean 仅在当前 Http Request 内有效,当前 Http 请求结束,该 bean实例也将会被销毁。
-
「session」:在一次 Http Session 中,容器会返回该 Bean 的同一实例。而对不同的 Session 请求则会创建新的实例,该 bean 实例仅在当前 Session 内有效。同 Http 请求相同,每一次session 请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的 session 请求内有效,请求结束,则实例将被销毁。
-
「global Session」:在一个全局的 Http Session 中,容器会返回该 Bean 的同一个实例,仅在使用 portlet context 时有效。
14.为什么 Spring 中的Bean默认为单例?
「单例bean的优势」
由于不会每次都创建新对象所以有下几个性能上的优势:
1.减少了新生成实例的消耗
新生成实例消耗包括两方面,第一,Spring会通过反射或者cglib来生成bean实例这都是耗性能的操作,其次给对象分配内存也会涉及复杂算法。
2.减少jvm垃圾回收
由于不会给每个请求都新生成bean实例,所以自然回收的对象少了。
3.可以快速获取到bean
因为单例的获取bean操作除了第一次生成之外其余的都是从缓存里获取的所以很快。
「单例bean的劣势」
单例的bean一个很大的劣势就是他不能做到线程安全!!!
由于所有请求都共享一个bean实例,所以这个bean要是有状态的一个bean的话可能在并发场景下出现问题,而原型的bean则不会有这样问题(但也有例外,比如他被单例bean依赖),因为给每个请求都新创建实例。
15.Spring 如何解决循环依赖?
关于Spring bean的创建,其本质上还是一个对象的创建,既然是对象,读者朋友一定要明白一点就是,一个完整的对象包含两部分:当前对象实例化
和对象属性的实例化
。
在Spring中,对象的实例化是通过反射实现的,而对象的属性则是在对象实例化之后通过一定的方式设置的。
这个过程可以按照如下方式进行理解:
理解这一个点之后,对于循环依赖的理解就已经帮助一大步了。
spring 使用三级缓存去解决循环依赖的,其「核心逻辑就是把实例化和初始化的步骤分开,然后放入缓存中」,供另一个对象调用
-
「第一级缓存」:用来保存实例化、初始化都完成的对象 -
「第二级缓存」:用来保存实例化完成,但是未初始化完成的对象 -
「第三级缓存」:用来保存一个对象工厂,提供一个匿名内部类,用于创建二级缓存中的对象
当 A、B 两个类发生循环引用时 大致流程
-
1.A 完成实例化后,去「「创建一个对象工厂,并放入三级缓存」」当中
-
-
如果 A 被 AOP 代理,那么通过这个工厂获取到的就是 A 代理后的对象 -
如果 A 没有被 AOP 代理,那么这个工厂获取到的就是 A 实例化的对象 -
2.A 进行属性注入时,去「「创建 B」」
-
3.B 进行属性注入,需要 A ,则「「从三级缓存中去取 A 工厂代理对象」」并注入,然后删除三级缓存中的 A 工厂,将 A 对象放入二级缓存
-
4.B 完成后续属性注入,直到初始化结束,将 B 放入一级缓存
-
5.「A 从一级缓存中取到 B 并且注入 B」, 直到完成后续操作,将 A 从二级缓存删除并且放入一级缓存,循环依赖结束
spring 解决循环依赖有两个前提条件:
-
1.「不全是构造器方式」的循环依赖(否则无法分离初始化和实例化的操作) -
2.「必须是单例」(否则无法保证是同一对象)
出现循环依赖一定是你的业务设计有问题。高层业务和底层业务的划分不够清晰,一般,业务的依赖方向一定是无环的,有环的业务,在后续的维护和拓展一定非常鸡肋。
16.为什么要使用三级缓存,二级缓存不能解决吗?
如果仅仅是解决循环依赖问题,二级缓存也可以,但是如果注入的对象实现了AOP,那么注入到其他bean的时候,不是最终的代理对象,而是原始的。通过三级缓存的ObjectFactory才能实现类最终的代理对象。
17.使用@Autowired注解自动装配的过程是怎样的?
在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config />
。
在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor
后置处理器,当容器扫描到@Autowied
、@Resource
或@Inject
时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired
时,首先在容器中查询对应类型的bean:
-
如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据; -
如果查询的结果不止一个,那么@Autowired会根据名称来查找; -
如果上述查找的结果为空,那么会抛出异常。解决方法时,使用 required=false
。
18.Spring 的不同事务传播行为有哪些,有什么作用?
Spring事务的传播行为在Propagation枚举类中定义了如下几种选择
「支持当前事务」
-
REQUIRED :如果当前存在事务,则加入该事务。如果当前没有事务,则创建一个新的事务(默认) -
SUPPORTS:如果当前存在事务,则加入该事务 。如果当前没有事务, 则以非事务的方式继续运行 -
MANDATORY :如果当前存在事务,则加入该事务 。如果当前没有事务,则抛出异常
「不支持当前事务」
-
REQUIRES_NEW :创建一个新事务,如果当前存在事务,则把当前事务挂起 -
NOT_SUPPORTED :以非事务方式运行,如果当前存在事务,则把当前事务挂起 -
NEVER :以非事务方式运行,如果当前存在事务,则抛出异常
「其他情况」
-
NESTED :如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来执行 。如果当前没有事务,则该取值等价于REQUIRED
以NESTED启动的事务内嵌于外部事务中 (如果存在外部事务的话),此时内嵌事务并不是一个独立的事务,它依赖于外部事务。只有通过外部事务的提交,才能引起内部事务的提交,嵌套的子事务不能单独提交
19.Spring 支持的事务管理类型有哪些?
Spring 支持两种类型的事务管理:
-
编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是 难维护。 -
声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和 XML 配置来管理事务。
20.你更倾向用哪种事务管理类型?
大多数 Spring 框架的用户选择声明式事务管理,因为它对应用代码的影响最小,因 此更符合一个无侵入的轻量级容器的思想。声明式事务管理要优于编程式事务管理, 虽然比编程式事务管理(这种方式允许你通过代码控制事务)少了一点灵活性。
21.Spring常用注解有哪些?
「声明bean的注解」
-
@Component
:组件,没有明确的角色 -
@Service
:在业务逻辑层使用 -
@Repository
:在数据访问层使用 -
@Controller
:在展现层使用,控制层的声明
「注入bean的注解」
-
@Autowired
: -
@Resource
:
「java配置类相关注解」
-
@Configuration
:声明当前类为配置类,相当于xml形式的Spring配置(类上),声明当前类为配置类。 -
@Bean
:注解在方法上,声明当前方法的返回值为一个bean,替代xml中的方式(方法上) -
@ComponentScan
:用于对Component进行扫描,相当于xml中的(类上)
「切面(AOP)相关注解」
Spring支持AspectJ的注解式切面编程
-
@Aspect
:声明一个切面(类上)。 -
@After
:在方法执行之后执行(方法上) -
@Before
:在方法执行之前执行(方法上) -
@Around
:在方法执行之前与之后执行(方法上) -
@PointCut
:声明切入点(方法上)
「@Bean的属性支持」
@Scope 设置Spring容器如何新建Bean实例(方法上,得有@Bean),其设置类型包括:
-
Singleton
:单例,一个Spring容器中只有一个bean实例,默认模式 -
Protetype
:每次调用新建一个bean -
Request
:web项目中,给每个http request新建一个bean -
Session
:web项目中,给每个http session新建一个bean -
Global
:Session给每一个 global http session新建一个Bean实例
「环境切换」
-
@Profile
:通过设定Environment的ActiveProfiles来设定当前context需要使用的配置环境。(类或方法上) -
@Conditional
:Spring4中可以使用此注解定义条件的bean,通过实现Condition接口,并重写matches方法,从而决定该bean是否被实例化。
「异步相关」
-
@EnableAsync
:配置类中,通过此注解开启对异步任务的支持,叙事性AsyncConfigurer接口(类上) -
@Async
:在实际执行的bean方法使用该注解来申明其是一个异步任务(方法上或类上所有的方法都将异步,需要@EnableAsync开启异步任务)
「定时任务相关」
-
@EnableScheduling
:在配置类上使用,开启计划任务的支持(类上) -
@Scheduled
:来申明这是一个任务,包括cron,fixDelay,fixRate等类型(方法上,需先开启计划任务的支持)
「SpringMVC部分」
-
@Controller
:声明该类为SpringMVC中的Controller -
@RequestMapping
:用于映射Web请求,包括访问路径和参数(类或方法上) -
@ResponseBody
:支持将返回值放在response内,而不是一个页面,通常用户返回json数据(返回值旁或方法上) -
@RequestBody
:允许request的参数在request体中,而不是在直接连接在地址后面。(放在参数前) -
@PathVariable
:用于接收路径参数,比如@RequestMapping(“/hello/{name}”)申明的路径,将注解放在参数中前,即可获取该值,通常作为Restful的接口实现方法。 -
@RestController
:该注解为一个组合注解,相当于@Controller和@ResponseBody的组合,注解在类上,意味着,该Controller的所有方法都默认加上了@ResponseBody。 -
@ControllerAdvice
:通过该注解,我们可以将对于控制器的全局配置放置在同一个位置,注解了@Controller的类的方法可使用@ExceptionHandler、@InitBinder、@ModelAttribute注解到方法上,
22.Spring @Resource和@Autowired有什么区别?
@Autowired注解是按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它required属性为false。
@Resource注解和@Autowired一样,也可以标注在字段或属性的setter方法上,但它默认按名称装配。名称可以通过@Resource的name属性指定,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。
@Resources按名字,是JDK的,是1.6以后才有的,只是Spring支持该注解的注入,而@Autowired 是spring 的注解。
23.Spring5 新特性有哪些?
-
依赖 JDK 8+和 Java EE7+以上版本 -
首次采用反应式编程模型 -
支持使用注解进行编程 -
新增函数式编程 -
支持使用 REST 断点执行反应式编程 -
支持 HTTP 2.0 -
新增 Kotlin 和 Spring WebFlux -
可使用 Lambda 表达式注册 Bean -
Spring WebMVC 支持最新的 API -
使用 JUnit5 执行条件和并发测试 -
使用 Spring WebFlux 执行集成测试 -
核心容器优化
原文始发于微信公众号(后端元宇宙):Spring 面试经典23问!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/28106.html