SSH框架-初识Spring

Author Avatar
Orange 4月 23, 2018
  • 在其它设备中阅读本文章

概述

最初的Java EE(现被Oracle公司改称Jakarta EE,真会玩😂)规范由Sun公司制定,包含JDBC、JNDI、JavaBean、Annotation、JPA、CDI(容器依赖注入)等多项重量级技术,其实现纷繁复杂。
而Spring框架轻量实现Java EE技术,封装诸如JDBC、JavaMail多项技术降低使用难度,且实现控制反转(IoC)模式与面向切面编程(AOP)的独特功能大大解放程序员双手!

IoC

IoC(控制反转,即对象控制权转交至容器)是一种全新的设计模式,通过DI(依赖注入,即自动注入被依赖属性)将对象的创建与维护转交IoC容器(或称工厂)控制。用户仅需编写(xml)配置文件无需 new 操作即可降低代码耦合度。
其原理类似工厂模式的反射机制 Class.forName(str).newInstance()

AOP

AOP(面向切面编程)是一种函数式编程的技术。通过预编译或动态代理插入执行业务无关片段,如日志记录、安全控制、性能统计。用户无需增添业务处理内容从而降低代码耦合度。
通俗地说,视业务代码为木板,视插入代码段为可重复使用的木楔,楔入行为就是面向切面编程。如下图所示
AOP概念图

  • 预编译指在编译过程中插入代码段,需要相关编译器,实现代表有AspectJ框架。
  • 动态代理指在执行过程中动态生成代理对象,无需特定编译器,实现代表分为JDK及CGLIB类库:

    • JDK采用反射机制实现动态代理,仅代理接口,使用方式如下

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      public interface Example {
      void doJob();
      }
      public class ExampleImpl implements Example {
      @Override
      public void doJob() {
      System.out.println("Running method: doJob()");
      }
      }
      public class ProxyHandler implements InvocationHandler {
      private Object proxiedObject;
      public ProxyHandler(Object proxiedObject) {
      this.proxiedObject = proxiedObject;
      }
      @Override
      public Object invoke(Object proxyObject, Method method, Object[] args) throws Throwable {
      //执行日志记录等功能
      System.out.println("Before method: " + method.getName());
      Obeject result = method.invoke(proxiedObject, args);
      System.out.println("After method: " + method.getName());
      return result;
      }
      }
      public class ProxyUtil {
      //该方法无需改动
      @SuppressWarnings("unchecked")
      public static <T> T getProxy(Object target) {
      ProxyHandler proxyHandler = new ProxyHandler(target);
      //第一个参数用于生成代理类,第二个参数用于指定代理接口集合,第三个参数用于实现代理对象方法
      T proxy = (T) Proxy.newProxyInstance(
      target.getClass().getClassLoader(),
      target.getClass().getInterfaces(),
      proxyHandler
      );
      return proxy;
      }
      }
      public class Test {
      public static void main(String[] args) {
      Example example = ProxyUtil.getProxy(new ExampleImpl());
      example.doJob();
      }
      }

      newProxyInstance 方法实现原理如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      public static Object newProxyInstance(ClassLoader classLoader, Class<?>[] intfs, InvocationHandler handler) throws IllegalArgumentException {
      Objects.requireNonNull(handler);
      final Class<?>[] interfaces = intfs.clone();
      //SecurityManager对象用于检测加载类与创建代理对象权限,子方法中均有判断(多此一举)
      final SecurityManager sm = System.getSecurityManager();
      if (sm != null)
      checkProxyAccess(Reflection.getCallerClass(), classLoader, interfaces);
      //创建继承于Proxy类(Proxy类含InvocationHandler类型属性供代理类调用)的代理类字节码并缓存
      Class<?> clazz = getProxyClass0(classLoader, interfaces);
      try {
      if (sm != null)
      checkNewProxyPermission(Reflection.getCallerClass(), clazz);
      //根据代理类获取构造函数对象并生成代理类实例
      final Constructor<?> constructor = clazz.getConstructor(constructorParams);
      final InvocationHandler finalHandler = handler;
      //允许代理私有类
      if (!Modifier.isPublic(clazz.getModifiers())) {
      AccessController.doPrivileged(new PrivilegedAction<Void>() {
      public Void run() {
      constructor.setAccessible(true);
      return null;
      }
      });
      }
      //传入参数handler(代理方法实现对象)反射出代理对象
      return constructor.newInstance(new Object[]{handler});
      } catch (IllegalAccessException|InstantiationException e) {
      throw new InternalError(e.toString(), e);
      } catch (InvocationTargetException e) {
      Throwable throwable = e.getCause();
      if (throwable instanceof RuntimeException)
      throw (RuntimeException) throwable;
      else
      throw new InternalError(throwable.toString(), throwable);
      } catch (NoSuchMethodException e) {
      throw new InternalError(e.toString(), e);
      }
      }
    • CGLIB采用asm(机器码操控框架)继承实现动态代理,可代理接口和类,使用方式如下

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      public interface Example {
      void doJob();
      }
      public class ExampleImpl implements Example {
      @Override
      public void doJob() {
      System.out.println("Running method: doJob()");
      }
      }
      public class ProxyMethodInterceptor implements MethodInterceptor {
      @Override
      //methodProxy为子类(代理类)方法的反射信息
      public Object intercept(Object proxiedObject, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
      //执行日志记录等功能
      System.out.println("Before method: " + method.getName());
      //使用invokeSuper方法调用父类(被代理类)的方法
      Obeject result = methodProxy.invokeSuper(proxiedObject, args);
      System.out.println("After method: " + method.getName());
      return result;
      }
      }
      public class ProxyUtil {
      //该方法无需改动
      @SuppressWarnings("unchecked")
      public static <T> T getProxy(Class<?> targetClazz) {
      Enhancer enhancer = new Enhancer();
      //用于生成被代理对象
      enhancer.setSuperclass(targetClazz);
      //用于实现代理对象方法
      enhancer.setCallback(new ProxyMethodInterceptor());
      T proxy = (T) enhancer.create();
      return proxy;
      }
      }
      public class Test {
      public static void main(String[] args) {
      Example example = ProxyUtil.getProxy(ExampleImpl.class);
      example.doJob();
      }
      }

