Spring 源码分析解答附录

IOC

ioc 是控制反转,俗称依赖注入,是 Spring Framework 的核心功能, 由 BeanFactory, BeanDefinition, BeanDefinitionMap, Spring 缓存池组成。

说说 aop 和 ioc 关系,可以独立实现没有关系, 但 Spring 在实现 ioc 的时候使用到了 aop 的功能。

bean 生命周期

bean 生命周期

参见博客

AOP

aop 是面向切面编程,使用切点和增强的概念非侵入式切入代码。aop 主要是实现有静态编译, Java 运行时动态代理,cglib 在运行时动态生成字节码技术

  • aop 是什么,有哪些实现方式?
  • aop 里面的 cglib 原理是什么? asm 字节码生成技术
  • aop 切方法的方法的时候,哪些方法是切不了的?为什么?
  • 同类调用为什么无法切?怎么样解决(AOPContext)?

Spring 默认使用 JDK 代理,同类中方法间互相调用是切不了的,因为调用的是 target 的方法, 所以无法切, 可以使用 AOPContext.getProxy() 获得代理对象

启用 AOP

在 conf 类中加注解 @EnableAspectJAutoProxy, 参见 aop-aspectj-support

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}

@Aspect 定义切面

使用 @Aspect 定义一个切面

package top.waterlaw.test;


import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAspectJ {

    @Pointcut("@annotation(top.waterlaw.app.MyApp)")
    public void pointCut() {
    }

//  @After("pointCut()")
//  public void advice() {
//      // advice 切入的时机
//      System.out.println("AOP before");
//  }
//
//  @Around("pointCut()")
//  public void around(JoinPoint pjp) {
//      System.out.println("AOP before");
//  }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("aOP before");
        pjp.getArgs(); // 获得参数
        Object retVal = pjp.proceed(); // 执行目标函数
        System.out.println("aop after");
        return retVal;
    }
}

@Pointcut 定义切点

@Pointcut 定义切点, 生命一个增强 @After,@Around

@Before, @After,@Around 定义增强

@Around 的 JoinPoint 和 ProceedingJoinPoint 的区别是 ProceedingJoinPoint 可以获得函数参数、方法

Bean

bean 作用域有哪些,说一下各种使用场景?

bean 的各种作用域是怎么样实现的?

工具类中如何注入 bean?具体使用场景?

1.使用 MethodInvokingFactoryBean 的静态方法注入

<bean id="messageSource"
      class="org.springframework.context.support.ResourceBundleMessageSource"
      p:basename="i18n/messages" />
<bean
  class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
  <property name="staticMethod" value="plm.common.utils.CheckUtil.setResources" />
  <!-- 这里配置参数 -->
  <property name="arguments" ref="messageSource">
  </property>
</bean>

使用场景则是在国际化的时候,注入一个 messageSource,还有一种不太雅观不推荐的方法,也是网上博客写的方法, 只有在工具类注册为 bean 的时候生效

2.工具类声明为 bean + @PostConstruct

@Component
public class MyUtils {

    @Autowired
    private IBeanService beanService;

    private static MyUtils myUtils;

    @PostConstruct
    public void init() {
        myUtils = this;
        myUtils.beanService = this.beanService;
    }

    private MyUtils() {
    }

    private static IBeanService getBeanService() {
        return myUtils.beanService;
    }

    public static void util1() {
            // 该工具方法中可以通过 getBeanService 获得 spring 中注入的实例进行操作了
    }

} 

3.SpringContextUtil.getBean("beanName")

Spring 提供的工具类 SpringContextUtil 也可以拿到 bean

注入的 bean 存在多份的时候有哪些解决办法? 当通过 autowiredByType 进行注入时 ,@Primary > @Priority > beanName

有没有用过 BeanFactory?场景?

ApplicationContext is a sub-interface of BeanFactory.

IOC 中的 ApplicationContext 是一个实现 BeanFactory 接口的子类。

注解

spring 支持自带注解和 jsr-250 规范注解,以及 jsr-330 注解

spring-core:

org.springframework.core.annotation.AliasFor

