Skip to content
标签
spring
字数
39596 字
阅读时间
168 分钟

一、统一异常处理

java

import com.commnetsoft.core.CommonError;
import com.commnetsoft.commons.Result;
import com.commnetsoft.exception.MicroException;
import com.commnetsoft.exception.MicroRuntimeException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;

/**
 * Controller  AOP 增强拦截器<br/>
 * 主要拦截异常通用处理
 * author Brack.zhu
 * date 2019/3/22
 */
@ControllerAdvice
public class MicroControllerAdvice {

    private static final Logger logger = LoggerFactory.getLogger(MicroControllerAdvice.class);

    /**
     * 应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器
     *
     * @param binder
     */
    @InitBinder
    public void initBinder(WebDataBinder binder) {
    }

    /**
     * 把值绑定到Model中,使全局@RequestMapping可以获取到该值
     *
     * @param model
     */
    @ModelAttribute
    public void addAttributes(Model model) {
    }

    /**
     * 全局异常捕捉处理
     *
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public void otherExceptionHandler(HttpServletRequest req, Exception ex) {
        String url = req.getRequestURI();
        logger.error(url, ex);
    }

    /**
     * 拦截捕捉自定义异常
     *
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = MicroException.class)
    public Result microExceptionHandler(HttpServletRequest req, MicroException ex) {
        String url = req.getRequestURI();
        logger.error(url + " " + ex.getDesc(), ex);
        return Result.create(ex);
    }

    /**
     * 拦截捕捉自定义异常
     *
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = MicroRuntimeException.class)
    public Result MicroRuntimeExceptionHandler(HttpServletRequest req, MicroRuntimeException ex) {
        if(logger.isDebugEnabled()){
            String url = req.getRequestURI();
            logger.debug(url + " " + ex.getDesc(), ex);
        }
        return Result.create(ex);
    }

    /**
     * 处理请求对象属性不满足校验规则的异常信息
     *
     * @param request
     * @param exception
     * @return
     * @author cxf
     */
    @ResponseBody
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Result<Void> exception(HttpServletRequest request, MethodArgumentNotValidException exception) {
        BindingResult result = exception.getBindingResult();
        final List<FieldError> fieldErrors = result.getFieldErrors();
        StringBuilder builder = new StringBuilder();
        for (FieldError error : fieldErrors) {
            builder.append("{'" + error.getField()).append("':'").append(error.getDefaultMessage() + "'}");
        }
        logger.error("请求参数错误:" + builder.toString());
        return Result.create(CommonError.form_valid_failed, builder.toString());
    }

    /**
     * 处理请求单个参数不满足校验规则的异常信息
     *
     * @param request
     * @param exception
     * @throws Exception
     * @author cxf
     */
    @ResponseBody
    @ExceptionHandler(value = ConstraintViolationException.class)
    public Result<Void> constraintViolationExceptionHandler(HttpServletRequest request, ConstraintViolationException exception) {
        StringBuilder builder = new StringBuilder();
        Set<ConstraintViolation<?>> cves = exception.getConstraintViolations();
        for (ConstraintViolation<?> constraintViolation : cves) {
            builder.append(constraintViolation.getMessage() + "/");
        }
        String desc = StringUtils.removeEnd(builder.toString(), "/");
        logger.error("请求参数错误:" + desc);
        return Result.create(CommonError.form_valid_failed, desc);
    }


}

二、注解Aop

以切面日志为例,记录注解的方法的返回值或异常信息。

自定义一个注解

java
/**
 * 自定义操作日志记录注解
 *
 * @author ruoyi
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
    /**
     * 模块
     */
    public String title() default "";

    /**
     * 功能 枚举类 增删改导出
     */
    public BusinessType businessType() default BusinessType.OTHER;

    /**
     * 操作人类别 枚举类
     */
    public OperatorType operatorType() default OperatorType.MANAGE;

    /**
     * 是否保存请求的参数
     */
    public boolean isSaveRequestData() default true;
}

切面处理类

java
/**
 * 操作日志记录处理
 *
 * @author ruoyi
 */
@Aspect
@Component
public class LogAspect {
    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

    // 配置织入点
    @Pointcut("@annotation(com.ruoyi.common.annotation.Log)")
    public void logPointCut() {
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "logPointCut()" , returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
        handleLog(joinPoint, null, jsonResult);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "logPointCut()" , throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        handleLog(joinPoint, e, null);
    }

    protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) {
        try {
            // 获得注解
            Log controllerLog = getAnnotationLog(joinPoint);
            if (controllerLog == null) {
                return;
            }

            // 获取当前的用户
            LoginUser loginUser = SpringUtils.getBean(TokenService.class).getLoginUser(ServletUtils.getRequest());

            // *========数据库日志=========*//
            SysOperLog operLog = new SysOperLog();
            operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
            // 请求的地址
            String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
            operLog.setOperIp(ip);
            // 返回参数
            operLog.setJsonResult(JSON.toJSONString(jsonResult));

            operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
            if (loginUser != null) {
                operLog.setOperName(loginUser.getUsername());
            }

            if (e != null) {
                operLog.setStatus(BusinessStatus.FAIL.ordinal());
                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
            }
            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            operLog.setMethod(className + "." + methodName + "()");
            // 设置请求方式
            operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, controllerLog, operLog);
            // 保存数据库
            AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
        } catch (Exception exp) {
            // 记录本地异常日志
            log.error("==前置通知异常==");
            log.error("异常信息:{}" , exp.getMessage());
            exp.printStackTrace();
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param log     日志
     * @param operLog 操作日志
     * @throws Exception
     */
    public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog) throws Exception {
        // 设置action动作
        operLog.setBusinessType(log.businessType().ordinal());
        // 设置标题
        operLog.setTitle(log.title());
        // 设置操作人类别
        operLog.setOperatorType(log.operatorType().ordinal());
        // 是否需要保存request,参数和值
        if (log.isSaveRequestData()) {
            // 获取参数的信息,传入到数据库中。
            setRequestValue(joinPoint, operLog);
        }
    }

    /**
     * 获取请求的参数,放到log中
     *
     * @param operLog 操作日志
     * @throws Exception 异常
     */
    private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception {
        String requestMethod = operLog.getRequestMethod();
        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
            String params = argsArrayToString(joinPoint.getArgs());
            operLog.setOperParam(StringUtils.substring(params, 0, 2000));
        } else {
            Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
        }
    }

    /**
     * 是否存在注解,如果存在就获取
     */
    private Log getAnnotationLog(JoinPoint joinPoint) throws Exception {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        if (method != null) {
            return method.getAnnotation(Log.class);
        }
        return null;
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        String params = "";
        if (paramsArray != null && paramsArray.length > 0) {
            for (int i = 0; i < paramsArray.length; i++) {
                if (!isFilterObject(paramsArray[i])) {
                    Object jsonObj = JSON.toJSON(paramsArray[i]);
                    params += jsonObj.toString() + " ";
                }
            }
        }
        return params.trim();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象,则返回true;否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Iterator iter = collection.iterator(); iter.hasNext(); ) {
                return iter.next() instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); ) {
                Map.Entry entry = (Map.Entry) iter.next();
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse;
    }
}

三、servlet生命周期注解

通过注解方式,参考@PostConstruct与@PreDestroy

@PostConstruct

@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。

在整个Bean初始化中的执行顺序:

Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)

例子:

java
@Autowired
private SysDictTypeMapper dictTypeMapper;

@Autowired
private SysDictDataMapper dictDataMapper;

/**
* 项目启动时,初始化字典到缓存
*/
@PostConstruct
public void init(){
    List<SysDictType> dictTypeList = dictTypeMapper.selectDictTypeAll();
    for (SysDictType dictType : dictTypeList){
        List<SysDictData> dictDatas = dictDataMapper.selectDictDataByType(dictType.getDictType());
        DictUtils.setDictCache(dictType.getDictType(), dictDatas);
    }
}

@PreDestroy

被@PreDestroy修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreDestroy修饰的方法会在destroy()方法之后运行,在Servlet被彻底卸载之前。

java
/**
 * 确保应用退出时能关闭后台线程
 *
 * @author ruoyi
 */
@Component
public class ShutdownManager {
    private static final Logger logger = LoggerFactory.getLogger("sys-user");

    @PreDestroy
    public void destroy() {
        shutdownAsyncManager();
    }

    /**
     * 停止异步执行任务
     */
    private void shutdownAsyncManager() {
        try {
            logger.info("====关闭后台任务任务线程池====");
            AsyncManager.me().shutdown();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }
}

四、spring注解

@InitBinder

1、从字面意思可以看出这个的作用是给Binder做初始化的,被此注解的方法可以对WebDataBinder初始化。webDataBinder是用于表单到方法的数据绑定的!

2、InitBinder只在@Controller中注解方法来为这个控制器注册一个绑定器初始化方法,方法只对本控制器有效

可以做字符串解析,如:

java
/**
     * 将前台传递过来的日期格式的字符串,自动转化为Date类型
     */
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        // Date 类型转换
        binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) {
                setValue(DateUtils.parseDate(text));
            }
        });
    }

用于注解驱动的注解

@Configuration

在spring3.0版本之后加入的。此注解是spring支持注解驱动开发的一个标志。表明当前类是spring的一个配置类,作用是替代spring applicationContext.xml。但其本质就是@Component注解,被此注解修饰的类,也会被存入spring的ioc容器。 属性: value:用于存入spring的Ioc容器中Bean的id。

@ComponentScan