Spring简史

Spring思想源于2002年,旨在开发一套通用框架使开发者聚焦业务逻辑而无需考虑对象构建和传递问题。次年,Spring框架诞生,名称寓意为J2EE的严冬带来春天。随后数年,该框架迅速发展,陆续支持xml配置、注解配置、嵌入式数据库、模块化等新特性。如今已成为一款炙手可热的Java web开发框架。同时,为解决配置纷繁问题,开发团队贴心推出包含默认配置与web容器(如Tomcat)的Spring Boot项目模板;为解决各包依赖问题,开发团队贴心推出自动控制各依赖包版本的Spring IO项目模板。以供快速开发微服务web应用。

Spring原理

IoC实现

Spring框架视所有对象为Bean(此bean非彼JavaBean,无任何编写限制),所有Bean全权交由Bean容器(BeanFactory接口实现类)管理,开发者只需配置Bean定义供Spring识别并自动进行构造函数参数注入和属性设值注入。欲取所需Bean只需调用Spring应用上下文(ApplicationContext接口实现类AbstractApplicationContext抽象类的子类)getBean方法即可。

Spring框架启动流程如下:

  1. Spring应用上下文调用抽象父类AbstractApplicationContext构造方法配置环境。
  2. Spring应用上下文调用父类方法存储Bean定义文件路径。
  3. Spring应用上下文调用抽象父类AbstractApplicationContext的refresh方法:
    1. 于方法中新建Bean容器(BeanFactory接口实现类)对象。
    2. Spring应用上下文新建BeanDefinitionReader接口实现类对象并传入Bean容器(BeanFactory接口实现类)对象为registry属性赋值。
    3. BeanDefinitionReader接口实现类对象通过Bean定义文件路径字段读取Bean定义文件并调用工具类(BeanDefinitionReaderUtils)的registerBeanDefinition静态方法存储所有Bean定义(BeanDefinition)至Bean容器registry的Bean定义集合(beanDefinitionMap)属性。
    4. 于方法中新建Bean容器后置处理器(BeanFactoryPostProcessor接口实现类)并调用。
    5. 于方法中注册Bean后置处理器(BeanPostProcessor接口实现类)于Bean创建时触发。
  4. 在需要时调用Bean容器(BeanFactory接口实现类)对象的getBean方法获取缓存Bean对象或反射指定Bean定义(BeanDefinition)对象的类型属性获取Bean对象。

下图为Spring通过读取xml类型的Bean定义文件加载Bean定义的启动流程:

Spring通过读取xml文件加载Bean定义流程

图注:从上往下方法并列执行,从左往右方法层层递进;绿色方法块实现接口方法,蓝色方法块实现抽象方法。

Spring框架装配Bean流程如下:

  1. 反射指定Bean定义(BeanDefinition)对象的类型属性并传入构造参数配置实例化Bean对象。
  2. 调用applyProrertyValues方法读取propertyValueList设置指定属性值。
  3. 若该Bean为BeanNameAware接口实现类,则调用其setBeanName方法设置名称编号属性。
  4. 若该Bean为BeanFactoryAware接口实现类,则调用其setBeanFactory方法设置Bean容器属性。
  5. 若该Bean为ApplicationContextAware接口实现类,则调用其setApplicationContext方法设置Spring应用上下文属性。
  6. 调用BeanPostProcessor接口实现类对象的前初始化postProcessBeforeInitialization方法。
  7. 若该Bean为InitializingBean接口实现类,则调用其afterPropertiesSet方法。
  8. 调用该Bean指定的init-method方法。
  9. 调用BeanPostProcessor接口实现类(如AOP实现类AnnotationAwareAspecJAutoProxyCreator)对象的后初始化postProcessAfterInitialization方法。

