1.软件框架技术简介

软件框架(software framework),通常指的是为了实现某个业界标准或完成特定基本任务的软件组件规范,也指为了实现某个软件组件规范时,提供规范所要求之基础功能的软件产品。
框架的功能类似于基础设施,与具体的软件应用无关,但是提供并实现最为基础的软件架构和体系。

  1. 为什么需要框架技术:
    1. 帮我们更快更好地构建程序
    2. 是一个应用程序的半成品
    3. 提供可重用的公共结构
    4. 按一定规则组织的一组组件
  2. 优势:
    1. 不用再考虑公共问题
    2. 专心在业务实现上
    3. 结构统一,易于学习、维护
    4. 新手也可写出好程序

      不要重复造轮子(Stop Trying to Reinvent the Wheel),已经成为开发人员的基本原则。

Java世界中的主流框架技术: Spring、SpringMVC、MyBatis、Struts、Hibernate、SpringBoot等。

2.Spring框架

2.1.Spring框架简介

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

2.2.Spring体系架构

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

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

    2.3.Spring两大核心

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

    3.DI(依赖注入)

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

    3.1.使用xml实现DI

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

    3.1.1.添加Spring依赖

    1. <project xmlns="http://maven.apache.org/POM/4.0.0"
    2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    4. <modelVersion>4.0.0</modelVersion>
    5. <groupId>com.neusoft</groupId>
    6. <artifactId>springtest</artifactId>
    7. <version>0.0.1-SNAPSHOT</version>
    8. <build>
    9. <plugins>
    10. <!-- 设置jdk版本 -->
    11. <plugin>
    12. <groupId>org.apache.maven.plugins</groupId>
    13. <artifactId>maven-compiler-plugin</artifactId>
    14. <configuration>
    15. <source>1.8</source>
    16. <target>1.8</target>
    17. <encoding>utf-8</encoding>
    18. </configuration>
    19. </plugin>
    20. </plugins>
    21. </build>
    22. <properties>
    23. <!-- spring 版本号 -->
    24. <spring.version>5.2.8.RELEASE</spring.version>
    25. </properties>
    26. <dependencies>
    27. <!-- 此依赖会关联引用Spring中的所有基础jar包 -->
    28. <dependency>
    29. <groupId>org.springframework</groupId>
    30. <artifactId>spring-context</artifactId>
    31. <version>${spring.version}</version>
    32. </dependency>
    33. </dependencies>
    34. </project>

    3.1.2.创建dao接口与实现类

    1. package com.neusoft.dao;
    2. import com.neusoft.po.User;
    3. public interface UserDao {
    4. public User getUser();
    5. }
    1. package com.neusoft.dao.impl;
    2. import com.neusoft.dao.UserDao;
    3. import com.neusoft.po.User;
    4. public class UserDaoImpl implements UserDao{
    5. @Override
    6. public User getUser() {
    7. return new User(1,"test","111");
    8. }
    9. }

    3.1.3.创建service接口与实现类

    1. package com.neusoft.service;
    2. import com.neusoft.po.User;
    3. public interface UserService {
    4. public User getUser();
    5. }
    1. package com.neusoft.service.impl;
    2. import com.neusoft.dao.UserDao;
    3. import com.neusoft.po.User;
    4. import com.neusoft.service.UserService;
    5. public class UserServiceImpl implements UserService{
    6. private UserDao userDao;
    7. @Override
    8. public User getUser() {
    9. return userDao.getUser();
    10. }
    11. public UserDao getUserDao() {
    12. return userDao;
    13. }
    14. public void setUserDao(UserDao userDao) {
    15. this.userDao = userDao;
    16. }
    17. }

    3.1.4.创建Spring配置文件

    在类路径下创建spring.xml配置文件:
    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <beans
    3. xmlns="http://www.springframework.org/schema/beans"
    4. xmlns:context="http://www.springframework.org/schema/context"
    5. xmlns:aop="http://www.springframework.org/schema/aop"
    6. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    7. xsi:schemaLocation="http://www.springframework.org/schema/beans
    8. http://www.springframework.org/schema/beans/spring-beans.xsd
    9. http://www.springframework.org/schema/context
    10. http://www.springframework.org/schema/context/spring-context.xsd
    11. http://www.springframework.org/schema/aop
    12. http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
    13. <bean id="userDao" class="com.neusoft.dao.impl.UserDaoImpl"></bean>
    14. <bean id="userService" class="com.neusoft.service.impl.UserServiceImpl">
    15. <property name="userDao" ref="userDao"/>
    16. </bean>
    17. </beans>
    1. Spring配置文件就相当于一个容器。此容器中负责创建对象,并实现对象与对象之间的装配。
    2. java中每一个类都是一个bean。所以上面的bean标签,就是在容器中创建一个java对象。
    3. bean标签中的class属性,就是类名; id属性,就是对象名。
    4. property标签,是给bean的属性注入其它对象。name属性,就是对象属性名; ref属性,就是给属性注入的对象。(如果想要注入基本数据类型,那么使用value属性)
    5. 给bean的属性注入其它对象,默认使用 get/set 方法注入。也可以使用其它方式注入:构造方法注入、P命名空间注入等。