org.springframework.core.annotation.Order

spring-beans:

org.springframework.beans.factory.annotation.Autowired

org.springframework.beans.factory.annotation.Configurable

org.springframework.beans.factory.annotation.Lookup

org.springframework.beans.factory.annotation.Qualifier

org.springframework.beans.factory.annotation.Required

org.springframework.beans.factory.annotation.Value

spring-aspects:

org.springframework.context.annotation.aspectj.EnableSpringConfigured

spring-jms:

org.springframework.jms.annotation.EnableJms

org.springframework.jms.annotation.JmsListener

org.springframework.jms.annotation.JmsListeners

spring-tx:

org.springframework.transaction.annotation.EnableTransactionManagement

org.springframework.transaction.annotation.Transactional

org.springframework.web.servlet.config.annotation.EnableWebMvc

spring-context:

cache 相关

org.springframework.cache.annotation.Cacheable

org.springframework.cache.annotation.CacheConfig

org.springframework.cache.annotation.CacheEvict

org.springframework.cache.annotation.CachePut

org.springframework.cache.annotation.Caching

org.springframework.cache.annotation.EnableCaching

Bean 相关

org.springframework.context.annotation.Bean

org.springframework.context.annotation.ComponentScan

org.springframework.context.annotation.ComponentScans

org.springframework.context.annotation.Conditional

org.springframework.context.annotation.Configuration

org.springframework.context.annotation.DependsOn

org.springframework.context.annotation.Description

org.springframework.context.annotation.EnableAspectJAutoProxy

org.springframework.context.annotation.EnableLoadTimeWeaving

org.springframework.context.annotation.EnableMBeanExport

org.springframework.context.annotation.Import

org.springframework.context.annotation.ImportResource

org.springframework.context.annotation.Lazy

org.springframework.context.annotation.Primary

org.springframework.context.annotation.Profile

org.springframework.context.annotation.PropertySource

org.springframework.context.annotation.PropertySources

org.springframework.context.annotation.Role

org.springframework.context.annotation.Scope

jmx 相关

org.springframework.jmx.export.annotation.ManagedAttribute

org.springframework.jmx.export.annotation.ManagedMetric

org.springframework.jmx.export.annotation.ManagedNotification

org.springframework.jmx.export.annotation.ManagedNotifications

org.springframework.jmx.export.annotation.ManagedOperation

org.springframework.jmx.export.annotation.ManagedOperationParameter

org.springframework.jmx.export.annotation.ManagedOperationParameters

org.springframework.jmx.export.annotation.ManagedResource

scheduling 相关

org.springframework.scheduling.annotation.Async

org.springframework.scheduling.annotation.EnableAsync

org.springframework.scheduling.annotation.EnableScheduling

org.springframework.scheduling.annotation.Scheduled

org.springframework.scheduling.annotation.Schedules

声明 Bean 的注解

org.springframework.stereotype.Component

org.springframework.stereotype.Controller

org.springframework.stereotype.Indexed

org.springframework.stereotype.Repository

org.springframework.stereotype.Service

validation 相关

org.springframework.validation.annotation.Validated

spring-web:

Mapping

org.springframework.web.bind.annotation.Mapping

org.springframework.web.bind.annotation.RequestMapping

org.springframework.web.bind.annotation.DeleteMapping

org.springframework.web.bind.annotation.GetMapping

org.springframework.web.bind.annotation.PatchMapping

org.springframework.web.bind.annotation.PostMapping

org.springframework.web.bind.annotation.PutMapping

请求相关

org.springframework.web.bind.annotation.ControllerAdvice

org.springframework.web.bind.annotation.CookieValue

org.springframework.web.bind.annotation.CrossOrigin

org.springframework.web.bind.annotation.ExceptionHandler

org.springframework.web.bind.annotation.InitBinder

org.springframework.web.bind.annotation.MatrixVariable

org.springframework.web.bind.annotation.ModelAttribute

org.springframework.web.bind.annotation.PathVariable

org.springframework.web.bind.annotation.RequestAttribute

