Spring学习

186 阅读9分钟

1、Spring框架概述

  • 轻量级开源javaEE框架
  • 解决企业开发应用复杂性
  • IOC,AOP
  • 方便解耦
  • 方便集合各种框架

2、IOC

概念

降低代码耦合度,对象创建,对象之间的引用交给Spring

底层原理

xml解析,工厂模式,反射

大致流程:例如xml文件中有以下bean

<bean id="dao" class="com.tao.UserDao"></bean>

伪代码流程:

class UserDaoFactory {
    public static UserDao getUserDao(){
        String classValue = class属性值 // xml文件解析,获取class
        Class clazz = Class.forName(classValue) // 反射获取class实例
        return clazz.newInstance();
    }
}

Spring两种IOC实现方法

BeanFactory

IOC最基本的实现方式,一般是Spring内部使用,开发人员并不适用,不同于ApplicationContext,加载配置文件的时候不会预先创建对象,获取对象的的时候才会创建对象(懒加载,节约资源)

ApplicationContext

BeanFactory子接口,提供更多更强大的功能,加载配置文件的时候就会对对象进行创建(耗时耗时间的都先创建,适合实际生产)

ApplicationContext实现类

1、FileSystemXmlApplicationContext: xml文件系统路径

2、ClassPathXmlApplicationContext: xml文件相对项目的路径

IOC操作Bean管理

主要是Spring创建对象,Spring注入属性

基于xml配置文件方式实现

<bean id="dao" class="com.tao.UserDao"></bean>

  • id属性:唯一标识
  • class属性:类全路径
  • name属性:和id一样,并且可以加入特殊符号如/,但是是早期属性,基本没人用了

默认执行bean的无参构造方法

基于xml注入属性:

  • DI:依赖注入,就是注入属性,DI是IOC的一种实现,在创建对象的基础上进行完成。
    • 创建方法setXXX this.XXX = xxx,使用setXXX注入
    • 有参构造器注入
      • 在类中创建有参构造器,通过xml文件中的name,value对应
          <bean id="user1" class="com.tao.User">
              <constructor-arg name="name" value="abcde"></constructor-arg>
          </bean>
      
    • xml文件中配置属性注入
      • 在bean标签里面使用标签property(对象中要有对应的setXXX方法)
      • name:属性名
      • value:属性值 <property name = "" value = ""></property>
      • 外部bean ref:对象注入(对象也需要创建setXXX方法), <property name = "" ref = ""></property>
      • 内部bean 嵌套使用以上两种就行
      <peoperty name="">
          <bean id="" value="">
              <property name=""></property>
          </bean>
      </property>
      
      • 内部bean的属性可以使用上面的property设置,也可以在类中设置好get方法后,name=类.属性 value=""设置
      • 集合类属性赋值 使用<array><list>等标签赋值

FactoryBean

除了普通的bean还有工厂bean即FactoryBean

  • 普通bean以上介绍的就是普通bean,定义类型就是返回类型
  • 工厂bean,配置文件中定义类型和返回类型可以不同

流程

  • 创建类,类做成工厂bean,实现接口FactoryBean
  • 实现接口的方法,方法中定义返回的bean类型
  • 相当于前面的bean定义的时候返回值就是你真正的返回值,而实现Factory接口之后可以自定义返回值
public class MyBean implements FactoryBean {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        User user = context.getBean("MyBean", User.class);
        System.out.println(user);
    }

    @Override
    public Object getObject() throws Exception {
        User user = new User("test");
        return user;
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }
}

bean管理(作用域和生命周期)

  • 可以设置bean实例是单例还是多例,默认是单例
  • 通过scope标签设置
    • 默认singleton单例
      • 加载配置文件就会创建该单例(自己创建单例也是static)
    • 可选prototype多例
      • 不是在加载配置文件创建对象,调用getBean方法创建
  • request:一次请求
  • session:一次会话

bean生命周期

  • 通过构造器创建bean实例(无参构造器)
  • 为bean属性设置值和其他bean的引用(调用set方法)
  • 调用bean的初始化方法(需要进行配置)
  • 可以使用了(对象获取了)
  • 当容器关闭的时候调用bean销毁的方法(需要进行配置销毁的方法)