3.1.5. 测试

  1. package com.neusoft;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. import com.neusoft.po.User;
  5. import com.neusoft.service.UserService;
  6. public class MySpringTest {
  7. public static void main(String[] args) {
  8. //读取Spring配置文件,获取Spring容器
  9. ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
  10. //通过Spring容器的getBean方法获得对象
  11. UserService service = (UserService)context.getBean("userService");
  12. User user = service.getUser();
  13. System.out.println(user);
  14. }
  15. }

3.3.使用注解实现DI

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

  1. 编写文档:通过代码里标识的元数据生成文档。
  2. 代码分析:通过代码里标识的元数据对代码进行分析。
  3. 编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查。
    1. //@Override就是一个编译检查注解
    2. @Override
    3. public int saveBusiness(String businessName) {}
    注解基本语法:@注解名称(属性=属性值)

    3.3.1.修改dao实现类

    1. package com.neusoft.dao.impl;
    2. import org.springframework.stereotype.Component;
    3. import com.neusoft.dao.UserDao;
    4. import com.neusoft.po.User;
    5. @Component
    6. public class UserDaoImpl implements UserDao{
    7. @Override
    8. public User getUser() {
    9. return new User(1,"test","111");
    10. }
    11. }

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

3.3.2.修改Service实现类

  1. package com.neusoft.service.impl;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.stereotype.Component;
  4. import com.neusoft.dao.UserDao;
  5. import com.neusoft.po.User;
  6. import com.neusoft.service.UserService;
  7. @Component("userService")
  8. public class UserServiceImpl implements UserService{
  9. @Autowired
  10. private UserDao userDao;
  11. @Override
  12. public User getUser() {
  13. return userDao.getUser();
  14. }
  15. //注意:UserDao属性自动注入,所以就可以不用get/set方法了
  16. }

@Autowired:默认按照类型在Spring容器寻找对象,并注入到属性中。 所以此时要注意:UserDao接口的实现类只能有一个。

3.3.3.修改Spring配置文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans
  3. xmlns="http://www.springframework.org/schema/beans"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xmlns:aop="http://www.springframework.org/schema/aop"
  6. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  7. xsi:schemaLocation="http://www.springframework.org/schema/beans
  8. http://www.springframework.org/schema/beans/spring-beans.xsd
  9. http://www.springframework.org/schema/context
  10. http://www.springframework.org/schema/context/spring-context.xsd
  11. http://www.springframework.org/schema/aop
  12. http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
  13. <!--开启注解扫描,设置需要扫描的包 -->
  14. <context:component-scan base-package="com.neusoft"/>
  15. </beans>

context:component-scan标签中的base-package属性,设置需要扫描的包。 会到指定包(包括指定包下的所有子包)中扫描类、方法、属性上面是否有注解。(如有多个,可使用逗号分隔)

3.3.4.测试