org.springframework.web.bind.annotation.RequestBody

org.springframework.web.bind.annotation.RequestHeader

org.springframework.web.bind.annotation.RequestParam

org.springframework.web.bind.annotation.RequestPart

org.springframework.web.bind.annotation.ResponseBody

org.springframework.web.bind.annotation.ResponseStatus

org.springframework.web.bind.annotation.RestController

org.springframework.web.bind.annotation.RestControllerAdvice

org.springframework.web.bind.annotation.SessionAttribute

org.springframework.web.bind.annotation.SessionAttributes

org.springframework.web.context.annotation.ApplicationScope

org.springframework.web.context.annotation.RequestScope

org.springframework.web.context.annotation.SessionScope

spring-websocket:

org.springframework.web.socket.config.annotation.EnableWebSocket

org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker

jsr-250 注解

javax.annotation.Resource

可以根据名字或类名注入 Bean

javax.annotation.PostConstruct

javax.annotation.PreDestroy

jsr-330 注解

javax.inject.Inject

javax.inject.Named / javax.inject.ManagedBean

javax.inject.Singleton

javax.inject.Qualifier / javax.inject.Named

需要引入额外包, 更多请参阅官方文档

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

Spring MVC

项目结构

  • java

  • resources

  • webapp

  • WEB-INF
    • xxx-servlet.xml
    • web.xml
  • index.jsp

主要是 web.xml 和 servlet.xml 配置

web.xml 包括配置 spring 核心监听器 , 过滤器,DispatcherServlet

Spring 核心监听器

Spring 核心监听器默认会以 /WEB-INF/applicationContext.xml作为配置文件,

public class ContextLoaderListener extends ContextLoader implements ServletContextListener

ContextLoader 可以指定在 Web 应用程序启动时载入 Ioc 容器, ServletContextListener 监听 ServletContext,

ContextLoaderListener 启动 Web 容器时,读取在 contextConfigLocation 中定义的 xml 文件,自动装配ApplicationContext 的配置信息,并产生 WebApplicationContext 对象,然后将这个对象放置在 ServletContext的属性里。

IOC

数据源,sessionFactory,component-scan,配置事务管理器, AOP 配置提供事务增强, 配置事务的传播特性。

DispatcherServlet

component-scan, 启动Spring MVC的注解功能,完成请求和注解POJO的映射 

AnnotationMethodHandlerAdapter, 静态资源访问, 视图解析器, 上传文件,国际化文件和异常处理。

过滤器

容器编码、登录认证、静态资源拦截, 在 DispatcherServlet 前后进行处理

拦截器

拦截器是什么,什么场景使用?

AOP 实现事务管理、日志打印、时间耗时统计、权限认证

说说 DispatcherServlet 做了什么

实现拦截器的方法:

Spring MVC 提供了 Interceptor 拦截机制,用于请求的预处理和后处理。在 Spring MVC 中定义一个拦截器有两种方法:

第一种是实现 HandlerInterceptor 接口,或者继承实现了 HandlerInterceptor 接口的类例如(HandlerInterceptorAdapter);

第二种方法是实现 Spring 的 WebRequestInterceptor 接口(该接口是针对请求的拦截器接口,接口方法参数中没有 response),或者继承实现了 WebrequestInterceptor 的类。两种方式都是在 Handler 的执行周期内进行拦截操作。

如果要实现 HandlerInterceptor 接口,需要实现三个方法,preHandle、postHandle、afterCompletion

preHandle 方法在执行 Handler 方法之前执行,返回 false 表示拦截请求,不在执行后续逻辑,可以用来做权限,日志等。

postHandle 方法在执行 Handler 方法之后,返回 modelAndView 之前执行,由于该方法会在 DispatcherServlet进行返回视图渲染之前被调用,所以此方法多被用于同一处理返回视图,例如将公用的模型数据添加到视图,或者根据其他情况制定公用的视图。

afterCompletion 方法在执行完 Handler 之后执行,由于是在 Controller 方法执行完毕后执行该方法,所以该方法适合进行统一的异常或者日志处理操作。