用于指定创建容器时要扫描的包。该注解在指定扫描的位置时,可以指定包名,也可以指定扫描的类。同时支持定义扫描规则,例如包含哪些或者排除哪些。同时,它还支持自定义Bean的命名规则
属性:
	value:
		用于指定要扫描的包。当指定了包的名称之后,spring会扫描指定的包及其子包下的所有类。
	basePackages:
		它和value作用是一样的。
	basePackageClasses:
		指定具体要扫描的类的字节码。
	nameGenrator:
		指定扫描bean对象存入容器时的命名规则。详情请参考第五章第4小节的BeanNameGenerator及其实现类。
	scopeResolver:
		用于处理并转换检测到的Bean的作用范围。
	soperdProxy:
		用于指定bean生成时的代理方式。默认是Default,则不使用代理。详情请参考第五章第5小节ScopedProxyMode枚举。
	resourcePattern:
		用于指定符合组件检测条件的类文件,默认是包扫描下的  **/*.class
	useDefaultFilters:
		是否对带有@Component @Repository @Service @Controller注解的类开启检测,默认是开启的。
	includeFilters:
		自定义组件扫描的过滤规则,用以扫描组件。
		FilterType有5种类型:
            ANNOTATION, 注解类型 默认
            ASSIGNABLE_TYPE,指定固定类
            ASPECTJ, ASPECTJ类型
            REGEX,正则表达式
            CUSTOM,自定义类型
        详细用法请参考第五章第6小节自定义组件扫描过滤规则
	excludeFilters:
		自定义组件扫描的排除规则。
	lazyInit:
		组件扫描时是否采用懒加载 ,默认不开启。
使用场景:
	在注解驱动开发时,我们自己编写的类都使用注解的方式进行配置,要想让spring添加到ioc容器中,就需要使用此注解来实现组件的扫描。
细节:
	在spring4.3版本之后还加入了一个@ComponentScans的注解,该注解就是支持配置多个@ComponentScan。

@Bean

作用:
	它写在方法上,表示把当前方法的返回值存入spring的ioc容器。
	同时还可以出现在注解上,作为元注解来使用。		
属性:
	name:
		用于指定存入spring容器中bean的标识。支持指定多个标识。当不指定该属性时,默认值是当前方法的名称。
	value:
		此属性是在4.3.3版本之后加入的。它和name属性的作用是一样的。
	autowireCandidate:
		用于指定是否支持自动按类型注入到其他bean中。只影响@Autowired注解的使用。不影响@Resource注解注入。默认值为true,意为允许使用自动按类型注入。
	initMethod:
		用于指定初始化方法。
	destroyMethod:
		用于指定销毁方法。
使用场景:
	通常情况下,在基于注解的配置中,我们用于把一个类存入spring的ioc容器中,首先考虑的是使用@Component以及他的衍生注解。但是如果遇到要存入容器的Bean对象不是我们写的类,此时无法在类上添加@Component注解,这时就需要此注解了。
示例:
	例如,在我们配置JdbcTemplate使用Spring内置数据源DriverManagerDataSource时,数据源类是spring-jdbc这个jar包中类,此时我们无法编辑,在上面加注解,此时就可以使用@Bean注解配置。

@Import

作用:
	该注解是写在类上的,通常都是和注解驱动的配置类一起使用的。其作用是引入其他的配置类。使用了此注解之后,可以使我们的注解驱动开发和早期xml配置一样,分别配置不同的内容,使配置更加清晰。同时指定了此注解之后,被引入的类上可以不再使用@Configuration,@Component等注解。
属性:
	value:
		用于指定其他配置类的字节码。它支持指定多个配置类。
		关于ImportSelector和ImportBeanDefinitionRegistrar请参考第五章第7小节@Import注解的高级分析。
使用场景:
	当我们在使用注解驱动开发时,由于配置项过多,如果都写在一个类里面,配置结构和内容将杂乱不堪,此时使用此注解可以把配置项进行分门别类进行配置。

@PropertySource

作用:
	用于指定读取资源文件的位置。注意,它不仅支持properties,也支持xml文件,并且通过YAML解析器,配合自定义PropertySourceFactory实现解析yml配置文件(详情请参考第五章第8小节自定义PropertySourceFactory实现YAML文件解析)。
属性:
	name:
		指定资源的名称。如果没有指定,将根据基础资源描述生成。
	value:
		指定资源的位置。可以是类路径,也可以是文件路径。
	ignoreResourceNotFound:
		指定是否忽略资源文件有没有,默认是false,也就是说当资源文件不存在时spring启动将会报错。
	encoding:
		指定解析资源文件使用的字符集。当有中文的时候,需要指定中文的字符集。
	factory:
		指定读取对应资源文件的工厂类,默认的是PropertySourceFactory。
使用场景:
	我们实际开发中,使用注解驱动后,xml配置文件就没有了,此时一些配置如果直接写在类中,会造成和java源码的紧密耦合,修改起来不方法。此时一些配置可以使用properties或者yml来配置就变得很灵活方便。

注解驱动开发之注入时机和设定注入条件的注解

@DependsOn

作用:
	用于指定某个类的创建依赖的bean对象先创建。spring中没有特定bean的加载顺序,使用此注解则可指定bean的加载顺序。(在基于注解配置中,是按照类中方法的书写顺序决定的)
属性:
	value: 
		用于指定bean的唯一标识。被指定的bean会在当前bean创建之前加载。
使用场景:
	在观察者模式中,分为事件,事件源和监听器。一般情况下,我们的监听器负责监听事件源,当事件源触发了事件之后,监听器就要捕获,并且做出相应的处理。以此为前提,我们肯定希望监听器的创建时间在事件源之前,此时就可以使用此注解。

@Lazy

作用:
	用于指定单例bean对象的创建时机。在没有使用此注解时,单例bean的生命周期与容器相同。但是当使用了此注解之后,单例对象的创建时机变成了第一次使用时创建。注意:这不是延迟加载思想(因为不是每次使用时都创建,只是第一次创建的时机改变了)。
属性:
	value:
		指定是否采用延迟加载。默认值为true,表示开启。
使用场景:
	在实际开发中,当我们的Bean是单例对象时,并不是每个都需要一开始都加载到ioc容器之中,有些对象可以在真正使用的时候再加载,当有此需求时,即可使用此注解。值得注意的是,此注解只对单例bean对象起作用,当指定了@Scope注解的prototype取值后,此注解不起作用。

@Conditional

作用:
	它的作用是根据条件选择注入的bean对象。
属性:
	value:
		用于提供一个Condition接口的实现类,实现类中需要编写具体代码实现注入的条件。
使用场景:
	当我们在开发时,可能会使用多平台来测试,例如我们的测试数据库分别部署到了linux和windows两个操作系统上面,现在根据我们的工程运行环境选择连接的数据库。此时就可以使用此注解。同时基于此注解引出的@Profile注解,就是根据不同的环境,加载不同的配置信息,详情请参考第五章第9小节@Profile的使用。

用于创建对象的注解

@Component和三个衍生注解

@Controller 、@Service、@Repository

作用:
	这四个注解都是用于修饰类的。是用于把当前类创建一个对象,存入spring的ioc容器中。在实例化时,首选默认无参构造函数。同时支持带参构造,前提是构造函数的参数依赖必须要有值。否则抛异常	
属性:
	value:
		用于指定存入容器时bean的id。当不指定时,默认值为当前类的名称。
使用场景:
	当我们需要把自己编写的类注入到Ioc容器中,就可以使用以上四个注解实现。以上四个注解中@Component注解通常用在非三层对象中。而@Controller,@Service,@Repository三个注解一般是针对三层对象使用的,提供更加精确的语义化配置。
	需要注意的是,spring在注解驱动开发时,要求必须先接管类对象,然后会处理类中的属性和方法。如果类没有被spring接管,那么里面的属性和方法上的注解都不会被解析。

用于注入数据的注解

@Autowired

作用:
	自动按照类型注入。当ioc容器中有且只有一个类型匹配时可以直接注入成功。当有超过一个匹配时,则使用变量名称(写在方法上就是方法名称)作为bean的id,在符合类型的bean中再次匹配,能匹配上就可以注入成功。当匹配不上时,是否报错要看required属性的取值。
属性:
	required:
		是否必须注入成功。默认值是true,表示必须注入成功。当取值为true的时候,注入不成功会报错。
使用场景:
	此注解的使用场景非常之多,在实际开发中应用广泛。通常情况下我们自己写的类中注入依赖bean对象时,都可以采用此注解。

@Qualifier

作用:
	当使用自动按类型注入时,遇到有多个类型匹配的时候,就可以使用此注解来明确注入哪个bean对象。注意它通常情况下都必须配置@Autowired注解一起使用
属性:
	value:
		用于指定bean的唯一标识。
使用场景:
	在我们的项目开发中,很多时候都会用到消息队列,我们一ActiveMQ为例。当和spring整合之后,Spring框架提供了一个JmsTemplate对象,它既可以用于发送点对点模型消息也可以发送主题模型消息。如果项目中两种消息模型都用上了,那么针对不同的代码,将会注入不同的JmsTemplate,而容器中出现两个之后,就可以使用此注解注入。当然不用也可以,我们只需要把要注入的变量名称改为和要注入的bean的id一致即可。

@Resource

作用:
	此注解来源于JSR规范(Java Specification Requests),其作用是找到依赖的组件注入到应用来,它利用了JNDI(Java Naming and Directory Interface Java命名目录接口 J2EE规范之一)技术查找所需的资源。
	默认情况下,即所有属性都不指定,它默认按照byType的方式装配bean对象。如果指定了name,没有指定type,则采用byName。如果没有指定name,而是指定了type,则按照byType装配bean对象。当byName和byType都指定了,两个都会校验,有任何一个不符合条件就会报错。
属性:
	name:
		资源的JNDI名称。在spring的注入时,指定bean的唯一标识。
	type:
		指定bean的类型。
	lookup:
		引用指向的资源的名称。它可以使用全局JNDI名称链接到任何兼容的资源。
	authenticationType:
		指定资源的身份验证类型。它只能为任何受支持类型的连接工厂的资源指定此选项,而不能为其他类型的资源指定此选项。
	shareable:
    	指定此资源是否可以在此组件和其他组件之间共享。
    mappedName:
    	指定资源的映射名称。
    description:
    	指定资源的描述。 
使用场景:
	当我们某个类的依赖bean在ioc容器中存在多个的时候,可以使用此注解指定特定的bean对象注入。当然我们也可以使用@Autowired配合@Qualifier注入。

@Value

作用:
	用于注入基本类型和String类型的数据。它支持spring的EL表达式,可以通过${} 的方式获取配置文件中的数据。配置文件支持properties,xml和yml文件。
属性:
	value:
		指定注入的数据或者spring的el表达式。
使用场景:
	在实际开发中,像连接数据库的配置,发送邮件的配置等等,都可以使用配置文件配置起来。此时读取配置文件就可以借助spring的el表达式读取。

@Inject

作用:
	它也是用于建立依赖关系的。和@Resource和@Autowired的作用是一样。在使用之前需要先导入坐标:
	<!-- https://mvnrepository.com/artifact/javax.inject/javax.inject -->
    <dependency>
        <groupId>javax.inject</groupId>
        <artifactId>javax.inject</artifactId>
        <version>1</version>
    </dependency>
	但是他们之前也有区别:
		@Autowired:来源于spring框架自身。
					默认是byType自动装配,当配合了@Qualifier注解之后,由@Qualifier实现byName装配。它有一个required属性,用于指定是否必须注入成功。
		@Resource:来源于JSR-250规范。
				  在没有指定name属性时是byType自动装配,当指定了name属性之后,采用byName方式自动装配。
		@Inject:来源于JSR-330规范。(JSR330是Jcp给出的官方标准反向依赖注入规范。)
				 它不支持任何属性,但是可以配合@Qualifier或者@Primary注解使用。
				 同时,它默认是采用byType装配,当指定了JSR-330规范中的@Named注解之后,变成byName装配。
			
属性:

使用场景:
	在使用@Autowired注解的地方,都可以替换成@Inject。它也可以出现在方法上,构造函数上和字段上,但是需要注意的是:因为JRE无法决定构造方法注入的优先级,所以规范中规定类中只能有一个构造方法带@Inject注解。

@Primary

作用:
	用于指定bean的注入优先级。被@Primary修饰的bean对象优先注入
属性:

使用场景:
	当我们的依赖对象,有多个存在时,@Autowired注解已经无法完成功能,此时我们首先想到的是@Qualifier注解指定依赖bean的id。但是此时就产生了,无论有多少个bean,每次都会使用指定的bean注入。但是当我们使用@Primary,表示优先使用被@Primary注解的bean,但是当不存在时还会使用其他的。

和生命周期以及作用范围相关的注解

@Scope

作用:
	用于指定bean对象的作用范围。
属性:
	value: 
		指定作用范围的取值。在注解中默认值是""。
		但是在spring初始化容器时,会借助ConfigurableBeanFactory接口中的类成员:
		String SCOPE_SINGLETON = "singleton";
	scopeName:
		它和value的作用是一样的。
	proxyMode:
		它是指定bean对象的代理方式的。指定的是ScopedProxyMode枚举的值
            DEFAULT:默认值。(就是NO)
            NO:不使用代理。
            INTERFACES:使用JDK官方的基于接口的代理。
            TARGET_CLASS:使用CGLIB基于目标类的子类创建代理对象。
使用场景:
	在实际开发中,我们的bean对象默认都是单例的。通常情况下,被spring管理的bean都使用单例模式来创建。但是也有例外,例如Struts2框架中的Action,由于有模型驱动和OGNL表达式的原因,就必须配置成多例的。

@PostConstruct

作用:
	用于指定bean对象的初始化方法。
属性:

使用场景:
	在bean对象创建完成后,需要对bean中的成员进行一些初始化的操作时,就可以使用此注解配置一个初始化方法,完成一些初始化的操作。

@PreDestroy

作用:
	用于指定bean对象的销毁方法。
属性:

使用场景:
	在bean对象销毁之前,可以进行一些清理操作。

五、容器

spring容器常用有两个接口:BeanFactory 接口 和 ApplicationContext 接口。

  • BeanFactory 接口 是 Spring 的核心容器,包含控制反转、基本依赖注、bean的生命周期。singletonObjects属性包含所有的单例bean。
    • 获取bean
  • ApplicationContext 接口,是 BeanFactory 的子接口。它扩展了 BeanFactory 接口的功能。国际化通配符方式获取Resource资源,整合Environment、事件发布与监听。
    • 国际化
    • 通配符方式获取一组 Resource 资源
    • 整合 Environment 环境(能通过它获取各种来源的配置信息)
    • 事件发布与监听,实现组件之间的解耦

5.1 国际化实现

java
public class TestMessageSource {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();

        context.registerBean("messageSource", MessageSource.class, () -> {
            ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
            ms.setDefaultEncoding("utf-8");
            ms.setBasename("messages");
            return ms;
        });

        context.refresh();

        System.out.println(context.getMessage("hi", null, Locale.ENGLISH));
        System.out.println(context.getMessage("hi", null, Locale.CHINESE));
        System.out.println(context.getMessage("hi", null, Locale.JAPANESE));
    }
}

国际化文件均在 src/resources 目录下

messages.properties(空)

messages_en.properties

properties
hi=Hello

messages_ja.properties

properties
hi=こんにちは

messages_zh.properties

properties
hi=你好

注意

  • ApplicationContext 中 MessageSource bean 的名字固定为 messageSource
  • 使用 SpringBoot 时,国际化文件名固定为 messages
  • 空的 messages.properties 也必须存在

5.2 容器实现类

  • DefaultListableBeanFactory,是 BeanFactory 最重要的实现,像控制反转依赖注入功能,都是它来实现
  • ClassPathXmlApplicationContext,从类路径查找 XML 配置文件,创建容器(旧)
  • FileSystemXmlApplicationContext,从磁盘路径查找 XML 配置文件,创建容器(旧)
  • XmlWebApplicationContext,传统 SSM 整合时,基于 XML 配置文件的容器(旧)
  • AnnotationConfigWebApplicationContext,传统 SSM 整合时,基于 java 配置类的容器(旧)
  • AnnotationConfigApplicationContext,Spring boot 中非 web 环境容器(新)
  • AnnotationConfigServletWebServerApplicationContext,Spring boot 中 servlet web 环境容器(新)
  • AnnotationConfigReactiveWebServerApplicationContext,Spring boot 中 reactive web 环境容器(新)

另外要注意的是,后面这些带有 ApplicationContext 的类都是 ApplicationContext 接口的实现,但它们是组合了 DefaultListableBeanFactory 的功能,并非继承而来

beanFactory 可以通过 registerBeanDefinition 注册一个 bean definition 对象,可通过配置类、xml、组件扫描等方式生成并注册到beanFactory中,该对象描述了bean的scope、创建方式(工厂、构造)、初始化销毁方法。

beanFactory需要手动调用后处理器对其增强,如通过解析 @Bean、@ComponentScan 等注解,来补充一些 bean definition。

beanFactory需手动添加后处理器,如 @Autowired,@Resource 等注解的解析都是 bean 后处理器完成的。

beanFactory需手动调用方法初始化单例。需要额外设置才能解析${} 与 #{}。

5.3 Bean生命周期

一个受 Spring 管理的 bean,生命周期主要阶段有

  1. 创建:根据 bean 的构造方法或者工厂方法来创建 bean 实例对象
  2. 依赖注入:根据 @Autowired,@Value 或其它一些手段,为 bean 的成员变量填充值、建立关系
  3. 初始化:回调各种 Aware 接口,调用对象的各种初始化方法
  4. 销毁:在容器关闭时,会销毁所有单例对象(即调用它们的销毁方法)
    • prototype 对象也能够销毁,不过需要容器这边主动调用

一些资料会提到,生命周期中还有一类 bean 后处理器:BeanPostProcessor,会在 bean 的初始化的前后,提供一些扩展逻辑。

创建前后的增强

  • postProcessBeforeInstantiation
    • 这里返回的对象若不为 null 会替换掉原本的 bean,并且仅会走 postProcessAfterInitialization 流程
  • postProcessAfterInstantiation
    • 这里如果返回 false 会跳过依赖注入阶段

依赖注入前的增强

  • postProcessProperties
    • 如 @Autowired、@Value、@Resource

初始化前后的增强

  • postProcessBeforeInitialization
    • 这里返回的对象会替换掉原本的 bean
    • 如 @PostConstruct、@ConfigurationProperties
  • postProcessAfterInitialization
    • 这里返回的对象会替换掉原本的 bean
    • 如代理增强

销毁之前的增强

  • postProcessBeforeDestruction
    • 如 @PreDestroy

5.4 bean后处理器

1. 排序

  1. 实现了 PriorityOrdered 接口的优先级最高
  2. 实现了 Ordered 接口与加了 @Order 注解的平级, 按数字升序
  3. 其它的排在最后

2. 作用

  1. @Autowired 等注解的解析属于 bean 生命周期阶段(依赖注入, 初始化)的扩展功能,这些扩展功能由 bean 后处理器来完成
  2. 每个后处理器各自增强什么功能
    • AutowiredAnnotationBeanPostProcessor 解析 @Autowired 与 @Value
    • CommonAnnotationBeanPostProcessor 解析 @Resource、@PostConstruct、@PreDestroy
    • ConfigurationPropertiesBindingPostProcessor 解析 @ConfigurationProperties
  3. 另外 ContextAnnotationAutowireCandidateResolver 负责获取 @Value 的值,解析 @Qualifier、泛型、@Lazy 等

3. @Autowired bean 后处理器运行分析

  1. AutowiredAnnotationBeanPostProcessor.findAutowiringMetadata 用来获取某个 bean 上加了 @Value @Autowired 的成员变量,方法参数的信息,表示为 InjectionMetadata
  2. InjectionMetadata 可以完成依赖注入
  3. InjectionMetadata 内部根据成员变量,方法参数封装为 DependencyDescriptor 类型
  4. 有了 DependencyDescriptor,就可以利用 beanFactory.doResolveDependency 方法进行基于类型的查找

5.5 BeanFactory 后处理器

  1. @ComponentScan, @Bean, @Mapper 等注解的解析属于核心容器(即 BeanFactory)的扩展功能
  2. 这些扩展功能由不同的 BeanFactory 后处理器来完成,其实主要就是补充了一些 bean 定义

1. @ComponentScan

  1. Spring 操作元数据的工具类 CachingMetadataReaderFactory
  2. 通过注解元数据(AnnotationMetadata)获取直接或间接标注的注解信息
  3. 通过类元数据(ClassMetadata)获取类名,AnnotationBeanNameGenerator 生成 bean 名
  4. 解析元数据是基于 ASM 技术

2. 解析Mapper

  1. Mapper 接口被 Spring 管理的本质:实际是被作为 MapperFactoryBean 注册到容器中
  2. Spring 的诡异做法,根据接口生成的 BeanDefinition 仅为根据接口名生成 bean 名

5.6 Aware 接口

1. Aware 接口及 InitializingBean 接口

  1. Aware 接口提供了一种【内置】 的注入手段,例如
    • BeanNameAware 注入 bean 的名字
    • BeanFactoryAware 注入 BeanFactory 容器
    • ApplicationContextAware 注入 ApplicationContext 容器
    • EmbeddedValueResolverAware 注入 ${} 解析器
  2. InitializingBean 接口提供了一种【内置】的初始化手段
  3. 对比
    • 内置的注入和初始化不受扩展功能的影响,总会被执行
    • 而扩展功能受某些情况影响可能会失效
    • 因此 Spring 框架内部的类常用内置注入和初始化

2. 配置类 @Autowired 失效分析

Java 配置类包含 BeanFactoryPostProcessor 的情况,因此要创建其中的 BeanFactoryPostProcessor 必须提前创建 Java 配置类,而此时的 BeanPostProcessor 还未准备好,导致 @Autowired 等注解失效

  • 用内置依赖注入和初始化取代扩展依赖注入和初始化
  • 用静态工厂方法代替实例工厂方法,避免工厂对象提前被创建

5.7 初始化与销毁

Spring 提供了多种初始化手段,除了课堂上讲的 @PostConstruct,@Bean(initMethod) 之外,还可以实现 InitializingBean 接口来进行初始化,如果同一个 bean 用了以上手段声明了 3 个初始化方法,那么它们的执行顺序是

  1. @PostConstruct 标注的初始化方法
  2. InitializingBean 接口的初始化方法
  3. @Bean(initMethod) 指定的初始化方法

与初始化类似,Spring 也提供了多种销毁手段,执行顺序为

  1. @PreDestroy 标注的销毁方法
  2. DisposableBean 接口的销毁方法
  3. @Bean(destroyMethod) 指定的销毁方法

5.8 Scope

在当前版本的 Spring 和 Spring Boot 程序中,支持五种 Scope

  • singleton,容器启动时创建(未设置延迟),容器关闭时销毁
  • prototype,每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁
  • request,每次请求用到此 bean 时创建,请求结束时销毁
  • session,每个会话用到此 bean 时创建,会话结束时销毁
  • application,web 容器用到此 bean 时创建,容器停止时销毁

有些文章提到有 globalSession 这一 Scope,也是陈旧的说法,目前 Spring 中已废弃

但要注意,如果在 singleton 注入其它 scope 都会有问题,解决方法有

  • @Lazy
  • @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
  • ObjectFactory
  • ApplicationContext.getBean

六、AOP

AOP术语:
Joinpoint(连接点): 
	所谓连接点是指那些被拦截到的点。在spring中,指的是方法,因为spring只支持方 法类型的连接点。 
Pointcut(切入点): 
	所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。 
Advice(通知/增强): 
	所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。 
	通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。 
Introduction(引介): 
	引介是一种特殊的通知在不修改类代码的前提下, 可以在运行期为类动态地添加一 些方法或Field。 
Target(目标对象): 
	代理的目标对象。 
Weaving(织入): 
	是指把增强应用到目标对象来创建新的代理对象的过程。 
	spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。 
Proxy(代理): 
	一个类被AOP织入增强后,就产生一个结果代理类。 
	Aspect(切面): 是切入点和通知(引介)的结合。

常用注解

@EnableAspectJAutoProxy

用于开启注解AOP支持的 
作用: 
	表示开启spring对注解aop的支持。它有两个属性,分别是指定采用的代理方式和 是否暴露代理对象,通过AopContext可以进行访问。从定义可以看得出,它引入 AspectJAutoProxyRegister.class对象,该对象是基于注解@EnableAspectJAutoProxy 注册一个AnnotationAwareAspectJAutoProxyCreator,该对象通过调用 AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(regist ry);注册一个aop代理对象生成器。关于AnnotationAwareAspectJAutoProxyCreator请参 考第五章第二小节《AnnotationAwareAspectJAutoProxyCreator对象的分析》 
属性: 
	proxyTargetClass: 指定是否采用cglib进行代理。默认值是false,表示使用jdk的代理。 
	exposeProxy: 指定是否暴露代理对象,通过AopContext可以进行访问。 
使用场景:
	当我们注解驱动开发时,在需要使用aop实现某些功能的情况下,都需要用到此注 解。

@Aspect

用于配置切面的
作用: 
	声明当前类是一个切面类。 
属性: 
	value: 默认我们的切面类应该为单例的。但是当切面类为一个多例类时,指定预 处理的切入点表达式。
	用法是perthis(切入点表达式)。 
	它支持指定切入点表达式,或者是用@Pointcut修饰的方法名称(要求全 限定方法名) 
使用场景:
	此注解也是一个注解驱动开发aop的必备注解。

@Pointcut

用于配置切入点表达式的
作用: 
	此注解是用于指定切入点表达式的。 
属性: 
	value: 用于指定切入点表达式。表达式的配置详解请参考第五章节第三小节《切 入点表达式的写法》 
	argNames:用于指定切入点表达式的参数。参数可以是execution中的,也可以是 args中的。通常情况下不使用此属性也可以获得切入点方法参数。 
使用场景:
	在实际开发中,当我们的多个通知需要执行,同时增强的规则确定的情况下,就可 以把切入点表达式通用化。此注解就是代替xml中的<aop:pointcut>标签,实现切入点表达 式的通用化。

配置通知的注解

@Before

作用: 
	被此注解修饰的方法为前置通知。前置通知的执行时间点是在切入点方法执行之 前。
属性:
	value: 用于指定切入点表达式。可以是表达式,也可以是表达式的引用。 
	argNames:用于指定切入点表达式参数的名称。它要求和切入点表达式中的参数名称 一致。通常不指定也可以获取切入点方法的参数内容。 
使用场景:
	在实际开发中,我们需要对切入点方法执行之前进行增强, 此时就用到了前置通 知。在通知(增强的方法)中需要获取切入点方法中的参数进行处理时,就要配合切入点表达 式参数来使用。

@AfterReturning

作用: 
	用于配置后置通知。后置通知的执行是在切入点方法正常执行之后执行。 需要注意的是,由于基于注解的配置时,spring创建通知方法的拦截器链时,后置 通知在最终通知之后,所以会先执行@After注解修饰的方法。 
属性:
	value: 用于指定切入点表达式,可以是表达式,也可以是表达式的引用。 
	pointcut:它的作用和value是一样的。 
	returning:指定切入点方法返回值的变量名称。它必须和切入点方法返回值名称一 致。
	argNames:用于指定切入点表达式参数的名称。它要求和切入点表达式中的参数名称 一致。通常不指定也可以获取切入点方法的参数内容。
使用场景:
	此注解是用于配置后置增强切入点方法的。被此注解修饰方法会在切入点方法正常 执行之后执行。在我们实际开发中,像提交事务,记录访问日志,统计方法执行效率等等都可 以利用后置通知实现。

@AfterThrowing

作用: 
	用于配置异常通知。 
属性: 
	value: 用于指定切入点表达式,可以是表达式,也可以是表达式的引用。 
	pointcut:它的作用和value是一样的。 
	throwing:指定切入点方法执行产生异常时的异常对象变量名称。它必须和异常变量 名称一致。
	argNames:用于指定切入点表达式参数的名称。它要求和切入点表达式中的参数名称 一致。通常不指定也可以获取切入点方法的参数内容。 
使用场景:
	用此注解修饰的方法执行时机是在切入点方法执行产生异常之后执行。

@After

作用: 
	用于指定最终通知。 
属性: 
	value: 用于指定切入点表达式,可以是表达式,也可以是表达式的引用。
	argNames:用于指定切入点表达式参数的名称。它要求和切入点表达式中的参数名称 一致。通常不指定也可以获取切入点方法的参数内容。 
使用场景:
	最终通知的执行时机,是在切入点方法执行完成之后执行,无论切入点方法执行是 否产生异常最终通知都会执行。所以被此注解修饰的方法,通常都是做一些清理操作。

@Around

作用: 
	用于指定环绕通知。 
属性: 
	value: 用于指定切入点表达式,可以是表达式,也可以是表达式的引用。
	argNames:用于指定切入点表达式参数的名称。它要求和切入点表达式中的参数名称 一致。通常不指定也可以获取切入点方法的参数内容。 
使用场景:
	环绕通知有别于前面介绍的四种通知类型。它不是指定增强方法执行时机的,而是 spring为我们提供的一种可以通过编码的方式手动控制增强方法何时执行的机制。

拓展目标类

@DeclareParents

作用: 
	用于给被增强的类提供新的方法。(实现新的接口) 
属性: 
	value: 用于指定目标类型的表达式。当在全限定类名后面跟上+时,表示当前类 及其子类
	defaultImpl: 指定提供方法或者字段的默认实现类。 
使用场景:
	当我们已经完成了一个项目的某个阶段开发,此时需要对已完成的某个类加入一些 新的方法,我们首先想到的是写一个接口,然后让这些需要方法的类实现此接口,但是如果目 标类非常复杂,牵一发而动全身,改动的话可能非常麻烦。此时就可以使用此注解,然后建一 个代理类,同时代理该类和目标类。

@EnableLoadTimeWeaving

作用: 
	用于切换不同场景下实现增强。 
属性: 
	aspectjWeaving:是否开启LTW的支持。 ENABLED 开启LTW DISABLED 不开启LTW AUTODETECT 如果类路径下能读取到META‐INF/aop.xml文件,则开启LTW,否则关 闭
使用场景:
	在Java 语言中,从织入切面的方式上来看,存在三种织入方式:编译期织入、类 加载期织入和运行期织入。编译期织入是指在Java编译期,采用特殊的编译器,将切面织入 到Java类中;而类加载期织入则指通过特殊的类加载器,在类字节码加载到JVM时,织入切 面;运行期织入则是采用CGLib工具或JDK动态代理进行切面的织入。 
	AspectJ提供了两种切面织入方式,第一种通过特殊编译器,在编译期,将AspectJ 语言编写的切面类织入到Java类中,可以通过一个Ant或Maven任务来完成这个操作;第二种 方式是类加载期织入,也简称为LTW(Load Time Weaving)

切入点表达式

指的是遵循特定的语法用于捕获每一个种类的可使用连接点的语法。 用于对符合语法格式的连接点进行增强。

分类:

主要的种类: 
	方法执行:execution(MethodSignature) 
	方法调用:call(MethodSignature)
    构造器执行:execution(ConstructorSignature) 
    构造器调用:call(ConstructorSignature) 
    类初始化:staticinitialization(TypeSignature) 
    属性读操作:get(FieldSignature) 
    属性写操作:set(FieldSignature) 
    例外处理执行:handler(TypeSignature) 
    对象初始化:initialization(ConstructorSignature) 
    对象预先初始化:preinitialization(ConstructorSignature)

关键字:

支持的AspectJ切入点指示符如下:
	execution:用于匹配方法执行的连接点;
    within:用于匹配指定类型内的方法执行; 
    this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这 样就可能包括引入接口也类型匹配;
    target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就 不包括引入接口也类型匹配; 
    args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
    @within:用于匹配所以持有指定注解类型内的方法; 
    @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解; 
    @args:用于匹配当前执行的方法传入的参数持有指定注解的执行; 
    @annotation:用于匹配当前执行方法持有指定注解的方法;
    bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的 执行方法;
    reference pointcut:表示引用其他命名切入点,只有@ApectJ风格支持,Schema风 格不支持。

切入点表达式的通配符

AspectJ类型匹配的通配符: 
	*:匹配任何数量字符; 
	..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式 中匹配任何数量参数。 
	+:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。 
	说明:
	   java.lang.String 匹配String类型; 
	   java.*.String 匹配java包下的任何“一级子包”下的String类型; 
	   如匹配java.lang.String,但不匹配java.lang.ss.String 
	   java..* 匹配java包及任何子包下的任何类型; 
	   如匹配java.lang.String、java.lang.annotation.Annotation 
	   java.lang.*ing 匹配任何java.lang包下的以ing结尾的类型; 
	   java.lang.Number+ 匹配java.lang包下的任何Number的自类型; 
	   如匹配java.lang.Integer,也匹配java.math.BigInteger

切入点表达式的逻辑条件

&& and 
|| or
! not

AOP 底层实现方式之一是代理,由代理结合通知和目标,提供增强功能

除此以外,aspectj 提供了两种另外的 AOP 底层实现:

  • 第一种是通过 ajc 编译器在编译 class 类文件时,就把通知的增强功能,织入到目标类的字节码中

  • 第二种是通过 agent 在加载目标类时,修改目标类的字节码,织入增强功能

  • 作为对比,之前学习的代理是运行时生成新的字节码

简单比较的话:

  • aspectj 在编译和加载时,修改目标字节码,性能较高
  • aspectj 因为不用代理,能突破一些技术上的限制,例如对构造、对静态方法、对 final 也能增强
  • 但 aspectj 侵入性较强,且需要学习新的 aspectj 特有语法,因此没有广泛流行

6.1 AOP 实现之 ajc 编译器

  1. 编译器也能修改 class 实现增强
  2. 编译器增强能突破代理仅能通过方法重写增强的限制:可以对构造方法、静态方法等实现增强

注意

  • 版本选择了 java 8, 因为目前的 aspectj-maven-plugin 1.14.0 最高只支持到 java 16
  • 一定要用 maven 的 compile 来编译, idea 不会调用 ajc 编译器

6.2 AOP 实现之 agent 类加载

类加载时可以通过 agent 修改 class 实现增强

6.3 AOP 实现之 proxy

jdk 动态代理要求目标必须实现接口,生成的代理类实现相同接口,因此代理与目标之间是平级兄弟关系

cglib 不要求目标实现接口,它生成的代理类是目标的子类,因此代理与目标之间是子父关系.限制:final 类无法被 cglib 增强

七、WEB

RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter 俩是一对,分别用来

  • 处理 @RequestMapping 映射
  • 调用控制器方法、并处理方法参数与方法返回值

7.1 DispatcherServlet

  1. DispatcherServlet 是在第一次被访问时执行初始化, 也可以通过配置修改为 Tomcat 启动后就初始化
  2. 在初始化时会从 Spring 容器中找一些 Web 需要的组件, 如 HandlerMapping、HandlerAdapter 等,并逐一调用它们的初始化
  3. RequestMappingHandlerMapping 初始化时,会收集所有 @RequestMapping 映射信息,封装为 Map,其中
    • key 是 RequestMappingInfo 类型,包括请求路径、请求方法等信息
    • value 是 HandlerMethod 类型,包括控制器方法对象、控制器对象
    • 有了这个 Map,就可以在请求到达时,快速完成映射,找到 HandlerMethod 并与匹配的拦截器一起返回给 DispatcherServlet
  4. RequestMappingHandlerAdapter 初始化时,会准备 HandlerMethod 调用时需要的各个组件,如:
    • HandlerMethodArgumentResolver 解析控制器方法参数
    • HandlerMethodReturnValueHandler 处理控制器方法返回值

7.2 自定义参数与返回值处理器

java

import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.yaml.snakeyaml.Yaml;

import javax.servlet.http.HttpServletResponse;

public class YmlReturnValueHandler implements HandlerMethodReturnValueHandler {
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        Yml yml = returnType.getMethodAnnotation(Yml.class);
        return yml != null;
    }

    @Override                   //  返回值
    public void handleReturnValue(Object returnValue, MethodParameter returnType,
                                  ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        // 1. 转换返回结果为 yaml 字符串
        String str = new Yaml().dump(returnValue);

        // 2. 将 yaml 字符串写入响应
        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
        response.setContentType("text/plain;charset=utf-8");
        response.getWriter().print(str);

        // 3. 设置请求已经处理完毕
        mavContainer.setRequestHandled(true);
    }
}

7.3 参数解析器

java

import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockPart;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.*;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/*
    目标: 解析控制器方法的参数值

    常见的参数处理器如下:
        org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@abbc908
        org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@44afefd5
        org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@9a7a808
        org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@72209d93
        org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@2687f956
        org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@1ded7b14
        org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@29be7749
        org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@5f84abe8
        org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@4650a407
        org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@30135202
        org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@6a4d7f76
        org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@10ec523c
        org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@53dfacba
        org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@79767781
        org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@78411116
        org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@aced190
        org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@245a060f
        org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@6edaa77a
        org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@1e63d216
        org.springframework.web.method.annotation.ModelMethodProcessor@62ddd21b
        org.springframework.web.method.annotation.MapMethodProcessor@16c3ca31
        org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@2d195ee4
        org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@2d6aca33
        org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@21ab988f
        org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@29314cc9
        org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@4e38d975
        org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@35f8a9d3
 */