具体演示:

public class Orders {

    private String oname;

    public Orders() {
        System.out.println("第一步,通过无参构造器创建bean");
    }

    public void setOname(String oname) {
        this.oname = oname;
        System.out.println("第二步,通过set方法设置值");
    }

    public void init() {
        System.out.println("第三步,执行初始化方法");
    }
    public void destroy(){
        System.out.println("第五步,执行销毁方法");
    }
    
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        Orders orders = context.getBean("orders", Orders.class);
        System.out.println("第四步,获取创建bean实例");
        System.out.println(orders);
        ((ClassPathXmlApplicationContext)context).close();

    }
}
<bean id="orders" class="com.tao.bean.Orders" init-method="init" destroy-method="destroy">
    <property name="oname" value="手机"></property>
</bean>

输出

  • 第一步,通过无参构造器创建bean
  • 第一步,通过无参构造器创建bean
  • 第二步,通过set方法设置值
  • 第三步,执行初始化方法
  • 第四步,获取创建bean实例
  • com.tao.bean.Orders@4671e53b
  • 第五步,执行销毁方法

实际上还有另外两步,在第三步前后还有两个方法

  • 初始化之前会把bean实例传递bean后置处理器的方法
  • 初始化之后会把bean实例传递bean后置处理器的方法
  • 注意: 所有bean都会执行这两个方法
public class MybeanPost implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化前执行的方法");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化后执行的方法");
        return bean;
    }
}
<bean id="mybeanPost" class="com.tao.bean.MybeanPost"></bean>

自动装配

  • 使用xml自动装配:根据属性名称类型自动装配值
    • bean中 autowire="byName"根据名称自动装配,注意bean的id名称和你的属性名要一致
    • bean中 autowire="byType"根据类型自动装配,但是如果一个类型有多个bean,会报错,无法对应。

IOC操作Bean管理(外部属性文件)

解决如果property变动需要大范围改动xml文件

  • 直接配置数据库信息:麻烦耦合度高。
  • 引入外部属性文件配置数据库连接池:方便修改,耦合度低,设立properties文件,表达式${val}

基于注解方式实现(可以简化xml配置)

  • @Component
  • @Service
  • @Controller
  • @Repository 流程:
  • 引入依赖 aop
  • 组件扫描,也可以将use-default-filters设置为false使用自定义的过滤器扫描
<context:component-scan base-package="扫描的路径"></context:component-scan>
  • 创建类,类上添加创建对象注解

注解的属性注入

  • @AutoWired 根据属性类型自动装配
  • @Qualifier 根据属性名称自动注入(多个实现类实现接口可能无法导根据属性注入,此时根据类型注入,指定value)
  • @Resource 可以根据类型也可以根据名称注入,加入name可以根据名称注入
  • @Value 注入普通类型 通过value给如String设置值

完全注解开发

创建配置类,替代xml文件

  • 创建类,添加@Configuration注解,以及@ComponentScan(basePackages = {""})

AOP

不改变源码,在主干功能里添加功能,比如登录流程中添加判断权限模块

原理:两种情况的动态代理

1、有接口的情况,使用JDK动态代理

interface UserDao{
    public void login();
}
class UserDaoImpl implementsUserDao{
    public void login(){
        //登录流程
    }
}

创建UserDao接口实现类的代理对象,增强类的方法。

具体步骤:

  • 使用JDK动态代理,使用Proxy类里的方法创建代理对象
  • 调用newProxyInstance方法,此方法返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。方法中有三个参数。
    • ClassLoader 类加载器
    • 增强方法所在的类,这个类实现的接口,支持多个接口
    • 实现这个接口InvocationHanlder,创建代理对象,写增强的方法。
public interface UserDao {
    public String update(String id);
    public int add(int a, int b);
}
@Repository
public class UserDaoImpl implements UserDao {
    @Override
    public String update(String id) {
        return id + "*********";
    }
    @Override
    public int add(int a, int b) {
        return a + b;
    }
}
public static void main(String[] args) {
    Class[] interfaces = {UserDao.class};
    UserDaoImpl userDao = new UserDaoImpl();
    UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 方法前
            System.out.println("方法前执行的" + method.getName() + "传递的参数" + Arrays.toString(args));
            // 被增强的方法执行
            Object res = method.invoke(userDao, args);
            // 方法后
            System.out.println("方法之后执行" + userDao);
            return res;
        }
    });
    dao.update("123");
    dao.add(1,4);
}
  • 以上是偏底层的代码,spring为我们做了封装

