一. Spring入门-概述及环境搭建

1.1 概述

Spring是一个开源框架,为简化企业级应用开发而生,是JavaSE/EE一站式框架。

1.2 IOC底层原理实现

OCP原则:open-close原则,对程序拓展是open,对修改程序代码是close

即尽量做到不修改程序的源码,实现对程序的拓展

1.3 环境搭建及代码入门(IOC+DI简介)

  • 导入Spring核心开发及其他相关jar包,并添加到依赖

    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
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.10</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.2.4.RELEASE</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.2.4.RELEASE</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>4.2.4.RELEASE</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
    <version>4.2.4.RELEASE</version>
    </dependency>

    <dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
    </dependency>

    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
    </dependency>

    (版本号仅供参考)

  • 新建一个接口类(Interface)

    1
    2
    3
    public interface UserService {
    public void sayHello();
    }
  • 新建一个实现类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class UserServiceImpl implements UserService {
    //添加属性
    private String name;

    public String getName() {
    return name;
    }

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

    public void sayHello(){
    System.out.println("Hello Spring" + name);
    }

    }
  • 传统方式开发代码展示

    1
    2
    3
    4
    5
    public void demo1(){
    UserService userService = new UserServiceImpl();
    //设置属性
    userService.sayHello();
    }

    说明:为了让userService类可以调用sayHello(),要new一个UserServiceImpl对象,而使用Spring框架可以进行解耦

  • 利用Spring框架开发代码展示

    在main目录下建立resources文件夹,建立Spring配置文件(applicationContext.xml)

    1
    2
    <!-- UserService的创建权交给Spring , id名字任意-->
    <bean id="userService" class="com.imooc.ioc.demo1.UserServiceImpl"></bean>

    新建一个测试类

    1
    2
    3
    4
    5
    6
    7
    8
     public void demo2(){
    //创建Spring的工厂
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    //通过工厂获得类
    UserService userService= (UserService)applicationContext.getBean("userService");
    //输入配置文件中的id名,并返回配置的对象
    userService.sayHello();
    }

    说明:这种方式为IOC(反转控制),即将创建UserService对象的控制权反转给Spring框架

  • 下面来介绍DI(Dependency Injection) 依赖注入

    依赖注入即在Spring创建对象过程中将对象的依赖注入进去

    案例:如果在实现类里增添私有成员,赋值时使用的set方法只能由实现类对象使用,为了解决这种耦合问题,可以使用Spring的依赖注入

    1
    2
    <!--在bean标签中设置属性即可-->
    <property name="name" value="李四"/>

二. Spring入门-Bean管理

2.1 Spring工厂类介绍

  • ApplicationContext接口(新版本工厂)

    ClassPathXmlApplicationContext(“”) //加载类路径下的配置文件

    FileSystemXmlApplicationContext(“”) //加载磁盘中的配置文件 例如:

    1
    ApplicationContext applicationContext = new FileSystemXmlApplicationContext("c:\\applicationContext.xml");
  • BeanFactory接口(旧版本工厂)

    1
    2
    3
    4
    5
    6
    7
    public void demo4(){
    //创建工厂类
    BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
    //通过工厂获得类
    UserService userService =(UserService)beanFactory.getBean("userService");
    userService.sayHello();
    }

2.2 Spring的Bean管理(XML方式)

Ⅰ. 使用类构造器实例化(无参)

1
2
3
4
5
6
7
8
public class Bean1 {
/*
Bean的三种实例化方式:1.采用无参数的构造方法的方式
*/
public Bean1(){
System.out.println("Bean1被实例化了...");
}
}

配置文件中:

1
2
<!-- 第一种:无参构造器-->
<bean id="bean1" class="com.imooc.ioc.demo2.Bean1"></bean>

Ⅱ. 使用静态工厂的方式实例化

1
2
3
4
5
6
7
//Bean2的静态工厂
public class Bean2Factory {
public static Bean2 createBean2(){
System.out.println("Bean2Factory的方法已经执行了...");
return new Bean2();
}
}

配置文件:

1
2
<!-- 第二种:静态工厂的方式-->
<bean id="bean2" class="com.imooc.ioc.demo2.Bean2Factory" factory-method="createBean2"></bean>

Ⅲ. 使用实例工厂的方式实例化

1
2
3
4
5
6
7
//Bean3的实例工厂
public class Bean3Factory {
public Bean3 createBean3(){
System.out.println("Bean3的工厂被执行了");
return new Bean3();
}
}

配置文件:

1
2
3
<!-- 第三种:实例工厂的方式-->
<bean id="bean3Factory" class="com.imooc.ioc.demo2.Bean3Factory"/>
<bean id="bean3" factory-bean="bean3Factory" factory-method="createBean3"/>

这种方法与第二种方法的区别就是创建Bean的函数是否为静态的,因为其不是静态的方法,所以要先在配置文件实例化bean3Factory

2.3 Bean的常用配置

  • id和name

    • 一般情况下,装配一个Bean时,通过指定一个id属性作为Bean的名称
    • id属性在IOC容器中必须是唯一的
    • 如果Bean的名称中有特殊字符,就要使用name字符
  • class

    • 用于设置一个类的完全路径名称,主要作用是IOC容器生成类的实例
  • scope中配置Bean的作用域

    • singleton(默认)

      在SpringIOC容器中仅存在一个Bean实例,Bean以单实例的方式存在

    • prototype

      每次调用getBean()都会返回一个新的实例

    • request

      每次http请求都会创建一个新的Bean,仅使用于WebApplicationContext环境

    • session

      同一个http session共享一个Bean,不同的http session使用不同Bean

2.4 Bean的生命周期

  • init-method和destroy-method

    当bean被加载到容器时调用init,当bean从容器中删除时调用destroy(singleton有效)

    1
    2
     <!-- 配置初始化和销毁方法 -->
    <bean id="man" class="com.imooc.ioc.demo3.Man" init-method="setup" destroy-method="teardown"/>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Man {
    public Man(){
    System.out.println("Man被实例化了...");
    }
    public void setup(){
    System.out.println("Man被初始化了...");
    }
    public void teardown(){
    System.out.println("Man被销毁了...");
    }
    }

    测试类:

    1
    2
    3
    4
    5
    6
    public void demo2(){
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    Man man = (Man)applicationContext.getBean("man");
    applicationContext.close();
    }
    // ApplicationContext接口没有销毁方法

    输出结果:

    1
    2
    3
    Man被实例化了...
    Man被初始化了...
    Man被销毁了...
  • BeanPostProcessor的类增强案例

    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
    public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    //System.out.println("第五步:初始化前方法...”");
    return bean;
    }

    @Override
    public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
    //System.out.println("第八步:初始化后方法...");
    if("userDAO".equals(beanName)){
    Object proxy = Proxy.newProxyInstance(bean.getClass().getClassLoader(),bean.getClass().getInterfaces(), new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if("save".equals(method.getName())){
    System.out.println("权限校验===");
    return method.invoke(bean,args);
    }
    return method.invoke(bean,args);
    }
    });
    return proxy;
    }
    return bean;
    }
    }

    这样就会在调用保存用户的save方法前进行权限校验

2.5 Spring的属性注入方式及构造方法的属性注入

