afredlyj / mynote

idea and note
1 stars 0 forks source link

Spring相关 #11

Open afredlyj opened 8 years ago

afredlyj commented 8 years ago

Spring MVC 学习笔记

Spring MVC 初始化

要构建一个基于web容器的Spring MVC项目,需要关注一下Spring MVC的基本要素:

在web.xml中指定入口程序

    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>

    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>  
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

配置业务bean

通过DispatcherServletcontextConfigLocation指定核心配置文件xml的路径,这些bean的生命周期将由Spring容器管理。

编写控制层代码

业务代码示例如下:

@Controller
public class SimpleController {

    @RequestMapping("/simple")
    public @ResponseBody String simple() {
        return "Hello world!";
    }
}

DispatcherServlet

DispatcherServlet属于Spring MVC的核心模块,属于控制器的重要组成部分,另外一部分是开发的业务控制器。

默认配置

DispatcherServlet有一系列的默认配置bean,具体的列表可以参考DispatcherServlet.properties文件,需要注意的是,这些默认配置是可以覆盖的。

Root WebApplicationContext vs Servlet WebApplicationContext

在Spring中,ApplicationContext是有作用域的,每个DispatcherServlet都有自己的WebApplicationContext,称为Servlet WebApplicationContext,这些context都从Root WebApplicationContext继承而来,如果DispatcherServlet就有两个不同的context,如果只需要一个context,可以将DispatcherServlet的contextConfigLocation设为空。这两个context的关系,可以参考12

ContextLoaderListener

ContextLoaderListener用来启动和关闭Root WebApplicationContext。

DelegatingFilterProxy

http://jinnianshilongnian.iteye.com/blog/1752171

Shiro

AuthenticatingRealm

Shiro中Realm的子类,作用类似数据源。AuthenticatingRealm判断认证是否通过,即判断用户名和密码是否正确,需要由子类实现抽象方法doGetAuthenticationInfo

    public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        AuthenticationInfo info = getCachedAuthenticationInfo(token);
        if (info == null) {
            //otherwise not cached, perform the lookup:
            info = doGetAuthenticationInfo(token);
            log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
            if (token != null && info != null) {
                cacheAuthenticationInfoIfPossible(token, info);
            }
        } else {
            log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
        }

        if (info != null) {
            // 判断principals (身份)和credentials(证明)是否正确
            assertCredentialsMatch(token, info);
        } else {
            log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
        }

        return info;
    }

    // 抽象方法
    protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

doGetAuthenticationInfo方法根据传入的参数查询本地数据源的用户信息,本地数据源可以是文件、缓存或数据库,具体查询逻辑由子类实现,之后返回AuthenticationInfo对象。如果查询成功,则会调用CredentialsMatcher#doCredentialsMatch判断用户名&密码是否正确,Shiro内置了几个CredentialsMatcher,比如SimpleCredentialsMatcher

public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        Object tokenCredentials = getCredentials(token);
        Object accountCredentials = getCredentials(info);
        return equals(tokenCredentials, accountCredentials);
    }

简单地判断密码是否正确。

ShiroFilterFactoryBean

ShiroFilterFactoryBean对象用来创建SpringShiroFilter,各个属性解释如下:

Filter Name Class
anon org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.authc.UserFilter

拿登录过程来说,如果通过Filter 完成登录业务,FormAuthenticationFilter代码如下:

if (isLoginRequest(request, response)) {
       if (isLoginSubmission(request, response)) {
            if (log.isTraceEnabled()) {
                    log.trace("Login submission detected.  Attempting to execute login.");
               }
             return executeLogin(request, response);
        } else {
            if (log.isTraceEnabled()) {
                  log.trace("Login page view.");
              }
             //allow them to see the login page ;)
             return true;
            }
        }

    protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {
        return (request instanceof HttpServletRequest) && WebUtils.toHttp(request).getMethod().equalsIgnoreCase(POST_METHOD);
    }

当请求的url和loginUrl匹配时,如果当前是POST请求,则执行登录业务逻辑,比如验证密码和账号状态判断,否则直接穿透到下一个流程,进入SpringMVC。

注解@PostConstruct

@PostConstruct是Java EE 5引入的注解,Spring允许开发者在受管Bean中使用它。当DI容器实例化当前受管Bean时,@PostConstruct注解的方法会被自动触发,从而完成一些初始化工作,一个bean中只有一个方法能使用本注解。

afredlyj commented 8 years ago

Spring Bean的生命周期