测试类没有变化。

  1. package com.neusoft;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. import com.neusoft.po.User;
  5. import com.neusoft.service.UserService;
  6. public class MySpringTest {
  7. public static void main(String[] args) {
  8. //读取Spring配置文件,获取Spring容器
  9. ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
  10. //通过Spring容器的getBean方法获得对象
  11. UserService service = (UserService)context.getBean("userService");
  12. User user = service.getUser();
  13. System.out.println(user);
  14. }
  15. }

3.3.5.相关注解说明

3.3.5.1.组件级注解

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

  1. @Service 业务层组件
  2. @Controller 控制层组件
  3. @Repository 数据层组件

修改上面代码,使用@Repository 和 @Service 替换 dao 与 service 组件上的注解。

  1. @Repository
  2. public class UserDaoImpl implements UserDao{ }
  1. @Service("userService")
  2. public class UserServiceImpl implements UserService{ }

3.3.5.2.Bean作用范围注解

@Scope() 注解:设置Bean的作用域。值如下:
Spring框架 - 图3
在UserServiceImpl中添加@Scope注解:

  1. @Service("userService")
  2. @Scope("prototype")
  3. public class UserServiceImpl implements UserService{ }

在测试类中测试多例:

  1. ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
  2. UserService service1 = (UserService)context.getBean("userService");
  3. UserService service2 = (UserService)context.getBean("userService");
  4. System.out.println(service1==service2); //false

3.3.5.3.自动注入组件注解

@Autowired 注解:默认按照类型自动给属性注入Spring容器中的对象。所以可以不取对象名。
如果需要按照对象名给属性注入,那么可以配合@Qualifier注解使用。

  1. 首先,给UserDao取对象名

    1. @Repository("userDao")
    2. public class UserDaoImpl implements UserDao{ }
  2. 使用@Qualifier通知@Autowired 注解,注入对象名为userDao的对象

    1. @Autowired
    2. @Qualifier("userDao")
    3. private UserDao userDao;

    3.4.使用javaConfig实现DI

    javaConfig,是在 Spring 3.0 开始从一个独立的项目并入到 Spring 中的。javaConfig 可以看成一个用于完成 Bean 装配的 Spring 配置文件,即 Spring 容器,只不过该容器不是 XML文件,而是由程序员使用 java 自己编写的 java 类。
    一个类中只要标注了@Configuration注解,这个类就可以为spring容器提供Bean定义的信息了,或者说这个类就成为一个spring容器了。
    标注了@Configuration和标注了@Component的类一样是一个Bean,可以被Spring的 context:component-scan 标签扫描到。类中的每个标注了@Bean的方法都相当于提供了一个Bean的定义信息。

    3.4.1.创建javaConfig类

    1. package com.neusoft;
    2. import org.springframework.beans.factory.annotation.Configurable;
    3. import org.springframework.context.annotation.Bean;
    4. import org.springframework.context.annotation.Configuration;
    5. import com.neusoft.dao.UserDao;
    6. import com.neusoft.dao.impl.UserDaoImpl;
    7. import com.neusoft.service.UserService;
    8. import com.neusoft.service.impl.UserServiceImpl;
    9. @Configuration
    10. public class AppConfig {
    11. @Bean
    12. public UserDao userDao() {
    13. return new UserDaoImpl();
    14. }
    15. @Bean
    16. public UserService userService() {
    17. //这里不能声明接口类型
    18. UserServiceImpl userService = new UserServiceImpl();
    19. //配置依赖关系(需要set方法)
    20. userService.setUserDao(userDao());
    21. return userService;
    22. }
    23. }

    3.4.2.测试

    1. ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
    2. UserService service = (UserService)context.getBean("userService");
    3. User user = service.getUser();
    4. System.out.println(user);

    3.5.BeanFactory与ApplicationContext

    获取Spring容器也可以使用BeanFactory接口。BeanFactory与ApplicationContext的区别是:

  3. BeanFactory
    Bean工厂(org.springframework.beans.factory.BeanFactory)是Spring框架最核心的接口,提供了IoC的配置机制,使管理不同类型的Java对象成为可能。特点是:采用延迟加载Bean,直到第一次使用Bean实例时才会创建Bean。

  4. ApplicationContext
    应用上下文(org.springframework.context.ApplicationContext),继承自BeanFactory,提供了更多面向应用的功能,比如国际化支持、框架事件体系,更易于创建实际应用。