public class A21 {

    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
        // 准备测试 Request
        HttpServletRequest request = mockRequest();

        // 要点1. 控制器方法被封装为 HandlerMethod
        HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getMethod("test", String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class, String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));

        // 要点2. 准备对象绑定与类型转换
        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);

        // 要点3. 准备 ModelAndViewContainer 用来存储中间 Model 结果
        ModelAndViewContainer container = new ModelAndViewContainer();

        // 要点4. 解析每个参数值
        for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
            // 多个解析器组合
            HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
            composite.addResolvers(
                    //                                          false 表示必须有 @RequestParam
                    new RequestParamMethodArgumentResolver(beanFactory, false),
                    new PathVariableMethodArgumentResolver(),
                    new RequestHeaderMethodArgumentResolver(beanFactory),
                    new ServletCookieValueMethodArgumentResolver(beanFactory),
                    new ExpressionValueMethodArgumentResolver(beanFactory),
                    new ServletRequestMethodArgumentResolver(),
                    new ServletModelAttributeMethodProcessor(false), // 必须有 @ModelAttribute
                    new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())),
                    new ServletModelAttributeMethodProcessor(true), // 省略了 @ModelAttribute
                    new RequestParamMethodArgumentResolver(beanFactory, true) // 省略 @RequestParam
            );

            String annotations = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());
            String str = annotations.length() > 0 ? " @" + annotations + " " : " ";
            parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());

            if (composite.supportsParameter(parameter)) {
                // 支持此参数
                Object v = composite.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
//                System.out.println(v.getClass());
                System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() + "->" + v);
                System.out.println("模型数据为:" + container.getModel());
            } else {
                System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());
            }
        }

        /*
            学到了什么
                a. 每个参数处理器能干啥
                    1) 看是否支持某种参数
                    2) 获取参数的值
                b. 组合模式在 Spring 中的体现
                c. @RequestParam, @CookieValue 等注解中的参数名、默认值, 都可以写成活的, 即从 ${ } #{ }中获取
         */
    }

    private static HttpServletRequest mockRequest() {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("name1", "zhangsan");
        request.setParameter("name2", "lisi");
        request.addPart(new MockPart("file", "abc", "hello".getBytes(StandardCharsets.UTF_8)));
        Map<String, String> map = new AntPathMatcher().extractUriTemplateVariables("/test/{id}", "/test/123");
        System.out.println(map);
        request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, map);
        request.setContentType("application/json");
        request.setCookies(new Cookie("token", "123456"));
        request.setParameter("name", "张三");
        request.setParameter("age", "18");
        request.setContent("""
                    {
                        "name":"李四",
                        "age":20
                    }
                """.getBytes(StandardCharsets.UTF_8));

        return new StandardServletMultipartResolver().resolveMultipart(request);
    }

    static class Controller {
        public void test(
                @RequestParam("name1") String name1, // name1=张三
                String name2,                        // name2=李四
                @RequestParam("age") int age,        // age=18
                @RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // spring 获取数据
                @RequestParam("file") MultipartFile file, // 上传文件
                @PathVariable("id") int id,               //  /test/124   /test/{id}
                @RequestHeader("Content-Type") String header,
                @CookieValue("token") String token,
                @Value("${JAVA_HOME}") String home2, // spring 获取数据  ${} #{}
                HttpServletRequest request,          // request, response, session ...
                @ModelAttribute("abc") User user1,          // name=zhang&age=18
                User user2,                          // name=zhang&age=18
                @RequestBody User user3              // json
        ) {
        }
    }

    static class User {
        private String name;
        private int age;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{" +
                   "name='" + name + '\'' +
                   ", age=" + age +
                   '}';
        }
    }
}

八、spring事务管理机制

Spring事务管理高层抽象主要包括3个接口,Spring的事务主要是由他们共同完成的:

  • PlatformTransactionManager:事务管理器—主要用于平台相关事务的管理

  • TransactionDefinition: 事务定义信息(隔离、传播、超时、只读)—通过配置如何进行事务管理。

  • TransactionStatus:事务具体运行状态—事务管理过程中,每个时间点事务的状态信息。

8.1 PlatformTransactionManager事务管理器

该接口提供三个方法:

l commit:提交事务

l rollback:回滚事务

l getTransaction:获取事务状态

Spring为不同的持久化框架提供了不同PlatformTransactionManager接口实现:

事务说明
org.springframework.jdbc.datasource.DataSourceTransactionManager使用Spring JDBC或iBatis 进行持久化数据时使用
org.springframework.orm.hibernate5.HibernateTransactionManager使用Hibernate5.0版本进行持久化数据时使用
org.springframework.orm.jpa.JpaTransactionManager使用JPA进行持久化时使用
org.springframework.jdo.JdoTransactionManager当持久化机制是Jdo时使用
org.springframework.transaction.jta.JtaTransactionManager使用一个JTA实现来管理事务,在一个事务跨越多个资源时必须使用

l DataSourceTransactionManager针对JdbcTemplate、MyBatis 事务控制,使用Connection(连接)进行事务控制:

开启事务 connection.setAutoCommit(false);

提交事务 connection.commit();

回滚事务 connection.rollback();

8.2 TransactionDefinition 事务定义信息

用来定义事务相关的属性的,给事务管理器用。

该接口主要提供的方法:

l getIsolationLevel:隔离级别获取

l getPropagationBehavior:传播行为获取

l getTimeout:获取超时时间(事务的有效期)

l isReadOnly 是否只读(保存、更新、删除—对数据进行操作-变成可读写的,查询-设置这个属性为true,只能读不能写),

这些事务的定义信息,都可以在配置文件中配置和定制。

1. 常用事务的隔离级别IsolationLevel

隔离级别含义
DEFAULT使用后端数据库默认的隔离级别(spring中的的选择项)
READ_UNCOMMITED允许你读取还未提交的改变了的数据。可能导致脏、幻读、不可重复读
READ_COMMITTED允许在并发事务已经提交后读取。可防止脏读,但幻读和 不可重复读仍可发生
REPEATABLE_READ对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏、不可重复读,但幻读仍可能发生。
SERIALIZABLE完全服从ACID的隔离级别,确保不发生脏、幻、不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的。

脏读:一个事务读取了另一个事务改写但还未提交的数据,如果这些数据被回滚,则读到的数据是无效的。

不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同。换句话说就是,后续读取可以读到另一事务已提交的更新数据。相反,“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是,后续读取不能读到另一事务已提交的更新数据。

幻读:一个事务读取了几行记录后,另一个事务插入一些记录,幻读就发生了。再后来的查询中,第一个事务就会发现有些原来没有的记录。

不同厂商的数据库产品,隔离级别的默认设置可能不同

Mysql 默认隔离级别 REPEATABLE_READ

Oracle 默认隔离级别 READ_COMMITTED

2. 事务的传播行为PropagationBehavior

事务传播行为用于解决两个被事务管理的方法互相调用问题

业务层两个方法面临的事务问题:

有些时候需要处于同一个事务(删除用户删除完成之后,需要同时删除用户对应的订单,需要事务回滚,例如商场工作人员删除订单业务)

有些时候不能在同一个事务(取款是一个事务操作,打印凭条是一个事务操作,例如ATM取款业务) !

事务的传播行为的7种类型:

事务传播行为类型说明
PROPAGATION_REQUIRED支持当前事务,如果不存在 就新建一个
PROPAGATION_SUPPORTS支持当前事务,如果不存在,就不使用事务
PROPAGATION_MANDATORY支持当前事务,如果不存在,抛出异常
PROPAGATION_REQUIRES_NEW如果有事务存在,挂起当前事务,创建一个新的事务
PROPAGATION_NOT_SUPPORTED以非事务方式运行,如果有事务存在,挂起当前事务
PROPAGATION_NEVER以非事务方式运行,如果有事务存在,抛出异常
PROPAGATION_NESTED如果当前事务存在,则嵌套事务执行 只对DataSourceTransactionManager 起效

主要分为三大类:

l PROPAGATION_REQUIRED(默认值)、PROPAGATION_SUPPORTS、PROPAGATION_MANDATORY

支持当前事务, A调用B,如果A事务存在,B和A处于同一个事务 。

事务默认传播行为 REQUIRED。最常用的。

l PROPAGATION_REQUIRES_NEW、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER

不会支持原来的事务,A调用B, 如果A事务存在, B肯定不会和A处于同一个事务。

常用的事务传播行为:PROPAGATION_REQUIRES_NEW

l PROPAGATION_NESTED

嵌套事务,只对DataSourceTransactionManager有效,底层使用JDBC的SavePoint机制,允许在同一个事务设置保存点,回滚保存点

REQUIRED、REQUIRES_NEW、NESTED 区分

​ REQUIRED:只有一个事务(默认,推荐)

​ REQUIRES_NEW:存在两个事务,如果事务存在,挂起事务,重新又开启了一个新的事务

​ NESTED 嵌套事务,事务可以设置保存点,回滚到保存点,选择提交或者回滚

8.3. TransactionStatus 事务状态

事务运行过程中,每个时间点 事务状态信息 !

flush(),给hibernate使用,底层发出sql的

hasSavepoint():判断是否有保留点

isCompleted():判断事务是否结束

isNewTransaction():判断当前事务是否是新开的一个事务。

isRollbackOnly():判断事务是否只能回滚

setRollbackOnly():设置事务是否回滚

事务的结束:必须通过commit 确认事务提交, rollback 作用标记为回滚。

数据库操作中,如果只是回滚,后面不操作,数据库在关闭连接的时候,自动发出了commit。

java
   try {

                操作

        } catch (){

               rollback

        } finally {

               commit 

}

【三个事务超级接口对象之间的关系】

1)首先用户管理事务,需要先配置TransactionManager(事务管理器)进行事务管理

2)然后根据TransactionDefinition(事物定义信息),通过TransactionManager(事务管理器)进行事务管理;

3)最后事务运行过程中,每个时刻都可以通过获取TransactionStatus(事务状态)来了解事务的运行状态。

8.4 Spring事务管理两种方式

1. 编程式事务管理

通过TransactionTemplate手动管理事务

在实际应用中很少使用,原因是要修改原来的代码,加入事务管理代码 (侵入性)

2. 使用XML或注解配置声明式事务

Spring的声明式事务是通过AOP实现的(环绕通知)

开发中经常使用(代码侵入性最小)--推荐使用!

8.5 事务注解

@EnableTransactionManagement

此注解是Spring支持注解事务配置的标志。表明Spring开启注解事务配置的支持。是注解驱动开发事 务配置的必备注解。

proxyTargetClass 指定基于目标类代理还是基于接口代理。 默认采用JDK官方的基于接口代理。

mode() 指定事务通知是如何执行的。默认是通过代理方式执行的。如果是同一个类中调用的话,请采用AdviceMode.ASPECTJ

order() 指示在特定连接点应用多个通知时事务处理的执行顺序。 默认值是:最低优先级(Integer.MAX_VALUE)

源码分析

1、@EnableTransactionManagement 
	通过在@Import注解中传入TransactionManagementConfigurationSelector类,会给容器 中导入两个组件: 
	AutoProxyRegistrar ProxyTransactionManagementConfiguration 
2、AutoProxyRegistrar: 
	给容器中注册一个 InfrastructureAdvisorAutoProxyCreator 组件; 
	利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理对象执行方法 利用拦截器链进行调用。 
3、ProxyTransactionManagementConfiguration使用@Configuration注解修饰,表明是一个配置 类。 
	3.1、transactionAdvisor方法: 给容器中注册事务增强器transactionAdvisor;
	3.2、transactionAttributeSource方法:事务增强器要用事务注解的信息,AnnotationTransactionAttributeSource解析事务注 解 
	3.3、transactionInterceptor方法: 
	返回值是:事务拦截器transactionInterceptor: 
	  TransactionInterceptor;保存了事务属性信息,事务管理器; 
	  TransactionInterceptor本身是一个 MethodInterceptor,MethodInterceptor 在spring-aop的课程中已经分析过了。在目标方法执行的时候,会getAdvisors()获取拦截器链,并执行 拦截器链,当只有事务拦截器时: 
		1)、先获取事务相关的属性 
		2)、再获取PlatformTransactionManager,如果事先没有添加指定任何 transactionmanger,最终会从容器中按照类型获取一个PlatformTransactionManager; 
		3)、执行目标方法 如果正常,利用事务管理器,提交事务 如果异常,获取到事务管理器,利用事务管理回滚操作;

@Transactional

此注解是Spring注解配置事务的核心注解,无论是注解驱动开发还是注解和XML混合开发,只有涉及配 置事务采用注解的方式,都需要使用此注解。 注解可以出现在接口上,类上和方法上。分别表明:

接口上:当前接口的所有实现类中重写接口的方法有事务支持。

类上:当前类中所有方法有事务支持。

方法上:当前方法有事务的支持。

优先级:方法上>类上>接口上

value() 指定事务管理器的唯一标识

transactionManager() 指定事务管理器的唯一标识

propagation() 指定事务的传播行为

isolation() 指定事务的隔离级别

timeout() 指定事务的超时时间

readOnly()指定事务是否只读

rollbackFor() 通过指定异常类的字节码,限定事务在特定情况下回滚

rollbackForClassName() 通过指定异常类的全限定类名,限定事务在特定情况下回滚

noRollbackFor() 通过指定异常类的字节码,限定事务在特定情况下不回滚

noRollbackForClassName() 通过指定异常类的全限定类名,限定事务在特定情况下不回滚

源码分析

1、在@EnableTransactionManagement注解中,有一个导入器: TransactionManagementConfigurationSelector,导入器中在AdiviceMode为默认值PROXY时,往 容器中注入了两个bean对象,AutoProxyRegistrar和 ProxyTransactionManagementConfiguration。 //通过@Import注解,Spring定义了一个事务管理配置类的导入器。 @Import(TransactionManagementConfigurationSelector.class) public @interface EnableTransactionManagement { //其余代码略 } 2、ProxyTransactionManagementConfiguration类中的一个方法: @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public TransactionAttributeSource transactionAttributeSource() { //创建了一个注解事务的属性解析对象 return new AnnotationTransactionAttributeSource(); } 3、AnnotationTransactionAttributeSource类的实例化 /***/ public class AnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource implements Serializable {
//用于存放解析后的集合 private final Set<TransactionAnnotationParser> annotationParsers; /*** 默认构造函数 */ public AnnotationTransactionAttributeSource() { this(true); }/*** 带参构造(表明是否为public方法) */ public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) { this.publicMethodsOnly = publicMethodsOnly; //判断是不是jta或者是ejp if (jta12Present || ejb3Present) { this.annotationParsers = new LinkedHashSet<>(4); this.annotationParsers.add(new SpringTransactionAnnotationParser()); //jta if (jta12Present) { this.annotationParsers.add(new JtaTransactionAnnotationParser()); }//ejp if (ejb3Present) { this.annotationParsers.add(new Ejb3TransactionAnnotationParser()); } }else {//当都不是的时候,构建一个SpringTransactionAnnotationParser(事务注解解析 器) this.annotationParsers = Collections.singleton(new SpringTransactionAnnotationParser()); } } } 4、SpringTransactionAnnotationParser的注解解析: public class SpringTransactionAnnotationParser implements TransactionAnnotationParser, Serializable { /*** 根据传入被注解的元素解析。 * 可以是Method,Field,Class,Package,Construct等等。 */ @Override @Nullable public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes( element, Transactional.class, false, false); if (attributes != null) { //调用根据传入注解属性解析 return parseTransactionAnnotation(attributes); }else {return null; }
}/*** 根据传入注解解析 */ public TransactionAttribute parseTransactionAnnotation(Transactional ann) { return parseTransactionAnnotation(AnnotationUtils.getAnnotationAttributes(ann, false, false)); }/*** 根据传入的注解属性解析 */ protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) { RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute(); Propagation propagation = attributes.getEnum("propagation"); rbta.setPropagationBehavior(propagation.value()); Isolation isolation = attributes.getEnum("isolation"); rbta.setIsolationLevel(isolation.value()); rbta.setTimeout(attributes.getNumber("timeout").intValue()); rbta.setReadOnly(attributes.getBoolean("readOnly")); rbta.setQualifier(attributes.getString("value")); List<RollbackRuleAttribute> rollbackRules = new ArrayList<>(); for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) { rollbackRules.add(new RollbackRuleAttribute(rbRule)); }for (String rbRule : attributes.getStringArray("rollbackForClassName")) { rollbackRules.add(new RollbackRuleAttribute(rbRule)); }for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) { rollbackRules.add(new NoRollbackRuleAttribute(rbRule)); }for (String rbRule : attributes.getStringArray("noRollbackForClassName")) { rollbackRules.add(new NoRollbackRuleAttribute(rbRule)); }rbta.setRollbackRules(rollbackRules); return rbta; }//其余代码略 }

@TransactionEventListener

它是spring在4.2版本之后加入的注解。用于配置一个事务的事件监听器。使我们在事务提交和回滚前 后可以做一些额外的功能。例如:对事务执行监控,执行中同步做一些操作等等。

phase() 指定事务监听器的执行是在何时。 * 取值有: * 事务提交之前 * 事务提交之后 默认值 * 事务回滚之后 * 事务执行完成之后

fallbackExecution() 若没有事务的时候,对应的event是否已经执行 默认值为false表示 没事务就不执行了

value() 指定事件类的字节码

classes() 和value属性的作用是一样的

condition() 用于指定执行事件处理器的条件。取值是基于Spring的el表达式编写的。

源码分析