有必要自己通过查看源码,辅助网友的分析资料,梳理Spring Bean的生命周期。下文的分析围绕下图:

image image

需要注意的是,以下分析的是lazyinit属性设为false,且单例的Bean(这个过程,称为预实例化),其他的Bean则会在getBean时才会初始化。两者的区别仅仅在于处罚的时间和场景不同,同样是调用容器的getBean方法。 Spring的ApplicationContext的启动入口在AbstractApplicationContext#refresh方法,接下来需要分析的是finishBeanFactoryInitialization方法,这个方法用于加载非lazy-init的单例bean:

    protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
        // Initialize conversion service for this context.
        if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
                beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
            beanFactory.setConversionService(
                    beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
        }

        // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
        String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
        for (String weaverAwareName : weaverAwareNames) {
            getBean(weaverAwareName);
        }

        // Stop using the temporary ClassLoader for type matching.
        beanFactory.setTempClassLoader(null);

        // Allow for caching all bean definition metadata, not expecting further changes.
        beanFactory.freezeConfiguration();

        // Instantiate all remaining (non-lazy-init) singletons.
        beanFactory.preInstantiateSingletons();
    }

重点在于beanFactory.preInstantiateSingletons(),在这个方法执行前,会调用freezeConfiguration冻结配置。Spring默认的BeanFactory是DefaultListableBeanFactory。在preInstantiateSingletons中,会遍历定义的beanDefinitionNames获取要加载的bean,加载之前会判断BeanDefinition是否符合加载要求,然后调用getBean方法:

for (String beanName : beanNames) {
    RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
    if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
        if (isFactoryBean(beanName)) {
            // 判断是否需要实例化

        } else {
            // 普通bean直接调用getBean方法
            getBean(beanName);
        }
    }
}

在实例化Bean前,会分析它依赖的Bean,然后调用createBean实例化,该方法是抽象方法,由子类AbstractAutowireCapableBeanFactory实现:


// AbstractBeanFactory.java
String[] dependsOn = mbd.getDependsOn();
            if (dependsOn != null) {
                for (String dependsOnBean : dependsOn) {
                    getBean(dependsOnBean);
                    registerDependentBean(dependsOnBean, beanName);
                }
            }

            // Create bean instance.
            if (mbd.isSingleton()) {
                sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                    public Object getObject() throws BeansException {
                        try {
                            return createBean(beanName, mbd, args);
                        }
                        catch (BeansException ex) {
                            // Explicitly remove instance from singleton cache: It might have been put there
                            // eagerly by the creation process, to allow for circular reference resolution.
                            // Also remove any beans that received a temporary reference to the bean.
                            destroySingleton(beanName);
                            throw ex;
                        }
                    }
                });
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            }

在实例化Bean时,如果该类由无参构造函数,则调用构造函数实例化,否则使用CGLIB。回到AbstractAutowireCapableBeanFactory#doCreateBean中,接下来会干两件事:装配依赖和Bean初始化:

    try {
            populateBean(beanName, mbd, instanceWrapper);
            if (exposedObject != null) {
                exposedObject = initializeBean(beanName, exposedObject, mbd);
            }
        }

Bean的初始化,如果Bean实现了相关的Aware接口,则会相继调用set方法,有BeanNameAware#setBeanNameBeanClassLoaderAware#setBeanClassLoaderBeanFactoryAware#setBeanFactory.

// AbstractAutowireCapableBeanFactory.java
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
        if (System.getSecurityManager() != null) {
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                public Object run() {
                    invokeAwareMethods(beanName, bean);
                    return null;
                }
            }, getAccessControlContext());
        }
        else {
            invokeAwareMethods(beanName, bean);
        }

        Object wrappedBean = bean;
        if (mbd == null || !mbd.isSynthetic()) {
            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
        }

        try {
            invokeInitMethods(beanName, wrappedBean, mbd);
        }
        catch (Throwable ex) {
            throw new BeanCreationException(
                    (mbd != null ? mbd.getResourceDescription() : null),
                    beanName, "Invocation of init method failed", ex);
        }

        if (mbd == null || !mbd.isSynthetic()) {
            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
        }
        return wrappedBean;
    }