应用时,我们一般称BeanFactory为IoC容器,ApplicationContext为应用上下文:

  1. BeanFactory是Spring框架的基础设施,面向Spring本身;
  2. ApplicationContext面向使用Spring框架的开发者;
  3. 几乎所有的应用场合都可以直接使用ApplicationContext而非底层的BeanFactory;

    3.6.IOC与DI

    IOC:控制反转(Inversion of Control):它是一种控制权的转移。即组件与组件之间的依赖由主动变为被动。也就是说:应用程序本身不再负责组件的创建、维护等,而是将控制权移交出去。从这一点来说,几乎所有的框架都是IOC框架。
    DI:依赖注入(Dependency Injection):依赖其他容器(比如spring)来创建和维护所需要的组件,并将其注入到应用程序中。

    IOC只是将组件控制权移交出去,但并没有说明组件如何获取。而DI明确说明:组件依赖Spring容器获取。 所以可以这样说:DI是IOC思想的一种具体实现。

4.AOP(面向切面)

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

  1. 先将方面代码抽取出来
    Spring框架 - 图4
  2. 运行时将业务代码和方面代码编织在一起
    Spring框架 - 图5

    Spring AOP 可以使用xml文件形式实现,也可以使用注解方式实现。下面只使用注解方式实现Spring AOP。

4.1.使用注解方式实现AOP

下面例子功能为:在运行业务方法前,输出一段日志。

4.1.1.添加aspectj依赖

Aspectj是一个基于java的、面向切面的AOP框架。Spring2.0之后增加了对Aspectj切点表达式的支持。而实际开发中一般都使用Aspectj方式来实现AOP。所以还要导入Aspectj相关jar包。

  1. <dependency>
  2. <groupId>org.aspectj</groupId>
  3. <artifactId>aspectjweaver</artifactId>
  4. <version>1.8.7</version>
  5. </dependency>

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

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

  1. package com.neusoft.advice;
  2. import org.aspectj.lang.JoinPoint;
  3. import org.aspectj.lang.annotation.Aspect;
  4. import org.aspectj.lang.annotation.Before;
  5. import org.springframework.stereotype.Component;
  6. @Component
  7. @Aspect //@Aspect定义此类为方面代码,即是一个通知。
  8. public class MyAdvice {
  9. @Before("execution(* com.neusoft.service.impl.*.*(..))")
  10. public void beforeMethod(JoinPoint joinpoint){
  11. System.out.println("【前置通知日志】" + joinpoint.toString());
  12. }
  13. }
  1. @Aspect注解:定义此类为方面代码,即是一个通知。
  2. @Before注解:定义一个前置通知。即在目标方法执行前切入此注解标注的方法。
  3. execution() 是一个Aspect表达式,语法为:execution(返回值类型 包名.类名.方法名 (参数) 异常)
  1. /*
  2. * 例如:execution(* com.neusoft.service.impl.*.*(..))
  3. * 第一个 *:所有的返回值类型
  4. * 第二个 *:所有的类
  5. * 第三个 *:所有的方法
  6. * 第四个 .. :所有的参数
  7. */

4.1.3.修改Spring配置文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans
  3. xmlns="http://www.springframework.org/schema/beans"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xmlns:aop="http://www.springframework.org/schema/aop"
  6. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  7. xsi:schemaLocation="http://www.springframework.org/schema/beans
  8. http://www.springframework.org/schema/beans/spring-beans.xsd
  9. http://www.springframework.org/schema/context
  10. http://www.springframework.org/schema/context/spring-context.xsd
  11. http://www.springframework.org/schema/aop
  12. http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
  13. <!--开启注解扫描,设置需要扫描的包 -->
  14. <context:component-scan base-package="com.neusoft"/>
  15. <!-- 声明自动为spring容器中那些配置@Aspect切面的bean创建代理,织入切面。 -->
  16. <aop:aspectj-autoproxy/>
  17. </beans>