实现 HandlerInterceptor 接口之后需要在 Spring 的类加载配置文件中配置拦截器实现类,才能使拦截器起到拦截的效果。HandlerInterceptor 类加载配置有两种方式,分别是”针对 HandlerMapping 配置”和 全局配置。

针对 HandlerMapping 配置需要在某个处理器映射器配置中将拦截器作为参数配置进去,之后通过此处理器映射器的 handler 就会使用配置好的拦截器,配置如下:

<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
    <property name="interceptors">
        <list>
            <ref bean="hInterceptor1" />
            <ref bean="hInterceptor2" />
        </list>
    </property>
    <property name="order" value="1"></property>
</bean>
<bean id="hInterceptor1" class="com.sl.interceptors.TestInterceptor"></bean>
<bean id="hInterceptor2" class="com.sl.interceptors.TestOrderInterceptor"></bean>

全局配置,springmvc 框架将配置的全局拦截器注入到每个 HandlerMapping 中

<!-- 配置自定义的拦截器 -->
<mvc:interceptors>       
    <bean class="com.sl.interceptors.TestInterceptor"></bean>  
</mvc:interceptors>

实现一个拦截器:

@Component
public class TestInterceptor implements HandlerInterceptor {

     /**
     * 当目标方法执行之前,执行此方法,返回 false,则不再执行后续逻辑 postHandle、afterCompletion
     */
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {
        System.out.println("First preHandle 最先执行..");
        return true;
    }

    /**
     * 执行目标方法之后,渲染视图之前调。 在转向 jsp 页面之前, 可以对请求域中的属性,或者视图进行修改
     */
    public void postHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.out.println("First postHandle 执行目标方法之后,渲染视图之前调。 在转向jsp页面之前,");
    }

    /**
     * 在渲染视图之后被调用,可以进行日志处理
     */
    public void afterCompletion(HttpServletRequest request,
            HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("First afterCompletion 渲染视图之后调用");
    }
}

拦截器的指定范围:配置拦截器时可以根据需要制定拦截器作用范围,针对特定处理器或方法进行拦截

<!-- 配置拦截器 -->
<!-- 使用 bean 定义一个 Interceptor,直接定义在 mvc:interceptors 根节点下则拦截所有的请求 -->
<!-- 定义在 mvc:interceptor 下面的表示是对特定的请求才进行拦截的 -->
<mvc:interceptors>
    <mvc:interceptor> 
        <!-- 指定拦截器作用路径 -->
        <mvc:mapping path="/product/*" /> 
        <bean class="com.sl.interceptors.TestInterceptor"></bean> 
    </mvc:interceptor> 
    <mvc:interceptor> 
        <mvc:mapping path="/order/*" /> 
        <bean class="com.sl.interceptors.TestOrderInterceptor"></bean> 
    </mvc:interceptor> 
 </mvc:interceptors>

表示针对该 Path 不拦截 , 表示针对该 Path 拦截,Path 可以使用通配符 .

日志框架

Java 日志统一接口

jcl: Jakarta Commons-logging 是 apache 最早提供的日志的门面接口, 也是 spring 默认使用的日志框架。

Commons-logging 的目的是为 “所有的 Java 日志实现” 提供一个统一的接口,它自身的日志功能平常弱(只有一个简单的SimpleLog?),所以一般不会单独使用它。Log4j 的功能非常全面强大,是目前的首选。

apache commons-logging

log4j

slf4j:Simple Logging Facade for Java,为 Java 提供的简单日志 Facade。Facade 门面,更底层一点说就是接口。它允许用户以自己的喜好,在工程中通过 slf4j 接入不同的日志系统。

因此 slf4j 入口就是众多接口的集合,它不负责具体的日志实现,只在编译时负责寻找合适的日志系统进行绑定。具体有哪些接口,全部都定义在 slf4j-api 中。

maven 仓库:slf4j-log4j12

10分钟搞定--混乱的 Java 日志体系

SpringMVC 配置 log4j

Logback 统一配置及环境变量加载问题

NDC 和 MDC 功能