Ⅰ. 简单类型的属性注入

  • 对于类成员变量,注入方式有三种

    1)构造函数注入

    通过构造方法注入Bean的属性值或依赖的对象,它保证了Bean实例在实例化后可以使用

    构造器注入在元素里声明的属性

    1
    2
    3
    4
    public User(String name,Integer age){
    this.name = name;
    this.age = age;
    }

    配置文件例子:

    1
    2
    3
    4
    5
    <bean id="user" class="com.imooc.ioc.demo4.User">
    <constructor-arg name="name" value="张三"/>
    <constructor-arg name="age" value="23"/>
    </bean>
    <!-- 这样是通过构造器注入属性 -->

    2)属性setter方法注入

    在Spring配置文件中,通过设置注入的属性

    配置文件例子:

    1
    2
    3
    4
    <bean id="person" class="com.imooc.ioc.demo4.Person">
    <property name="name" value="李四"/>
    <property name="age" value="12"/>
    </bean>

    再看另外一个例子:

    1
    2
    3
    4
    5
    6
    7
    <bean id="cat" class="com.imooc.ioc.demo4.Cat">
    <property name="name" value="kitty"></property>
    </bean>
    <bean id="person" class="com.imooc.ioc.demo4.Person">
    <property name="cat" ref="cat"/>
    </bean>
    <!-- ref 对应bean的id,用于注入对象的值-->

    3)接口注入(不常用)

    4)p名称空间的属性注入

    p:<属性名>=“xxx” 引用常量值

    p:<属性名>-ref=“xxx” 引用其他Bean对象

    1
    xmlns:p="http://www.springframework.org/schema/p"
    1
    2
    3
    <!-- p名称空间的属性注入-->
    <bean id="person" class="com.imooc.ioc.demo4.Person" p:name="小白" p:age="16" p:cat-ref="cat"/>
    <bean id="cat" class="com.imooc.ioc.demo4.Cat" p:name="小红"/>

    5)SpEL注入(Spring表达式注入)

    语法:#{表达式}

    1
    <bean id="" value="#{表达式}">

    例子

    1
    2
    3
    4
    5
    6
    7
    <bean id="" value="#{表达式}">
    <bean id="product" class="com.imooc.ioc.demo4.Product">
    <property name="name" value="#{'男装'}"/>
    <property name="price" value="#{productInfo.calculatePrice()}"/>
    <!-- 可以在里面调用函数 如上所示 -->
    <property name="category" value="#{category}"/>
    </bean>

Ⅱ. 复杂类型的属性注入

  • 数组类型的属性注入

  • List集合类型的属性注入

  • Set集合类型的属性注入

  • Map集合类型的属性注入

  • Properties类型的属性注入

    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
    <!-- 复杂类型的属性注入 -->
    <bean id="collectionBean" class="com.imooc.ioc.demo5.CollectionBean">
    <!--数组类型-->
    <property name="arrs" >
    <list>
    <value>aaa</value>
    <value>bbb</value>
    </list>
    </property>
    <!--List集合的属性注入-->
    <property name="list">
    <list>
    <value>111</value>
    <value>222</value>
    </list>
    </property>
    <!--Set集合的属性注入-->
    <property name="set">
    <set>
    <value>ddd</value>
    <value>eee</value>
    </set>
    </property>
    <!--Map集合的属性注入-->
    <property name="map">
    <map>
    <entry key="aaa" value="111"/>
    <entry key="bbb" value="222"/>
    </map>
    </property>
    <!--Properties的属性注入 -->
    <property name="properties">
    <props>
    <prop key="username">root</prop>
    <prop key="password">123456</prop>
    </props>
    </property>
    </bean>

2.6 注解方式

加载依赖:

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>

配置文件:

1
2
<!-- 开启注解扫描-->
<context:component-scan base-package="com.imooc.demo1"/>

在类中:

1
2
3
4
5
6
7
/* Spring的Bean管理的注解方式:*/
@Component("userService")
public class UserService {
public String sayHello(String name){
return "Hello" + name;
}
}

测试类:

1
2
3
4
5
6
7
@Test
public void demo1(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService)applicationContext.getBean("userService");
String s = userService.sayHello("张三");
System.out.println(s);
}

Ⅰ. bean的注解标签

  • @Component 描述Spring框架中的Bean
  • @Repository 用于对DAO实现类进行标注
  • @Service 用于对Service实现类进行标注
  • @Controller 用于对Controller实现类进行标注

注:不同注解是为了让标注类本身用途清晰

Ⅱ. 属性注入的注解

  • value - 注入简单类型

    1
    2
    @Value("米饭")
    private String something;
  • Autoweird - 自动注入

    • 默认按照类型注入,若有相同Bean类型,按照名称注入
    • 若想按照名称注入,需加上@Qualifier(“Bean的名称”),或采用下面的注解
  • @Resource(name=" ")

    效果等同于@Autoweird ➕ @Qualifier(" ")