1、在IoC容器加载时,执行AbstractApplicationContext的refresh()方法,一共十二个步骤,在执 行到// Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory);//触发其他单例bean的加载 2、AbstractApplicationContext的finishBeanFactoryInitialization方法执行时,初始化剩余 的单例bean对象。 /*** 初始化剩余单例bean对象的方法 */ protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { //方法中的其他代码略 //初始化剩余单例bean对象.调用的是DefaultListableBeanFactory类中的 preInstantiateSingletons方法。 beanFactory.preInstantiateSingletons(); } 3、DefaultListableBeanFactory类中的preInstantiateSingletons方法中执行了 afterSingletonsInstantiated()方法。此方法是SmartInitializingSingleton接口中声明的。具 体实现类包含:EventListenerMethodProcessor事件监听器方法处理器。 4、EventListenerMethodProcessor中的afterSingletonsInstantiated会被执行,该方法中包含 处理bean的方法:processBean。 5、在processBean方法中调用了创建事件监听器的方法createApplicationListener。该方法是 EventListenerFactory接口中声明的方法。 6、TransactionalEventListenerFactory类实现了EventListenerFactory接口,并重写了 createApplicationListener方法。 /*** 重写接口中的创建监听器方法 */ @Override public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) { return new ApplicationListenerMethodTransactionalAdapter(beanName, type, method); } 7、ApplicationListenerMethodTransactionalAdapter的实例化。至此,解析 TransactionalEventListener注解的过程完成。 /*** 构造函数源码 */ public ApplicationListenerMethodTransactionalAdapter(String beanName, Class<?> targetClass, Method method) { super(beanName, targetClass, method); TransactionalEventListener ann = AnnotatedElementUtils.findMergedAnnotation(method, TransactionalEventListener.class); if (ann == null) { throw new IllegalStateException("No TransactionalEventListener annotation found on method: " + method); }this.annotation = ann; }

8.6 事务涉及的类

TransactionTemplate

用于编程式事务的模板对象

DataSourceUtils

spring中数据源的工具类。定义着获取连接的方法

TransactionSynchronizationManager

事务的同步管理器类,实现连接和线程绑定从而控制事务的核心类 * 它是个抽象类,但是没有任何子类 因为它所有的方法都是静态的

TransactionAwareDataSourceProxy

这是Spring提供的一个数据源代理类,它继承了DelegatingDataSource类。而DelegatingDataSource类实现了javax.sql.DataSource接口。 * Spring通过装饰者模式,把原始DataSource中一些不希望用户直接使用的方法又套了一个壳子。 * 因为数据连接泄露是个很头疼的问题,Spring框架也提供了很多种办法来避免这个问题。 * 比如使用XXXTemplate,当然其背后是DataSourceUtils。 * 同时还有另外一种办法,使用TransactionAwareDataSourceProxy。 * 通过TransactionAwareDataSourceProxy对数据源代理后,数据源对象就有了事务上下文感知的能 力了。 * 当然看源码会发现,其实它还是使用的DataSourceUtils.

TransactionAwareInvocationHandler

InvocationHandler的具体实现(增强的部分)

8.7 High-level Synchronization Approach和Low-level Synchronization Approach

High-level Synchronization Approach

首选的方法是使用基于Spring的和持久化集成的API高级模板,或者使用原生的ORM API, 应用于事务 支持型工厂bean 或者管理原生资源的工厂的代理. 这些事务型解决方案内建对资源创建、重用、清理、资源 的可选事务同步以及 异常的映射的支持. 这样用户的数据访问代码就可以不再关心定位任务, 专心于非样板 化的持久化逻辑. 通常, 你使用原生的ORM API或者使用JdbcTemplate的方法来进行JDBC访问.

Low-level Synchronization Approach

像DataSourceUtils (JDBC), EntityManagerFactoryUtils (JPA),SessionFactoryUtils(Hibernate), PersistenceManagerFactoryUtils (JDO), 等等 这些类都是属于低级方法中的.当你的代码想要直接使用有关本地持久化事务API的时候, 你需要让这些类明 确Spring 框架管理的实例已经得到了,事务已经同步好了(可选的),并且异常运行中的异常也都会映射到 一个一致的API. 例如, 在JDBC的例子中, 可以使用Spring框架中提供的 org.springframework.jdbc.datasource .Datasourceutils类,而不是对数据源调用getconnection()这种原始JDBC方法,就像下面这样: Connection conn = DataSourceUtils.getConnection(dataSource); 如果存在一个已经和他同步(已连接)的事务, 那就返回它. 否则, 方法就会激发一个触发器创建一个新 的连接, 并且是(可选的)与任何存在的事务同步的, 并且已经准备好在接下来在相同的事务中重用. 就像提 到的那样, 所有 的SQLException都会被包装成Spring Framework的 CannotGetJdbcConnectionException, 这是 Spring Framework的非检查型数据访问异常 (DataAccessExceptions)的一种层次. 这个方法给你的信息比 SQLException给你的信息多, 并且确 保跨数据库, 即使是不同的持久化技术的可移植性. 该方法同样可以独立于Spring事务管理工作(事务同步是可选的), 所以你可以使用它不管你是使用或者 不使用 Spring的事务管理.

九、IOC深入剖析

1. Spring中的BeanFactory

BeanFactory类视图

ioc-beanfactory

BeanFactory

	BeanFactory 中定义的各种方法如上面方法注释,整个设计还是比较简洁、直观的,其中将近一半是获取 bean 对象的各种方法,另外就是对 bean 属性的获取和判定,该接口仅仅是定义了 IoC 容器的最基本基本形式,具体实现都交由子类来实现。

HierarchicalBeanFactory

	HierarchicalBeanFactory 译为中文是“分层的”,它相对于 BeanFactory 增加了对父 BeanFactory 的获取,子容器可以通过接口方法访问父容器,让容器的设计具备了层次性。这种层次性增强了容器的扩展性和灵活性,我们可以通过编程的方式为一个已有的容器添加一个或多个子容器,从而实现一些特殊功能。层次容器有一个特点就是子容器对于父容器来说是透明的,而子容器则能感知到父容器的存在。典型的应用场景就是 Spring MVC,控制层的 bean 位于子容器中,并将业务层和持久层的 bean 所在的容器设置为父容器,这样的设计可以让控制层的 bean 访问业务层和持久层的 bean,反之则不行,从而在容器层面对三层软件结构设计提供支持。

ListableBeanFactory

ListableBeanFactory 引入了获取容器中 bean 的配置信息的若干方法,比如获取容器中 bean 的个数,获取容器中所有 bean 的名称列表,按照目标类型获取 bean 名称,以及检查容器中是否包含指定名称的 bean 等等。Listable 中文译为“可列举的”,对于容器而言,bean 的定义和属性是可以列举的对象。

AutowireCapableBeanFactory

	AutowireCapableBeanFactory 提供了创建 bean、自动注入,初始化以及应用 bean 的后置处理器等功能。自动注入让配置变得更加简单,也让注解配置成为可能,Spring 提供了四种自动注入类型:
		byName:
			根据名称自动装配。假设 bean A 有一个名为 b 的属性,如果容器中刚好存在一个 bean 的名称为 b,则将该 bean 装配给 bean A 的 b 属性。
		byType:
			根据类型自动匹配。假设 bean A 有一个类型为 B 的属性,如果容器中刚好有一个 B 类型的 bean,则使用该 bean 装配 A 的对应属性。
		constructor:
			仅针对构造方法注入而言,类似于 byType。如果 bean A 有一个构造方法,构造方法包含一个 B 类型的入参,如果容器中有一个 B 类型的 bean,则使用该 bean 作为入参,如果找不到,则抛出异常。
		autodetect:
			根据 bean 的自省机制决定采用 byType 还是 constructor 进行自动装配。如果 bean 提供了默认的构造函数,则采用 byType,否则采用 constructor。
	总结:
		<beans/> 元素标签中的 default-autowire 属性可以配置全局自动匹配,default-autowire 默认值为 no,表示不启用自动装配。在实际开发中,XML 配置方式很少启用自动装配功能,而基于注解的配置方式默认采用 byType 自动装配策略。

ConfigurableBeanFactory

ConfigurableBeanFactory 提供配置 Factory 的各种方法,增强了容器的可定制性,定义了设置类装载器、属性编辑器、容器初始化后置处理器等方法。

DefaultListableBeanFactory

DefaultListableBeanFactory 是一个非常重要的类,它包含了 IoC 容器所应该具备的重要功能,是容器完整功能的一个基本实现,XmlBeanFactory 是一个典型的由该类派生出来的 Factory,并且只是增加了加载 XML 配置资源的逻辑,而容器相关的特性则全部由 DefaultListableBeanFactory 来实现。

ApplicationContext

ApplicationContext 是 Spring 为开发者提供的高级容器形式,也是我们初始化 Spring 容器的常用方式,除了简单容器所具备的功能外,ApplicationContext 还提供了许多额外功能来降低开发人员的开发量,提升框架的使用效率。这些额外的功能主要包括:
	国际化支持:ApplicationContext 实现了 org.springframework.context.MessageSource 接口,该接口为容器提供国际化消息访问功能,支持具备多语言版本需求的应用开发,并提供了多种实现来简化国际化资源文件的装载和获取。
	发布应用上下文事件:ApplicationContext 实现了 org.springframework.context.ApplicationEventPublisher 接口,该接口让容器拥有发布应用上下文事件的功能,包括容器启动、关闭事件等,如果一个 bean 需要接收容器事件,则只需要实现 ApplicationListener 接口即可,Spring 会自动扫描对应的监听器配置,并注册成为主题的观察者。
	丰富的资源获取的方式:ApplicationContext 实现了 org.springframework.core.io.support.ResourcePatternResolver 接口,ResourcePatternResolver 的实现类 PathMatchingResourcePatternResolver 让我们可以采用 Ant 风格的资源路径去加载配置文件。

ConfigurableApplicationContext

ConfigurableApplicationContext 中主要增加了 refresh 和 close 两个方法,从而为应用上下文提供了启动、刷新和关闭的能力。其中 refresh 方法是高级容器的核心方法,方法中概括了高级容器初始化的主要流程(包含简单的容器的全部功能,以及高级容器特有的扩展功能)

WebApplicationContext

WebApplicationContext 是为 WEB 应用定制的上下文,可以基于 WEB 容器来实现配置文件的加载,以及初始化工作。对于非 WEB 应用而言,bean 只有 singleton 和 prototype 两种作用域,而在 WebApplicationContext 中则新增了 request、session、globalSession,以及 application 四种作用域。
WebApplicationContext 将整个应用上下文对象以属性的形式放置到 ServletContext 中,所以在 WEB 应用中,我们可以通过 WebApplicationContextUtils 的 getWebApplicationContext(ServletContext sc) 方法,从 ServletContext 中获取到 ApplicationContext 实例。为了支持这一特性,WebApplicationContext 定义了一个常量:

ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"

并在初始化应用上下文时以该常量为 key,将 WebApplicationContext 实例存放到 ServletContext 的属性列表中,当我们在调用 WebApplicationContextUtils 的 getWebApplicationContext(ServletContext sc) 方法时,本质上是在调用 ServletContext 的 getAttribute(String name) 方法,只不过 Spring 会对获取的结果做一些校验。

高级容器的一些具体实现类型

AnnotationConfigApplicationContext:
	是基于注解驱动开发的高级容器类,该类中提供了AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner两个成员,AnnotatedBeanDefinitionReader用于读取注解创建Bean的定义信息,ClassPathBeanDefinitionScanner负责扫描指定包获取Bean的定义信息。
	
ClasspathXmlApplicationContext:
	是基于xml配置的高级容器类,它用于加载类路径下配置文件。
	
FileSystemXmlApplicationContext:
	是基于xml配置的高级容器类,它用于加载文件系统中的配置文件。
	
AnnotationConfigWebApplicationContext:
	是注解驱动开发web应用的高级容器类。

2.Spring中的BeanDefinition

BeanDefinition类视图

ioc-beandefinition

说明

现实中的容器都是用来装物品的,Spring 的容器也不例外,这里的物品就是 bean。我通常对于 bean 的印象是一个个躺在配置文件中的 <bean/> 标签,或者是被注解的类,但是这些都是 bean 的静态表示,是还没有放入容器的物料,最终(加载完配置,且在 getBean 之前)加载到容器中的是一个个 BeanDefinition 实例。BeanDefinition 的继承关系如下图,RootBeanDefinition、ChildBeanDefinition,以及 GenericBeanDefinition 是三个主要的实现。有时候我们需要在配置时,通过 parent 属性指定 bean 的父子关系,这个时候父 bean 则用 RootBeanDefinition 表示,而子 bean 则用 ChildBeanDefinition 表示。GenericBeanDefinition 自 2.5 版本引入,是对于一般的 bean 定义的一站式服务中心。

Bean的定义信息详解

源码分析

java
/**
 * 在上一小节我们介绍了RootBeanDefinition,ChildBeanDefinition,GenericBeanDefinition三个类
 * 他们都是由AbstractBeanDefinition派生而来,该抽象类中包含了bean的所有配置项和一些支持程序运
 * 行的属性。以下是类中属性的说明。
 */
public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor implements BeanDefinition, Cloneable {
    // 常量定义略

    /** bean 对应的类实例 */
    private volatile Object beanClass;
    /** bean的作用域,对应scope属性 */
    private String scope = SCOPE_DEFAULT;
    /** 是否是抽象类,对应abstract属性 */
    private boolean abstractFlag = false;
    /** 是否延迟加载,对应lazy-init属性 */
    private boolean lazyInit = false;
    /** 自动装配模式,对应autowire属性 */
    private int autowireMode = AUTOWIRE_NO;
    /** 依赖检查,对应dependency-check属性 */
    private int dependencyCheck = DEPENDENCY_CHECK_NONE;
    /** 对应depends-on,表示一个bean实例化前置依赖另一个bean */
    private String[] dependsOn;
    /** 对应autowire-candidate属性,设置为false时表示取消当前bean作为自动装配候选者的资格 */
    private boolean autowireCandidate = true;
    /** 对应primary属性,当自动装配存在多个候选者时,将其作为首选 */
    private boolean primary = false;
    /** 对应qualifier属性 */
    private final Map<String, AutowireCandidateQualifier> qualifiers = new LinkedHashMap<String, AutowireCandidateQualifier>(0);
    /** 非配置项:表示允许访问非公开的构造器和方法,由程序设置 */
    private boolean nonPublicAccessAllowed = true;
    /**
     * 非配置项:表示是否允许以宽松的模式解析构造函数,由程序设置
     *
     * 例如:如果设置为true,则在下列情况时不会抛出异常(示例来源于《Spring源码深度解析》)
     * interface ITest{}
     * class ITestImpl implements ITest {}
     * class Main {
     * Main(ITest i){}
     * Main(ITestImpl i){}
     * }
     */
    private boolean lenientConstructorResolution = true;
    /** 对应factory-bean属性 */
    private String factoryBeanName;
    /** 对应factory-method属性 */
    private String factoryMethodName;
    /** 记录构造函数注入属性,对应<construct-arg/>标签 */
    private ConstructorArgumentValues constructorArgumentValues;
    /** 记录<property/>属性集合 */
    private MutablePropertyValues propertyValues;
    /** 记录<lookup-method/>和<replaced-method/>标签配置 */
    private MethodOverrides methodOverrides = new MethodOverrides();
    /** 对应init-method属性 */
    private String initMethodName;
    /** 对应destroy-method属性 */
    private String destroyMethodName;
    /** 非配置项:是否执行init-method,由程序设置 */
    private boolean enforceInitMethod = true;
    /** 非配置项:是否执行destroy-method,由程序设置 */
    private boolean enforceDestroyMethod = true;
    /** 非配置项:表示是否是用户定义,而不是程序定义的,创建AOP时为true,由程序设置 */
    private boolean synthetic = false;
    /**
     * 非配置项:定义bean的应用场景,由程序设置,角色如下:
     * ROLE_APPLICATION:用户
     * ROLE_INFRASTRUCTURE:完全内部使用
     * ROLE_SUPPORT:某些复杂配置的一部分
     */
    private int role = BeanDefinition.ROLE_APPLICATION;
    /** bean的描述信息,对应description标签 */
    private String description;
    /** bean定义的资源 */
    private Resource resource;
    
    // 方法定义略
}

总结

BeanDefinition 是容器对于bean配置的内部表示,Spring 将各个 bean 的 BeanDefinition 实例注册记录在 BeanDefinitionRegistry 中,该接口定义了对 BeanDefinition 的各种增删查操作,类似于内存数据库,其实现类 SimpleBeanDefinitionRegistry 主要以 Map 作为存储标的。

3. 注解驱动执行过程分析

  • 使用配置类字节码的构造函数

构造函数源码

/**
 * Create a new AnnotationConfigApplicationContext that needs to be populated
 * through {@link #register} calls and then manually {@linkplain #refresh refreshed}.
 */
public AnnotationConfigApplicationContext() {
	this.reader = new AnnotatedBeanDefinitionReader(this);
	this.scanner = new ClassPathBeanDefinitionScanner(this);
}
/**
 * Create a new AnnotationConfigApplicationContext, deriving bean definitions
 * from the given annotated classes and automatically refreshing the context.
 * @param annotatedClasses one or more annotated classes,
 * e.g. {@link Configuration @Configuration} classes
 */
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
	this();
	register(annotatedClasses);
	refresh();
}

register方法说明

	它是根据传入的配置类字节码解析Bean对象中注解的(包括类上的和类中方法和字段上的注解。如果类没有被注解,那么类中方法和字段上的注解不会被扫描)。使用的是AnnotatedGenericBeanDefinition,里面包含了BeanDefinition和Scope两部分信息,其中BeanDefinition是传入注解类的信息,即SpringConfiguration;scope是指定bean的作用范围,默认情况下为单例。
	同时,借助AnnotationConfigUtils类中processCommonDefinitionAnnotations方法判断是否使用了Primary,Lazy,DependsOn等注解来决定Bean的加载时机。
	在ConfigurationClassBeanDefinitionReader类中的registerBeanDefinitionForImportedConfigurationClass方法会把导入的JdbcConfig类注册到容器中。而loadBeanDefinitionsForBeanMethod方法会解析Bean注解,把被Bean注解修饰的方法返回值存入容器。
  • 使用包扫描的构造函数

构造函数源码

/**
 * Create a new AnnotationConfigApplicationContext that needs to be populated
 * through {@link #register} calls and then manually {@linkplain #refresh refreshed}.
 */
public AnnotationConfigApplicationContext() {
	this.reader = new AnnotatedBeanDefinitionReader(this);
	this.scanner = new ClassPathBeanDefinitionScanner(this);
}
/**
 * Create a new AnnotationConfigApplicationContext, scanning for bean definitions
 * in the given packages and automatically refreshing the context.
 * @param basePackages the packages to check for annotated classes
 */
public AnnotationConfigApplicationContext(String... basePackages) {
    this();
    scan(basePackages);
    refresh();
}

scan方法说明

	它是根据传入的类路径下(classpath*)的包解析Bean对象中注解的(包括类上以及类成员的),使用的是ClassPathBeanDefinitionScanner类中的doScan方法,该方法最终将得到的BeanDefinitionHolder信息存储到LinkedHashSet中,为后面初始化容器做准备。
	doScan中的findCandidateComponents方法调用ClassPathScanningCandidateComponentProvider类中的scanCandidateComponents方法,而此方法又去执行了PathMatchingResourcePatternResolver类中的doFindAllClassPathResources方法,找到指定扫描包的URL(是URL,不是路径。因为是带有file协议的),然后根据磁盘路径读取当前目录及其子目录下的所有类。接下来执行AnnotationConfigUtils类中的processCommonDefinitionAnnotations方法,剩余就和本章节第一小节后面的过程一样了。
  • 注册注解类型过滤器

ClassPathScanningCandidateComponentProvider的registerDefaultFilters方法说明