spring 自带的 logback 和 log4j 都有 NDC 和 MDC 功能,

log4j 有个叫 MDC(Mapped Diagnostic Context) 的类(技术上使用了 ThreadLocal 实现,重点技术)。具体使用方法请自行查询。

UserFilter

/**
 * 用户信息相关的filter
 * 
 * @author 晓风轻 https://xwjie.github.io/PLMCodeTemplate/
 *
 */
public class UserFilter implements Filter {

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {

  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response,
      FilterChain chain) throws IOException, ServletException {

    // 得到用户个人相关的信息(登陆的用户,用户的语言)
    fillUserInfo((HttpServletRequest) request);

    try {
      chain.doFilter(request, response);
    } finally {
      // 由于tomcat线程重用,记得清空
      clearAllUserInfo();
    }
  }

  private void clearAllUserInfo() {
    UserUtil.clearAllUserInfo();
  }

  private void fillUserInfo(HttpServletRequest request) {
    // 用户信息
    String user = getUserFromSession(request);

    if (user != null) {
      UserUtil.setUser(user);
    }

    // 语言信息
    String locale = getLocaleFromCookies(request);

    // 放入到threadlocal,同一个线程任何地方都可以拿出来
    if (locale != null) {
      UserUtil.setLocale(locale);
    }
  }

  private String getLocaleFromCookies(HttpServletRequest request) {
    Cookie[] cookies = request.getCookies();

    if (cookies == null) {
      return null;
    }

    for (int i = 0; i < cookies.length; i++) {
      if (UserUtil.KEY_LANG.equals(cookies[i].getName())) {
        return cookies[i].getValue();
      }
    }

    return null;
  }

  private String getUserFromSession(HttpServletRequest request) {
    HttpSession session = request.getSession(false);

    if (session == null) {
      return null;
    }

    // 从session中获取用户信息放到工具类中
    return (String) session.getAttribute(UserUtil.KEY_USER);
  }

  @Override
  public void destroy() {

  }

}

用户信息放入MDC:

/**
 * 用户工具类
 * 
 * @author 晓风轻 https://xwjie.github.io/PLMCodeTemplate/
 *
 */
public class UserUtil {

  private final static ThreadLocal<String> tlUser = new ThreadLocal<String>();

  private final static ThreadLocal<Locale> tlLocale = new ThreadLocal<Locale>() {
    protected Locale initialValue() {
      // 语言的默认值
      return Locale.CHINESE;
    };
  };

  public static final String KEY_LANG = "lang";

  public static final String KEY_USER = "user";

  public static void setUser(String userid) {
    tlUser.set(userid);

    // 把用户信息放到log4j
    MDC.put(KEY_USER, userid);
  }

  /**
   * 如果没有登录,返回null
   * 
   * @return
   */
  public static String getUserIfLogin() {
    return tlUser.get();
  }

  /**
   * 如果没有登录会抛出异常
   * 
   * @return
   */
  public static String getUser() {
    String user = tlUser.get();

    if (user == null) {
      throw new UnloginException();
    }

    return user;
  }

  public static void setLocale(String locale) {
    setLocale(new Locale(locale));
  }

  public static void setLocale(Locale locale) {
    tlLocale.set(locale);
  }

  public static Locale getLocale() {
    return tlLocale.get();
  }

  public static void clearAllUserInfo() {
    tlUser.remove();
    tlLocale.remove();

    MDC.remove(KEY_USER);
  }
}

log4j 配置

<!-- Appenders -->
<appender name="console" class="org.apache.log4j.ConsoleAppender">
  <param name="Target" value="System.out" />
  <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern"
      value="[%t]%-d{MM-dd HH:mm:ss,SSS} %-5p:%X{user} - %c - %m%n" />
  </layout>
</appender>

异常处理

你之前项目中异常/国际化如何处理?

首先看看 Spring MVC 处理异常的 3 中方式,进行比较,最终选用一个比较合适的方式。

  • Spring MVC 提供的简单异常处理器 SimpleMappingExceptionResolver;

  • Spring MVC 异常处理接口 HandlerExceptionResolver 自定义自己的异常处理器;

  • @ExceptionHandler 注解实现异常处理;

