Spring 框架详解

132 阅读7分钟

1、Spring 优点

  • Spring 是一个开源的免费框架(容器)
  • Spring 是一个轻量级的、非入侵式的框架
  • 控制反转(IOC),面向切面编程(AOP)
  • 支持事务处理,对框架整合的支持

总结一句话:Spring 就是一个轻量级的控制反转(IoC)和面向切面编程(Aop)的框架!

2、Spring 组成

image.png

3、Ioc 控制反转

  • 之前,程序是主动创建对象!控制权在程序员手上
  • 通过 set 动态注入,将控制权给用户,即控制反转(Ioc)
  • 牵一发而动全身

Ioc 和之前最大的区别就是,将控制权交给用户,而不是程序员

public class UserServiceImpl  implements UserService {

    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void getUser() {
        userDao.getUser();
    }
}

4、Spring 入门案例

application.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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

        <bean id="UserDaoImpl" class="com.lyq.dao.UserDaoImpl"></bean>
        <bean id="UserMysqlImpl" class="com.lyq.dao.UserMysqlImpl"></bean>
        <bean id="UserOracleImpl" class="com.lyq.dao.UserOracleImpl"></bean>

        <bean id="UserServiceImpl" class="com.lyq.service.Impl.UserServiceImpl">
            <property name="userDao" ref="UserOracleImpl"/>
        </bean>
</beans>

测试类

public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
    UserService userServiceImpl = (UserService) applicationContext.getBean("UserServiceImpl");
    userServiceImpl.getUser();
}

5、IOC 创建对象的方式

  • 使用无参构造器创建对象(默认)
  • 使用有参构造器创建对象
    • 1. 通过下标
    <!-- 第一种,通过构造器的下标方式  -->
    <bean id="User" class="com.lyq.pojo.User">
            <constructor-arg index="0" value="xxxx"/>
    </bean>
    
    • 2. 通过类型
      <!--  第二种,通过构造器,里面的参数类型传递,不建议使用      -->
    <bean id="User" class="com.lyq.pojo.User">
            <constructor-arg type="java.lang.String" value="dasdas"/>
    </bean>
    
    • 3. 通过参数名
    <!--   第三种,直接通过参数名传递     -->
    <bean id="User" class="com.lyq.pojo.User">
            <constructor-arg name="name" value="aaa"/>
    </bean>
    

总结:在配置文件加载时,容器中管理的对象就已经初始化了

6、Spring 配置

alias

<alias name="user" alias="user2"/>

Bean 的配置

<bean id="user" class="com.lyq.pojo.User" name="user3,user4">
    <property name="name" value="zxc"/>
</bean>

import

一般用于团队开发使用,他开源将多个配置文件,导入合并为一个

application.xml里面导入其他开发者写的配置文件

<import resource="application2.xml"/>
<import resource="application3.xml"/>
<import resource="application4.xml"/>

7、依赖注入(DI)

7.1 构造器注入

前面已经说过

7.2 Set 方式注入【重点】

  • 依赖注入:Set 注入
    • 依赖:bean 对象的创建依赖于容器
    • 注入:bean 对象中的所有属性,由容器来注入!

【环境搭建】

1. 复杂类型

public class Address {
    private String address;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

2. 真实测试对象

public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobbys;
    private Map<String, String> card;
    private Set<String> games;
    private String wife;
    private Properties info;
}

3. application.xml

