Spring框架

Spring框架

课程目标

  1. 理解Spring框架的两大核心
  2. 掌握Spring框架三种依赖注入的实现方式
  3. 掌握Spring框架AOP的配置及使用

课程重点

  1. Spring框架的三种依赖注入的实现方式
  2. Spring框架AOP的配置及使用

1.Spring框架

1.1.Spring框架简介

Spring是一个基于java的轻量级的、一站式框架。 虽然Spring是一个轻量级框架,但并不表示它的功能少。实际上,spring是一个庞然大物,包罗万象。 时至今日,Spring已经成为java世界中事实上的标准。
Spring之父:Rod Johnson(罗德.约翰逊) 他是悉尼大学音乐学博士,而计算机仅仅是学士学位。 由于Rod对JAVAEE笨重、臃肿的现状深恶痛绝,以至于他将他在JAVAEE实战中的经历称为噩梦般的经历。他决定改变这种现状,于是就有了Spring。



J2EE development without EJB

1.2.Spring体系架构


Spring 总共大约有 20 个模块,由 1300 多个不同的文件构成。而这些组件被分别整合在6 个模块中:

  1. 核心容器(Core Container)
  2. AOP(Aspect Oriented Programming)
  3. 设备支持(Instrmentation)
  4. 数据访问及集成(Data Access/Integeration)
  5. Web报文发送(Messaging)
  6. Test测试

1.3.Spring两大核心

DI:依赖注入(Dependency Injection) AOP:面向切面编程(Aspect Oriented Programming)

2.DI(依赖注入)

依赖注入(Dependency Injection)是一种设计模式,也是Spring框架的核心概念之一。其作用是去除组件之间的依赖关系,实现解耦合。 也就是说:所谓依赖注入,是指工程中需要的组件无须自己创建,而是依赖于外部环境注入。
Spring实现容器管理依赖注入有三种方式:

  1. xml配置文件方式(了解)
    2.注解方式(官方推荐方式)
    3.JavaConfig方式。

2.1.使用xml配置文件实现

下面使用 Spring 来重构dao层组件与service层组件。 也就是说:由Spring创建dao层组件和service层组件,并使用Spring将dao层组件注入给service层组件。

2.1.1. 修改pom.xml添加Spring依赖

 <dependencies>
        <!-- 此依赖会关联引用Spring中的所有基础jar包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.15</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

2.1.2.创建日志配置文件

在 resources 文件夹中创建log4j.properties配置文件

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %m%n
log4j.rootLogger=debug,stdout

2.1.3.创建dao接口与实现类

public interface UserDao {
    void insert();
}
public interface UserDaoImpl implements UserDao {
    public void insert(){
    System.out.println("插入一行User数据");
   }
}

2.1.4.创建service接口与实现类

public interface UserService {
    public void addUser();
}
import com.neuedu.spring.di.dao.UserDao;
public class UserServiceImpl implements UserService{
    private UserDao dao;
    public void setDao(UserDao dao) {
        this.dao= dao;
    }
    @Override
    public  void addUser() {
         System.out.println("添加User业务");
         dao.insert();
    }
}

2.1.5.创建Spring配置文件

在resources目录下创建spring.xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        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
                    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-4.1.xsd">
  <bean id="userDao" class="com.neuedu.spring.di.dao.UserDaoImpl"></bean>
  <bean id="userService" class="com.neuedu.spring.di.service.UserServiceImpl">
    <property name="dao" ref="userDao"/>
  </bean>
</beans>
  1. Spring框架相当于一个容器。此容器中负责创建对象,并实现对象与对象之间的装配。
  2. java中每一个类都是一个bean。所以上面的bean标签,就是在容器中创建一个java对象。
  3. bean标签中的class属性,就是类名; id属性,就是对象名。
  4. property标签,是给bean的属性注入其它对象。name属性,就是对象属性名; ref属性,就是给属性注入的对象。(如果想要注入基本数据类型,那么使用value属性)
  5. 给bean的属性注入其它对象,默认使用 get/set 方法注入。也可以使用其它方式注入:构造方法注入、接口注入等详细参看

2.1.6. 测试

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.neuedu.spring.di.service.UserService;
public class XmlTest
{
    ApplicationContext context = null;
    @Before
    public void before() throws Exception {
        context = new  ClassPathXmlApplicationContext("spring.xml");
    }
    @Test 
    public void test() {
          UserService service = (UserService)context.getBean("userService");
          service.addUser();        
    }   
}