private void invokeAwareMethods(final String beanName, final Object bean) {
        if (bean instanceof Aware) {
            if (bean instanceof BeanNameAware) {
                ((BeanNameAware) bean).setBeanName(beanName);
            }
            if (bean instanceof BeanClassLoaderAware) {
                ((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader());
            }
            if (bean instanceof BeanFactoryAware) {
                ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
            }
        }
    }

protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
            throws Throwable {

        boolean isInitializingBean = (bean instanceof InitializingBean);
        if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
            if (logger.isDebugEnabled()) {
                logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
            }
            if (System.getSecurityManager() != null) {
                try {
                    AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
                        public Object run() throws Exception {
                            ((InitializingBean) bean).afterPropertiesSet();
                            return null;
                        }
                    }, getAccessControlContext());
                }
                catch (PrivilegedActionException pae) {
                    throw pae.getException();
                }
            }
            else {
                ((InitializingBean) bean).afterPropertiesSet();
            }
        }

        if (mbd != null) {
            String initMethodName = mbd.getInitMethodName();
            if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
                    !mbd.isExternallyManagedInitMethod(initMethodName)) {
                invokeCustomInitMethod(beanName, bean, mbd);
            }
        }
    }

接着会看到invokeInitMethods方法的调用,该方法会调用InitializingBean#afterPropertiesSet方法,该方法同样是对Bean的回调,最后判断Bean是否配置了initMethod,如果有,则通过invokeCustomInitMethod直接调用,完成Bean的初始化。

总的来说,IOC容器中的Bean生命周期,包括如下几个阶段:

随后遍历BeanFactory中定义的BeanPostProcessor,在调用Bean初始化方法前,调用postProcessBeforeInitialization,初始化后调用postProcessAfterInitialization。 而初始化方法invokeInitMethods先执行InitializingBean#afterPropertiesSet,随后执行BeanDefinition#initMethodName。初始化过程完成之后,会注册Bean的销毁方法。

参考文档:

afredlyj commented 8 years ago

Spring 文档地址:http://docs.spring.io/autorepo/docs/spring/

afredlyj commented 8 years ago

Spring IOC 容器的初始化

IOC 的核心在于控制反转,但是反转的到底是什么? 答案是,依赖对象的获取被IOC容器反转了,之前需要对象自己实现获取依赖对象的逻辑,现在交由IOC容器管理,降低了面向对象系统的复杂性,相关模块解耦之后,系统更加灵活。

依赖反转也叫依赖注入,在具体的注入实现中,有三种主要的注入方式:

回到Spring,Spring抽象了BeanFactoryApplicationContext两个类(或接口)来代表IOC容器的表现形式,BeanFactory是基础的IOC容器,而后者在基础功能之上提供更多API。Spring 通过定义BeanDefinition来管理基于Spring的应用中的各种对象已经它们之间的相互依赖关系。

IOC 容器的初始化包括三块内容:

可以看到,Spring IOC容器的初始化都是围绕BeanDefinition,要注意的是,IOC容器的初始化一般不包含Bean的依赖注入。

Resource 的定位

Resource的定位是指BeanDefinition的资源定位,通过ResourceLoader来完成,Spring提供多种定位方式,与之对应的,有多种ResourceLoader,比如FileSystemResourceLoader

AbstractApplicationContext的入口方法在refresh,该方法会调用obtainFreshBeanFactory

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (logger.isDebugEnabled()) {
            logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
        }
        return beanFactory;
    }

refreshBeanFactory负责BeanDefinition的定位和载入。

BeanDefinition的载入

继续看上文的refresh方法,以FileSystemXmlApplicationContext为例,BeanDefinition的定位和载入都是由XmlBeanDefinitionReader完成的,省略掉Xml文件的解析代码,我们需要关注的是BeanDefinition怎么转化成容器的内部数据结构:

// XmlBeanDefinitionReader.java
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
            int validationMode = getValidationModeForResource(resource);
            Document doc = this.documentLoader.loadDocument(
                    inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
            return registerBeanDefinitions(doc, resource);
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (SAXParseException ex) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                    "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
        }
        catch (SAXException ex) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                    "XML document from " + resource + " is invalid", ex);
        }
        catch (ParserConfigurationException ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "Parser configuration exception parsing XML from " + resource, ex);
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "IOException parsing XML document from " + resource, ex);
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "Unexpected exception parsing XML document from " + resource, ex);
        }
    }

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        documentReader.setEnvironment(this.getEnvironment());
        int countBefore = getRegistry().getBeanDefinitionCount();
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

BeanDefinition的注册

注册过程相对简单,将BeanDefinition对象插入到beanDefinitionMap中:

//DefaultBeanDefinitionDocumentReader.java
    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                // Register the final decorated instance.
                // 注册
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            }
            catch (BeanDefinitionStoreException ex) {
                getReaderContext().error("Failed to register bean definition with name '" +
                        bdHolder.getBeanName() + "'", ele, ex);
            }
            // Send registration event.
            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
        }
    }
afredlyj commented 8 years ago

http://jinnianshilongnian.iteye.com/blog/1857189

afredlyj commented 8 years ago

Spring 注解

http://stackoverflow.com/questions/7414794/difference-between-contextannotation-config-vs-contextcomponent-scan

ComponentScanBeanDefinitionParser

afredlyj commented 7 years ago

Spring AOP

Spring AOP有几个基本概念:

官网上给出的概念:

  • Aspect: a modularization of a concern that cuts across multiple classes. Transaction management is a good example of a crosscutting concern in enterprise Java applications. In Spring AOP, aspects are implemented using regular classes (the schema-based approach) or regular classes annotated with the @Aspect annotation (the @AspectJ style).
  • Join point: a point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.
  • Advice: action taken by an aspect at a particular join point. Different types of advice include "around," "before" and "after" advice. (Advice types are discussed below.) Many AOP frameworks, including Spring, model an advice as an interceptor, maintaining a chain of interceptors around the join point.
  • Pointcut: a predicate that matches join points. Advice is associated with a pointcut expression and runs at any join point matched by the pointcut (for example, the execution of a method with a certain name). The concept of join points as matched by pointcut expressions is central to AOP, and Spring uses the AspectJ pointcut expression language by default.
  • Introduction: declaring additional methods or fields on behalf of a type. Spring AOP allows you to introduce new interfaces (and a corresponding implementation) to any advised object. For example, you could use an introduction to make a bean implement an IsModified interface, to simplify caching. (An introduction is known as an inter-type declaration in the AspectJ community.)
  • Target object: object being advised by one or more aspects. Also referred to as the advised object. Since Spring AOP is implemented using runtime proxies, this object will always be a proxied object.
  • AOP proxy: an object created by the AOP framework in order to implement the aspect contracts (advise method executions and so on). In the Spring Framework, an AOP proxy will be a JDK dynamic proxy or a CGLIB proxy.
  • Weaving: linking aspects with other application types or objects to create an advised object. This can be done at compile time (using the AspectJ compiler, for example), load time, or at runtime. Spring AOP, like other pure Java AOP frameworks, performs weaving at runtime.

Advice 通知

定义在连接点做什么,为切面增强提供织入接口,在Spring AOP中,它主要描述Spring AOP围绕方法调用而注入的切面行为。

具体的接口定义在org.aopalliance.aop.Advice

Pointcut 切点

决定Adice通知应该作用于哪个连接点,也就是说通过切点来定义需要增强的方法的集合,这些集合的选取可以按照一定的规则来完成。

Advisor 通知器

完成对目标方法的切面增加设计(Advice)和关注点的设计(Pointcut)以后,需要一个对象把它们结合起来,这个对象就是Advisor。通过Advisor,可以定义应该使用哪个通知并在哪个关注点使用它。

afredlyj commented 7 years ago

MethodMatcher

MethodMatcher 定义如下:

public interface MethodMatcher {

    boolean matches(Method m, Class targetClass);

    boolean isRuntime();

    boolean matches(Method m, Class targetClass, Object[] args);
}

只有当前两个方法都返回true时,第三个方法才会执行。如果第二个方法isRuntime返回false,则该MethodMatcher是静态的,这种MethodMatcher,第三个方法永远不会被调用。

Spring建议尽量使用静态的MethodMatcher,方便Spring 缓存Pointcut的评估结果。Spring定义了抽象类,用来描述这类对象:

/**
 * Convenient abstract superclass for static method matchers, which don't care
 * about arguments at runtime.
 */
public abstract class StaticMethodMatcher implements MethodMatcher {

    public final boolean isRuntime() {
        return false;
    }

    public final boolean matches(Method method, Class<?> targetClass, Object[] args) {
        // should never be invoked because isRuntime() returns false
        throw new UnsupportedOperationException("Illegal MethodMatcher usage");
    }

}

官方文档:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop-api.html http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop-api.html

afredlyj commented 7 years ago

Spring MVC @Value 解析失败

Spring @Value annotation in @Controller class not evaluating to value inside properties file

afredlyj commented 7 years ago

Spring AOP 在Controller中失败

http://mergetag.com/spring-aop-advice-on-annotated-controllers-2/