<bean id="address" class="com.lyq.pojo.Address"/>
<bean id="student" class="com.lyq.pojo.Student">
    <!--   普通类型注入-->
    <property name="name" value="李永奇"/>
    <!--   bean 注入-->
    <property name="address" ref="address"/>
    <!--   数组注入-->
    <property name="books">
        <array>
            <value>红楼梦</value>
            <value>西游记</value>
            <value>大话西游</value>
        </array>
    </property>
    <!--   list 注入-->
    <property name="hobbys">
        <list>
            <value>听歌</value>
            <value>看电影</value>
        </list>
    </property>
    <!--  map 注入-->
    <property name="card">
        <map>
            <entry key="no1" value="我是第一"/>
            <entry key="no2" value="我是第二"/>
            <entry key="no3" value="我是第三"/>
        </map>
    </property>
    <!--  set 注入-->
    <property name="games">
        <set>
            <value>1</value>
            <value>3</value>
            <value>3</value>
        </set>
    </property>
    <!--  properties 注入-->
    <property name="info">
        <props>
            <prop key="1">dasd</prop>
        </props>
    </property>
    <!--  null -->
    <property name="wife">
        <null></null>
    </property>
</bean>

4. 测试类

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");
        Student student = (Student) ctx.getBean("student");
        System.out.println(student.getAddress());
    }
}

7.3 拓展方式注入

我们可以使用 p 命名空间和 c 命名空间进行注入

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- p 命名空间   -->
    <bean id="user" class="com.lyq.pojo.User" p:name="user2" p:age="18"/>

    <!-- c 命名空间   -->
    <bean id="user2" class="com.lyq.pojo.User" c:age="19" c:name="校招"/>
</beans>

测试:

@Test
public void t(){
    ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
    User user = ctx.getBean("user", User.class);
    System.out.println(user);
}

@Test
public void t2(){
    ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
    User user = ctx.getBean("user2", User.class);
    System.out.println(user);
}

注意:p 命名空间和 c 命名空间不能直接使用,需要添加约束

       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"

7.4 bean 的作用域

image.png 1. 单例模式(Spring 默认机制)

<bean id="user2" class="com.lyq.pojo.User" c:age="19" c:name="校招" 
 scope="singleton"/>

2. 原型模式:每次从容器中 get 的时候,都会产生一个新对象!

<bean id="user2" class="com.lyq.pojo.User" c:age="19" c:name="校招" 
 scope="prototype"/>

3. 其余的 request、session、application 这些只能在 web 开发中使用

8、Bean 的自动装配

  • 自动装配是 Spring 满足 bean 依赖的一种方式
  • Spring 会在上下文中自动寻找,并自动给 bean 装配属性

在Spring 中有三种装配的的方式

  1. 在xml中显示的配置
  2. 在 java 中显示配置
  3. 隐式的自动装配 bean【重要】

8.1 测试

环境搭建:一个人有两个宠物

8.2 ByName 自动装配

<!--
    byName :会自动在容器上下文中查找,和自己对象 set 方法后面的值对应的 beanid!
    因为设置了 setDog 和 setCat ,byName 会自动在 xml 寻找
-->
<bean id="people" class="com.lyq.pojo.People" autowire="byName">
    <property name="name" value="李永奇"/>
</bean>

8.3 ByType 自动装配

<!--
    byName :会自动在容器上下文中查找,和自己对象 set 方法后面的值对应的 beanid!
    byType :会自动在容器上下文查找,和自己对象属性类型相同的
-->
<bean id="people" class="com.lyq.pojo.People" autowire="byType">
    <property name="name" value="李永奇"/>
</bean>

小结:

  • byName 的时候,需要保证所有 bean 的 id 唯一,并且这个 bean 需要和自动注入的属性的 set 方法值一致
  • byType 的时候,需要保证所有 bean 的 class 唯一,并且这个 bean 需要和自动注入的属性的类型一致

8.4 使用注解实现自动装配

1. 导入约束 : context 约束

xmlns:context="http://www.springframework.org/schema/context"

2. 配置注解的支持:context:annotation-config/

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">
    <context:annotation-config></context:annotation-config>
 </beans>

@Autowired

  • 直接在属性上使用即可!也可以在 set 方法上使用!
  • 使用@Autuwired可以省略写 set 方法,前提是需要自动装配的属性在 IOC(Spring)容器中存在,且符合名字 byName !

科普:

@Nullable  字段标记了这个注解,说明这个字段可以为 null

测试代码:

public class People {
    @Autowired(required = false)
    private Cat cat;
    @Autowired
    private Dog dog;
    private String name;
}

注意: 如果 @Autuwired自动装配的环境比较复杂,自动装配无法通过一个注解【@Autuwired】完成时,我们可以使用@Qualifier(value="xxx")去配置 @Autuwired 的使用,指定注入的 bean 对象

@Resource注解

public class People {
    @Resource(name = "cat")
    private Cat cat;
}

小结:

  • 都是用来自动装配的,都可以放在属性字段上
  • @Autowired 通过 byType 的方式实现
  • @Resource 默认通过 byName 的方式实现,如果找不到名字,则通过 byType 实现
  • 执行顺序不同:

9、使用注解开发

在 spring4 之后,必须要导入 aop 的包

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 指定要扫描的包,这个包下的注解就会生效 -->
    <context:component-scan base-package="com.lyq.dao"/>

</beans>

1. bean 2. 属性如何注入

@Component // 等价于在 xml 文件中写 <bean id="user">
public class User {
    // 等价于 <property name="name" value="lyq" />
    @Value("lyq ")
    public String name;
}

3. 衍生注解

@Component 有几个衍生注解,我们在 web 开发中,按照 mvc 三层架构分层

  • dao 【@Repository】
  • service 【@Service】
  • controller 【@Controller】

这四个注解功能一样,都是将某个类放到 spring 中,进行 bean 装配

4. 自动装配

@Autowired :自动装配通过类型,名字
    如果 Autowired 不能唯一自动装配上属性,则需要通过 @Qualifier(value="xxx")
@Nullable :字段标记了这个注解,表示可以为null
@Resource    自动装配通过名字,类型

5. 作用域(@Scope)

@Component
@Scope("property")
public class User {
    // 等价于 <property name="name" value="lyq" />
    @Value("lyq ")
    public String name;
}

6. 小结

xml 与 注解:

  • xml更加万能,使用任何场合
  • 注解不是自己类使用不了,维护复杂

xml 与 注解最佳实践:

  • 注解适用于属性注入
  • xml 用来管理 bean
  • 我们在使用过程中,只需要注意一个问题:必须让注解生效(先在xml扫描),开启注解支持

10、使用 java 方式配置 spring

lyqConfig.class 配置类

@Configuration
@ComponentScan("com.lyq.pojo")
@Import(UserConfig.class)
public class lyqConfig {
    @Value("lyq")
    private String name;

    public String getName() {
        return name;
    }

    @Bean
    public User getUser(){
        return new User();
    }
}

Test.class测试类

public static void main(String[] args) {
    ApplicationContext ctx = 
            new AnnotationConfigApplicationContext(lyqConfig.class);
    User getUser = ctx.getBean("getUser", User.class);
    System.out.println(getUser.name);
}

11. 代理模式

为什么要学习代理模式? 因为这个是 springAop 和 springmvc 的底层

代理模式的分类:

  • 动态代理
  • 静态代理 image.png

11.1 静态代理

角色分析:

  • 抽象角色:一般使用接口或者抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真实角色会,一般会做一些附魔操作
  • 客户:访问代理对象的人

代码步骤: 1. 接口

// 租房
public interface Rent {
    public void rent();
}

2. 真实角色

// 房东
public class Host implements Rent{

    public void rent() {
        System.out.println("房东出租");
    }
}

3. 代理角色

public class Proxy implements Rent{
    private Host host;

    public Proxy() {
    }

    public Proxy(Host host) {
        this.host = host;
    }

    public void rent() {
        seeHouse();
        host.rent();
        hetong();
        fare();
    }

    public void seeHouse(){
        System.out.println("中介带你看房");
    }
    public void fare(){
        System.out.println("中介费");
    }
    public void hetong(){
        System.out.println("签合同");
    }
}