AOP实现

进行AOP的切入行为时需考虑三大要素:切入位置、切入时机与切入内容。

切入位置切入时机合称连接点(JoinPoint)。连接点分为三类:位于属性的存取时机、位于方法的调用时机、位于方法的异常抛出时机。Spring框架仅支持位于方法的连接点。

Spring框架提供以下接口供开发者进行AOP切入行为:

  • 切入点(Pointcut)接口可细微指定欲切入类与方法(即通过语法规则指定切入位置集合)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    //类过滤器
    public interface ClassFilter {
    //提供一个默认匹配所有类的类过滤器
    ClassFilter TRUE = TrueClassFilter.INSTANCE;
    //过滤匹配类
    boolean matches(Class<?> clazz);
    }
    //方法过滤器
    public interface MethodMatcher {
    //提供一个默认匹配所有方法的方法过滤器
    MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
    //过滤匹配方法
    boolean matches(Method method, Class<?> targetClass);
    //过滤匹配运行时特定参数方法
    boolean matches(Method method, Class<?> targetClass, Object... args);
    //是否运行时过滤
    boolean isRuntime();
    }
    //切入点
    public interface Pointcut {
    //提供一个默认匹配所有类的所有方法的切入点
    Pointcut TRUE = TruePointcut.INSTANCE;
    ClassFilter getClassFilter();
    MethodMatcher getMethodMatcher();
    }
  • 通知(Advice)接口可指定切入时机切入内容,根据切入行为不同分为两种:

    • 引入(Introduction)行为通知:向类引入新方法,此种通知是继承于通知接口的拦截器接口实现类。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      public interface Advice {
      }
      public interface Interceptor extends Advice {
      }
      public abstract class DelegatingIntroductionInterceptor implements Interceptor {
      Object invock(Method method, Object[] params, Object obj) throws Exception;
      }
      public class ExampleInterceptor extends DelegatingIntroductionInterceptor implements Example {
      void doJob() {
      System.out.println("doJob");
      }
      }

      开发者只需定义接口实现类子类便可在类中定义切入方法,如记录日志,代码如下

      1
      2
      3
      4
      5
      6
      7
      public LogBeforeAdvice implements MethodBeforeAdvice {
      @Override
      public void before(Method method, Object[] args, Object proxiedObject) throws Throwable
      {
      Logger.getGlobal().log(Level.INFO, "Before method: " + method.getName());
      }
      }
    • 织入(Weaving)行为通知:向方法织入更多内容,共分五种

      1. 前置通知:切入点之前执行(可抛出异常阻断进入切入点)
      2. 后置通知:切入点之后执行
      3. 返回通知:切入点执行成功之后执行
      4. 异常通知:切入点执行异常之后执行
      5. 环绕通知:切入点执行前后执行(可阻止进入切入点)
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        public interface Advice {
        }
        public interface BeforeAdvice extends Advice {
        }
        public interface AfterAdvice extends Advice {
        }
        //前置通知
        public interface MethodBeforeAdvice extends BeforeAdvice {
        void before(Method method, Object[] args, Object proxiedObject) throws Throwable;
        }
        //后置通知
        //Spring原生(即不含AspectJ)不支持此种通知,直接使用将抛出UnknownAdviceTypeException异常
        public interface AfterAdvice {
        }
        //返回通知
        public interface AfterReturningAdvice extends AfterAdvice {
        void afterReturning(Object returnValue, Method method, Object[] args, Object proxiedObject) throws Throwable;
        }
        //异常通知
        public interface ThrowsAdvice extends AfterAdvice {
        //该接口并未定义方法,但其实现类须任选以下方法形式实现,以供Spring反射调用
        //可多次重载方法,指定详细ex类型(如IOException、NullPointException等)进行分类
        //public void afterThrowing(Exception ex);
        //public void afterThrowing(Method method, Object[] args, Object target, Exception ex);
        }

      开发者只需定义接口实现类便可在方法中定义切入内容,如记录日志,代码如下

      1
      2
      3
      4
      5
      6
      7
      public LogBeforeAdvice implements MethodBeforeAdvice {
      @Override
      public void before(Method method, Object[] args, Object proxiedObject) throws Throwable
      {
      Logger.getGlobal().log(Level.INFO, "Before method: " + method.getName());
      }
      }