aop:aspectj-autoproxy标签:声明自动为spring容器中那些配置@Aspect切面的bean创建代理,织入切面。

4.1.4.测试

  1. public static void main(String[] args) {
  2. //读取Spring配置文件,获取Spring容器
  3. ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
  4. //通过Spring容器的getBean方法获得对象
  5. UserService service = (UserService)context.getBean("userService");
  6. User user = service.getUser();
  7. System.out.println(user);
  8. }

4.2.五种通知类型

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

  1. 前置通知:在某连接点(JoinPoint 就是要织入的业务方法)之前执行的通知。
  2. 后置通知:当某连接点退出时执行的通知(不论是正常结束还是发生异常)。
  3. 返回通知:(最终通知)在这里可以得到业务方法的返回值。但在发生异常时无法得到返回值。
  4. 环绕通知:包围一个连接点的通知,也就是在业务方法执行前和执行后执行的通知。
  5. 异常通知:在业务方法发生异常时执行的通知。
    1. package com.neusoft.advice;
    2. import org.aspectj.lang.JoinPoint;
    3. import org.aspectj.lang.ProceedingJoinPoint;
    4. import org.aspectj.lang.annotation.After;
    5. import org.aspectj.lang.annotation.AfterReturning;
    6. import org.aspectj.lang.annotation.AfterThrowing;
    7. import org.aspectj.lang.annotation.Around;
    8. import org.aspectj.lang.annotation.Aspect;
    9. import org.aspectj.lang.annotation.Before;
    10. import org.aspectj.lang.annotation.Pointcut;
    11. import org.springframework.stereotype.Component;
    12. @Component
    13. @Aspect
    14. public class MyAdvice {
    15. //定义通用Aspect表达式,下面通知方法就可以引用此方法的规则了
    16. @Pointcut("execution(* com.neusoft.service.impl.*.*(..))")
    17. private void anyMethod(){}
    18. @Before("anyMethod()")
    19. public void beforeMethod(JoinPoint joinpoint){
    20. System.out.println("【前置通知日志】" + joinpoint.toString());
    21. }
    22. @After("anyMethod()")
    23. public void afterMethod(JoinPoint joinpoint){
    24. System.out.println("后置通知日志" + joinpoint.toString());
    25. }
    26. @AfterReturning(pointcut="anyMethod()",returning="result")
    27. public void afterReturnning(JoinPoint joinpoint,Object result){
    28. System.out.println("返回通知日志" + joinpoint.toString());
    29. }
    30. @AfterThrowing(pointcut="anyMethod()",throwing="ex")
    31. public void afterThrowing(JoinPoint joinpoint,Exception ex){
    32. System.out.println("异常通知日志" + joinpoint.toString());
    33. }
    34. @Around("anyMethod()")
    35. public Object aroundMethod(ProceedingJoinPoint pjp) {
    36. Object obj = null;
    37. try{
    38. System.out.println("环绕通知日志" + pjp.toString());
    39. obj = pjp.proceed();
    40. }catch(Throwable e){
    41. e.printStackTrace();
    42. }
    43. return obj;
    44. }
    45. }

    注意:

    1. 如果配置了环绕通知,那么业务方法的执行将在环绕通知中的obj = pjp.proceed();这段代码时开始执行。此时要注意:如果环绕通知方法不写返回值,或者obj = pjp.proceed()这段代码如果不取得返回值,那么返回通知也不能取得返回值。
    2. 有了环绕通知,异常通知也将失去作用。
    3. 实际上,如果要配置环绕通知,那么其他通知就失去意义了。因为在环绕通知中,也可以在方法执行前、执行后做方面代码,包括获取返回值、做异常处理等。

4.3.Spring动态代理的两种形式

4.3.1.两种动态代理

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

  1. jdk动态代理:根据目标类接口获取代理类实现规则,生成代理对象。这个代理对象,也是目标类接口的一个实现类。
  2. cglib动态代理:根据目标类本身获取代理类实现规则,生成代理对象。这个代理对象,也是目标类的一个子类。 (如果目标类为final,则不能使用CGLib实现动态代理)