Ⅲ. Spring的其他注解

  • 初始化bean和销毁bean的注解

    • @PostConstruct - 初始化

    • @PreDestroy - 销毁

      1
      2
      3
      4
      5
      6
      7
      8
      @PostConstruct
      public void init(){
      System.out.println("initBean...");
      }
      @PreDestroy
      public void destroy(){
      System.out.println("destroyBean...");
      }
  • Bean的作用范围

    • @scope来指定Bean的作用范围(默认为singleton)

      1
      2
      3
      4
      5
      @Component("bean2")
      @Scope("prototype")
      public class Bean2 {
      //测试案例
      }

Ⅳ. Spring的xml和注解整合开发

  • 1.引入context命名空间
  • 2.在配置文件中添加context:annotation-config标签

三. Spring的AOP

3.1 概述

  • 面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术

3.2 AOP相关术语

  • Joinpoint(连接点),在Spring中指的是可以增强的方法
  • Pointcut(切入点),指真正被拦截到的点
  • Advice(通知/增强),指拦截到Joinpoint后所要做的事情
  • Introduction(引介),在运行期为类动态地添加一些方法或Field
  • Target(目标对象),指被增强的目标对象
  • Weaving(织入),将Advice应用到Target的过程
  • Proxy(代理),一个类被AOP增强后,产生一个结果代理类
  • Aspect(切面),是切入点和通知的结合

3.3 AOP的底层实现

  • jdk的动态代理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class MyJdkProxy implements InvocationHandler {
    private UserDAO userDAO;

    public MyJdkProxy(UserDAO userDAO){
    this.userDAO = userDAO;
    }

    public Object createProxy(){
    Object proxy = Proxy.newProxyInstance(userDAO.getClass().getClassLoader(),userDAO.getClass().getInterfaces(),this);
    return proxy;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if("save".equals(method.getName())){
    System.out.println("权限校验");
    }
    return method.invoke(userDAO,args);
    }
    }

    测试类:

    1
    2
    3
    UserDAO userDAO = new UserDaoImpl();
    UserDAO proxy = (UserDAO)new MyJdkProxy(userDAO).createProxy();
    proxy.save();

    对于不使用接口的业务类,无法使用JDK动态代理

  • 使用CGLIB生成代理

    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
    public class MyCglibProxy implements MethodInterceptor {

    private ProductDao productDao;

    public MyCglibProxy(ProductDao productDao){
    this.productDao = productDao;
    }

    public Object createProxy(){
    // 1.创建核心类
    Enhancer enhancer = new Enhancer();
    // 2.设置父类
    enhancer.setSuperclass(productDao.getClass());
    // 3.设置回调
    enhancer.setCallback(this);
    // 4.生成代理
    Object proxy = enhancer.create();
    return proxy;
    }

    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    if("save".equals(method.getName())){
    System.out.println("权限校验");
    }
    return methodProxy.invokeSuper(proxy,args);
    }
    }

    测试类:

    1
    2
    3
    ProductDao productDao = new ProductDao();
    ProductDao proxy = (ProductDao)new MyCglibProxy(productDao).createProxy();
    proxy.save();

3.4 AOP的通知类型

通知类型 描述
前置通知 org.springframework.aop.MethodBeforeAdvice 在目标方法执行前实施增强
后置通知 org.springframework.aop.AfterReturningAdvice 在目标方法执行后实施增强
环绕通知 org.aopalliance.intercept.MethodInterceptor 在目标方法执行前后均实施增强
异常抛出通知 org.springframework.aop.IntroductionInterceptor 在方法抛出异常后实施增强