4. 客户端访问代理角色

public class Client {
    public static void main(String[] args) {
        Host host = new Host();
        Proxy proxy = new Proxy(host);
        proxy.rent();
    }
}

代理模式的好处:

  • 可以使真实角色的操作更加纯粹!不用关注公共业务
  • 公共业务就交给代理角色!实现业务分工
  • 公共业务发生拓展的时候,方便集中管理

代理模式缺点:

  • 一个真实角色就会产生一个代理角色;代码量会翻倍

11.2 静态代理案例(给所有方法添加日志功能)

userService 接口

public interface UserService {
    void add();
    void delete();
    void update();
    void query();
}

UserServiceImpl 实现类

public class UserServiceImpl implements UserService{
    public void add() {
        System.out.println("增加");
    }

    public void delete() {
        System.out.println("删除");
    }

    public void update() {
        System.out.println("修改");
    }

    public void query() {
        System.out.println("查询");
    }
}

UserServiceProxy 代理类

public class UserServiceProxy implements UserService {
    private UserServiceImpl userService;

    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }

    public void add() {
        log("add");
        userService.add();
    }

    public void delete() {
        log("delete");
        userService.add();
    }

    public void update() {
        log("update");
        userService.add();
    }

    public void query() {
        log("query");
        userService.add();
    }
    // 日志方法
    public void log(String msg){
        System.out.println("使用了" + msg + "方法");
    }

}

11.3 动态代理

public class ProxyClass implements InvocationHandler {
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    // 用来执行方法
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());
        Object invoke = method.invoke(target, args);
        return invoke;
    }

    public Object getProxy(){
        // public static Object newProxyInstance(ClassLoader loader,
        //         Class<?>[] interfaces,
        //         InvocationHandler h)
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }

    public void log(String msg){
        System.out.println("执行了" + msg + "方法");
    }
}

12、AOP

12.1 使用原生 Spring APi 接口实现

application.xml

<aop:config>
    <!--切入点,要执行的位置-->
    <aop:pointcut id="pointcut" expression="execution(* com.lyq.service.Impl.UserServiceImpl.*(..))"/>
    <!--执行环绕-->
    <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
    <aop:advisor advice-ref="afterlog" pointcut-ref="pointcut"/>

</aop:config>

log类

public class AfterLog implements AfterReturningAdvice {
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println(method.getName() + "  " + o );
    }
}

afterLog类

public class log implements MethodBeforeAdvice {

    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "   " + method.getName());
    }
}

12.2 自定义 aop 类实现

application.xml

<bean id="diy" class="com.lyq.diy.DiyPointCut"/>
<aopL:config>
    <!-- 自定义切面 -->
    <aop:aspect ref="diy">
        <!-- 切入点 -->
        <aop:pointcut id="point" expression="execution(* com.lyq.service.*.*(..))"/>
        <!-- 通知 -->
        <aop:before method="before" pointcut-ref="point"/>
        <aop:after method="after" pointcut-ref="point"/>
    </aop:aspect>
</aopL:config>

DiyPointCut类

public class DiyPointCut {
    public void before(){
        System.out.println("===========方法执行前=========");
    }

    public void after() {
        System.out.println("==========方法执行后========");
    }
}

12.3 使用注解实现

<bean id="annotationPointCut" class="com.lyq.diy.AnnocationPointCunt"/>
<aop:aspectj-autoproxy/>
@Aspect
public class AnnocationPointCunt {
    @Before("execution(* com.lyq.service.*.*(..))")
    public void before(){
        System.out.println("方法执行前");
    }

    @After("execution(* com.lyq.service.*.*(..))")
    public void after(){
        System.out.println("方法执行后");
    }

    @Around("execution(* com.lyq.service.*.*(..))")
    public void aroud(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        Object proceed = jp.proceed();
        System.out.println("环绕后");
        System.out.println(jp.getSignature());
    }
}