@ControllerAdvice + @ ExceptionHandler

Spring MVC 中通过使用 @ControllerAdvice + @ ExceptionHandler 两个注解可以实现全局的异常捕捉。

@ExceptionHandler 注解的作用是当出现其定义的异常时进行处理的方法,其可以使用 springmvc 提供的数据绑定,比如注入 HttpServletRequest 等,还可以接受一个当前抛出的 Throwable 对象

@ControllerAdvice 注解可以把异常处理器应用到所有控制器 @Controller ,而不是 @Controller 注解的单个控制器,该异常处理器对当前控制器的所有方法有效;如果单独某个控制器需要自定义处理异常,不用顶层的异常处理器,可以在当前控制器内用 @ExceptionHandler 注解 ,这样当前控制器的异常处理就在当前类中。

备注:使用 ControllerAdvice 注解类里面的异常的处理的优先级低于直接定义在处理方法的类中

实现一个异常处理器:

@ControllerAdvice
public class ExceptionHandlers {

    @ExceptionHandler({ArithmeticException.class})
    public ModelAndView toException(Exception e){
        ModelAndView mv = new ModelAndView("home");
        System.out.println("gobal handler exception");
        //虽然不能使用 Map 往 request 中存值,但是可以使用下面的方法
        mv.addObject("error", e);
        System.out.println(e);
        return mv;
    }
}
@Controller
@RequestMapping("exception")
public class ExceptionController {
    // 示例1
    @RequestMapping("test")
    public ModelAndView test() {
        System.out.println(10/0); //抛异常
        return new ModelAndView("order/info", "message", "test exception");
    }  
}

比 @ControllerAdvice + @ ExceptionHandler 更好的办法

@ControllerAdvice + @ ExceptionHandler 确实能更好地处理异常, 但是不能打印日志,统计每一个 controller方法的耗时,所以自定义 AOP 来了, 虽然 @ControllerAdvice + @ ExceptionHandler 也是 AOP.

统一返回一个 ResultBean

如果是后端只是设计接口的话, 可以定义一个 ResultBean

@Data // lombok 语法
public class ResultBean<T> implements Serializable {

  private static final long serialVersionUID = 1L;

  public static final int NO_LOGIN = -1;

  public static final int SUCCESS = 0;

  public static final int FAIL = 1;

  public static final int NO_PERMISSION = 2;

  private String msg = "success";

  private int code = SUCCESS;

  private T data;

  public ResultBean() {
    super();
  }

  public ResultBean(T data) {
    super();
    this.data = data;
  }

  public ResultBean(Throwable e) {
    super();
    this.msg = e.toString();
    this.code = FAIL;
  }
}

异常要区分已知异常和未知异常

/**
 * 处理和包装异常
 */
public class ControllerAOP {
  private static final Logger logger = LoggerFactory.getLogger(ControllerAOP.class);

  public Object handlerControllerMethod(ProceedingJoinPoint pjp) {
    long startTime = System.currentTimeMillis();

    ResultBean<?> result;

    try {
      result = (ResultBean<?>) pjp.proceed();
      logger.info(pjp.getSignature() + "use time:" + (System.currentTimeMillis() - startTime));
    } catch (Throwable e) {
      result = handlerException(pjp, e);
    }

    return result;
  }

  /**
    * 封装异常信息,注意区分已知异常(自己抛出的)和未知异常
    */
  private ResultBean<?> handlerException(ProceedingJoinPoint pjp, Throwable e) {
    ResultBean<?> result = new ResultBean();

    // 已知异常
    if (e instanceof CheckException) {
      result.setMsg(e.getLocalizedMessage());
      result.setCode(ResultBean.FAIL);
    } else if (e instanceof UnloginException) {
      result.setMsg("Unlogin");
      result.setCode(ResultBean.NO_LOGIN);
    } else {
      logger.error(pjp.getSignature() + " error ", e);
      //TODO 未知的异常,应该格外注意,可以发送邮件通知等
      result.setMsg(e.toString());
      result.setCode(ResultBean.FAIL);
    }

    return result;
  }
}