2.2.使用注解实现

注解(Annotation),也叫元数据。它是一种代码级别的说明,是jdk1.5之后引入的一个特性。
注解的作用:

  1. 编写文档:通过代码里标识的元数据生成文档。
  2. 代码分析:通过代码里标识的元数据对代码进行分析。
  3. 编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查。

2.2.1.修改dao实现类

@Component
public interface UserDaoImpl implements UserDao {
    public void insert(){
    System.out.println("插入一行User数据");
   }
}

@Component:此类的对象创建由Spring容器负责。 @Component("xxxx"):创建此类的对象,取一个对象名,并放入到Spring容器中。

2.2.2.修改Service实现类

@Component
public class UserServiceImpl implements UserService{
    @Autowired //自动装配
    private UserDao dao;
    //注意:dao属性自动注入,所以就可以不用get/set方法了
    public void setDao(UserDao dao) {
        this.dao= dao;
    }
    @Override
    public  void addUser() {
         System.out.println("添加User业务");
         dao.insert();
    }
}

@Autowired:默认按照类型在Spring容器寻找对象,并注入到属性中。 所以此时要注意:UserDao接口的实现类只能有一个。
@Resource注解 @Resource 是Java标准规范(JSR-250 JakartaEE)中定义的注解,用于进行依赖注入。
@Autowired和@Resource的区别:

  1. @Autowired默认按类型装配,@Resource默认是按名字装配 。
  2. @Autowire如果想使用名称装配可以结合@Qualifier注解进行使用 。
  3. 使用Spring框架时,建议使用@Autowired

2.2.3. 测试

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class AnnotationTest
{
    ApplicationContext context = null;
    @Before
    public void before() throws Exception {
        context = new  AnnotationConfigApplicationContext("com.neuedu.spring");
    }
    @Test 
    public void test() {
          UserService service = context.getBean(UserService.class);
          service.addUser();        
    }   
}

2.2.4.相关注解说明

2.2.4.1.组件级注解

除了@Component这个泛指组件的注解外,Spring还提供了与@Component功能相同的三个语义化注解。

  1. @Service 业务层组件
  2. @Controller 控制层组件
  3. @Repository 数据层组件
    修改上面代码,使用@Repository 和 @Service 替换 dao 与 service 组件上的注解。

2.2.4.2.Bean作用范围注解

@Scope注解:设置Bean的作用域。值如下:


2.3.使用JavaConfig实现

JavaConfig,是在 Spring 3.0 开始从一个独立的项目并入到 Spring 中的。javaConfig是一个用于完成 Bean配置的 java 类。
一个类中只要标注了@Configuration注解,这个类就可以为spring容器提供Bean定义的信息了,或者说这个类就成为一个spring容器了。
类中的每个标注了@Bean的方法都相当于提供了一个Bean的定义信息。

2.3.1.创建Config类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class AppConfig {
    @Bean
    public List<String> list() {
        return new ArrayList<>();
    }
   
}

2.3.2.测试

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.*;
public class  JavaConfigTest
{
    ApplicationContext context = null;
    @Before
    public void before() throws Exception {
        context = new  AnnotationConfigApplicationContext("com.neuedu.spring");
    }
    @Test 
    public void test() {
           List<String> list = context.getBean(List.class);
           System.out.print(list.hashCode());
    }   
}

2.4.IOC与DI

IOC:控制反转(Inversion of Control):它是一种控制权的转移。即组件与组件之间的依赖由主动变为被动。也就是说:应用程序本身不再负责组件的创建、维护等,而是将控制权移交出去。
DI:依赖注入(Dependency Injection):依赖其他容器(比如spring)来创建和维护所需要的组件,并将其注入到应用程序中。
IOC只是将组件控制权移交出去,但并没有说明组件如何获取。而DI明确说明:组件依赖Spring容器获取。 所以可以这样说:DI是IOC思想的一种具体实现。

3.AOP(面向切面)

AOP:全称是 Aspect Oriented Programming 即:面向切面编程。
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
即当需要扩展功能时,传统方式采用纵向继承方式实现。但这种方式有很多缺点。 比如:父类方法名称改变时,子类也要修改。给多个方法扩展功能时,子类也需要修改。 因此,spring的AOP,实际上是采用横向抽取机制,取代传统的纵向继承体系。
实现AOP示意图:
1.先将方面代码抽取出来



2.运行时将业务代码和方面代码编织在一起运行


3.1.使用注解方式实现AOP