java
/**
	 * Register the default filter for {@link Component @Component}.
	 * <p>This will implicitly register all annotations that have the
	 * {@link Component @Component} meta-annotation including the
	 * {@link Repository @Repository}, {@link Service @Service}, and
	 * {@link Controller @Controller} stereotype annotations.
	 * <p>Also supports Java EE 6's {@link javax.annotation.ManagedBean} and
	 * JSR-330's {@link javax.inject.Named} annotations, if available.
	 *
	 */
	@SuppressWarnings("unchecked")
	protected void registerDefaultFilters() {
		this.includeFilters.add(new AnnotationTypeFilter(Component.class));
		ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
		try {
			this.includeFilters.add(new AnnotationTypeFilter(
					((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
			logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
		}
		catch (ClassNotFoundException ex) {
			// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
		}
		try {
			this.includeFilters.add(new AnnotationTypeFilter(
					((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
			logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
		}
		catch (ClassNotFoundException ex) {
			// JSR-330 API not available - simply skip.
		}
	}
  • **准备和初始化容器 **

AbstractApplicationContext的refresh方法说明

java
@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        //Prepare this context for refreshing.
      	//1.准备容器,设置一些初始化信息,例如启动时间。验证必须要的属性等等。
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        //2.告诉子类刷新内部bean工厂。实际就是重新创建一个Bean工厂
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        //3.准备使用创建的这个BeanFactory,添加或者注册到当前Bean工厂一些必要对象。
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
          	//4.允许子容器对BeanFactory进行后处理。例如,在web环境中bean的作用范围等等。
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
          	//5.在Singleton的Bean对象初始化前,对Bean工厂进行一些处理
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
          	//6.注册拦截bean创建的处理器
            registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
          	//7.初始化消息资源接口的实现类。主要用于处理国际化(i18n)
            initMessageSource();

            // Initialize event multicaster for this context.
          	//8、为容器注册一个事件组播器
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
          	//9.在AbstractApplicationContext的子类中初始化其他特殊的bean
            onRefresh();

            // Check for listener beans and register them.
          	//10.注册应用的监听器。就是注册实现了ApplicationListener接口的监听器bean
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
          	//11.实例化所有剩余的(非lazy init)单例。(就是没有被@Lazy修饰的单例Bean)
            finishBeanFactoryInitialization(beanFactory);//十一、

            // Last step: publish corresponding event.
          	//12.完成context的刷新。主要是调用LifecycleProcessor的onRefresh()方法,并且发布事件(ContextRefreshedEvent)。
            finishRefresh();
        }

        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();//如果刷新失败那么就会将已经创建好的单例Bean销毁掉

            // Reset 'active' flag.
            cancelRefresh(ex);//重置context的活动状态

            // Propagate exception to caller.
            throw ex;//抛出异常
        }

        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();//重置的Spring内核的缓存。因为可能不再需要metadata给单例Bean了。
        }
    }
}
  • 实例化和获取Bean对象

AbstractBeanFactory的doGetBean方法说明

java
protected <T> T doGetBean(
            final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {
    /*
     * 获取name对应的真正beanName
     *
     * 因为传入的参数可以是alias,也可能是FactoryBean的name,所以需要进行解析,包含以下内容:
     * 1. 如果是FactoryBean,则去掉修饰符“&”
     * 2. 沿着引用链获取alias对应的最终name
     */
    final String beanName = this.transformedBeanName(name);
 
    Object bean;
 
    /*
     * 检查缓存或者实例工厂中是否有对应的单例
     *
     * 在创建单例bean的时候会存在依赖注入的情况,而在创建依赖的时候为了避免循环依赖
     * Spring创建bean的原则是不等bean创建完成就会将创建bean的ObjectFactory提前曝光(将对应的ObjectFactory加入到缓存)
     * 一旦下一个bean创建需要依赖上一个bean,则直接使用ObjectFactory对象
     */
    Object sharedInstance = this.getSingleton(beanName); // 获取单例
    if (sharedInstance != null && args == null) {
        // 实例已经存在
        if (logger.isDebugEnabled()) {
            if (this.isSingletonCurrentlyInCreation(beanName)) {
                logger.debug("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference");
            } else {
                logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
            }
        }
        // 返回对应的实例
        bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, null);
    } else {
        // 单例实例不存在
        if (this.isPrototypeCurrentlyInCreation(beanName)) {
            /*
             * 只有在单例模式下才会尝试解决循环依赖问题
             * 对于原型模式,如果存在循环依赖,也就是满足this.isPrototypeCurrentlyInCreation(beanName),抛出异常
             */
            throw new BeanCurrentlyInCreationException(beanName);
        }
 
        // 获取parentBeanFactory实例
        BeanFactory parentBeanFactory = this.getParentBeanFactory();
        // 如果在beanDefinitionMap中(即所有已经加载的类中)不包含目标bean,则尝试从parentBeanFactory中获取
        if (parentBeanFactory != null && !this.containsBeanDefinition(beanName)) {
            String nameToLookup = this.originalBeanName(name);  // 获取name对应的真正beanName,如果是factoryBean,则加上“&”前缀
            if (args != null) {
                // 递归到BeanFactory中寻找
                return (T) parentBeanFactory.getBean(nameToLookup, args);
            } else {
                return parentBeanFactory.getBean(nameToLookup, requiredType);
            }
        }
 
        // 如果不仅仅是做类型检查,标记bean的状态已经创建,即将beanName加入alreadyCreated集合中
        if (!typeCheckOnly) {
            this.markBeanAsCreated(beanName);
        }
 
        try {
            /*
             * 将存储XML配置的GenericBeanDefinition实例转换成RootBeanDefinition实例,方便后续处理
             * 如果存在父bean,则同时合并父bean的相关属性
             */
            final RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName);
            // 检查bean是否是抽象的,如果是则抛出异常
            this.checkMergedBeanDefinition(mbd, beanName, args);
 
            // 加载当前bean依赖的bean
            String[] dependsOn = mbd.getDependsOn();
            if (dependsOn != null) {
                // 存在依赖,递归实例化依赖的bean
                for (String dep : dependsOn) {
                    if (this.isDependent(beanName, dep)) {
                        // 检查dep是否依赖beanName,从而导致循环依赖
                        throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                    }
                    // 缓存依赖调用
                    this.registerDependentBean(dep, beanName);
                    this.getBean(dep);
                }
            }
 
            // 完成加载依赖的bean后,实例化mbd自身
            if (mbd.isSingleton()) {
                // scope == singleton
                sharedInstance = this.getSingleton(beanName, new ObjectFactory<Object>() {
                    @Override
                    public Object getObject() throws BeansException {
                        try {
                            return createBean(beanName, mbd, args);
                        } catch (BeansException ex) {
                            // 清理工作,从单例缓存中移除
                            destroySingleton(beanName);
                            throw ex;
                        }
                    }
                });
                bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            } else if (mbd.isPrototype()) {
                // scope == prototype
                Object prototypeInstance;
                try {
                    // 设置正在创建的状态
                    this.beforePrototypeCreation(beanName);
                    // 创建bean
                    prototypeInstance = this.createBean(beanName, mbd, args);
                } finally {
                    this.afterPrototypeCreation(beanName);
                }
                // 返回对应的实例
                bean = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
            } else {
                // 其它scope
                String scopeName = mbd.getScope();
                final Scope scope = this.scopes.get(scopeName);
                if (scope == null) {
                    throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
                }
                try {
                    Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
                        @Override
                        public Object getObject() throws BeansException {
                            beforePrototypeCreation(beanName);
                            try {
                                return createBean(beanName, mbd, args);
                            } finally {
                                afterPrototypeCreation(beanName);
                            }
                        }
                    });
                    // 返回对应的实例
                    bean = this.getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                } catch (IllegalStateException ex) {
                    throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex);
                }
            }
        } catch (BeansException ex) {
            cleanupAfterBeanCreationFailure(beanName);
            throw ex;
        }
    }
 
    // 检查需要的类型是否符合bean的实际类型,对应getBean时指定的requireType
    if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) {
        try {
            // 执行类型转换,转换成期望的类型
            return this.getTypeConverter().convertIfNecessary(bean, requiredType);
        } catch (TypeMismatchException ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", ex);
            }
            throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
        }
    }
    return (T) bean;
}

4. BeanNameGenerator及其实现类

BeanNameGenerator

BeanNameGenerator接口位于 org.springframework.beans.factory.support 包下面:

java
/**
 * Strategy interface for generating bean names for bean definitions.
 *
 * @author Juergen Hoeller
 * @since 2.0.3
 */
public interface BeanNameGenerator {

	/**
	 * Generate a bean name for the given bean definition.
	 * @param definition the bean definition to generate a name for
	 * @param registry the bean definition registry that the given definition
	 * is supposed to be registered with
	 * @return the generated bean name
	 */
	String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);

}

它有两个实现类:分别是:

ioc-BeanNameGenerator

其中DefaultBeanNameGenerator是给资源文件加载bean时使用(BeanDefinitionReader中使用);AnnotationBeanNameGenerator是为了处理注解生成bean name的情况。

4.2、AnnotationBeanNameGenerator

java
/**
 * 此方法是接口中抽象方法的实现。
 * 该方法分为两个部分:
 *	  第一个部分:当指定了bean的名称,则直接使用指定的名称。
 *	  第二个部分:当没有指定bean的名称时,则使用当前类的短类名作为bean的唯一标识。
 */
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
  	//判断bean的定义信息是否为基于注解的
	if (definition instanceof AnnotatedBeanDefinition) {
      	//解析注解中的属性,看看有没有指定的bean的唯一标识
		String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
		if (StringUtils.hasText(beanName)) {
			//返回注解的属性指定的bean的唯一标识
			return beanName;
		}
	}
	// 调用方法,使用注解bean名称的命名规则,生成bean的唯一标识
	return buildDefaultBeanName(definition, registry);
}

/**
 * Derive a default bean name from the given bean definition.
 * <p>The default implementation delegates to {@link #buildDefaultBeanName(BeanDefinition)}.
 * @param definition the bean definition to build a bean name for
 * @param registry the registry that the given bean definition is being registered with
 * @return the default bean name (never {@code null})
 */
protected String buildDefaultBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
  return buildDefaultBeanName(definition);
}

/**
 * Derive a default bean name from the given bean definition.
 * <p>The default implementation simply builds a decapitalized version
 * of the short class name: e.g. "mypackage.MyJdbcDao" -> "myJdbcDao".
 * <p>Note that inner classes will thus have names of the form
 * "outerClassName.InnerClassName", which because of the period in the
 * name may be an issue if you are autowiring by name.
 * @param definition the bean definition to build a bean name for
 * @return the default bean name (never {@code null})
 */
protected String buildDefaultBeanName(BeanDefinition definition) {
  String beanClassName = definition.getBeanClassName();
  Assert.state(beanClassName != null, "No bean class name set");
  String shortClassName = ClassUtils.getShortName(beanClassName);
  return Introspector.decapitalize(shortClassName);
}
java
ClassUtils的代码节选:
/**
 * Miscellaneous class utility methods.
 * Mainly for internal use within the framework.
 *
 * @author Juergen Hoeller
 * @author Keith Donald
 * @author Rob Harrop
 * @author Sam Brannen
 * @since 1.1
 * @see TypeUtils
 * @see ReflectionUtils
 */
public abstract class ClassUtils {

	/** Suffix for array class names: {@code "[]"}. */
	public static final String ARRAY_SUFFIX = "[]";

	/** Prefix for internal array class names: {@code "["}. */
	private static final String INTERNAL_ARRAY_PREFIX = "[";

	/** Prefix for internal non-primitive array class names: {@code "[L"}. */
	private static final String NON_PRIMITIVE_ARRAY_PREFIX = "[L";

	/** The package separator character: {@code '.'}. */
	private static final char PACKAGE_SEPARATOR = '.';

	/** The path separator character: {@code '/'}. */
	private static final char PATH_SEPARATOR = '/';

	/** The inner class separator character: {@code '$'}. */
	private static final char INNER_CLASS_SEPARATOR = '$';

	/** The CGLIB class separator: {@code "$$"}. */
	public static final String CGLIB_CLASS_SEPARATOR = "$$";

	/** The ".class" file suffix. */
	public static final String CLASS_FILE_SUFFIX = ".class";
	
  	/**
	 * Get the class name without the qualified package name.
	 * @param className the className to get the short name for
	 * @return the class name of the class without the package name
	 * @throws IllegalArgumentException if the className is empty
	 */
	public static String getShortName(String className) {
		Assert.hasLength(className, "Class name must not be empty");
		int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR);
		int nameEndIndex = className.indexOf(CGLIB_CLASS_SEPARATOR);
		if (nameEndIndex == -1) {
			nameEndIndex = className.length();
		}
		String shortName = className.substring(lastDotIndex + 1, nameEndIndex);
		shortName = shortName.replace(INNER_CLASS_SEPARATOR, PACKAGE_SEPARATOR);
		return shortName;
	}
  
  	//其余代码略
}

4.3、DefaultBeanNameGenerator

java
/**
 * Default implementation of the {@link BeanNameGenerator} interface, delegating to
 * {@link BeanDefinitionReaderUtils#generateBeanName(BeanDefinition, BeanDefinitionRegistry)}.
 *
 * @author Juergen Hoeller
 * @since 2.0.3
 */
public class DefaultBeanNameGenerator implements BeanNameGenerator {

	@Override
	public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
		return BeanDefinitionReaderUtils.generateBeanName(definition, registry);
	}
}
java
DefaultBeanNameGenerator类将具体的处理方式委托给了,BeanDefinitionReaderUtils#generateBeanName(BeanDefinition, BeanDefinitionRegistry)方法处理。 

以下是代码节选:
public abstract class BeanDefinitionReaderUtils {

	public static String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {
		//此方法除了bean的定义信息和定义注册之外,还有一个布尔类型的值,用于确定是内部bean还是顶层bean
		return generateBeanName(beanDefinition, registry, false);
	}

	/**
	 * 生成bean的唯一标识(默认规则)
	 */
	public static String generateBeanName(
			BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean)
			throws BeanDefinitionStoreException {

		String generatedBeanName = definition.getBeanClassName();
		if (generatedBeanName == null) {
			if (definition.getParentName() != null) {
				generatedBeanName = definition.getParentName() + "$child";
			}
			else if (definition.getFactoryBeanName() != null) {
				generatedBeanName = definition.getFactoryBeanName() + "$created";
			}
		}
		if (!StringUtils.hasText(generatedBeanName)) {
			throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither " +
					"'class' nor 'parent' nor 'factory-bean' - can't generate bean name");
		}

		String id = generatedBeanName;
		if (isInnerBean) {
			// Inner bean: generate identity hashcode suffix.
			id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
		}
		else {
			// Top-level bean: use plain class name with unique suffix if necessary.
			return uniqueBeanName(generatedBeanName, registry);
		}
		return id;
	}

	//其他代码略
}

5、ScopedProxyMode枚举

java
/**
 * Enumerates the various scoped-proxy options.
 *
 * <p>For a more complete discussion of exactly what a scoped proxy is, see the
 * section of the Spring reference documentation entitled '<em>Scoped beans as
 * dependencies</em>'.
 *
 * @author Mark Fisher
 * @since 2.5
 * @see ScopeMetadata
 */
public enum ScopedProxyMode {

	/**
	 * Default typically equals {@link #NO}, unless a different default
	 * has been configured at the component-scan instruction level.
	 */
	DEFAULT,

	/**
	 * Do not create a scoped proxy.
	 * <p>This proxy-mode is not typically useful when used with a
	 * non-singleton scoped instance, which should favor the use of the
	 * {@link #INTERFACES} or {@link #TARGET_CLASS} proxy-modes instead if it
	 * is to be used as a dependency.
	 */
	NO,

	/**
	 * Create a JDK dynamic proxy implementing <i>all</i> interfaces exposed by
	 * the class of the target object.
	 */
	INTERFACES,

	/**
	 * Create a class-based proxy (uses CGLIB).
	 */
	TARGET_CLASS;

}

6、自定义组件扫描过滤规则

6.1、FilterType枚举

java
public enum FilterType {

	/**
	 * Filter candidates marked with a given annotation.
	 * @see org.springframework.core.type.filter.AnnotationTypeFilter
	 */
	ANNOTATION,

	/**
	 * Filter candidates assignable to a given type.
	 * @see org.springframework.core.type.filter.AssignableTypeFilter
	 */
	ASSIGNABLE_TYPE,

	/**
	 * Filter candidates matching a given AspectJ type pattern expression.
	 * @see org.springframework.core.type.filter.AspectJTypeFilter
	 */
	ASPECTJ,

	/**
	 * Filter candidates matching a given regex pattern.
	 * @see org.springframework.core.type.filter.RegexPatternTypeFilter
	 */
	REGEX,

	/** Filter candidates using a given custom
	 * {@link org.springframework.core.type.filter.TypeFilter} implementation.
	 */
	CUSTOM

}

6.2、TypeFilter接口

java
@FunctionalInterface
public interface TypeFilter {

	/**
	 * 此方法返回一个boolean类型的值。
	 * 当返回true时,表示加入到spring的容器中。返回false时,不加入容器。
	 * 参数metadataReader:表示读取到的当前正在扫描的类的信息
	 * 参数metadataReaderFactory:表示可以获得到其他任何类的信息
	 */
	boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
			throws IOException;

}

6.3、使用Spring提供的过滤规则-AnnotationTypeFilter

java
/**
 * @author 黑马程序员
 * @Company http://www.itheima.com
 * 当我们使用注解驱动开发JavaEE项目时,spring提供的容器分为RootApplicationContext和 
 * ServletApplicationContext。此时我们不希望Root容器创建时把Controller加入到容器中,
 * 就可以使用过滤规则排除@Controller注解配置的Bean对象。
 */
@Configuration
@ComponentScan(value = "com.itheima",excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class))
public class SpringConfiguration {
}

6.4、自定义过滤规则

6.4.1、场景分析
	在实际开发中,有很多下面这种业务场景:一个业务需求根据环境的不同可能会有很多种实现。针对不同的环境,要加载不同的实现。我们看下面这个案例:
	我们现在是一个汽车销售集团,在成立之初,只是在北京销售汽车,我们的项目研发完成后只在北京部署上线。但随着公司的业务发展,现在全国各地均有销售大区,总部设在北京。各大区有独立的项目部署,但是每个大区的业绩计算和绩效提成的计算方式并不相同。
	例如:
		在华北区销售一台豪华级轿车绩效算5,提成销售额1%,销售豪华级SUV绩效算3,提成是0.5%。
		在西南区销售一台豪华级轿车绩效算3,提成销售额0.5%,销售豪华级SUV绩效算5,提成是1.5%。
	这时,我们如果针对不同大区对项目源码进行删减替换,会带来很多不必要的麻烦。而如果加入一些if/else的判断,显然过于简单粗暴。此时应该考虑采用桥接设计模式,把将涉及到区域性差异的模块功能单独抽取到代表区域功能的接口中。针对不同区域进行实现。并且在扫描组件注册到容器中时,采用哪个区域的具体实现,应该采用配置文件配置起来。而自定义TypeFilter就可以实现注册指定区域的组件到容器中。
6.4.2、代码实现
java
/**
 * 区域的注解
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface District {

    /**
     * 指定区域的名称
     * @return
     */
    String value()  default "";
}

/**
 * 销售分成的桥接接口
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
public interface DistrictPercentage {

    /**
     * 不同车型提成
     * @param carType
     */
    void salePercentage(String carType);
}

/**
 * 绩效计算桥接接口
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
public interface DistrictPerformance {

    /**
     * 计算绩效
     * @param carType
     */
    void calcPerformance(String carType);
}