Spring框架提供代理工厂类(ProxyFactory类)封装动态代理,根据接口有无选择JDK或CGLIB代理方式。欲生成代理对象,需加入通知(Advice接口实现类)对象并调用代理生成类实例的getProxy方法即可,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public interface Example {
void doJob();
}
public class ExampleImpl implements Example {
@Override
public void doJob() {
System.out.println("Running method: doJob()");
}
}
//使用MethodInterceptor接口亦可达到同样的环绕通知效果
public class ExampleAdvice implements MethodBeforeAdvice, AfterReturningAdvice {
@Override
public void before(Method method, Object[] args, Object proxiedObject) throws Throwable {
System.out.println("Before method: " + method.getName());
}
@Override
public void AfterReturning(Obejct result, Method method, Object[] args, Object proxiedObject) throws Throwable {
System.out.println("After method: " + method.getName());
}
}
public class Test {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new ExampleImpl());
//如同拦截器可加入多个通知对象(实际上拦截器接口便是继承于Advice接口实现)
proxyFactory.addAdvice(new ExampleAdvice());
Example example = (Example)proxyFactory.getProxy();
example.doJob();
}
}

仅有切入点无法控制切入时机并添加切入内容;仅有通知无法指定切入位置。故Spring框架提供增强(Advisor)将两者结合方便开发者实际调用。

Advisor接口仅可获取通知(Advice),代理将作用于所有位置(所有类的所有方法)。其子类接口PointcutAdvisor另可获取切入点,故能指定切入位置,广为使用。

PointcutAdvisor接口主要有六个具体实现类:

  1. DefaultPointcutAdvisor:切入点为Pointcut,即自定义切入点类型。默认匹配所有类的所有方法
  2. NameMatchMethodPointcutAdvisor:切入点为NameMatchMethodPointcut,即按方法名匹配所有类的部分方法
  3. RegexpMethodPointcutAdvisor:切入点为RegexpMethodPointcut,即按正则表达式匹配部分类的部分方法
  4. StaticMethodMatcherPointcutAdvisor:切入点为StaticMethodMatcherPointcut,即按方法名或正则表达式匹配所有(或部分)类的部分静态方法。默认匹配所有类
  5. AspecJExpressionPointcutAdvisor:切入点为AspecJExpressionPointcut,即按AspecJ切点表达式匹配部分类的部分方法
  6. AspecJPointcutAdvisor:切入点为ComposablePointcut,即按AspecJ切点表达式匹配相反部分类的部分方法

实际在如上代码中,addAdvice方法会将传入的通知对象重新封装为DefaultPointcutAdvisor对象,匹配所有类与方法。故代理工厂类(ProxyFactory类)也可通过addAdvisor方法传入增强(Advisor接口实现类)对象。

如下所示:

1
2
3
4
5
6
7
8
9
public interface Example {
void doJob();
}
public class ExampleImpl implements Example {
@Override
public void doJob() {
System.out.println("Running method: doJob()");
}
}

Spring使用

环境配置

1 . 创建Spring应用上下文启动Spring框架

  • 普通Java应用
    导入与Spring相关的Jar开发包:spring-core.jar、spring-beans.jar、spring-jcl.jar(日志统计)。
    创建Spring应用上下文读取Bean定义,分为文件读取、注解读取以及代码定义三种方式:

    • 文件读取

      配置文件路径字段前缀规则:

      • http::远程文件路径
      • file::根目录为项目相对路径或文件绝对路径
      • classpath::根目录为类相对路径(源码工程的src目录,编译项目的classes目录),匹配单次且忽略jar包
      • classpath*::根目录为类相对路径(源码工程的src目录,编译项目的classes目录),匹配所有且含jar包内容
      • FileSystemXmlApplicationContext:默认使用file:前缀路径,读取xml文件。
      • ClassPathXmlApplicationContext:默认使用classpath:前缀路径,读取xml文件。
      • GenericGroovyApplicationContext:读取Groovy文件。
      • GenericXmlApplicationContext:自定义路径规则以及配置解析方式。
    • 注解读取
      • AnnotationApplicationContext:读取@Configuration注解Java类。
    • 代码定义
      • GenericApplicationContext:自定义环境、Bean容器。
      • ResourceAdapterApplicationContext:自定义环境、Bean容器。
      • StaticApplicationContext:仅允许调用registerXXX方法定义Bean,用于测试。
        1
        2
        3
        4
        5
        6
        //通过文件读取配置创建Spring应用上下文方式
        ApplicationContext context = new XXXApplicationContext("configFilePath");
        //通过注解读取配置创建Spring应用上下文方式
        ApplicationContext context = new AnnotationApplicationContext(ConfigClass.class);
        //通过代码定义配置创建Spring应用上下文方式
        ApplicationContext context = new XXXApplicationContext();
  • Java web应用
    导入与Spring相关的Jar开发包:spring-core.jar、spring-beans.jar、spring-jcl.jar(日志统计)、spring-web.jar

    • WebXmlApplicationContext:默认根目录为web目录路径,读取xml文件。

      Spring框架已提供上下文载入监听器(ServletContextListener接口实现类ContextLoaderListener)供web容器启动时调用contextInitialized方法创建WebXmlApplicationContext对象并存入web容器上下文(ServletContext)中。使用时调用工具类(WebApplicationContextUtils)的getWebApplicationContext静态方法即可。配置web.xml,加入Spring启动监听器(listener),配置如下:

1
2
3
4
5
6
7
8
9
10
11
<web-app ...>
...
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<!-- 默认值为/WEB-INF/applicationContext.xml -->
<param-value>classpath:spring-*.xml</param-value>
</context-param>
</web-app>
  • Java 测试应用
    导入与Spring相关的Jar开发包:spring-core.jar、spring-beans.jar、spring-jcl.jar(日志统计)。导入Junit相关的Jar开发包:junit-4.12.jar。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    //使用SpringJUnit4ClassRunner类运行
    @RunWith(SpringJUnit4ClassRunner.class)
    //仅支持测试环境的配置文件读取(locations属性用于读取文件,classes属性用于读取@Configuration注解类)
    @ContextConfiguration(locations = {"classpath:spring-*.xml"})
    //配置测试监听器(DependencyInjectionTestExecutionListener用于识别@Autowired等注解自动注入,TransactionalTestExecutionListener用于识别@Transactional注解开启事务,以上监听器已默认开启)
    @TestExecutionListeners({DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
    public class ApplicationTest {
    ...
    }

2 . 创建Bean定义表spring-beans.xml文件,该文件用于配置全局属性、Bean以及Aspect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<!--属性default-init-method对应全局Bean默认构造后自动调用的方法名-->
<!--属性default-destroy-method对应全局Bean默认销毁后自动调用的方法名-->
<!--属性default-autowire-candidate对应正则表达式(regex)匹配的Bean不作为属性被自动注入-->
<!--属性default-autowire对应全局Bean默认自动注入方式-->
<!--分为no、byName、byType、constructor四种取值,no为默认值,即不执行自动注入,byName即根据属性名注入,byType即根据属性类型注入,constructor即根据属性类型对构造函数参数注入-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
default-init-method="initMethodName"
default-destroy-method="destroyMathodName"
default-autowire-candidate="regex"
default-autowire="byType">

</beans>

spring-beans.xml

IoC相关标签

beans

beans标签用于囊括所有bean标签,例如

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<!--添加beans默认命名空间,语法规则详见XML Shema规范-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
...
</beans>

bean

bean标签用于定义名字对应的Bean类,通过调用Spring应用上下文(ApplicationContext接口实现类)对象的getBean("id|name")方法即可获得idname对应Bean实例,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!--属性id对应Bean的身份,仅可设置一个-->
<!--属性name对应Bean的名称,可设置多个(即具有别名)-->
<!--属性class对应Bean的类名-->
<!--属性autowire-candidate对应是否允许该Bean作为属性被自动注入-->
<!--属性lazy-init对应Bean是否懒加载(使用时才创建)-->
<!--属性init-method对应Bean构造后自动调用的方法名-->
<!--属性destroy-method对应Bean销毁前自动调用的方法名-->
<!--属性scope对应Bean的缓存方式-->
<!--分为singleton、prototype、session、request四种取值,singleton为默认值,即单例缓存,prototype即不缓存,造成destroy方法失效,session即每个会话各自缓存,request即每个请求各自缓存-->
<!--属性autowire对应Bean自动注入方式-->
<!--分为no、byName、byType、constructor四种取值,no为默认值,即不执行自动注入,byName即根据属性名注入,byType即根据属性类型注入,constructor即根据属性类型对构造函数参数注入-->
<bean
id="identifier1"
name="name1,name2,name3"
class="com.example.bean.Example1"
autowire-candidate="true"
lazy-init="false"
init-method="initMethodName"
destroy-method="destroyMethodName"
scope="singleton"
autowire="byType"/>

constructor-arg

constructor-arg标签用于定义Bean时进行构造注入,例如

1
2
3
4
5
6
7
8
<bean id="identifier1" class="com.example.bean.Example1">
<!--属性name对应属性名-->
<!--属性value对应基本类型值-->
<!--属性ref对应Bean的id或name-->
<constructor-arg name="constructorArg1" value="1"/>
<constructor-arg name="constructorArg2" ref="identifier2"/>
</bean>
<bean id="identifier2" class="com.example.bean.Example2"/>

property

property标签用于定义Bean时进行设值注入,例如

1
2
3
4
5
6
7
8
<bean id="identifier1" class="com.example.bean.Example1">
<!--属性name对应属性名-->
<!--属性value对应基本类型值-->
<!--属性ref对应Bean的id或name-->
<property name="property1" value="1"/>
<property name="property2" ref="identifier2"/>
</bean>
<bean id="identifier2" class="com.example.bean.Example2"/>

alias

property标签用于定义Bean的别称,例如

1
2
3
4
<bean id="identifier1" class="com.example.bean.Example1"/>
<!--属性name对应真实bean身份-->
<!--属性alias对应别称-->
<alias name="identifier1" alias="alias1"/>

context:property-placeholder

context:property-placeholder标签用于读取property键值对形式配置文件,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<!--添加context命名空间,语法规则详见XML Shema规范-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--属性location对应property键值对形式配置文件路径-->
<context:property-placeholder location="classpath:config.properties"/>
<bean class="com.example.bean.User">
<!--通过${name}读取键值name对应内容-->
<property name="name" value="${name}"/>
</bean>
</beans>

AOP相关标签

aop:config

aop:config标签用于囊括所有AOP标签,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<!--添加aop命名空间,语法规则详见XML Shema规范-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:config>
...
</aop:config>
</beans>

aop:aspect

aop:aspect标签用于定义名字对应的(Aspect)切面类(即实现AOP功能的Bean类),通过调用Spring应用上下文(ApplicationContext接口实现类)对象的getBean("id")方法即可获得id对应Aspect实例,例如

1
2
3
4
5
6
<aop:config>
<!--属性id对应Aspect的身份-->
<!--属性ref对应切面Bean的id或name-->
<aop:aspect id="aspect1" ref="identifier1">
</aop:config>
<bean id="identifier1" class="com.example.bean.Example1"/>

aop:pointcut

注解

注解原理

首先了解一下注解识别原理。为识别类内注解,需“激活”如下Spring框架提供的内置BeanPostProcessor接口实现类:

1
2
3
4
5
6
7
8
<!--识别@Autowired(自动注入)注解-->
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
<!--识别@Required(必须注入)注解-->
<bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/>
<!--识别@PersistenceContext注解(主要用于数据库实体持久化)-->
<bean class="org.springframework.beans.factory.annotation.PersistenceAnnotationBeanPostProcessor"/>
<!--识别@Resource(加载资源)、@PostConstruct(构造后执行)、@PreDestroy(销毁前执行)等注解-->
<bean class="org.springframework.beans.factory.annotation.CommonAnnotationBeanPostProcessor"/>

鉴于定义复杂,故而提供如下简写方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<!--添加context命名空间,语法规则详见XML Shema规范-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--context:annotation-config标签用于识别注解-->
<context:annotation-config/>
</beans>

又因使用注解的类路径须指定,故提供包含识别注解的类文件扫描标签,写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<!--添加context命名空间,语法规则详见XML Shema规范-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--context:component-scan标签用于识别注解并扫描类进行转配-->
<!--属性base-package对应扫描包路径-->
<!--属性use-default-filters对应是否使用默认过滤器-->
<!--分为true、false两种取值,true为默认值,即识别@Service(业务逻辑层)、@Controller(web表现层)、@Repository(异常捕获持久层)等@Component(基本单例Bean)及其子类注解,false即使用自定义过滤器-->
<context:component-scan base-package="com.example.bean" use-default-filters="false">
<!--context:include-filter标签用于包含该注解修饰类-->
<!--属性type对应过滤类型(annotation即注解)-->
<!--属性expression对应过滤内容(此处包含Service注解类)-->
<context:include-filter type="annotation" expression="com.alibaba.dubbo.config.annotation.Service"/>
<!--context:exclude-filter标签用于排除该注解修饰类-->
<!--属性type对应过滤类型(annotation即注解)-->
<!--属性expression对应过滤内容(此处排除Controller注解类)-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>

经过如此波折,终于隆重拉开注解时代的崭新帷幕☆(*≧▽≦)ツ。

IoC相关注解

除某些表示类的注解外,适用于属性的注解既可置于属性上,也可置于对应get或set方法上,建议置于方法上

@Configuration

@Configuration注解(如同beans标签)用于表示类为配置类,囊括所有bean定义,例如

1
2
3
4
@Configuration
public class AppConfig {
...
}

@ComponentScan

@ComponentScan注解(如同context:component-scan标签)用于识别注解并扫描类进行装配,例如

1
2
3
4
5
6
@Configuration
//扫描com.example.bean包下所有类
@ComponentScan("com.example.bean")
public class AppConfig {
...
}

@Import

@Import注解用于导入指定包含@Configuration注解的配置类(测试环境仅支持@ContextConfiguration注解,该注解无效),例如

1
2
3
4
5
6
7
8
9
@Configuration
@Import("com.example.bean.AppConfig2")
public class AppConfig {
...
}
@Configuration
public class AppConfig2 {
...
}

@ImportResource

@ImportResource注解用于导入指定配置文件(测试环境仅支持@ContextConfiguration注解,该注解无效),例如

1
2
3
4
5
@Configuration
@ImportResource("classpath:spring-*.xml")
public class AppConfig {
...
}

1
2
3
4
5
6
7
8
9
<!--spring-config.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
...
</beans>

@PropertySource

@PropertySource注解(如同context:property-placeholder标签)用于读取property键值对形式配置文件

@Value

@Value注解用于表示将属性赋值为键值对应内容,配合@PropertySource注解使用,例如

1
2
//config.properties
name=小明

1
2
3
4
5
6
7
8
@Component
@PropertySource("classpath:config.properties")
public class User {
//读取键值name对应内容(小明)赋值到name属性
@Value("${name}")
private String name;
...
}

@Bean

@Bean注解(如同bean标签)用于定义bean的获取及创建方式,仅在@Configuration注解类中使用时生成代理对象,例如

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class AppConfig {
...
//属性name对应Bean的名称,默认为函数名
//属性initMethod对应Bean构造后自动调用的方法名
//属性destroyMethod对应Bean销毁前自动调用的方法名
@Bean(name = "id", initMethod = "init", destroyMethod = "destroy")
public void createUser() {
return new User();
}
}

@Component

@Component注解用于表示类为基本单例Bean类,例如

1
2
3
4
@Component
public class User {
...
}

@Service

@Service注解用于表示类为业务逻辑处理类,例如

1
2
3
4
@Service
public class UserServiceImpl {
...
}

@Controller

@Controller注解用于表示类为web表现控制类,例如

1
2
3
4
@Controller
public class UserController {
...
}

@Repository

@Repository注解用于表示类为异常捕获持久层数据库操作类,例如

1
2
3
4
@Repository
public class UserDAO {
...
}

@Order

@Order注解用于设置Bean构建顺序,值越小构建优先级越高,默认值为Integer.MAX_VALUE,例如

1
2
3
4
5
6
7
8
9
10
11
12
@Component
//Class类优于Student类构建
@Order(1)
public class Class {
...
}
@Component
//Class类优于Student类构建
@Order(2)
public class Student {
...
}

@Scope

@Scope注解(如同bean标签的scope属性)用于设置Bean的缓存方式,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
//属性value对应Bean的缓存方式,默认为singleton单例模式,prototype表示普通模式(即无缓存)
//属性proxyMode对应Bean的代理方式
//分为DEFAULT、NO、INTERFACES、TARGET_CLASS四种取值,DEFAULT为默认值,即NO,NO即无代理,INTERFACES即使用JDK代理接口,TARGET_CLASS即使用CGLIB代理类
//当无缓存(scope为prototype)的Bean作为其他单例(scope为singleton)的Bean的属性时,需要设置proxyMode为INTERFACES或TARGET_CLASS生成代理类,否则无缓存属性值将在单例Bean首次属性注入时固定
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Class {
...
}
@Component
//单例模式创建
@Scope("singleton")
public class Student {
@Autowired
//由于oroxyMode的设定,每次取值均不相同
private Class classA;
...
}

@PostConstruct

@PostConstruct注解(如同bean标签的init-method属性)用于表示方法为Bean构造后自动调用的方法,例如

1
2
3
4
5
6
7
@Component
public class UserDAO {
...
@PostConstruct
public void init() {
}
}

@PreDestroy

@PreDestroy注解(如同bean标签的destroy-method属性)用于表示方法为Bean销毁前自动调用的方法,例如

1
2
3
4
5
6
7
@Component
public class UserDAO {
...
@PreDestroy
public void destroy() {
}
}

@Autowired

@Autowired注解用于表示属性由Bean容器根据类型自动注入(含构造注入、赋值注入两种方式),该注解由Spring框架提供,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class Class {
...
}
@Component
public class Student {
private Class classA;

//属性required对应是否允许空指针
//分为true、false两种取值,true为默认值,即禁止空指针,false即允许空指针
@Autowired(required = false)
public void setClassA(Class classA) { this.classA = classA; }
...
}

值得注意的是,当注入对象为列表(或Map)时,会将泛型所有实现对象(及其名称)存入其中,例如

1
2
3
public interface Class {
...
}

1
2
3
4
@Component
public class ClassSub1 implements Class {
...
}
1
2
3
4
@Component
public class ClassSub2 implements Class {
...
}
1
2
3
4
5
6
7
8
9
@Component
public class Student {
private List<Class> classes;

//@Autowired注解根据类型查找到两个Class接口实现对象(ClassSub1对象、ClassSub2对象),自动注入至列表中
@Autowired
public void setClasses(List<Class> classes) { this.classes = classes; }
...
}

@Inject

@Inject注解用于表示属性由Bean容器根据类型自动注入(含构造注入、赋值注入两种方式),该注解由Java依赖注入标准(JSR-330)提供,例如

1
2
3
4
@Component
public class Class {
...
}

1
2
3
4
5
6
7
8
@Component
public class Student {
private Class classA;

@Inject
public void setClassA(Class classA) { this.classA = classA; }
...
}

@Resource

@Resource注解用于表示属性由Bean容器根据名称自动注入(含构造注入、赋值注入两种方式),该注解由Java依赖注入标准(JSR-250)提供,例如

1
2
3
4
@Component("A班")
public class Class {
...
}

1
2
3
4
5
6
7
8
@Component
public class Student {
private Class classA;

@Resource("A班")
public void setClassA(Class classA) { this.classA = classA; }
...
}

@Qualifier

@Qualifier(限定符)注解用于表示属性指定为根据名称注入,该注解配合自动注入注解(@Autowired、@Inject、@Resource)使用,例如

1
2
3
4
5
6
7
public interface Class {
...
}
@Component
public class ClassSub1 implements Class {
...
}

1
2
3
4
@Component
public class ClassSub2 implements Class {
...
}
1
2
3
4
5
6
7
8
9
10
11
@Component
public class Student {
private Class classA;

//@Autowired注解根据类型查找到两个Class接口实现对象,无法自动注入
@Autowired
//@Qualifier注解指明注入对象为Class接口实现类ClassSub1实例,排除二义性
@Qualifier("classSub1")
public void setClassA(Class classA) { this.classA = classA; }
...
}

@Named

@Named注解与@Qualifier注解同理,在此不再累述。

@自定义限定符注解

使用@Qualifier注解须传入对应Bean名称,为方便书写以及代码解耦,用户可自定义Qualifire形式的注解,步骤如下:

  1. 定义自定义注解类,代码如下

    1
    2
    3
    4
    5
    6
    7
    //注解可用于接口、类、枚举、注解类型(ElementType.TYPE)、属性字段(ElementType.FIELD)以及方法参数(ElementType.PARAMETER)
    @Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER})
    //注解在运行时依然保持有效
    @Retention(RetentionPolicy.RUNTIME)
    @Qualifier
    public @interface ClassA {
    }
  2. 添加接口及其多个实现类,代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public interface Class {
    ...
    }

    public class ClassSub1 implements Class {
    ...
    }

    public class ClassSub2 implements Class {
    ...
    }
  3. “激活”如下Spring框架提供的内置BeanFactoryPostProcessor接口实现类CustomAutowireConfigurer,传入自定义注解类路径,代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <bean class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
    <!--自定义注解集合-->
    <set>
    <!--自定义注解类路径-->
    <value>com.example.qualifier.ClassA</value>
    </set>
    </property>
    </bean>
    <bean class="com.example.bean.ClassSub1">
    <!--指明ClassSub1类对应@ClassA注解-->
    <qualifier type="ClassA"/>
    </bean>
    <bean class="com.example.bean.ClassSub2"/>
  4. 使用自定义注解消除二义性,代码如下

    1
    2
    3
    4
    5
    6
    7
    @Component
    public class Student {
    @Autowired
    //如同@Qualifier("ClassSub1")
    @ClassA
    private Class classA;
    }