3.5 AOP的切面类型

  • Advisor:代表一般切面,对目标类所有方法进行拦截

    引入依赖:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <dependency>
    <groupId>aopalliance</groupId>
    <artifactId>aopalliance</artifactId>
    <version>1.0</version>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>4.2.4.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>4.2.4.RELEASE</version>
    </dependency>
  • 案例(以前置通知为例)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!-- 配置目标类 -->
    <bean id="studentDao" class="com.imooc.app.demo3.StudentDaoImpl"/>

    <!-- 前置通知类型-->
    <bean id="myBeforeAdvice" class="com.imooc.app.demo3.MyBeforeAdvice"/>

    <!-- Spring的AOP产生代理对象-->
    <bean id="studentDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <!-- 配置目标类-->
    <property name="target" ref="studentDao"/>
    <!-- 实现的接口 -->
    <property name="proxyInterfaces" value="com.imooc.app.demo3.StudentDao" />
    <!-- 采用拦截的名称 -->
    <property name="interceptorNames" value="myBeforeAdvice"/>
    </bean>

    其他属性的配置:

    • proxyTargetClass:是否对类代理而不是接口,设置为true时使用CGLIB代理
    • singleton:返回代理是否为单例,默认为单例
    • optimize:设置为true时,强制使用CGLIB

    前置通知类:

    1
    2
    3
    4
    5
    public class MyBeforeAdvice implements MethodBeforeAdvice {
    public void before(Method method, Object[] args, Object target) throws Throwable {
    System.out.println("前置增强");
    }
    }

    测试类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    public class SpringDemo3 {
    @Resource(name="studentDaoProxy")
    private StudentDao studentDao;
    @Test
    public void demo1(){
    studentDao.find();
    studentDao.save();
    studentDao.update();
    studentDao.delete();
    }
    }
  • PointcutAdvisor:代表具有切点的切面,可以指定拦截目标类的方法

    常用实现类:JdkRegexpMethodPointcut 构造正则表达式切点

    • 案例(以环绕通知为例):

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      <!--配置目标类 -->
      <bean id="customerDao" class="com.imooc.app.demo4.CustomerDao"/>
      <!--配置通知 -->
      <bean id="myAroundAdvice" class="com.imooc.app.demo4.MyAroundAdvice"/>
      <!--对某些方法增强,要配置一个带有切入点的切面-->
      <bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
      <!-- pattern中配置正则表达式: .任意字符 *任意次数-->
      <!--<property name="pattern" value=".*"/>-->
      <property name="patterns" value=".*save.*,.*delete.*"/>
      <property name="advice" ref="myAroundAdvice"/>
      </bean>
      <!--配置产生代理-->
      <bean id="customerDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
      <property name="target" ref="customerDao"/>
      <property name="proxyTargetClass" value="true"/> <!--没有接口要这样配置-->
      <property name="interceptorNames" value="myAdvisor"/>
      </bean>
    • 环绕通知类:

      1
      2
      3
      4
      5
      6
      7
      8
      public class MyAroundAdvice implements MethodInterceptor {
      public Object invoke(MethodInvocation invocation) throws Throwable {
      System.out.println("环绕前增强");
      Object obj = invocation.proceed();
      System.out.println("环绕后增强");
      return null;
      }
      }
    • 测试类:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      @RunWith(SpringJUnit4ClassRunner.class)
      @ContextConfiguration("classpath:applicationContext2.xml") //注意填写正确的配置文件
      public class SpringDemo4 {
      @Resource(name="customerDaoProxy")
      private CustomerDao customerDao;
      @Test
      public void demo1(){
      customerDao.find();
      customerDao.delete();
      customerDao.save();
      customerDao.update();
      }
      }

3.6 AOP的自动代理方法

  • 通过ProxyFactoryBean织入切面代理,后期难以维护

  • 自动创建代理

    代理方式 描述
    BeanNameAutoProxyCreator 根据Bean名称创建代理
    DefaultAdvisorAutoProxyCreator 根据Advisor本身包含信息创建代理
    AnnotationAwareAspectJAutoProxyCreator 基于Bean中的AspectJ注解进行自动代理
  • DefaultAdvisorAutoProxyCreator举例

    配置文档:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!--配置目标类-->

    <!--配置增强-->

    <!--配置切面-->
    <bean id="myAdvisor" class=org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <!-- 要进行字符转义 -->
    <property name="pattern" value="com\.imooc\.app\.demo5\.CustomerDao\.save"/>
    <property name="advice" value="myAroundAdvice"/>
    </bean>

    <bean class="DefaultAdvisorAutoProxyCreator"></bean>