SpringAOP可以自动在jdk动态代理和CGLib动态代理之间进行切换,规则如下:

  1. 如果目标对象实现了接口,采用jdk动态代理实现aop。
  2. 如果目标对象没有实现接口,采用CGLib动态代理实现aop。
  3. 如果目标对象实现了接口,但仍然想要使用CGLIB实现aop,可以手动进行配置。

    4.3.2.性能测试

    我们通过获取执行代码所耗费的时间,来实际测试两种动态代理方式的性能对比。

  4. jdk动态代理测试:

    1. public static void main(String[] args) {
    2. ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
    3. long begin = System.currentTimeMillis();
    4. //使用接口
    5. UserService service = (UserService)context.getBean("userService");
    6. User user = service.getUser();
    7. long end = System.currentTimeMillis();
    8. System.out.println("执行时间:"+(end-begin));
    9. }
  5. cglib动态代理测试:

    1. public static void main(String[] args) {
    2. ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
    3. long begin = System.currentTimeMillis();
    4. //不使用接口
    5. UserServiceImpl service = (UserServiceImpl)context.getBean("userService");
    6. User user = service.getUser();
    7. long end = System.currentTimeMillis();
    8. System.out.println("执行时间:"+(end-begin));
    9. }

    注意:

    1. dao层不能再实现接口。
    2. service层不能再实现接口。

5.Spring集成MyBatis

所谓的Spring集成MyBatis,就是由Spring给MyBatis注入需要的组件。比如:DataSource(数据源)、SqlSessionFactory、sqlSession、事务管理对象等。

5.1.添加集成MyBatis的依赖

  1. <properties>
  2. <spring.version>5.2.8.RELEASE</spring.version>
  3. </properties>
  4. <dependencies>
  5. <dependency>
  6. <groupId>org.springframework</groupId>
  7. <artifactId>spring-context</artifactId>
  8. <version>${spring.version}</version>
  9. </dependency>
  10. <!--spring对jdbc的支持依赖包(关联spring-tx包) -->
  11. <dependency>
  12. <groupId>org.springframework</groupId>
  13. <artifactId>spring-jdbc</artifactId>
  14. <version>${spring.version}</version>
  15. </dependency>
  16. <!-- mybatis-spring 依赖包 -->
  17. <dependency>
  18. <groupId>org.mybatis</groupId>
  19. <artifactId>mybatis-spring</artifactId>
  20. <version>2.0.0</version>
  21. </dependency>
  22. <!--mybatis 依赖包 -->
  23. <dependency>
  24. <groupId>org.mybatis</groupId>
  25. <artifactId>mybatis</artifactId>
  26. <version>3.5.1</version>
  27. </dependency>
  28. <!-- mysql驱动 依赖包 -->
  29. <dependency>
  30. <groupId>mysql</groupId>
  31. <artifactId>mysql-connector-java</artifactId>
  32. <version>5.1.6</version>
  33. </dependency>
  34. </dependencies>

5.2.创建Mapper

  1. package com.neusoft.ms.mapper;
  2. import org.apache.ibatis.annotations.Mapper;
  3. import org.apache.ibatis.annotations.Select;
  4. import org.apache.ibatis.annotations.Update;
  5. import com.neusoft.ms.po.Emp;
  6. @Mapper
  7. public interface EmpMapper {
  8. @Select("select * from emp where empno=#{empno}")
  9. public Emp getEmpById(int empno);
  10. }

在Mapper接口上添加@Mapper注解,Spring容器扫描到此注解后,就会将SqlSession注入给Mapper组件。

5.3.添加db.properties文件

  1. jdbc.driver=com.mysql.jdbc.Driver
  2. jdbc.url=jdbc:mysql://localhost:3306/emp?characterEncoding=utf-8
  3. jdbc.username=root
  4. jdbc.password=123