2、没有接口的情况,使用CGLIB动态代理

class User{
    public void add() {
        //add方法
    }
}

正常来说需要创建当前类的子类来增强方法,也可以使用CGLIB动态代理,创建子类的代理对象即可

3、AspectJ实现

AOP术语

  • 连接点
    • 类里面的哪些方法可以被增强,这些方法称为连接点
  • 切入点
    • 实际增强的方法被称为切入点
  • 通知(增强)
    • 增强的逻辑部分(切入点前后添加的部分)
    • 前置通知、后置通知、环绕通知、异常通知、最终通知(finally)
  • 切面
    • 是动作,把通知应用到切入点的过程叫切面。

Spring具体操作

  • 一般基于AspectJ实现AOP操作
    • AspectJ不是Spring组成部分,是独立AOP框架,一般把二者一起使用进行AOP操作。
    • AspectJ可以基于xml配置文件,也可以基于注解方式实现AOP操作
  • 项目中引入AOP依赖
  • 切入点表达式:要对哪个类中的哪个方法进行增强
    • execution([权限修饰符(*代表都行)][返回类型(可以不写)][类全路径][方法名称] ([参数列表]))
      • 例:对com.tao.dao.BookDao类里面的add方法进行增强
      • execution(* com.tao.dao.BookDao.add(..))
      • 例: 对com.tao.dao.BookDao类里面的所有方法进行增强
      • execution(* com.tao.dao.BookDao.*(..))
      • 例: 对com.tao.dao里所有类所有方法增强
      • execution(* com.tao.dao.*.*(..))
  • 基于注解方式
    • Spring配置文件中开启注解的扫描
    • 使用注解创建User和UserProxy对象
    • 增强类上增加注解@Aspect
    • Spring配置文件中开启生成代理对象
    • 增强类的方法添加@Before,@After,@AfterReturning,@AfterThrowing,@Around
    • 顺序 环绕前-Before-方法-环绕后-after-afterReturning
    • 如果有异常 环绕前-Before-方法-after-AfterThrowing
    • after也叫最终通知,有没有异常都执行
    • afterReturning 后置通知,有异常不执行
    • spring5中做出修改:顺序:环绕前-before-方法-afterReturning-after-环绕后
    • 有异常则是 环绕前-before-方法-afterThrowing-after
@Component
public class User {
    public void add() {
        System.out.println("add***************");
    }
}
@Component
@Aspect // 生成代理对象
public class UserProxy {
    @Before(value = "execution(* com.tao.aop.User.add(..))")
    public void before() {
        System.out.println("before.........");
    }

    @Around(value = "execution(* com.tao.aop.User.add(..))")
    public void around(ProceedingJoinPoint p) throws Throwable {
        System.out.println("环绕前");
        p.proceed();
        System.out.println("环绕后");
    }

    @Test
    public void test() {
        ApplicationContext context = new AnnotationConfigApplicationContext(Conf.class);
        User user = context.getBean("user", User.class);
        user.add();
    }
}
@Configuration
@ComponentScan(basePackages = {"com.tao"})
@EnableAspectJAutoProxy
public class Conf {
}

上面conf配置也可以在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"
       xmlns:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.tao"></context:component-scan>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

公共切入点的提取

多个切入点相同的提取出来

代理方法中添加方法

@Pointcut(value = "execution(* com.tao.aop.User.add(..))")
public void pointdemo(){
}

@Before(value = "pointdemo()")
public void beforePointDemo() {
    System.out.println("before.........");
}
  • 多个增强类对同一个方法进行增强,设置增强优先级
    • 增强类添加注解@Order(num) 0~n 0最优先
  • Aop操作(配置文件)
    • 创建两个类,增强类,被增强类,方法
    • spring配置文件中创建两个两个类的对象
    • spring配置文件中配置切入点