AOP相关注解

创建Bean

使用Bean定义表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--spring-config.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="com.example.bean.Student">
<property name="id" value="S0001"/>
<property name="name" value="小明"/>
<property name="sex" value="♂"/>
<property name="age" value="12"/>
<property name="class" ref="classA"/>
</bean>
<bean id="classA" class="com.example.bean.Class">
<constructor-arg name="id" value="C0001"/>
<constructor-arg name="name" value="一班"/>
<constructor-arg name="headTeacher" value="王老师"/>
</bean>
</beans>

使用注解

XML文件手动编写并不方便。所以Spring提供注解方式(部分注解属于JPA标准),无需编写配置文件。本文建议使用注解,步骤如下:

  1. 创建各种 Bean 类,实现默认构造函数,以及get、set方法,并加上注解,代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    //Student.java
    @Component
    public class Student {
    private String id;
    private String name;
    private String sex;
    private int age;
    private Class classA;

    public Student() {}

    public String getId() { return id; }
    public String getName() { return name; }
    public String getSex() { return sex; }
    public int getAge() { return age; }
    public Class getClassA() { return classA; }

    @Autowired
    public void setId(String id) { this.id = id; }
    @Autowired
    public void setName(String name) { this.name = name; }
    @Autowired
    public void setSex(String sex) { this.sex = sex; }
    @Autowired
    public void setAge(int age) { this.age = age; }
    @Autowired
    public void setClassA(Class classA) { this.classA = classA; }
    }

    //Class.java
    @Component
    public class Class {
    @Autowired
    private String id;
    @Autowired
    private String name;
    @Autowired
    private String headTeacher;

    public Class() {}

    public String getId() { return id; }
    public String getName() { return name; }
    public String getHeadTeacher() { return headTeacher; }

    public void setId(String id) { this.id = id; }
    public void setName(String name) { this.name = name; }
    public void setHeadTeacher(String headTeacher) { this.headTeacher = headTeacher; }
    }
  2. 同时,在配置类AppConfig.java中写入如下内容:

    1
    2
    3
    4
    5
    @Configuration
    @ComponentScan("com.example.bean")
    @EnableTransactionManagement
    public class AppConfig {
    }
  3. 最后,在主方法中创建Spring应用上下文即可:

    1
    2
    3
    4
    5
    public class Main {
    public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    }
    }

载入Resource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
public class MyReader {
@Autowired
private ApplicationContext applicationContext;

public void read() {
Resource resource = applicationContext.getResource("classpath:test.txt");
try {
InputStream inputStream = resource.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
//输出每行内容
reader.lines().forEach(System.out::println);
reader.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}

操作方法

CC许可协议署名非商业性使用相同方式共享
本文采用 CC BY-NC-SA 3.0 Unported 协议进行许可