3.1.1.抽取方面代码封装通知对象

在实际开发中,除了业务逻辑这个主要功能之外,还需要处理许多辅助功能。 比如:日志、异常处理、事务、输入验证、安全等等,我们将这些代码称为:方面代码。而方面代码,就是我们要抽取出来的。
下面抽取日志方面代码:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Component
@EnableAspectJAutoProxy  // 关键注解:启用AspectJ自动代理
@Aspect     //@Aspect定义此类为方面代码。
public class MyAspect {
    //通知方法 切入点
    @Before("execution(* com.neuedu.spring.di.service.*.*(..))")
    public void beforeMethod(JoinPoint joinpoint){
       System.out.println(joinpoint.getSignature().getName()  + "-开始执行");
    }
}
  1. @Aspect注解:定义此类为方面代码。
  2. @Before注解:定义一个前置通知。即在目标方法执行前切入此注解标注的方法。
  3. execution() 是一个Aspect表达式,语法为:execution(返回值类型 包名.类名.方法名 (参数) 异常)
    例如:execution(* com.neuedu.spring.di.service..(..))
  • 第一个 *:所有的返回值类型
  • 第二个 *:所有的类
  • 第三个 *:所有的方法
  • 第四个 .. :所有的参数

3.1.2.测试

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.neuedu.spring.di.service.UserService;
public class AopTest
{
    ApplicationContext context = null;
    @Before
    public void before() throws Exception {
        context = new  AnnotationConfigApplicationContext("com.neuedu.spring");
    }
    @Test 
    public void test() {
          UserService service = (UserService)context.getBean(UserService.class);
          service.addUser();
    }   
}

3.2.五种通知类型

方面代码一般也称为通知:定义一个“切面”要实现的功能。通知有五种:

  1. 前置通知:在某连接点(JoinPoint 就是要织入的业务方法)之前执行的通知。
  2. 后置通知:当某连接点退出时执行的通知(不论是正常结束还是发生异常)。
  3. 返回通知:(最终通知)在这里可以得到业务方法的返回值。但在发生异常时无法得到返回值。
  4. 环绕通知:包围一个连接点的通知,也就是在业务方法执行前和执行后执行的通知。
  5. 异常通知:在业务方法发生异常时执行的通知。
 import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Component
@EnableAspectJAutoProxy  // 关键注解:启用AspectJ自动代理
@Aspect     //@Aspect定义此类为方面代码。
public class MyAspect {
    //通知方法 切入点
    @Pointcut("execution(* com.neuedu.spring.di.service.*.*(..))")
    private void anyMethod(){}
    @Before("anyMethod()")
    public void beforeMethod(JoinPoint joinpoint){
        System.out.println( "前置通知" );
    }
   /*  @After("anyMethod()")
    public void afterMethod(JoinPoint joinpoint){
        System.out.println( "后置通知" );
    }
    @AfterReturning(pointcut="anyMethod()",returning="result")
    public void afterReturnning(JoinPoint joinpoint,Object result){
        System.out.println( "返回通知" );
    }
    @AfterThrowing(pointcut="anyMethod()",throwing="ex")
    public void afterThrowing(JoinPoint joinpoint,Exception ex){
        System.out.println( "抛出通知" );
    }
    @Around("anyMethod()")
    public Object aroundMethod(ProceedingJoinPoint pjp) {
        Object obj = null;
        try{
            System.out.println("环绕通知日志-1" );
            obj = pjp.proceed();
            System.out.println("环绕通知日志-2" );
        }catch(Throwable e){
            e.printStackTrace();
        }
        return obj;
    }*/
}
    

注意:有了环绕通知,异常通知也将失去作用

3.3.SpringAop内部实现

3.3.1.动态代理模式

动态代理是一种常用的设计模式,广泛应用于框架中,Spring框架的AOP特性就是应用动态代理实现的。



Spring框架采用两种形式动态代理:

  1. jdk动态代理:根据目标类接口获取代理类实现规则,生成代理对象。这个代理对象,也是目标类接口的一个实现类。详情参考
  2. cglib动态代理:根据目标类本身获取代理类实现规则,生成代理对象。这个代理对象,也是目标类的一个子类。 (如果目标类为final,则不能使用CGLib实现动态代理)详情参考
    SpringAOP使用规则如下:
  3. 如果目标对象实现了接口,采用jdk动态代理实现aop。
  4. 如果目标对象没有实现接口,采用CGLib动态代理实现aop。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容