/**
 * 华北区销售分成具体实现
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
@Component("districtPercentage")
@District("north")
public class NorthDistrictPercentage implements DistrictPercentage {


    @Override
    public void salePercentage(String carType) {
        if("SUV".equalsIgnoreCase(carType)) {
            System.out.println("华北区"+carType+"提成1%");
        }else if("car".equalsIgnoreCase(carType)){
            System.out.println("华北区"+carType+"提成0.5%");
        }
    }
}

/**
 * 华北区销售绩效具体实现
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
@Component("districtPerformance")
@District("north")
public class NorthDistrictPerformance implements DistrictPerformance {

    @Override
    public void calcPerformance(String carType) {
        if("SUV".equalsIgnoreCase(carType)) {
            System.out.println("华北区"+carType+"绩效3");
        }else if("car".equalsIgnoreCase(carType)){
            System.out.println("华北区"+carType+"绩效5");
        }
    }
}

/**
 * 西南区销售分成具体实现
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
@Component("districtPercentage")
@District("southwest")
public class SouthwestDistrictPercentage implements DistrictPercentage {

    @Override
    public void salePercentage(String carType) {
        if("SUV".equalsIgnoreCase(carType)) {
            System.out.println("西南区"+carType+"提成1.5%");
        }else if("car".equalsIgnoreCase(carType)){
            System.out.println("西南区"+carType+"提成0.5%");
        }
    }
}


/**
 * 西南区绩效计算具体实现
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
@Component("districtPerformance")
@District("southwest")
public class SouthwestDistrictPerformance implements DistrictPerformance {

    @Override
    public void calcPerformance(String carType) {
        if("SUV".equalsIgnoreCase(carType)) {
            System.out.println("西南区"+carType+"绩效5");
        }else if("car".equalsIgnoreCase(carType)){
            System.out.println("西南区"+carType+"绩效3");
        }
    }
}

/**
 * spring的配置类
 * 用于替代xml配置
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
@Configuration
@PropertySource(value = "classpath:district.properties")
@ComponentScan(value = "com.itheima",
            excludeFilters = @ComponentScan.Filter(type=FilterType.CUSTOM,classes = DistrictTypeFilter.class))
public class SpringConfiguration {
}
java
/**
 * spring的自定义扫描规则
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
public class DistrictTypeFilter extends AbstractTypeHierarchyTraversingFilter {

    //定义路径校验类对象
    private PathMatcher pathMatcher;

    //注意:使用@Value注解的方式是获取不到配置值的。
    //因为Spring的生命周期里,负责填充属性值的InstantiationAwareBeanPostProcessor 与TypeFilter的实例化过程压根搭不上边。
//    @Value("${common.district.name}")
    private String districtName;

    /**
     * 默认构造函数
     */
    public DistrictTypeFilter() {
        //1.第一个参数:不考虑基类。2.第二个参数:不考虑接口上的信息
        super(false, false);


        //借助Spring默认的Resource通配符路径方式
        pathMatcher = new AntPathMatcher();

        //硬编码读取配置信息
        try {
            Properties loadAllProperties = PropertiesLoaderUtils.loadAllProperties("district.properties");
            districtName = loadAllProperties.getProperty("common.district.name");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    //注意本类将注册为Exclude, 返回true代表拒绝
    @Override
    protected boolean matchClassName(String className) {
        try{
            if (!isPotentialPackageClass(className)) {
                return false;
            }

            // 判断当前区域是否和所配置的区域一致, 不一致则阻止载入Spring容器
            Class<?> clazz = ClassUtils.forName(className, DistrictTypeFilter.class.getClassLoader());
            District districtAnnotation = clazz.getAnnotation(District.class);
            if(null == districtAnnotation){
                return false;
            }
            final String districtValue = districtAnnotation.value();
            return (!districtName.equalsIgnoreCase(districtValue));
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    // 潜在的满足条件的类的类名, 指定package下
    private static final String PATTERN_STANDARD = ClassUtils
            .convertClassNameToResourcePath("com.itheima.service.impl.*");

    // 本类逻辑中可以处理的类 -- 指定package下的才会进行逻辑判断,
    private boolean isPotentialPackageClass(String className) {
        // 将类名转换为资源路径, 以进行匹配测试
        final String path = ClassUtils.convertClassNameToResourcePath(className);
        return pathMatcher.match(PATTERN_STANDARD, path);
    }

}
java
/**
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
public class SpringAnnotationTypeFilterTest {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext("config");
        DistrictPerformance districtPerformance = ac.getBean("districtPerformance", DistrictPerformance.class);
        districtPerformance.calcPerformance("SUV");

        DistrictPercentage districtPercentage = ac.getBean("districtPercentage",DistrictPercentage.class);
        districtPercentage.salePercentage("car");

    }
}

7、@Import注解的高级分析

7.1、ImportSelector和ImportBeanDefinitionRegistrar介绍

特别说明:
	我们在注入bean对象时,可选的方式有很多种。
	例如:
		我们自己写的类,可以使用@Component,@Service,@Repository,@Controller等等。
		我们导入的第三方库中的类,可以使用@Bean(当需要做一些初始化操作时,比如DataSource),也可以使用@Import注解,直接指定要引入的类的字节码。
		但是当我们的类很多时,在每个类上加注解会很繁琐,同时使用@Bean或者@Import写起来也很麻烦。此时我们就可以采用自定义ImportSelector或者ImportBeanDefinitionRegistrar来实现。顺便说一句,在SpringBoot中,@EnableXXX这样的注解,绝大多数都借助了ImportSelector或者ImportBeanDefinitionRegistrar。在我们的spring中,@EnableTransactionManagement就是借助了ImportSelector,而@EnableAspectJAutoporxy就是借助了ImportBeanDefinitionRegistrar。
		
共同点:
	他们都是用于动态注册bean对象到容器中的。并且支持大批量的bean导入。
区别:
	ImportSelector是一个接口,我们在使用时需要自己提供实现类。实现类中返回要注册的bean的全限定类名数组,然后执行ConfigurationClassParser类中中的processImports方法注册bean对象的。
	ImportBeanDefinitionRegistrar也是一个接口,需要我们自己编写实现类,在实现类中手动注册bean到容器中。

注意事项:
	实现了ImportSelector接口或者ImportBeanDefinitionRegistrar接口的类不会被解析成一个Bean注册到容器中。
	同时,在注册到容器中时bean的唯一标识是全限定类名,而非短类名。

7.2、自定义ImportSelector

java
/**
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
public interface UserService {
    void saveUser();
}

/**
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
public class UserServiceImpl implements UserService {

    @Override
    public void saveUser() {
        System.out.println("保存用户");
    }
}

/**
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
@Configuration
@ComponentScan("com.itheima")
@Import(CustomeImportSelector.class)
public class SpringConfiguration {
}
java
/**
 * customeimport.properties配置文件中的内容:
 * 		custome.importselector.expression= com.itheima.service.impl.*
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
public class CustomeImportSelector implements ImportSelector {

    private String expression;

    public CustomeImportSelector(){
        try {
            Properties loadAllProperties = PropertiesLoaderUtils.loadAllProperties("customeimport.properties");
            expression = loadAllProperties.getProperty("custome.importselector.expression");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * 生成要导入的bean全限定类名数组
     * @param importingClassMetadata
     * @return
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //1.定义扫描包的名称
        String[] basePackages = null;
        //2.判断有@Import注解的类上是否有@ComponentScan注解
        if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())) {
            //3.取出@ComponentScan注解的属性
            Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
            //4.取出属性名称为basePackages属性的值
            basePackages = (String[]) annotationAttributes.get("basePackages");
        }
        //5.判断是否有此属性(如果没有ComponentScan注解则属性值为null,如果有ComponentScan注解,则basePackages默认为空数组)
        if (basePackages == null || basePackages.length == 0) {
            String basePackage = null;
            try {
                //6.取出包含@Import注解类的包名
                basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            //7.存入数组中
            basePackages = new String[] {basePackage};
        }
        //8.创建类路径扫描器
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        //9.创建类型过滤器(此处使用切入点表达式类型过滤器)
        TypeFilter typeFilter = new AspectJTypeFilter(expression,this.getClass().getClassLoader());
        //10.给扫描器加入类型过滤器
        scanner.addIncludeFilter(typeFilter);
        //11.创建存放全限定类名的集合
        Set<String> classes = new HashSet<>();
        //12.填充集合数据
        for (String basePackage : basePackages) {
            scanner.findCandidateComponents(basePackage).forEach(beanDefinition -> classes.add(beanDefinition.getBeanClassName()));
        }
        //13.按照规则返回
        return classes.toArray(new String[classes.size()]);
    }
}
java
/**
 * 测试类
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
public class SpringCustomeImportSelectorTest {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext("config");
        String[] names = ac.getBeanDefinitionNames();
        for(String beanName : names){
            Object obj = ac.getBean(beanName);
            System.out.println(beanName+"============"+obj);
        }
    }
}

ioc-importselector

7.3、自定义ImportBeanDefinitionRegistrar

java
借助7.2小节的案例代码,只需要把配置改一下:
/**
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
@Configuration
@ComponentScan("com.itheima")
@Import(CustomeImportDefinitionRegistrar.class)
public class SpringConfiguration {
}

/**
 * 自定义bean导入注册器
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
public class CustomeImportDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    private String expression;

    public CustomeImportDefinitionRegistrar(){
        try {
            Properties loadAllProperties = PropertiesLoaderUtils.loadAllProperties("customeimport.properties");
            expression = loadAllProperties.getProperty("custome.importselector.expression");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }


    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //1.定义扫描包的名称
        String[] basePackages = null;
        //2.判断有@Import注解的类上是否有@ComponentScan注解
        if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())) {
            //3.取出@ComponentScan注解的属性
            Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
            //4.取出属性名称为basePackages属性的值
            basePackages = (String[]) annotationAttributes.get("basePackages");
        }
        //5.判断是否有此属性(如果没有ComponentScan注解则属性值为null,如果有ComponentScan注解,则basePackages默认为空数组)
        if (basePackages == null || basePackages.length == 0) {
            String basePackage = null;
            try {
                //6.取出包含@Import注解类的包名
                basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            //7.存入数组中
            basePackages = new String[] {basePackage};
        }
        //8.创建类路径扫描器
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
        //9.创建类型过滤器(此处使用切入点表达式类型过滤器)
        TypeFilter typeFilter = new AspectJTypeFilter(expression,this.getClass().getClassLoader());
        //10.给扫描器加入类型过滤器
        scanner.addIncludeFilter(typeFilter);
        //11.扫描指定包
        scanner.scan(basePackages);
    }
}

7.4、原理分析

java
我们写的自定义导入器的解析写在了ConfigurationClassParser类中的processImports方法,以下是源码节选:
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

        if (importCandidates.isEmpty()) {
            return;
        }

        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        }
        else {
            this.importStack.push(configClass);
            try {
                for (SourceClass candidate : importCandidates) {
            //对ImportSelector的处理
                    if (candidate.isAssignable(ImportSelector.class)) {
                        // Candidate class is an ImportSelector -> delegate to it to determine imports
                        Class<?> candidateClass = candidate.loadClass();
                        ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                        ParserStrategyUtils.invokeAwareMethods(
                                selector, this.environment, this.resourceLoader, this.registry);
                        if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
                //如果为延迟导入处理则加入集合当中
                            this.deferredImportSelectors.add(
                                    new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
                        }
                        else {
                //根据ImportSelector方法的返回值来进行递归操作
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                            processImports(configClass, currentSourceClass, importSourceClasses, false);
                        }
                    }
                    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        // Candidate class is an ImportBeanDefinitionRegistrar ->
                        // delegate to it to register additional bean definitions
                        Class<?> candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar =
                                BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
                        ParserStrategyUtils.invokeAwareMethods(
                                registrar, this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    }
                    else {
              // 如果当前的类既不是ImportSelector也不是ImportBeanDefinitionRegistar就进行@Configuration的解析处理
                        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                        // process it as an @Configuration class
                        this.importStack.registerImport(
                                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        processConfigurationClass(candidate.asConfigClass(configClass));
                    }
                }
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration class [" +
                        configClass.getMetadata().getClassName() + "]", ex);
            }
            finally {
                this.importStack.pop();
            }
        }
    }

8、自定义PropertySourceFactory实现YAML文件解析

8.1、PropertySourceFactory及DefaultPropertySourceFactory

java
/**
 * Strategy interface for creating resource-based {@link PropertySource} wrappers.
 *
 * @author Juergen Hoeller
 * @since 4.3
 * @see DefaultPropertySourceFactory
 */
public interface PropertySourceFactory {

	/**
	 * Create a {@link PropertySource} that wraps the given resource.
	 * @param name the name of the property source
	 * @param resource the resource (potentially encoded) to wrap
	 * @return the new {@link PropertySource} (never {@code null})
	 * @throws IOException if resource resolution failed
	 */
	PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException;

}
java
/**
 * The default implementation for {@link PropertySourceFactory},
 * wrapping every resource in a {@link ResourcePropertySource}.
 *
 * @author Juergen Hoeller
 * @since 4.3
 * @see PropertySourceFactory
 * @see ResourcePropertySource
 */
public class DefaultPropertySourceFactory implements PropertySourceFactory {

	@Override
	public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
		return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
	}

}

8.2、执行过程分析

8.2.1、ResourcePropertySource
java
/**
 *  DefaultPropertySourceFactory在创建PropertySource对象时使用的是此类的构造函数
 *  在构造时,调用的
 */
public class ResourcePropertySource extends PropertiesPropertySource {

	/**
	 * 当我们没有指定名称时,执行的是此构造函数,此构造函数调用的是父类的构造函数
	 * 通过读取父类的构造函数,得知第二个参数是一个properties文件
	 * spring使用了一个工具类获取properties文件
	 */
	public ResourcePropertySource(EncodedResource resource) throws IOException {
		super(getNameForResource(resource.getResource()), PropertiesLoaderUtils.loadProperties(resource));
		this.resourceName = null;
	}
	
    //其他方法略
}
8.2.2、PropertiesPropertySource
java
/**
 * 此类是ResourcePropertySource的父类
 */
public class PropertiesPropertySource extends MapPropertySource {
	
  	/**
  	 * 此构造函数中包含两个参数:第一个是名称。第二个是解析好的properties文件
  	 */
	@SuppressWarnings({"unchecked", "rawtypes"})
	public PropertiesPropertySource(String name, Properties source) {
		super(name, (Map) source);
	}

	protected PropertiesPropertySource(String name, Map<String, Object> source) {
		super(name, source);
	}

}
8.2.3、PropertiesLoaderUtils
java
/**
 * 获取properties的工具类
 */
public abstract class PropertiesLoaderUtils {

	private static final String XML_FILE_EXTENSION = ".xml";


	/**
	 * ResourcePropertySource类中的构造函数就是执行了此方法
	 */
	public static Properties loadProperties(EncodedResource resource) throws IOException {
		Properties props = new Properties();
		fillProperties(props, resource);
		return props;
	}

	/**
	 * 
	 */
	public static void fillProperties(Properties props, EncodedResource resource)
			throws IOException {

		fillProperties(props, resource, new DefaultPropertiesPersister());
	}

	/**
	 * 通过此方法源码,可以看出spring在使用@PropertySource注解时,支持xml和properties文件
	 */
	static void fillProperties(Properties props, EncodedResource resource, PropertiesPersister persister)
			throws IOException {

		InputStream stream = null;
		Reader reader = null;
		try {
			String filename = resource.getResource().getFilename();
			if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
				stream = resource.getInputStream();
                //读取xml文件
				persister.loadFromXml(props, stream);
			}
			else if (resource.requiresReader()) {
				reader = resource.getReader();
                //读取properties文件
				persister.load(props, reader);
			}
			else {
				stream = resource.getInputStream();
				persister.load(props, stream);
			}
		}
		finally {
			if (stream != null) {
				stream.close();
			}
			if (reader != null) {
				reader.close();
			}
		}
	}
	//其他代码略
}
8.2.4、PropertiesPersister
java
/**
 * spring中声明的解析xml和properties文件的接口
 */
public interface PropertiesPersister {

	
	/**
	 * Load properties from the given Reader into the given
	 * Properties object.
	 * @param props the Properties object to load into
	 * @param reader the Reader to load from
	 * @throws IOException in case of I/O errors
	 */
	void load(Properties props, Reader reader) throws IOException;

	/**
	 * Load properties from the given XML InputStream into the
	 * given Properties object.
	 * @param props the Properties object to load into
	 * @param is the InputStream to load from
	 * @throws IOException in case of I/O errors
	 * @see java.util.Properties#loadFromXML(java.io.InputStream)
	 */
	void loadFromXml(Properties props, InputStream is) throws IOException;
  
  	//其他代码略
}
java
/** 
 *  PropertiesPersister接口的实现类
 */
public class DefaultPropertiesPersister implements PropertiesPersister {


	@Override
	public void load(Properties props, Reader reader) throws IOException {
		props.load(reader);

	@Override
	public void loadFromXml(Properties props, InputStream is) throws IOException {
		props.loadFromXML(is);
	}

	//其他代码略
}

8.3、自定义PropertySourceFactory

我们通过分析@PropertySource源码,得知默认情况下此注解只能解析properties文件和xml文件,而遇到yaml(yml)文件,解析就会报错。此时就需要我们自己编写一个PropertySourceFactory的实现类,借助yaml解析器,实现yml文件的解析。

8.3.1、编写yml配置文件
yaml
jdbc:
 driver: com.mysql.jdbc.Driver
 url: jdbc:mysql://localhost:3306/spring_day01
 username: root
 password: 1234
8.3.2、导入yaml解析器的坐标
xml
<!-- yaml解析器 https://mvnrepository.com/artifact/org.yaml/snakeyaml -->
<dependency>
  <groupId>org.yaml</groupId>
  <artifactId>snakeyaml</artifactId>
  <version>1.23</version>
</dependency>
8.3.3、编写自定义PropertySourceFactory
java
/**
 * 自定义资源文件解析器,用于解析yaml yml文件
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
public class CustomerPropertySourceFactory implements PropertySourceFactory {

    /**
     * 重写接口中的方法
     * @param name
     * @param resource
     * @return
     * @throws IOException
     */
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        //1.创建yaml文件解析工厂
        YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
        //2.设置资源内容
        yaml.setResources(resource.getResource());
        //3.解析成properties文件
        Properties properties = yaml.getObject();
        //4.返回符合spring的PropertySource对象
        return name != null ? new PropertiesPropertySource(name,properties) : new PropertiesPropertySource(resource.getResource().getFilename(), properties);

    }
}
8.3.4、使用@PropertyeSource的factory属性配置自定义工厂
java
/**
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
@Configuration
@Import(JdbcConfig.class)
@PropertySource(value = "classpath:jdbc.yml",factory = CustomerPropertySourceFactory.class)
@ComponentScan("com.itheima")
public class SpringConfiguration {
}
java
/**
 * 连接数据库的配置
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;


    @Bean("jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }


    @Bean("dataSource")
    public DataSource createDataSource(){
        System.out.println(driver);
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}
8.3.5、测试运行结果
java
/**
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
public class SpringAnnotationDrivenTest {

    /**
     * 测试
     * @param args
     */
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext("config");
    }
}

9、@Profile注解的使用

9.1、使用场景分析

	@Profile注解是spring提供的一个用来标明当前运行环境的注解。我们正常开发的过程中经常遇到的问题是,开发环境是一套环境,测试是一套环境,线上部署又是一套环境。这样从开发到测试再到部署,会对程序中的配置修改多次,尤其是从测试到上线这个环节,让测试的也不敢保证改了哪个配置之后能不能在线上运行。为了解决上面的问题,我们一般会使用一种方法,就是针对不同的环境进行不同的配置,从而在不同的场景中跑我们的程序。
  而spring中的@Profile注解的作用就体现在这里。在spring使用DI来注入的时候,能够根据当前制定的运行环境来注入相应的bean。最常见的就是使用不同的DataSource了。

9.2、代码实现

9.2.1、自定义不同环境的数据源
java
package config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile;