5.4.Spring容器中添加组件

  1. <!--开启注解扫描,设置需要扫描的包(用于扫描测试时用的Service组件) -->
  2. <context:component-scan base-package="com.neusoft.ms"/>
  3. <!-- 引入db配置文件 -->
  4. <context:property-placeholder location="classpath:db.properties" />
  5. <!-- 配置dataSource数据源 -->
  6. <bean id="dataSource"
  7. class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  8. <property name="driverClassName" value="${jdbc.driver}"></property>
  9. <property name="url" value="${jdbc.url}"></property>
  10. <property name="username" value="${jdbc.username}"></property>
  11. <property name="password" value="${jdbc.password}"></property>
  12. </bean>
  13. <!-- 创建SqlSessionFactory,并配置实体对象别名 -->
  14. <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  15. <property name="dataSource" ref="dataSource"></property>
  16. <property name="typeAliasesPackage" value="com.neusoft.ms.po" />
  17. </bean>
  18. <!-- 配置Mapper,自动扫描Mapper接口,并为其注入SqlSessionFactory -->
  19. <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  20. <property name="basePackage" value="com.neusoft.ms.mapper"></property>
  21. <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
  22. </bean>

这里,配置了sqlSessionFactory组件,并将sqlSessionFactory组件注入给Mapper组件。

5.5.测试

创建Service组件,然后测试

  1. @Service("empService")
  2. public class EmpServiceImpl implements EmpService{
  3. @Autowired
  4. private EmpMapper empMapper;
  5. @Override
  6. public Emp getEmpById(int empno) {
  7. return empMapper.getEmpById(empno);
  8. }
  9. }
  1. public class MsTest {
  2. public static void main(String[] args) {
  3. ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
  4. EmpService service = (EmpService)context.getBean("empService");
  5. Emp emp = service.getEmpById(7369);
  6. System.out.println(emp);
  7. }
  8. }

5.6.添加事务管理

Spring为了支持事务管理,专门封装了事务管理对象。我们只要在Spring容器中配置这个对象,即可使用。
在Spring容器中添加事务管理的配置:

  1. <!-- 配置Spring提供的事务管理器 -->
  2. <bean id="transactionManager"
  3. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  4. <property name="dataSource" ref="dataSource"></property>
  5. </bean>
  6. <!-- 开启注解事务 -->
  7. <tx:annotation-driven transaction-manager="transactionManager" />

在Service组件中,使用@Transactional注解,就可以给业务方法添加事务管理。

  1. @Transactional
  2. public int transferAccounts(Emp emp1,Emp emp2) {
  3. //需要事务管理的业务
  4. }

注意:

  1. 需要事务管理的service,在方法上加上@Transactional 注解即可。
  2. 必须为public方法才行,不要捕捉异常,要让异常自动抛出,否则不能进行事务回滚。

5.7.事务传播行为

@Transactional 注解中的 propagation 属性,可以设置事务传播行为。属性值为:

  1. REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,就加入到这个事务中。这是最常见的选择。
  2. SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
  3. MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。
  4. REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
  5. NOT_SUPPORTED:以非事务方式执行操作,如果存在事务,就把当前事务挂起。
  6. NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

    1. @Override
    2. @Transactional(propagation=Propagation.SUPPORTS)
    3. public Dept getDeptById(Integer deptno) {
    4. return deptMapper.getDeptById(deptno);
    5. }


    本章作业

  7. 列举一些重要的Spring模块?

  8. Spring中的bean的作用域有哪些?
  9. 什么是Spring中的单例bean的线程安全问题?
  10. @Component和@Bean的区别是什么?
  11. 将一个类声明为Spring的bean的注解有哪些?
  12. Spring事务管理的方式有几种?
  13. 有哪些不同类型的依赖注入方式?
  14. 编程题:使用Spring+MyBatis完成转账功能:
    1. 模拟MVC模式的三个组件:数据层组件、业务层组件、控制层组件。
    2. 数据层组件使用MyBatis框架。
    3. 将数据层组件注入给业务层组件,将业务层组件注入给控制层组件。最后,通过Spring容器获取控制层组件。
    4. 业务层组件调用数据层组件,完成转账功能。
    5. 转账功能必须要进行事务管理,所以要给业务层组件切入事务管理。