AOP 配置

<!-- aop -->
  <aop:aspectj-autoproxy />
  <beans:bean id="controllerAop" class="xxx.common.aop.ControllerAOP" />
  <aop:config>
    <aop:aspect id="myAop" ref="controllerAop">
      <aop:pointcut id="target"
        expression="execution(public xxx.common.beans.ResultBean *(..))" />
      <aop:around method="handlerControllerMethod" pointcut-ref="target" />
    </aop:aspect>
  </aop:config>

简单的 controller

/**
 * 配置对象处理器
 * 
 * @author 晓风轻 https://github.com/xwjie/PLMCodeTemplate
 */
@RequestMapping("/config")
@RestController
public class ConfigController {

  private final ConfigService configService;

  public ConfigController(ConfigService configService) {
    this.configService = configService;
  }

  @GetMapping("/all")
  public ResultBean<Collection<Config>> getAll() {
    return new ResultBean<Collection<Config>>(configService.getAll());
  }

  /**
   * 新增数据, 返回新对象的id
   * 
   * @param config
   * @return
   */
  @PostMapping("/add")
  public ResultBean<Long> add(Config config) {
    return new ResultBean<Long>(configService.add(config));
  }

  /**
   * 根据id删除对象
   * 
   * @param id
   * @return
   */
  @PostMapping("/delete")
  public ResultBean<Boolean> delete(long id) {
    return new ResultBean<Boolean>(configService.delete(id));
  }

  @PostMapping("/update")
  public ResultBean<Boolean> update(Config config) {
    configService.update(config);
    return new ResultBean<Boolean>(true);
  }
}

国际化

你之前项目中异常/国际化如何处理?

使用 ResourceBundleMessageSource, 在 i18n/messages 加上对应翻译文件,根据请求头 lang 加载翻译文件

工具类注入 bean

工具类里面使用 spring 的 bean,使用了 MethodInvokingFactoryBean 的静态方法注入

<bean id="messageSource"
      class="org.springframework.context.support.ResourceBundleMessageSource"
      p:basename="i18n/messages" />
<bean
  class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
  <property name="staticMethod" value="plm.common.utils.CheckUtil.setResources" />
  <!-- 这里配置参数 -->
  <property name="arguments" ref="messageSource">
  </property>
</bean>

校验工具类 CheckUtil

package plm.common.utils;

import org.springframework.context.MessageSource;

import plm.common.exceptions.CheckException;

public class CheckUtil {
  private static MessageSource resources;

  public static void setResources(MessageSource resources) {
    CheckUtil.resources = resources;
  }

  public static void check(boolean condition, String msgKey, Object... args) {
    if (!condition) {
      fail(msgKey, args);
    }
  }

  public static void notEmpty(String str, String msgKey, Object... args) {
    if (str == null || str.isEmpty()) {
      fail(msgKey, args);
    }
  }

  public static void notNull(Object obj, String msgKey, Object... args) {
    if (obj == null) {
      fail(msgKey, args);
    }
  }

  private static void fail(String msgKey, Object... args) {
    throw new CheckException(resources.getMessage(msgKey, args, UserUtil.getLocale()));
  }
}

更多请查看 https://xwjie.github.io/rule/i18n-valid.html

常用注解

常见的使用多的注解问几个(requestbody, responsebody, ModelAttribute 等)

@ModelAttribute 注释的方法会在此 controller 每个方法执行前被执行,

@ModelAttribute 运用详解

应用

返回视图和 json 对象

ModelAndView, @ResponseBody: 获取 reponse.getWriter(), redirect 传参数放在 ModelAndView

Model 和 ModelAndView 可以在 redirect/forword 中设置参数

主要扩展点

spring 的主要扩展点有哪些?(最重要最有用的应该就是 bpp 了)

SpringBoot2 | Spring 核心扩展接口 | 核心扩展方法总结(十一)

不错的 blog

晓风轻技术小站

发表评论

评论内容
 

评论列表, 共 0 条评论