/**
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;




    /**
     * @return
     */
    @Bean("dataSource")
    @Profile("dev")
    public DruidDataSource createDevDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setMaxActive(10);
        return dataSource;
    }

    /**
     * @return
     */
    @Bean("dataSource")
    @Profile("test")
    public DruidDataSource createTestDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setMaxActive(50);
        return dataSource;
    }

    /**
     * @return
     */
    @Bean("dataSource")
    @Profile("produce")
    public DruidDataSource createProduceDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setMaxActive(100);
        return dataSource;
    }
}
9.2.2、编写配置类
java
/**
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
@Configuration
@Import(JdbcConfig.class)
public class SpringConfiguration {
}
9.2.3、编写测试类
java
/**
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
@ActiveProfiles("test")
public class SpringProfileTest {

    @Autowired
    private DruidDataSource druidDataSource;

    @Test
    public void testProfile(){
        System.out.println(druidDataSource.getMaxActive());
    }
}

十、MVC注解驱动

初始化过程分析

ServletContainerInitializer是Servlet3.0规范加入的内容。

  • onStartup是启动容器是做一些初始化操作,例如注册Servlet,Filter,Listener等等。

HandlesTypes 用于指定要加载到ServletContainerInitializer接口实现类中的字节码

  • value() 指定要加载到ServletContainerInitializer实现类的onStartUp方法中类的字节码。 字节码可以是接口,抽象类或者普通类。

任何要使用Servlet3.0规范且脱离web.xml的配置,在使用时都必须在对应的jar包的META- INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内 容指定具体的ServletContainerInitializer实现类,那么,当web容器启动时就会运行这个初始化器做 一些组件内的初始化工作。

常用注解

@Controller

* 作用:
* 用于建立请求URL和处理请求方法之间的对应关系。 
* 注意:
* 属性只要出现2个或以上时,他们的关系是与的关系。表示必须同时满足条件。 
* 出现位置:
* 写在类上:
* 请求URL的第一级访问目录。此处不写的话,就相当于应用的根目录。
* 它出现的目的是为了使我们的URL可以按照模块化管理,使我们的URL更加精细:
* 方法上: 
* 请求URL的第二级访问目录。

name()给请求URL提供一个名称

value() 用于指定请求的URL。它和path属性的作用是一样的。 在配置此属性时,写不写/都是可以的。

path()  是4.2版本中加入的注解,和value属性是一样的。

method() 用于指定请求的方式。它支持以下这些类型: * GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE. * 这些值是通过RequestMethod枚举指定的。

params() params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的key和value必须 和配置的一模一样。
 
headers() 用于指定限制请求消息头的条件。

consumes() 用于指定可以接收的请求正文类型(MIME类型)

 produces() 用于指定可以生成的响应正文类型。(MIME类型)

@Controller衍生注解

@GetMapping@PostMapping@PutMapping@DeleteMapping

@RequestParam

 * 此注解是从请求正文中获取请求参数,给控制器方法形参赋值的。 
 * 当请求参数的名称和控制器方法形参变量名称一致时,无须使用此注解。
 * 同时,当没有获取到请求参数时,此注解还可以给控制器方法形参提供默认值。 
 * 注意:
 * 它只能出现在方法的参数上
 
 value * 用于指定获取请求参数的名称。它和name属性的作用是一样的,
 
 name 在4.2版本中加入的。和value属性互为引用
 
 required  指定参数是否必须有值。当为true时,参数没有值会报错。
 
 defaultValue  在参数没有值时的默认值。

@InitBinder

用于初始化表单请求参数的数据绑定器。

value  指定给哪些参数进行绑定操作。

扩展注解@DateTimeFormat

时间格式化 @DateTimeFormat(pattern = "yyyy-MM-dd")

@ControllerAdvice

用于给控制器提供一个增强的通知。
* 以保证可以在多个控制器之间实现增强共享。 
* 它可以配合以下三个注解来用:
* {@link exceptionhandler@exceptionhandler}
* {@link initbinder@initbinder} 
* {@link modeltattribute@modeltattribute}

value  用于指定对哪些包下的控制器进行增强

basePackages  是4.0版本新加入的属性,作用和value是一样的。

basePackageClasses  是4.0新加入的注解,可以通过指定类的字节码的方式来指定增强作用范围。

assignableTypes  是4.0新加入的注解,用于指定特定的类型提供增强。

annotations  是4.0新加入的注解,用于指定给特定注解提供增强。

@RequestHeader

从请求消息头中获取消息头的值,并把值赋给控制器方法形参。
* 注意: 
* 它只能出现在方法的参数上

value  用于指定请求消息头的名称。它和name属性作用一样。

name  是4.2版本引入的。和value属性互为引用。

required  用于指定是否必须有此消息头。当取默认值时,没有此消息头会报错。

defaultValue   用于指定消息头的默认值。

@CookieValue

此注解是从请求消息头中获取Cookie的值,并把值赋给控制器方法形参。 
* 注意: 
* 它只能出现在方法的参数上

value  用于指定cookie的名称

name  是4.2版本引入的。和value属性互为引用

required  用于指定是否必须有此消息头。当取默认值时,没有此消息头会报错。

defaultValue  用于指定消息头的默认值。

@ModelAttribute

它可以用于修饰方法,或者是参数。 
* 当修饰方法时,表示执行控制器方法之前,被此注解修饰的方法都会执行。
* 当修饰参数时,用于获取指定的数据给参数赋值。。

value  当注解写在方法上,则表示存入时的名称。(值是方法的返回值) * 当注解写在参数上,可以从ModelMap,Model,Map中的获取数据。(前提是之前存入过) * 指定的是存入时的key。

name  和value的作用是一样的。

binding  用于指定是否支持数据绑定。它是4.3版本中新加入的属性。

@SessionAttribute和@SessionAttributes

SessionAttribute
让开发者和ServletAPI进行解耦。 * 让开发者可以无需使用HttpSession的getAttribute方法即可从会话域中获取数据。

value  用于指定在会话域中数据的名称。

name  和value属性互为引用

required  用于指定是否必须从会话域中获取到数据。默认值是true,表示如果指定名称不存在会报错。

SessionAttributes
* 此注解是用于让开发者和ServletAPI进行解耦。 * 通过此注解即可实现把数据存入会话域,而无需在使用HttpSession的setAttribute方法。 * 当我们在控制器方法形参中加入Model或者ModelMap类型参数时,默认是存入请求域的。 * 但当控制器上使用了此注解,就会往会话域中添加数据。

value  指定可以存入会话域中的名称。

names  是4.2版本中加入的属性。作用和value是一样的。

types  指定可以存入会话域中的数据类型。

@ExceptionHandler

用于注释方法,表明当前方法是控制器执行产生异常后的处理方法

value  指定用于需要捕获的异常类型

@RequestBody

用于获取全部的请求体

required  * 用于指定是否必须有请求体。

@ResponseBody

用于用流输出响应正文

@RestController

它具备@Controller注解的全部功能,同时多了一个@ResponseBody注解的功能

value  用于指定存入ioc容器时bean的唯一标识。

@RestControllerAdvice

和@ControllerAdvice注解的作用一样,并且支持@ResponseBody的功能

@PathVariable

它是springmvc框架支持rest风格url的标识。 * 它可以用于获取请求url映射中占位符对应的值。

value  指定url映射中占位符的名称

name  是4.3.3版本新加入的属性。作用和value是一样的。

required  是4.3.3版本新加入的属性,用于指定是否必须有此占位符。当取默认值时,没有会报错。

@CrossOrigin

此注解用于指定是否支持跨域访问

value  和origins属性一样

origins  * 所有支持域的集合,例如"http://domain1.com"。 * <p>这些值都显示在请求头中的Access-Control-Allow-Origin * "*"代表所有域的请求都支持 * <p>如果没有定义,所有请求的域都支持

allowedHeaders  允许请求头中的header,默认都支持

exposedHeaders  响应头中允许访问的header,默认为空

methods  用于指定支持的HTTP请求方式列表

allowCredentials  是否允许cookie随请求发送,使用时必须指定具体的域

maxAge  预请求的结果的有效期 * 默认值是:1800秒 (30分钟).

十一、面试概述

1. Spring refresh 流程

Spring refresh 概述

refresh 是 AbstractApplicationContext 中的一个方法,负责初始化 ApplicationContext 容器,容器必须调用 refresh 才能正常工作。它的内部主要会调用 12 个方法,我们把它们称为 refresh 的 12 个步骤:

  1. prepareRefresh

  2. obtainFreshBeanFactory

  3. prepareBeanFactory

  4. postProcessBeanFactory

  5. invokeBeanFactoryPostProcessors

  6. registerBeanPostProcessors

  7. initMessageSource

  8. initApplicationEventMulticaster

  9. onRefresh

  10. registerListeners

  11. finishBeanFactoryInitialization

  12. finishRefresh

功能分类

  • 1 为准备环境

  • 2 3 4 5 6 为准备 BeanFactory

  • 7 8 9 10 12 为准备 ApplicationContext

  • 11 为初始化 BeanFactory 中非延迟单例 bean

1. prepareRefresh

  • 这一步创建和准备了 Environment 对象,它作为 ApplicationContext 的一个成员变量

  • Environment 对象的作用之一是为后续 @Value,值注入时提供键值

  • Environment 分成三个主要部分

    • systemProperties - 保存 java 环境键值
    • systemEnvironment - 保存系统环境键值
    • 自定义 PropertySource - 保存自定义键值,例如来自于 *.properties 文件的键值

image-20210902181639048

2. obtainFreshBeanFactory

  • 这一步获取(或创建) BeanFactory,它也是作为 ApplicationContext 的一个成员变量
  • BeanFactory 的作用是负责 bean 的创建、依赖注入和初始化,bean 的各项特征由 BeanDefinition 定义
    • BeanDefinition 作为 bean 的设计蓝图,规定了 bean 的特征,如单例多例、依赖关系、初始销毁方法等
    • BeanDefinition 的来源有多种多样,可以是通过 xml 获得、配置类获得、组件扫描获得,也可以是编程添加
  • 所有的 BeanDefinition 会存入 BeanFactory 中的 beanDefinitionMap 集合

image-20210902182004819

3. prepareBeanFactory

  • 这一步会进一步完善 BeanFactory,为它的各项成员变量赋值
  • beanExpressionResolver 用来解析 SpEL,常见实现为 StandardBeanExpressionResolver
  • propertyEditorRegistrars 会注册类型转换器
    • 它在这里使用了 ResourceEditorRegistrar 实现类
    • 并应用 ApplicationContext 提供的 Environment 完成 ${ } 解析
  • registerResolvableDependency 来注册 beanFactory 以及 ApplicationContext,让它们也能用于依赖注入
  • beanPostProcessors 是 bean 后处理器集合,会工作在 bean 的生命周期各个阶段,此处会添加两个:
    • ApplicationContextAwareProcessor 用来解析 Aware 接口
    • ApplicationListenerDetector 用来识别容器中 ApplicationListener 类型的 bean

image-20210902182541925

4. postProcessBeanFactory

  • 这一步是空实现,留给子类扩展。
    • 一般 Web 环境的 ApplicationContext 都要利用它注册新的 Scope,完善 Web 下的 BeanFactory
  • 这里体现的是模板方法设计模式

5. invokeBeanFactoryPostProcessors

  • 这一步会调用 beanFactory 后处理器
  • beanFactory 后处理器,充当 beanFactory 的扩展点,可以用来补充或修改 BeanDefinition
  • 常见的 beanFactory 后处理器有
    • ConfigurationClassPostProcessor – 解析 @Configuration、@Bean、@Import、@PropertySource 等
    • PropertySourcesPlaceHolderConfigurer – 替换 BeanDefinition 中的 $
    • MapperScannerConfigurer – 补充 Mapper 接口对应的 BeanDefinition

image-20210902183232114

6. registerBeanPostProcessors

  • 这一步是继续从 beanFactory 中找出 bean 后处理器,添加至 beanPostProcessors 集合中
  • bean 后处理器,充当 bean 的扩展点,可以工作在 bean 的实例化、依赖注入、初始化阶段,常见的有:
    • AutowiredAnnotationBeanPostProcessor 功能有:解析 @Autowired,@Value 注解
    • CommonAnnotationBeanPostProcessor 功能有:解析 @Resource,@PostConstruct,@PreDestroy
    • AnnotationAwareAspectJAutoProxyCreator 功能有:为符合切点的目标 bean 自动创建代理

image-20210902183520307

7. initMessageSource

  • 这一步是为 ApplicationContext 添加 messageSource 成员,实现国际化功能
  • 去 beanFactory 内找名为 messageSource 的 bean,如果没有,则提供空的 MessageSource 实现

image-20210902183819984

8. initApplicationContextEventMulticaster

  • 这一步为 ApplicationContext 添加事件广播器成员,即 applicationContextEventMulticaster
  • 它的作用是发布事件给监听器
  • 去 beanFactory 找名为 applicationEventMulticaster 的 bean 作为事件广播器,若没有,会创建默认的事件广播器
  • 之后就可以调用 ApplicationContext.publishEvent(事件对象) 来发布事件

image-20210902183943469

9. onRefresh

  • 这一步是空实现,留给子类扩展
    • SpringBoot 中的子类在这里准备了 WebServer,即内嵌 web 容器
  • 体现的是模板方法设计模式

10. registerListeners

  • 这一步会从多种途径找到事件监听器,并添加至 applicationEventMulticaster
  • 事件监听器顾名思义,用来接收事件广播器发布的事件,有如下来源
    • 事先编程添加的
    • 来自容器中的 bean
    • 来自于 @EventListener 的解析
  • 要实现事件监听器,只需要实现 ApplicationListener 接口,重写其中 onApplicationEvent(E e) 方法即可

image-20210902184343872

11. finishBeanFactoryInitialization

  • 这一步会将 beanFactory 的成员补充完毕,并初始化所有非延迟单例 bean
  • conversionService 也是一套转换机制,作为对 PropertyEditor 的补充
  • embeddedValueResolvers 即内嵌值解析器,用来解析 @Value 中的 ${ },借用的是 Environment 的功能
  • singletonObjects 即单例池,缓存所有单例对象
    • 对象的创建都分三个阶段,每一阶段都有不同的 bean 后处理器参与进来,扩展功能

image-20210902184641623

12. finishRefresh

  • 这一步会为 ApplicationContext 添加 lifecycleProcessor 成员,用来控制容器内需要生命周期管理的 bean
  • 如果容器中有名称为 lifecycleProcessor 的 bean 就用它,否则创建默认的生命周期管理器
  • 准备好生命周期管理器,就可以实现
    • 调用 context 的 start,即可触发所有实现 LifeCycle 接口 bean 的 start
    • 调用 context 的 stop,即可触发所有实现 LifeCycle 接口 bean 的 stop
  • 发布 ContextRefreshed 事件,整个 refresh 执行完成

image-20210902185052433

2. Spring bean 生命周期

bean 生命周期 概述

bean 的生命周期从调用 beanFactory 的 getBean 开始,到这个 bean 被销毁,可以总结为以下七个阶段:

  1. 处理名称,检查缓存
  2. 处理父子容器
  3. 处理 dependsOn
  4. 选择 scope 策略
  5. 创建 bean
  6. 类型转换处理
  7. 销毁 bean

注意

  • 划分的阶段和名称并不重要,重要的是理解整个过程中做了哪些事情

1. 处理名称,检查缓存

  • 这一步会处理别名,将别名解析为实际名称
  • 对 FactoryBean 也会特殊处理,如果以 & 开头表示要获取 FactoryBean 本身,否则表示要获取其产品
  • 这里针对单例对象会检查一级、二级、三级缓存
    • singletonFactories 三级缓存,存放单例工厂对象
    • earlySingletonObjects 二级缓存,存放单例工厂的产品对象
      • 如果发生循环依赖,产品是代理;无循环依赖,产品是原始对象
    • singletonObjects 一级缓存,存放单例成品对象

2. 处理父子容器

  • 如果当前容器根据名字找不到这个 bean,此时若父容器存在,则执行父容器的 getBean 流程
  • 父子容器的 bean 名称可以重复

3. 处理 dependsOn

  • 如果当前 bean 有通过 dependsOn 指定了非显式依赖的 bean,这一步会提前创建这些 dependsOn 的 bean
  • 所谓非显式依赖,就是指两个 bean 之间不存在直接依赖关系,但需要控制它们的创建先后顺序

4. 选择 scope 策略

  • 对于 singleton scope,首先到单例池去获取 bean,如果有则直接返回,没有再进入创建流程
  • 对于 prototype scope,每次都会进入创建流程
  • 对于自定义 scope,例如 request,首先到 request 域获取 bean,如果有则直接返回,没有再进入创建流程

5.1 创建 bean - 创建 bean 实例

要点总结
有自定义 TargetSource 的情况由 AnnotationAwareAspectJAutoProxyCreator 创建代理返回
Supplier 方式创建 bean 实例为 Spring 5.0 新增功能,方便编程方式创建 bean 实例
FactoryMethod 方式 创建 bean 实例① 分成静态工厂与实例工厂;② 工厂方法若有参数,需要对工厂方法参数进行解析,利用 resolveDependency;③ 如果有多个工厂方法候选者,还要进一步按权重筛选
AutowiredAnnotationBeanPostProcessor① 优先选择带 @Autowired 注解的构造;② 若有唯一的带参构造,也会入选
mbd.getPreferredConstructors选择所有公共构造,这些构造之间按权重筛选
采用默认构造如果上面的后处理器和 BeanDefiniation 都没找到构造,采用默认构造,即使是私有的

5.2 创建 bean - 依赖注入

要点总结
AutowiredAnnotationBeanPostProcessor识别 @Autowired 及 @Value 标注的成员,封装为 InjectionMetadata 进行依赖注入
CommonAnnotationBeanPostProcessor识别 @Resource 标注的成员,封装为 InjectionMetadata 进行依赖注入
resolveDependency用来查找要装配的值,可以识别:① Optional;② ObjectFactory 及 ObjectProvider;③ @Lazy 注解;④ @Value 注解(${ }, #{ }, 类型转换);⑤ 集合类型(Collection,Map,数组等);⑥ 泛型和 @Qualifier(用来区分类型歧义);⑦ primary 及名字匹配(用来区分类型歧义)
AUTOWIRE_BY_NAME根据成员名字找 bean 对象,修改 mbd 的 propertyValues,不会考虑简单类型的成员
AUTOWIRE_BY_TYPE根据成员类型执行 resolveDependency 找到依赖注入的值,修改 mbd 的 propertyValues
applyPropertyValues根据 mbd 的 propertyValues 进行依赖注入(即xml中 `<property name ref

5.3 创建 bean - 初始化

要点总结
内置 Aware 接口的装配包括 BeanNameAware,BeanFactoryAware 等
扩展 Aware 接口的装配由 ApplicationContextAwareProcessor 解析,执行时机在 postProcessBeforeInitialization
@PostConstruct由 CommonAnnotationBeanPostProcessor 解析,执行时机在 postProcessBeforeInitialization
InitializingBean通过接口回调执行初始化
initMethod根据 BeanDefinition 得到的初始化方法执行初始化,即 <bean init-method> 或 @Bean(initMethod)
创建 aop 代理由 AnnotationAwareAspectJAutoProxyCreator 创建,执行时机在 postProcessAfterInitialization

5.4 创建 bean - 注册可销毁 bean

在这一步判断并登记可销毁 bean

  • 判断依据
    • 如果实现了 DisposableBean 或 AutoCloseable 接口,则为可销毁 bean
    • 如果自定义了 destroyMethod,则为可销毁 bean
    • 如果采用 @Bean 没有指定 destroyMethod,则采用自动推断方式获取销毁方法名(close,shutdown)
    • 如果有 @PreDestroy 标注的方法
  • 存储位置
    • singleton scope 的可销毁 bean 会存储于 beanFactory 的成员当中
    • 自定义 scope 的可销毁 bean 会存储于对应的域对象当中
    • prototype scope 不会存储,需要自己找到此对象销毁
  • 存储时都会封装为 DisposableBeanAdapter 类型对销毁方法的调用进行适配

6. 类型转换处理

  • 如果 getBean 的 requiredType 参数与实际得到的对象类型不同,会尝试进行类型转换

7. 销毁 bean

  • 销毁时机
    • singleton bean 的销毁在 ApplicationContext.close 时,此时会找到所有 DisposableBean 的名字,逐一销毁
    • 自定义 scope bean 的销毁在作用域对象生命周期结束时
    • prototype bean 的销毁可以通过自己手动调用 AutowireCapableBeanFactory.destroyBean 方法执行销毁
  • 同一 bean 中不同形式销毁方法的调用次序
    • 优先后处理器销毁,即 @PreDestroy
    • 其次 DisposableBean 接口销毁
    • 最后 destroyMethod 销毁(包括自定义名称,推断名称,AutoCloseable 接口 多选一)

3. Spring bean 循环依赖

循环依赖的产生

  • 首先要明白,bean 的创建要遵循一定的步骤,必须是创建、注入、初始化三步,这些顺序不能乱
image-20210903085238916
  • set 方法(包括成员变量)的循环依赖如图所示

    • 可以在【a 创建】和【a set 注入 b】之间加入 b 的整个流程来解决

    • 【b set 注入 a】 时可以成功,因为之前 a 的实例已经创建完毕

    • a 的顺序,及 b 的顺序都能得到保障

image-20210903085454603
  • 构造方法的循环依赖如图所示,显然无法用前面的方法解决
image-20210903085906315

构造循环依赖的解决

  • 思路1
    • a 注入 b 的代理对象,这样能够保证 a 的流程走通
    • 后续需要用到 b 的真实对象时,可以通过代理间接访问
image-20210903091627659
  • 思路2
    • a 注入 b 的工厂对象,让 b 的实例创建被推迟,这样能够保证 a 的流程先走通
    • 后续需要用到 b 的真实对象时,再通过 ObjectFactory 工厂间接访问
image-20210903091743366
  • 示例1:用 @Lazy 为构造方法参数生成代理
java
public class App60_1 {

    static class A {
        private static final Logger log = LoggerFactory.getLogger("A");
        private B b;

        public A(@Lazy B b) {
            log.debug("A(B b) {}", b.getClass());
            this.b = b;
        }

        @PostConstruct
        public void init() {
            log.debug("init()");
        }
    }

    static class B {
        private static final Logger log = LoggerFactory.getLogger("B");
        private A a;

        public B(A a) {
            log.debug("B({})", a);
            this.a = a;
        }

        @PostConstruct
        public void init() {
            log.debug("init()");
        }
    }

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("a", A.class);
        context.registerBean("b", B.class);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.refresh();
        System.out.println();
    }
}
  • 示例2:用 ObjectProvider 延迟依赖对象的创建
java
public class App60_2 {

    static class A {
        private static final Logger log = LoggerFactory.getLogger("A");
        private ObjectProvider<B> b;

        public A(ObjectProvider<B> b) {
            log.debug("A({})", b);
            this.b = b;
        }

        @PostConstruct
        public void init() {
            log.debug("init()");
        }
    }

    static class B {
        private static final Logger log = LoggerFactory.getLogger("B");
        private A a;

        public B(A a) {
            log.debug("B({})", a);
            this.a = a;
        }

        @PostConstruct
        public void init() {
            log.debug("init()");
        }
    }

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("a", A.class);
        context.registerBean("b", B.class);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.refresh();

        System.out.println(context.getBean(A.class).b.getObject());
        System.out.println(context.getBean(B.class));
    }
}
  • 示例3:用 @Scope 产生代理
java
public class App60_3 {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context.getDefaultListableBeanFactory());
        scanner.scan("com.itheima.app60.sub");
        context.refresh();
        System.out.println();
    }
}
java
@Component
class A {
    private static final Logger log = LoggerFactory.getLogger("A");
    private B b;

    public A(B b) {
        log.debug("A(B b) {}", b.getClass());
        this.b = b;
    }

    @PostConstruct
    public void init() {
        log.debug("init()");
    }
}
java
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
class B {
    private static final Logger log = LoggerFactory.getLogger("B");
    private A a;

    public B(A a) {
        log.debug("B({})", a);
        this.a = a;
    }

    @PostConstruct
    public void init() {
        log.debug("init()");
    }
}
  • 示例4:用 Provider 接口解决,原理上与 ObjectProvider 一样,Provider 接口是独立的 jar 包,需要加入依赖
xml
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>
java
public class App60_4 {

    static class A {
        private static final Logger log = LoggerFactory.getLogger("A");
        private Provider<B> b;

        public A(Provider<B> b) {
            log.debug("A({}})", b);
            this.b = b;
        }

        @PostConstruct
        public void init() {
            log.debug("init()");
        }
    }

    static class B {
        private static final Logger log = LoggerFactory.getLogger("B");
        private A a;

        public B(A a) {
            log.debug("B({}})", a);
            this.a = a;
        }

        @PostConstruct
        public void init() {
            log.debug("init()");
        }
    }

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("a", A.class);
        context.registerBean("b", B.class);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.refresh();

        System.out.println(context.getBean(A.class).b.get());
        System.out.println(context.getBean(B.class));
    }
}

解决 set 循环依赖的原理

一级缓存

image-20210903100752165

作用是保证单例对象仅被创建一次

  • 第一次走 getBean("a") 流程后,最后会将成品 a 放入 singletonObjects 一级缓存
  • 后续再走 getBean("a") 流程时,先从一级缓存中找,这时已经有成品 a,就无需再次创建

一级缓存与循环依赖

image-20210903100914140

一级缓存无法解决循环依赖问题,分析如下

  • 无论是获取 bean a 还是获取 bean b,走的方法都是同一个 getBean 方法,假设先走 getBean("a")
  • 当 a 的实例对象创建,接下来执行 a.setB() 时,需要走 getBean("b") 流程,红色箭头 1
  • 当 b 的实例对象创建,接下来执行 b.setA() 时,又回到了 getBean("a") 的流程,红色箭头 2
  • 但此时 singletonObjects 一级缓存内没有成品的 a,陷入了死循环

二级缓存

image-20210903101849924

解决思路如下:

  • 再增加一个 singletonFactories 缓存
  • 在依赖注入前,即 a.setB() 以及 b.setA() 将 a 及 b 的半成品对象(未完成依赖注入和初始化)放入此缓存
  • 执行依赖注入时,先看看 singletonFactories 缓存中是否有半成品的对象,如果有拿来注入,顺利走完流程

对于上面的图

  • a = new A() 执行之后就会把这个半成品的 a 放入 singletonFactories 缓存,即 factories.put(a)
  • 接下来执行 a.setB(),走入 getBean("b") 流程,红色箭头 3
  • 这回再执行到 b.setA() 时,需要一个 a 对象,有没有呢?有!
  • factories.get() 在 singletonFactories 缓存中就可以找到,红色箭头 4 和 5
  • b 的流程能够顺利走完,将 b 成品放入 singletonObject 一级缓存,返回到 a 的依赖注入流程,红色箭头 6

二级缓存与创建代理

image-20210903103030877

二级缓存无法正确处理循环依赖并且包含有代理创建的场景,分析如下

  • spring 默认要求,在 a.init 完成之后才能创建代理 pa = proxy(a)
  • 由于 a 的代理创建时机靠后,在执行 factories.put(a) 向 singletonFactories 中放入的还是原始对象
  • 接下来箭头 3、4、5 这几步 b 对象拿到和注入的都是原始对象

三级缓存

image-20210903103628639

简单分析的话,只需要将代理的创建时机放在依赖注入之前即可,但 spring 仍然希望代理的创建时机在 init 之后,只有出现循环依赖时,才会将代理的创建时机提前。所以解决思路稍显复杂:

  • 图中 factories.put(fa) 放入的既不是原始对象,也不是代理对象而是工厂对象 fa
  • 当检查出发生循环依赖时,fa 的产品就是代理 pa,没有发生循环依赖,fa 的产品是原始对象 a
  • 假设出现了循环依赖,拿到了 singletonFactories 中的工厂对象,通过在依赖注入前获得了 pa,红色箭头 5
  • 这回 b.setA() 注入的就是代理对象,保证了正确性,红色箭头 7
  • 还需要把 pa 存入新加的 earlySingletonObjects 缓存,红色箭头 6
  • a.init 完成后,无需二次创建代理,从哪儿找到 pa 呢?earlySingletonObjects 已经缓存,蓝色箭头 9

当成品对象产生,放入 singletonObject 后,singletonFactories 和 earlySingletonObjects 就中的对象就没有用处,清除即可

4. Spring 事务失效

1. 抛出检查异常导致事务不能正确回滚

java
@Service
public class Service1 {

    @Autowired
    private AccountMapper accountMapper;

    @Transactional
    public void transfer(int from, int to, int amount) throws FileNotFoundException {
        int fromBalance = accountMapper.findBalanceBy(from);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            new FileInputStream("aaa");
            accountMapper.update(to, amount);
        }
    }
}
  • 原因:Spring 默认只会回滚非检查异常

  • 解法:配置 rollbackFor 属性

    • @Transactional(rollbackFor = Exception.class)

2. 业务方法内自己 try-catch 异常导致事务不能正确回滚

java
@Service
public class Service2 {

    @Autowired
    private AccountMapper accountMapper;

    @Transactional(rollbackFor = Exception.class)
    public void transfer(int from, int to, int amount)  {
        try {
            int fromBalance = accountMapper.findBalanceBy(from);
            if (fromBalance - amount >= 0) {
                accountMapper.update(from, -1 * amount);
                new FileInputStream("aaa");
                accountMapper.update(to, amount);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}
  • 原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉

  • 解法1:异常原样抛出

    • 在 catch 块添加 throw new RuntimeException(e);
  • 解法2:手动设置 TransactionStatus.setRollbackOnly()

    • 在 catch 块添加 TransactionInterceptor.currentTransactionStatus().setRollbackOnly();

3. aop 切面顺序导致导致事务不能正确回滚

java
@Service
public class Service3 {

    @Autowired
    private AccountMapper accountMapper;

    @Transactional(rollbackFor = Exception.class)
    public void transfer(int from, int to, int amount) throws FileNotFoundException {
        int fromBalance = accountMapper.findBalanceBy(from);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            new FileInputStream("aaa");
            accountMapper.update(to, amount);
        }
    }
}
java
@Aspect
public class MyAspect {
    @Around("execution(* transfer(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        LoggerUtils.get().debug("log:{}", pjp.getTarget());
        try {
            return pjp.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}
  • 原因:事务切面优先级最低,但如果自定义的切面优先级和他一样,则还是自定义切面在内层,这时若自定义切面没有正确抛出异常…

  • 解法1、2:同情况2 中的解法:1、2

  • 解法3:调整切面顺序,在 MyAspect 上添加 @Order(Ordered.LOWEST_PRECEDENCE - 1) (不推荐)

4. 非 public 方法导致的事务失效

java
@Service
public class Service4 {

    @Autowired
    private AccountMapper accountMapper;

    @Transactional
    void transfer(int from, int to, int amount) throws FileNotFoundException {
        int fromBalance = accountMapper.findBalanceBy(from);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            accountMapper.update(to, amount);
        }
    }
}
  • 原因:Spring 为方法创建代理、添加事务通知、前提条件都是该方法是 public 的

  • 解法1:改为 public 方法

  • 解法2:添加 bean 配置如下(不推荐)

java
@Bean
public TransactionAttributeSource transactionAttributeSource() {
    return new AnnotationTransactionAttributeSource(false);
}

5. 父子容器导致的事务失效

java
package day04.tx.app.service;

// ...

@Service
public class Service5 {

    @Autowired
    private AccountMapper accountMapper;

    @Transactional(rollbackFor = Exception.class)
    public void transfer(int from, int to, int amount) throws FileNotFoundException {
        int fromBalance = accountMapper.findBalanceBy(from);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            accountMapper.update(to, amount);
        }
    }
}

控制器类

java
package day04.tx.app.controller;

// ...

@Controller
public class AccountController {

    @Autowired
    public Service5 service;

    public void transfer(int from, int to, int amount) throws FileNotFoundException {
        service.transfer(from, to, amount);
    }
}

App 配置类

java
@Configuration
@ComponentScan("day04.tx.app.service")
@EnableTransactionManagement
// ...
public class AppConfig {
    // ... 有事务相关配置
}

Web 配置类

java
@Configuration
@ComponentScan("day04.tx.app")
// ...
public class WebConfig {
    // ... 无事务配置
}

现在配置了父子容器,WebConfig 对应子容器,AppConfig 对应父容器,发现事务依然失效

  • 原因:子容器扫描范围过大,把未加事务配置的 service 扫描进来

  • 解法1:各扫描各的,不要图简便

  • 解法2:不要用父子容器,所有 bean 放在同一容器

6. 调用本类方法导致传播行为失效

java
@Service
public class Service6 {

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void foo() throws FileNotFoundException {
        LoggerUtils.get().debug("foo");
        bar();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void bar() throws FileNotFoundException {
        LoggerUtils.get().debug("bar");
    }
}
  • 原因:本类方法调用不经过代理,因此无法增强

  • 解法1:依赖注入自己(代理)来调用

  • 解法2:通过 AopContext 拿到代理对象,来调用

  • 解法3:通过 CTW,LTW 实现功能增强

解法1

java
@Service
public class Service6 {

	@Autowired
	private Service6 proxy; // 本质上是一种循环依赖

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void foo() throws FileNotFoundException {
        LoggerUtils.get().debug("foo");
		System.out.println(proxy.getClass());
		proxy.bar();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void bar() throws FileNotFoundException {
        LoggerUtils.get().debug("bar");
    }
}

解法2,还需要在 AppConfig 上添加 @EnableAspectJAutoProxy(exposeProxy = true)

java
@Service
public class Service6 {
    
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void foo() throws FileNotFoundException {
        LoggerUtils.get().debug("foo");
        ((Service6) AopContext.currentProxy()).bar();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void bar() throws FileNotFoundException {
        LoggerUtils.get().debug("bar");
    }
}

7. @Transactional 没有保证原子行为

java
@Service
public class Service7 {

    private static final Logger logger = LoggerFactory.getLogger(Service7.class);

    @Autowired
    private AccountMapper accountMapper;

    @Transactional(rollbackFor = Exception.class)
    public void transfer(int from, int to, int amount) {
        int fromBalance = accountMapper.findBalanceBy(from);
        logger.debug("更新前查询余额为: {}", fromBalance);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            accountMapper.update(to, amount);
        }
    }

    public int findBalance(int accountNo) {
        return accountMapper.findBalanceBy(accountNo);
    }
}

上面的代码实际上是有 bug 的,假设 from 余额为 1000,两个线程都来转账 1000,可能会出现扣减为负数的情况

  • 原因:事务的原子性仅涵盖 insert、update、delete、select … for update 语句,select 方法并不阻塞
image-20210903120436365
  • 如上图所示,红色线程和蓝色线程的查询都发生在扣减之前,都以为自己有足够的余额做扣减

8. @Transactional 方法导致的 synchronized 失效

针对上面的问题,能否在方法上加 synchronized 锁来解决呢?

java
@Service
public class Service7 {

    private static final Logger logger = LoggerFactory.getLogger(Service7.class);

    @Autowired
    private AccountMapper accountMapper;

    @Transactional(rollbackFor = Exception.class)
    public synchronized void transfer(int from, int to, int amount) {
        int fromBalance = accountMapper.findBalanceBy(from);
        logger.debug("更新前查询余额为: {}", fromBalance);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            accountMapper.update(to, amount);
        }
    }

    public int findBalance(int accountNo) {
        return accountMapper.findBalanceBy(accountNo);
    }
}

答案是不行,原因如下:

  • synchronized 保证的仅是目标方法的原子性,环绕目标方法的还有 commit 等操作,它们并未处于 sync 块内
  • 可以参考下图发现,蓝色线程的查询只要在红色线程提交之前执行,那么依然会查询到有 1000 足够余额来转账

image-20210903120800185

  • 解法1:synchronized 范围应扩大至代理方法调用

  • 解法2:使用 select … for update 替换 select

5. Spring MVC 执行流程

概要

我把整个流程分成三个阶段

  • 准备阶段
  • 匹配阶段
  • 执行阶段

准备阶段

  1. 在 Web 容器第一次用到 DispatcherServlet 的时候,会创建其对象并执行 init 方法

  2. init 方法内会创建 Spring Web 容器,并调用容器 refresh 方法

  3. refresh 过程中会创建并初始化 SpringMVC 中的重要组件, 例如 MultipartResolver,HandlerMapping,HandlerAdapter,HandlerExceptionResolver、ViewResolver 等

  4. 容器初始化后,会将上一步初始化好的重要组件,赋值给 DispatcherServlet 的成员变量,留待后用

image-20210903140657163

匹配阶段

  1. 用户发送的请求统一到达前端控制器 DispatcherServlet

  2. DispatcherServlet 遍历所有 HandlerMapping ,找到与路径匹配的处理器

    ① HandlerMapping 有多个,每个 HandlerMapping 会返回不同的处理器对象,谁先匹配,返回谁的处理器。其中能识别 @RequestMapping 的优先级最高

    ② 对应 @RequestMapping 的处理器是 HandlerMethod,它包含了控制器对象和控制器方法信息

    ③ 其中路径与处理器的映射关系在 HandlerMapping 初始化时就会建立好

image-20210903141017502
  1. 将 HandlerMethod 连同匹配到的拦截器,生成调用链对象 HandlerExecutionChain 返回
image-20210903141124911
  1. 遍历HandlerAdapter 处理器适配器,找到能处理 HandlerMethod 的适配器对象,开始调用
image-20210903141204799

调用阶段

  1. 执行拦截器 preHandle
image-20210903141445870
  1. 由 HandlerAdapter 调用 HandlerMethod

    ① 调用前处理不同类型的参数

    ② 调用后处理不同类型的返回值

image-20210903141658199
  1. 第 2 步没有异常

    ① 返回 ModelAndView

    ② 执行拦截器 postHandle 方法

    ③ 解析视图,得到 View 对象,进行视图渲染

image-20210903141749830
  1. 第 2 步有异常,进入 HandlerExceptionResolver 异常处理流程
image-20210903141844185
  1. 最后都会执行拦截器的 afterCompletion 方法

  2. 如果控制器方法标注了 @ResponseBody 注解,则在第 2 步,就会生成 json 结果,并标记 ModelAndView 已处理,这样就不会执行第 3 步的视图渲染

6. Spring 注解

提示

  • 注解的详细列表请参考:面试题-spring-注解.xmind
  • 下面列出了视频中重点提及的注解,考虑到大部分注解同学们已经比较熟悉了,仅对个别的作简要说明

事务注解

  • @EnableTransactionManagement,会额外加载 4 个 bean
    • BeanFactoryTransactionAttributeSourceAdvisor 事务切面类
    • TransactionAttributeSource 用来解析事务属性
    • TransactionInterceptor 事务拦截器
    • TransactionalEventListenerFactory 事务监听器工厂
  • @Transactional

核心

  • @Order

切面

  • @EnableAspectJAutoProxy
    • 会加载 AnnotationAwareAspectJAutoProxyCreator,它是一个 bean 后处理器,用来创建代理
    • 如果没有配置 @EnableAspectJAutoProxy,又需要用到代理(如事务)则会使用 InfrastructureAdvisorAutoProxyCreator 这个 bean 后处理器

组件扫描与配置类

  • @Component

  • @Controller

  • @Service

  • @Repository

  • @ComponentScan

  • @Conditional

  • @Configuration

    • 配置类其实相当于一个工厂, 标注 @Bean 注解的方法相当于工厂方法
    • @Bean 不支持方法重载, 如果有多个重载方法, 仅有一个能入选为工厂方法
    • @Configuration 默认会为标注的类生成代理, 其目的是保证 @Bean 方法相互调用时, 仍然能保证其单例特性
    • @Configuration 中如果含有 BeanFactory 后处理器, 则实例工厂方法会导致 MyConfig 提前创建, 造成其依赖注入失败,解决方法是改用静态工厂方法或直接为 @Bean 的方法参数依赖注入, 针对 Mapper 扫描可以改用注解方式
  • @Bean

  • @Import

    • 四种用法

      ① 引入单个 bean

      ② 引入一个配置类

      ③ 通过 Selector 引入多个类

      ④ 通过 beanDefinition 注册器

    • 解析规则

      • 同一配置类中, @Import 先解析 @Bean 后解析
      • 同名定义, 默认后面解析的会覆盖前面解析的
      • 不允许覆盖的情况下, 如何能够让 MyConfig(主配置类) 的配置优先? (虽然覆盖方式能解决)
      • 采用 DeferredImportSelector,因为它最后工作, 可以简单认为先解析 @Bean, 再 Import
  • @Lazy

    • 加在类上,表示此类延迟实例化、初始化
    • 加在方法参数上,此参数会以代理方式注入
  • @PropertySource

依赖注入

  • @Autowired
  • @Qualifier
  • @Value

mvc mapping

  • @RequestMapping,可以派生多个注解如 @GetMapping 等

mvc rest

  • @RequestBody
  • @ResponseBody,组合 @Controller => @RestController
  • @ResponseStatus

mvc 统一处理

  • @ControllerAdvice,组合 @ResponseBody => @RestControllerAdvice
  • @ExceptionHandler

mvc 参数

  • @PathVariable

mvc ajax

  • @CrossOrigin

boot auto

  • @SpringBootApplication
  • @EnableAutoConfiguration
  • @SpringBootConfiguration

boot condition

  • @ConditionalOnClass,classpath 下存在某个 class 时,条件才成立
  • @ConditionalOnMissingBean,beanFactory 内不存在某个 bean 时,条件才成立
  • @ConditionalOnProperty,配置文件中存在某个 property(键、值)时,条件才成立

boot properties

  • @ConfigurationProperties,会将当前 bean 的属性与配置文件中的键值进行绑定
  • @EnableConfigurationProperties,会添加两个较为重要的 bean
    • ConfigurationPropertiesBindingPostProcessor,bean 后处理器,在 bean 初始化前调用下面的 binder
    • ConfigurationPropertiesBinder,真正执行绑定操作

7. SpringBoot 自动配置原理

自动配置原理

@SpringBootConfiguration 是一个组合注解,由 @ComponentScan、@EnableAutoConfiguration 和 @SpringBootConfiguration 组成

  1. @SpringBootConfiguration 与普通 @Configuration 相比,唯一区别是前者要求整个 app 中只出现一次

  2. @ComponentScan

    • excludeFilters - 用来在组件扫描时进行排除,也会排除自动配置类
  3. @EnableAutoConfiguration 也是一个组合注解,由下面注解组成

    • @AutoConfigurationPackage – 用来记住扫描的起始包
    • @Import(AutoConfigurationImportSelector.class) 用来加载 META-INF/spring.factories 中的自动配置类

为什么不使用 @Import 直接引入自动配置类

有两个原因:

  1. 让主配置类和自动配置类变成了强耦合,主配置类不应该知道有哪些从属配置
  2. 直接用 @Import(自动配置类.class),引入的配置解析优先级较高,自动配置类的解析应该在主配置没提供时作为默认配置

因此,采用了 @Import(AutoConfigurationImportSelector.class)

  • AutoConfigurationImportSelector.class 去读取 META-INF/spring.factories 中的自动配置类,实现了弱耦合。
  • 另外 AutoConfigurationImportSelector.class 实现了 DeferredImportSelector 接口,让自动配置的解析晚于主配置的解析

8. Spring 中的设计模式

1. Spring 中的 Singleton

请大家区分 singleton pattern 与 Spring 中的 singleton bean

  • 根据单例模式的目的 Ensure a class only has one instance, and provide a global point of access to it
  • 显然 Spring 中的 singleton bean 并非实现了单例模式,singleton bean 只能保证每个容器内,相同 id 的 bean 单实例
  • 当然 Spring 中也用到了单例模式,例如
    • org.springframework.transaction.TransactionDefinition#withDefaults
    • org.springframework.aop.TruePointcut#INSTANCE
    • org.springframework.aop.interceptor.ExposeInvocationInterceptor#ADVISOR
    • org.springframework.core.annotation.AnnotationAwareOrderComparator#INSTANCE
    • org.springframework.core.OrderComparator#INSTANCE

2. Spring 中的 Builder

定义 Separate the construction of a complex object from its representation so that the same construction process can create different representations

它的主要亮点有三处:

  1. 较为灵活的构建产品对象

  2. 在不执行最后 build 方法前,产品对象都不可用

  3. 构建过程采用链式调用,看起来比较爽

Spring 中体现 Builder 模式的地方:

  • org.springframework.beans.factory.support.BeanDefinitionBuilder

  • org.springframework.web.util.UriComponentsBuilder

  • org.springframework.http.ResponseEntity.HeadersBuilder

  • org.springframework.http.ResponseEntity.BodyBuilder

3. Spring 中的 Factory Method

定义 Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses

根据上面的定义,Spring 中的 ApplicationContext 与 BeanFactory 中的 getBean 都可以视为工厂方法,它隐藏了 bean (产品)的创建过程和具体实现

Spring 中其它工厂:

  • org.springframework.beans.factory.FactoryBean

  • @Bean 标注的静态方法及实例方法

  • ObjectFactory 及 ObjectProvider

前两种工厂主要封装第三方的 bean 的创建过程,后两种工厂可以推迟 bean 创建,解决循环依赖及单例注入多例等问题

4. Spring 中的 Adapter

定义 Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces

典型的实现有两处:

  • org.springframework.web.servlet.HandlerAdapter – 因为控制器实现有各种各样,比如有
    • 大家熟悉的 @RequestMapping 标注的控制器实现
    • 传统的基于 Controller 接口(不是 @Controller注解啊)的实现
    • 较新的基于 RouterFunction 接口的实现
    • 它们的处理方法都不一样,为了统一调用,必须适配为 HandlerAdapter 接口
  • org.springframework.beans.factory.support.DisposableBeanAdapter – 因为销毁方法多种多样,因此都要适配为 DisposableBean 来统一调用销毁方法

5. Spring 中的 Composite

定义 Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly

典型实现有:

  • org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
  • org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite
  • org.springframework.web.servlet.handler.HandlerExceptionResolverComposite
  • org.springframework.web.servlet.view.ViewResolverComposite

composite 对象的作用是,将分散的调用集中起来,统一调用入口,它的特征是,与具体干活的实现实现同一个接口,当调用 composite 对象的接口方法时,其实是委托具体干活的实现来完成

6. Spring 中的 Decorator

定义 Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality

典型实现:

  • org.springframework.web.util.ContentCachingRequestWrapper

7. Spring 中的 Proxy

定义 Provide a surrogate or placeholder for another object to control access to it

装饰器模式注重的是功能增强,避免子类继承方式进行功能扩展,而代理模式更注重控制目标的访问

典型实现:

  • org.springframework.aop.framework.JdkDynamicAopProxy
  • org.springframework.aop.framework.ObjenesisCglibAopProxy

8. Spring 中的 Chain of Responsibility

定义 Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it

典型实现:

  • org.springframework.web.servlet.HandlerInterceptor

9. Spring 中的 Observer

定义 Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically

典型实现:

  • org.springframework.context.ApplicationListener
  • org.springframework.context.event.ApplicationEventMulticaster
  • org.springframework.context.ApplicationEvent

10. Spring 中的 Strategy

定义 Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it

典型实现:

  • org.springframework.beans.factory.support.InstantiationStrategy
  • org.springframework.core.annotation.MergedAnnotations.SearchStrategy
  • org.springframework.boot.autoconfigure.condition.SearchStrategy

11. Spring 中的 Template Method

定义 Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure

典型实现:

  • 大部分以 Template 命名的类,如 JdbcTemplate,TransactionTemplate
  • 很多以 Abstract 命名的类,如 AbstractApplicationContext