Spring详解

一、Spring框架

1.1.原生web开发中存在哪些问题?

  • 传统Web开发存在硬编码所造成的过度程序耦合(例如:Service中作为属性Dao对象)。

  • 部分Java EE API较为复杂,使用效率低(例如:JDBC开发步骤)。

  • 侵入性强,移植性差(例如:DAO实现的更换,从Connection到SqlSession)。

1.2.什么是Spring

Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器(框架)

  • Spring是一个项目管理框架,同时也是一套Java EE解决方案。

  • Spring是众多优秀设计模式的组合(工厂、单例、代理、适配器、包装器、观察者、模板、策略)。

  • Spring并未替代现有框架产品,而是将众多框架进行有机整合,简化企业级开发,俗称"胶水框架"。

1.3.Spring架构组成

Spring架构由诸多模块组成,可分类为

  • 核心技术:依赖注入,事件,资源,i18n,验证,数据绑定,类型转换,SpEL,AOP。

  • 测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient。

  • 数据访问:事务,DAO支持,JDBC,ORM,封送XML。

  • Spring MVC和 Spring WebFlux Web框架。 

  • 集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。

  • 语言:Kotlin,Groovy,动态语言。

Spring详解_第1张图片

GroupId ArtifactId 说明
org.springframework spring-beans Beans 支持,包含 Groovy
org.springframework spring-aop 基于代理的AOP支持
org.springframework spring-aspects 基于AspectJ 的切面
org.springframework spring-context 应用上下文运行时,包括调度和远程抽象
org.springframework spring-context-support 支持将常见的第三方类库集成到 Spring 应用上下文
org.springframework spring-core 其他模块所依赖的核心模块
org.springframework spring-expression Spring 表达式语言,SpEL
org.springframework spring-instrument JVM 引导的仪表(监测器)代理
org.springframework spring-instrument-tomcat Tomcat 的仪表(监测器)代理
org.springframework spring-jdbc 支持包括数据源设置和 JDBC 访问支持
org.springframework spring-jms 支持包括发送/接收JMS消息的助手类
org.springframework spring-messaging 对消息架构和协议的支持
org.springframework spring-orm 对象/关系映射,包括对 JPA 和 Hibernate 的支持
org.springframework spring-oxm 对象/XML 映射(Object/XML Mapping,OXM)
org.springframework spring-test 单元测试和集成测试支持组件
org.springframework spring-tx 事务基础组件,包括对 DAO 的支持及 JCA 的集成
org.springframework spring-web web支持包,包括客户端及web远程调用
org.springframework spring-webmvc REST web 服务及 web 应用的 MVC 实现
org.springframework spring-webmvc-portlet 用于 Portlet 环境的MVC实现
org.springframework spring-websocket WebSocket 和 SockJS 实现,包括对 STOMP 的支持
org.springframework spring-jcl Jakarta Commons Logging 日志系统

 二、Spring环境搭建

2.1.构建maven项目

2.1.1新建项目

使用IDEA打开已创建的文件夹目录
Spring详解_第2张图片

2.1.2选择Maven依赖 

选择Maven项目
Spring详解_第3张图片

2.1.3GAV坐标 

GAV坐标
Spring详解_第4张图片

2.2pom.xml中引入Spring常用依赖

    
        
        
            org.springframework
            spring-context
            5.2.14.RELEASE
        

        
        
            junit
            junit
            4.13.2
            test
        

        
        
            org.projectlombok
            lombok
            1.18.22
        
    

2.3创建Spring配置文件

  • 位置: resources下

  • 格式: xml

  • 名称: 一般叫做applicationContext.xml、beans.xml、spring-context.xml、app.xml




    

三、Spring工厂编码

定义Bean类型。

public class MyClass{
    public void show(){
        System.out.println("HelloWorld");
    }
}

applicationContext.xml中的< beans >内部配置bean标签。

  • id:唯一标识
  • class:需要被创建的目标对象全限定名

        调用Spring工厂API(ApplicationContext接口) 

  • 程序中的对象都交由Spring的ApplicationContext工厂进行创建。
public class TestFactory{
    /**
     * 程序中的对象都交由Spring的ApplicationContext工厂进行创建。
     */
    public static void main(String[] args){
        //1. 读取配置文件中所需创建的bean对象,并获得工厂对象
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        //2. 通过id获取bean对象
		MyClass myClass = (MyClass) context .getBean("mc");

        //3. 使用对象
		myClass .show();
    }
}

四、依赖与配置文件详解

Spring框架包含多个模块,每个模块各司其职,可结合需求引入相关依赖Jar包实现功能。  

4.1 Spring依赖关系

Spring常用功能的Jar包依赖关系
Spring详解_第5张图片

注意:Jar包彼此存在依赖,只需引入最外层Jar即可由Maven自动将相关依赖Jar引入到项目中。

4.2 schema

配置文件中的顶级标签中包含了语义化标签的相关信息

  • xmlns:语义化标签所在的命名空间。

  • xmlns:xsi:XMLSchema-instance 标签遵循Schema标签标准。

  • xsi:schemaLocation:xsd文件位置,用以描述标签语义、属性、取值范围等。

五、IOC(Inversion of Control)控制反转

IOC控制反转,负责创建对象,管理对象,依赖注入的方式将创建的对象和对象之间的维护交由Spring管理。

Spring:Spring容器使用工厂模式创建对象,不用自己创建,直接调用即可。

  • 反转了依赖关系的满足方式,由之前的自己创建依赖对象,变为由工厂推送。(变主动为被动,即反转)

5.1 项目中强耦合问题

public interface UserDao {
    void findUserById();
}

public class UserDaoImpl implements UserDao {
    @Override
    public void findUserById() {
        System.out.println("UserDaoImpl执行了");
    }
}
public interface UserService {
    void findUserById();
}

public class UserServiceImpl implements UserService {

    // 强耦合了UserDAOImp!,使得UserServiceImpl变得不稳健!
    private UserDao userDao= new UserDaoImpl();

    @Override
    public void queryUser() {
        userDao.findUserById();
        System.out.println("UserServiceImpl执行了");
    }
    ....
}

 5.2 解决方案

public interface UserService {
    void findUserById();
}

// 不引用任何一个具体的组件(实现类),在需要其他组件的位置预留存取值入口(set/get)
public class UserServiceImpl implements UserService {

    // 不再耦合任何Dao实现!消除不稳健因素!
    private UserDao userDao;
    // 为userDao定义set/get方法,允许userDao属性接受spring赋值
    // Getters And Setters

    @Override
    public void findUserById() {
        System.out.println("UserServiceImpl.findUserById()执行" );
    }
    ....
}

 5.3 applicaionContext.xml配置 

  • 一个bean标签代表一个类,会创建该类对象
  • class:需要被创建的目标对象全限定名
  • id:创建完成后的对象名


    

    
    

    
    

    
    
    

此时,如果需要更换其他UserDao实现类,则UserServiceImpl不用任何改动!

则此时的UserServiceImpl组件变得更加稳健!

5.4测试

public class TsetIOC {
    @Test
    public void test(){
        String path = "applicationContext.xml";
        // 根据配置文件创建出spring容器
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(path);

        // 从容器中取出对象(根据对象名获得对象,配置文件中bean的id)
        UserService userService = (UserService) context.getBean("userService");
        userService.findUserById();
    }
}

Spring详解_第6张图片

六、DI(Dependency Injection)依赖注入

依赖注入:在Spring创建对象的同时,为其属性赋值,称之为依赖注入。

6.1 Set注入

创建对象时,Spring工厂会通过Set方法为对象的属性赋值。

6.1.2 基本类型+日期类型

创建实体类。

public class User {

    private int id;

    private String name;

    private Date birthday;

    private double score;
    
    // get/set
}
    
        
        
        
        
        
        
     

 6.1.3 容器类型

创建实体类。

public class Info {
    private String[] phone;

    private List list;

    private Set set;

    private Map map;
    // get/set
}
    
        
        
            
                110
                119
                120
            
        

        
        
            
                沃尔沃
                魏派
                魏派
                大众
            
        

        
        
            
                华为
                小米
                苹果
                苹果
            
        

        
        
            
                
                
                
            
        
    

6.1.4 对象类型 

public class Object {
    /**
     * 对象类型
     * 在Object类里给User赋值属性
     */
    // 将上面User类当做Object类的属性
    private User user;
    // set/get
}
    
    
        
        
    

6.2构造注入

创建对象时,Spring工厂会通过构造方法为对象的属性赋值。

6.2.1注入

创建实体类。 

public class Student {
    private Integer id;
    private String name;
    private String sex;
    private Integer age;
  
    // 创建有参构造方法 Constructors
  	public Student(Integer id , String name , String sex , Integer age){
      	this.id = id;
    	this.name = name;
  	    this.sex = sex;
	    this.age = age;
    }
}
 


    
     
    
    
    

6.3自动注入

不用在配置中指定为哪个属性赋值,及赋什么值.

由spring自动根据某个 "原则" ,在工厂中查找一个bean,为属性注入属性值。

public interface UserDao {
    void findUserById();
}

public class UserDaoImpl implements UserDao {
    @Override
    public void findUserById() {
        System.out.println("UserDaoImpl执行了");
    }
}
public interface UserService {
    void findUserById();
}

public class UserServiceImpl implements UserService {
    private UserDao userDao;

    // get/set方法
    @Override
    public void findUserById() {
        userDao.findUserById();
        System.out.println("UserServiceImpl执行了");
    }
}

6.3.1 byName注入

applicationContext.xml文件配置。 

    
    
    
    
    

 6.3.1 byType注入

applicationContext.xml文件配置。 

    
    
    
    
    

       总结:

  • byName: 通过属性名和容器中对象名一致,即可自动注入
  • byType: 属性类型和容器中对象的类型一致,即可自动注入

七、注解实现IOC-DI

上面我们都是使用xml配置实现IOC和DI,以后将使用注解实现IOC和DI,即XML中不再配置很多标签了。

7.1 常用注解

注解 作用 被替代标签 位置
@Component 创建对象 类上
@Controller 创建对象 控制层的类上
@Service 创建对象 业务层的类上
@Repository 创建对象 持久层的类上
@Value 给基本类型属性赋值 属性上
@Autowired 给引用类型属性赋值 autowired的属性 属性上

注意:@Component、@Controller、@Service、@Repository都是用来创建对象,只不过建议是在相应的位置使用相应的注解。

7.2 注解实现DI

@Component:默认对象名是类名小写

可以通过@Component(value="user")来指定对象名,但在测试中getBean("user")两个对象名要一致。

@Component // 替代,默认对象名就是类名小写
public class User {
    @Value("109")
    private int id;

    @Value("YMK")
    private String name;

    @Value("1999/12/28")
    private Date birthday;

    @Value("95.2")
    private double score;
    
    // get、set、toString
}

applicationContext.xml开启注解,让注解生效。




    
    
    @Test
    public void test() {
        // 根据配置文件创建出spring容器
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext3.xml");

        // 从容器中取出对象(默认是类名小写)
        User user = (User)context.getBean("user");
        System.out.println(user);
    }

7.3 注解实现IOC

@Scope:用户控制bean的创建模式,默认是单例模式。

@Scope("singleton"):设置单例模式

@Scope("prototype"):设置多例模式

public interface UserDao {
    void findUserById();
}

@Repository // 创建对象,取代
public class UserDaoImpl implements UserDao {
    @Override
    public void findUserById() {
        System.out.println("UserDaoImpl执行了");
    }
}
public interface UserService {
    void findUserById();
}

@Service  // 创建对象,取代
@Scope("singleton") // 声明创建模式,默认为单例模式 @Scope("prototype")可设置多例模式
public class UserServiceImpl implements UserService {

    @Autowired  // 自动注入,按照类型注入byType
    private UserDao userDao;

    @Override
    public void findUserById() {
        userDao.findUserById();
        System.out.println("UserServiceImpl执行了");
    }
}

applicationContext.xml开启注解,让注解生效。

    
    

@autowired:自动注入,按照类型注入

按类型注入多个同类型,注入失败怎么解决?

  • 当同类型的对象有多个时,无法自动注入,需要手动指定注入那个对象
  • @Qualifier("userDaoImpl2"):限定要自动注入的bean的id,一般和@Autowired联用
@Service
public class UserServiceImpl implements UserService {

    @Autowired  // 自动注入,按照类型注入

    // 当同类型的对象有多个时,需要使用@Qualifier指定注入的对象名
    @Qualifier("userDaoImpl2")
    private UserDao userDao;
    // ...   
}

八、Bean细节

8.1 控制简单对象的单例、多例模式

配置< bean scope="singleton | prototype"/> 

  • singleton(默认):每次调用工厂,得到的都是同一对象
  • prototype:每次调用工厂,都会创建新的对象

 

 注意:需要根据场景决定对象的单例、多例模式。

  • 可以共用:Service、Dao、SQLSessionFactory(或者是所有的工厂)
  • 不可共用:Connection、SqlSession、ShoppingCart

8.2 FactoryBean创建复杂对象

作用:让Spring可以创建复杂对象,或者无法直接通过反射创建的对象。

FactoryBean解决复杂对象创建
Spring详解_第7张图片

8.2.1 实现FactoryBean接口

接口方法描述
Spring详解_第8张图片

注意:isSingleton方法的返回值,需根据所创建对象的特点决定返回true/false。

  • 例:Connection 不应该被多个用户共享,返回false。
  • 例:SqlSessionFactory 重量级资源,不该过多创建,返回true。

8.2.2 配置applicationContext.xml

配置与获取方式
Spring详解_第9张图片

8.2.3 特例

获取FactoryBean接口的实现类对象,而非getObject()所生产的对象
Spring详解_第10张图片

九、Spring工厂特性

9.1 饿汉式创建优势

  • 工厂创建之后,会将Spring配置文件中的所有对象都创建完成(饿汉式)。
  • 提高程序运行效率。避免多次IO,减少对象创建时间。(概念接近连接池,一次性创建好,使用时直接获取)

9.2 生命周期方法

  • 自定义初始化方法:添加“init-method”属性,Spring则会在创建对象之后,调用此方法。

  • 自定义销毁方法:添加“destroy-method”属性,Spring则会在销毁对象之前,调用此方法。

  • 销毁:工厂的close()方法被调用之后,Spring会毁掉所有已创建的单例对象。

  • 分类:Singleton对象由Spring容器销毁、Prototype对象由JVM销毁。

9.3 生命周期注解

初始化注解、销毁注解。

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@PostConstruct //初始化 
public void init(){
    System.out.println("init method executed");
}

@PreDestroy //销毁
public void destroy(){
    System.out.println("destroy method executed");
}

9.4 生命周期阶段

  • 单例bean:singleton

随工厂启动创建——> 构造方法——> set方法(注入值) ——> init(初始化)——> 构建完成 ——>  随工厂关闭销毁

  • 多例bean:prototype

被使用时创建——> 构造方法 ——> set方法(注入值) ——> init(初始化) ——> 构建完成 ——> JVM垃圾回收销毁

十、代理设计模式

10.1 概念

将核心功能与辅助功能(事务、日志、性能监控代码)分离,达到核心业务功能更纯粹、服务业务功能可复用。

  • 作用: 实现对目标对象原有的功能增强,即扩展目标对象的功能方法,并且不会修改原有代码。
  • 分类:
  • (1) 静态代理:在运行前,通过编写代码的方式生成代理类
  • (2) 动态代理:在运行后,通过反射机制生成代理类

        Spring详解_第11张图片 

 10.2 静态代理设计模式

通过代理类的对象,为原始类的对象(目标类的对象)添加辅助功能,更容易更换代理实现类、利于维护。

  • 注意:一个代理只能做一件事,做其他事情,需要再创建新代理.。

Spring详解_第12张图片

代理类 = 实现原始类相同接口 + 添加辅助功能 + 调用原始类的业务方法

静态代理的问题:

  • 代理类数量过多,不利于项目的管理
  • 多个代理类的辅助功能冗余,修改时,维护性差

10.2.1 静态代理实例

需求:实现房东出租

// 创建一个接口
public interface FangDong {
    void zufang();
}

被代理者和代理者实现同一个接口。

/**
  * 被代理类实现接口
  */
public class FangdongImpl implements FangDong {
    @Override
    public void zufang() {
        System.out.println("房东出租房..." );
    }
}
/**
  * 代理者实现接口
  */
public class FangdongProxy {
    // 目标对象
    private FangDong fangDong;

    // 有参构造方法,只要new FandDongProxy就要给被代理对象
    public FangdongProxy(FangDong fangDong){
        this.fangDong = fangDong;
    }

    /**
     * 代理类
     */
    void zufang(){
        // 前置增强
        System.out.println("前:发传单,找客源" );
        // 房东出租
        fangDong.zufang();
        // 后置增强
        System.out.println("后:签合同" );
    }
}

测试

    @Test
    public void test1() {
        // 找代理
        FangDongProxy proxy = new FangDongProxy(new FangDongImpl());
        //真正租房
        proxy.zuFang();
    }

Spring详解_第13张图片

10.3 动态代理设计模式

动态代理会动态的为目标类产生代理对象,动态代理有两种实现方案:

  1. JDK动态代理,只能代理接口,即目标类要有接口
  2. CGLIB动态代理,可以代理接口,也可以代理类,目录类可以有接口,也可以没有接 

10.3.1 JDK动态代理

        JDK代理,是Java自有技术无需导包

需求:使用动态代理技术,动态的产生房东代理对象,汽车厂商的代理。

JDK代理需要实现InvocationHandler接口

/**
 * 这是代理的模板,可以创建出任何一个类的代理对象
 */
public class JDKProxy implements InvocationHandler {
    // 目标类,这里给Object因为可以是任何类
    private Object target;

    // 有参构造方法,只要new JDKProxy就要给被代理对象
    public JDKProxy(Object target){
        this.target=target;
    }

    /**
     * @param proxy 被代理对象
     * @param method 目标方法
     * @param args 目标方法的参数
     * @return 目标方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 目标方法前
        System.out.println("前置增强");
        // 目标方法执行
        Object obj = method.invoke(target, args);
        //目标方法后
        System.out.println("后置增强");
        return obj;
    }
}

1.房东出租接口和被代理类。 

/**
 * 接口
 */
public interface FangDong {
    void zufang();
}

/**
 * 被代理类
 */
public class FangDongImpl implements FangDong {
    @Override
    public void zuFang() {
        System.out.println("房东出租房...");
    }
}

测试。

    @Test
    public void test2() {
        // 目标类的类加载器
        ClassLoader classLoader = FangDongImpl.class.getClassLoader();
        // 目标类实现的所有接口
        Class[] interfaces = FangDongImpl.class.getInterfaces();
        // 目标方法执行处理器
        JDKProxy proxy = new JDKProxy(new FangDongImpl());
        /**
         * 参数1:目标类的类加载器
         * 参数2:目标类所实现的所有接口
         * 参数3:JDKProxy的对象
         */
        FangDong fangDong = (FangDong) Proxy.newProxyInstance(classLoader, interfaces, proxy);
        fangDong.zuFang();
    }

Spring详解_第14张图片

 2.汽车厂卖车接口和被代理类。 

/**
 * 接口
 */
public interface CarFactory {
    void seleCar();
}

/**
 * 被代理类
 */
public class CarFactoryImpl implements CarFactory{
    @Override
    public void seleCar() {
        System.out.println("汽车厂卖车...");
    }
}

测试。 

@Test
    public void test3() {
        ClassLoader classLoader = CarFactoryImpl.class.getClassLoader();
        Class[] interfaces = CarFactoryImpl.class.getInterfaces();
        JDKProxy proxy = new JDKProxy(new CarFactoryImpl());
        CarFactory carFactory = (CarFactory) Proxy.newProxyInstance(classLoader, interfaces, proxy);
        carFactory.seleCar();
    }

Spring详解_第15张图片

总结:动态代理,会动态产生目标类的代理对象,JDK的动态代理,目标必须有接口

10.3.2 CGLIB动态代理

CGLIB动态代理技术,又叫字节码增强。动态产生代理对象,可以代理接口也可以代理实现类。CGLIB是第三方技术,spring框架中已经整合了cglib的技术,所以只需导入spring-aop的依赖即可。

需求:使用动态代理技术,动态的产生房东代理对象,汽车厂商的代理。

CGLIB代理需要实现MethodInterceptor接口

public class MyCglibInterceptor implements MethodInterceptor {
    // cglib提供的增强工具类
    private Enhancer enhancer = new Enhancer();

    // 创建代理拦截器时,指定目标类的字节码
    public MyCglibInterceptor(Class clazz){
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
    }

    // 提供一个获得代理对象的方法
    public Object createProxyBean(){
        return enhancer.create();
    }
    /**
     * @param target 目标对象
     * @param method 目标方法
     * @param args 目标方法的参数
     * @param methodProxy 目标方法的代理对象
     * @return 目标方法的返回值
     * @throws Throwable
     */
    @Override
    public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // 目标方法执行前
        System.out.println("前置增强:权限校验、事务开启");
        // 目标方法执行
        Object ret = methodProxy.invokeSuper(target, args);
        // 目标方法执行前
        System.out.println("后置增强:记录日志、事务提交");
        return ret;
    }
}

测试。

    /**
     * CGLIB动态代理
     */
    @Test
    public void test4() {
        // 此处,只需要将其他类字节码文件传入此处就可得到其他类的代理对象
        MyCglibInterceptor interceptor = new MyCglibInterceptor(FangDongImpl.class);
        FangDong fangDong = (FangDong) interceptor.createProxyBean();
        fangDong.zuFang();
    }

Spring详解_第16张图片

总结:CGLIB动态代理,会动态的给类产生代理对象,目标类可以没有接口,也可以有接口,都可以代理。  

10.3.3 总结

  • 代理的目的: 将目标方法功能增强
  • 代理的方式: 静态代理和动态代理
  • 区别:

(1)静态代理: 每个类都需要自己创建代理对象,一个代理只能代理一个类

(2)动态代理: 给每个类动态产生代理对象(按需产生)

  • 动态代理如何实现:

(1)jdk的动态代理: jdk动态代理只能代理接口(目标类必须有接口)

(2)cglib的动态代理:可以代理接口,也可以代理类(目标类可以有接口,也可以没接口)

十一、AOP编程

11.1概念

AOP即切面编程,利用一种称为“横切”的技术,剖开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为“Aspect”,即切面。

  • AOP切面编程,在原有的基础上通过Aop添加新的功能,而原有的功能并不知道新添加的功能,简单来说就是:在某个方法或类执行前打个标记声明该干什么,之后执行什么,插入的新的方法。

面向切面编程的作用:

  • 将项目中与核心逻辑无关的代码横向抽取成切面类,通过织入作用到目标方法,以使目标方法执行前后达到增强的效果。

优点

  • 可以对业务逻辑的各个部分进行隔离
  • 抽取代码,复用,提供效率
  • 减少耦合
  • 利于代码扩展

11.2AOP开发术语

  • 目标类(Target): 被代理的类
  • 连接点(JoinPoint): 目标类中准备被切入的方法
  • 切入点(Pointcut): 真正执行的目标方法
  • 切面(Aspect): 切面中定义中增强的方法
  • 通知、增强(Advice): 在切入点中具体要做的事情,分为:前置通知、后置通知、异常通知、环绕通知等
  • 织入(Weaving): 将增强的方法作用到目标方法的过程
  • 代理(Proxy):被AOP织入通知后产生的结果类

11.3 AOP分类

5种通知的分类:

  • 前置通知(Before Advice):在目标方法被调用前调用通知功能,场景:一般用来做权限校验

  • 后置通知(After Advice):在目标方法被调用之后调用通知功能,场景: 释放资源,或者记录日志

  • 返回通知(After-returning):在目标方法成功执行之后调用通知功能,场景:数据库事务

  • 异常通知(After-throwing):在目标方法抛出异常之后调用通知功能,场景:得到目标方法返回值再处理

  • 环绕通知(Around):把整个目标方法包裹起来,在被调用前和调用之后分别调用通知功能。  场景:可以获得目标方法的异常信息,用于记录日志,或者进行异常拦截

11.4 AOP增强实例

11.4.1 加入依赖

        
            org.springframework
            spring-context
            5.1.6.RELEASE
        
        
            org.springframework
            spring-aspects
            5.1.6.RELEASE
        

11.4.2 创建接口和实现类

public interface UserService {
    int addUser();
}

public class UserServiceImpl implements UserService {
    @Override
    public int addUser() {
        System.out.println("UserServiceImpl的addUser()方法执行了");
        // 返回值用来测试后置返回通知
        return 999;
    }
}

 11.4.3 创建切面类MyAspect

/**
 * 切面类中定义各种增强的方法
 */
public class MyAspect {

    /**
     * 前置增强
     * @param joinPoint 目标类对象
     * @return
     * @throws Throwable
     */
    public void before(JoinPoint joinPoint) throws Throwable {
        //目标方法前
        Object target = joinPoint.getTarget();
        System.out.println("前置增强,获得目标类对象:"+target);

        Signature signature = joinPoint.getSignature();
        System.out.println("前置增强,获得目标方法:"+signature);

        String name = signature.getName();
        System.out.println("前置增强,获得目标方法名:"+name);

        System.out.println("前置增强:权限校验");
        System.out.println("-----------前置通知执行完毕----------");
    }

    /**
     * 环绕通知
     * @param joinPoint 目标方法
     * @return
     */
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 目标方法前
        System.out.println("前置增强:开启事务");

        // 目标方法执行
        Object ret = joinPoint.proceed();

        // 目标方法后
        System.out.println("后置增强:提交事务");
        System.out.println("-----------环绕通知执行完毕----------");
        return ret;
    }

    /**
     * 后置通知
     * @param joinPoint
     */
    public void after(JoinPoint joinPoint){
        // 应用场景:还可以做一些关流、释放资源
        System.out.println("后置通知:记录执行的日志");
        System.out.println("-----------后置通知执行完毕----------");
    }

    /**
     * 后置返回通知
     * 只能得到目标方法的返回值
     * @param ret
     * @return
     */
    public Object afterReturn(Object ret){
        System.out.println("后置返回通知:"+ret);
        System.out.println("-----------后置返回通知执行完毕----------");
        return ret;
    }

    /**
     * 异常处理通知
     * @param e
     */
    public void MyThrow(Exception e){
        // 可以接受到目标方法执行的异常信息
        // 可以做全局异常处理,记录异常信息到日志
        System.out.println("异常通知,获得异常信息:"+e.getMessage());
    }
}

11.4.4 applicationContext.xml配置AOP 

applicationContext.xml引入AOP命名空间。 

配置通知时:

  • method:切面类中的方法名
  • pointcut:切入点,内写切入点表达式
  • execution(* com.ymk.service.*.*(..))
  • 第一个*:返回值的意思,返回值任意
  • com.ymk.service.*:通过路径确定切入点所在类,*的意思是service包下所有类
  • .*:方法名,*指所有方法
  • ():方法的参数列表
  • .. :方法的任意参数



    
    

    
    

    
    

        
        

            
            

            
            
            

            
            

            
            

            
            

            
            
        

    

11.4.5 测试

    @Test
    public void testAop() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.addUser();
    }

Spring详解_第17张图片

11.5.AOP注解开发 

  • @Service:让Spring创建对象,将该类交给Spring托管
  • @Component:让Spring创建对象,将该类交给Spring托管,不需要写标签了
  • @Aspect:声明这是个切面
  • @Pointcut("execution(* com.ymk.service.*.*(..))"):将表达式抽取出来,方便复用
  • @Before("pointcut()"):前置通知
  • @After("pointcut()"):后置通知
  • @Around("pointcut()"):环绕通知

HouseService接口和HosueServiceImpl实现类。

public interface HouseService {
    int updateHouse();
}

@Service // 交由sprng容器创建对象
public class HouseServiceImpl implements HouseService {
    @Override
    public int updateHouse() {
        System.out.println("HouseServiceImpl.addHouse()执行");
        return 8686;
    }
}

创建切面类。

@Component // 交由spring创建bean对象
@Aspect //声明这是一个切面
public class MyAspect {

    /**
     * 抽取表达式,方便复用
     */
    @Pointcut("execution(* com.ymk.service.*.*(..))")
    public void pointCut(){}

    /**
     * 前置通知,里面写的是切入点表达式方法名,注意不要忘了带括号()
     * @param joinPoint
     */
    @Before("pointCut()")
    public void before(JoinPoint joinPoint){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println("获取目标方法名:"+name);
        System.out.println("前置通知:权限校验");
        System.out.println("-------前置执行完毕-------");
    }

    /**
     * 环绕通知
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("前置增强:开启事务");
        Object ret = joinPoint.proceed();
        System.out.println("后置增强:提交事务");
        System.out.println("-------环绕置执行完毕-------");
        return ret;
    }

    /**
     * 后置通知
     * @param joinPoint
     */
    @After("pointCut()")
    public void after(JoinPoint joinPoint){
        System.out.println("后置通知:记录执行的日志");
        System.out.println("-------后置执行完毕-------");
    }

    /**
     * 后置返回通知
     * @param ret
     */
    @AfterReturning(returning = "ret",pointcut = "pointCut()")
    public void afterReturn(Object ret){
        System.out.println("后置返回通知:"+ret);
        System.out.println("-------后置返回执行完毕-------");
    }

    /**
     * 异常处理通知
     * @param e
     */
    @AfterThrowing(throwing = "e",pointcut = "pointCut()")
    public void myThrow(Exception e){
        System.out.println("获取异常信息:"+e.getMessage());
        System.out.println("-------异常处理执行完毕-------");
    }

}

applicationContext.xml文件配置




    
    

    
    

测试

    @Test
    public void test() {
        // 获得容器对象
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
        // 从容器获得对象,对象名一定是类名首字母小写
        HouseService houseService = context.getBean("houseServiceImpl", HouseService.class);
        // 执行目标方法,目标方法前后都会执行增强方法
        houseService.updateHouse();
    }

Spring详解_第18张图片

   注意:AOP的动态代理,底层使用了两种代理方案:

  • 默认情况下使用JDK动态代理,及目标类必须有接口,JDK代理接口
  • 当目标类没有实现接口时,自动切换使用CGLIB实现动态代理 

十二、MyBatis和Spring框架整合

12.1 环境配置

12.1.1 加入依赖

    

        
        
            mysql
            mysql-connector-java
            5.1.48
        

        
        
            org.mybatis
            mybatis
            3.5.6
        

        
        
            org.slf4j
            slf4j-log4j12
            1.7.25
        

        
        
            javax.servlet
            javax.servlet-api
            4.0.1
        

        
        
            javax.servlet.jsp
            javax.servlet.jsp-api
            2.3.1
        

        
        
            junit
            junit
            4.13.2
            test
        

        
        
            org.mybatis.generator
            mybatis-generator-core
            1.3.5
        

        
        
            org.springframework
            spring-context
            5.1.6.RELEASE
        

        
        
            org.mybatis
            mybatis-spring
            1.3.2
        

        
        
            org.springframework
            spring-aop
            5.1.6.RELEASE
        

        
        
            org.springframework
            spring-aspects
            5.1.6.RELEASE
        

        
        
            org.springframework
            spring-tx
            5.2.14.RELEASE
        

        
        
            org.springframework
            spring-jdbc
            5.2.14.RELEASE
        

        
        
            com.alibaba
            druid
            1.2.8
        

        
        
            com.github.pagehelper
            pagehelper
            5.3.0
        
    

    
        
            
                src/main/java
                
                    **/*.xml
                
                false
            
        
    

12.1.2 db.properties文件配置

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/bank?useSSL=false
jdbc.username=root
jdbc.password=123456
# 配置初始化大小、最小、最大
jdbc.initialSize=5
jdbc.minIdle=3
jdbc.maxActive=20
#配置获取连接等待超时的时间
jdbc.maxWait=0
#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
jdbc.timeBetweenEvictionRunsMillis=600000
#配置一个连接在池中最小生存的时间,单位是毫秒
jdbc.minEvictableIdleTimeMillis=300000

12.1.3 mybatis-config.xml文件配置



    
    
    
        
    

12.1.4 applicationContext.xml文件配置



    
    

    
    

    
    

    
    
        
        
        
        

        
        
        
        
        
        
        
        
        
        
        
        
        
        

    

    
    
        
        
        
        
        
        
    
    
    
    
        
        
        
    

12.2 Java代码

UserService接口和UserServiceImpl实现类。

public interface UserService {
    User findUserById(int id);
}

@Service
public class UserServiceImpl implements UserService {
    // 设置Dao属性
    @Autowired // 自动注入Mapper
    private UserMapper userMapper;

    @Override
    public User findUserById(int id) {
        User user = userMapper.findUserById(id);
        return user;
    }
}

UserMapper.java和UserMapper.xml。

public interface UserMapper {
    User findUserById(int id);
}




    

测试。

public class TestSpringMybatis {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService service = context.getBean("userServiceImpl", UserService.class);
        User user = service.findUserById(51);
        System.out.println(user);
    }
}

12.3 Spring整合Mybatis后Log4j不生效问题

解决方法:

将log4j依赖更新为log4j12依赖

        
        
            org.slf4j
            slf4j-log4j12
            1.7.25
        

12.4.分页插件

分页插件作用: 可以自动完成分页功能。

使用分页插件,只需要正常查询全部数据,不用考虑关于分页的limit拼接问题,和count的数据条数等问题,插件全部搞定。

加入依赖.


    com.github.pagehelper
    pagehelper
    5.3.0

将分页插件的配置放在applicationContext.xml的SqlSessionFactory中

        
        
            
                
                
                    
                    
                        
                            
                            mysql
                        
                    
                
            
        

创建UserService接口和UserServiceImpl实现类

public interface UserService {
    List findAll();
}
@Service
public class UserServiceImpl implements UserService {
    // 设置Dao属性
    @Autowired
    private UserMapper userMapper;
    @Override
    public List findAll() {
        return userMapper.findAll();
    }
}

创建UserMapper和UserMapper.xml。

public interface UserMapper {
    List findAll();
}

测试,在代码中使用分页功能。

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService service = context.getBean("userServiceImpl", UserService.class);
        // 每页为5条
        int pageSize = 5;
        // 页码
        int pageNum = 1;

        PageHelper.startPage(pageNum ,pageSize);

        // 查询全部
        List all = service.findAll( );
        for (User user : all) {
            System.out.println(user );
        }

        // 将查询返回的集合,交给分页插件,返回给所有分页数据
        PageInfo pageInfo = new PageInfo<>(all);
        System.out.println(pageInfo );
    }

底层原理:

拦截Mybatis发出SQL语句,自动拼接Limit 语句,完成分页语句,另外还会发出SELECT count(0) FROM user 计算数据条数。

十三、事务

13.1 配置DataSourceTransactionManager

事务管理器,其中持有DataSource,可以控制事务功能(commit,rollback等)。



    

注意:DataSourceTransactionManager 和 SqlSessionFactoryBean 要注入同一个DataSource的Bean,否则事务控制失败!!!

13.2 配置事务通知

基于事务管理器,进一步定制,生成一个额外功能:Advice。

此Advice可以切入任何需要事务的方法,通过事务管理器为方法控制事务。


    
        

        
        

        
        

        
        
    

13.3 事务属性

13.3.1 隔离级别

13.3.1.1概念

isolation隔离级别.

名称 描述
default (默认值)(采用数据库的默认的设置) (建议)
read-uncommited 读未提交
read-commited 读提交 (Oracle数据库默认的隔离级别)
repeatable-read 可重复读 (MySQL数据库默认的隔离级别)
serialized-read 序列化读

隔离级别由低到高为:read-uncommited < read-commited < repeatable-read < serialized-read

13.3.1.2 特性

  • 安全性:级别越高,多事务并发时,越安全。因为共享的数据越来越少,事务间彼此干扰减少。

  • 并发性:级别越高,多事务并发时,并发越差。因为共享的数据越来越少,事务间阻塞情况增多。

13.3.1.3 并发问题

事务并发时的安全问题。

问题 描述
脏读 一个事务读取到另一个事务还未提交的数据。大于等于 read-commited 可防止
不可重复读 一个事务内多次读取一行数据的相同内容,其结果不一致。大于等于 repeatable-read 可防止
幻影读 一个事务内多次读取一张表中的相同内容,其结果不一致。serialized-read 可防止

13.3.2 propapgation传播行为

propagation传播行为.

当涉及到事务嵌套(Service调用Service)时,可以设置:

  • SUPPORTS = 不存在外部事务,则不开启新事务;存在外部事务,则合并到外部事务中。(适合查询)

  • REQUIRED = 不存在外部事务,则开启新事务;存在外部事务,则合并到外部事务中。 (默认值)(适合增删改)

13.3.3 readonly读写性

readonly 读写性

  • true:只读,可提高查询效率。(适合查询)

  • false:可读可写。 (默认值)(适合增删改)

13.3.4 timeout事务超时

timeout事务超时时间

当前事务所需操作的数据被其他事务占用,则等待。

  • 100:自定义等待时间100(秒)。

  • -1:由数据库指定等待时间,默认值。(建议)

13.3.5 rollback-for事务回滚

rollback-for 回滚属性

  • 如果事务中抛出 RuntimeException,则自动回滚

  • 如果事务中抛出 CheckException(非运行时异常 Exception),不会自动回滚,而是默认提交事务

  • 处理方案 : 将CheckException转换成RuntimException上抛,或 设置 rollback-for="Exception"

13.4 编织

将事务管理的Advice 切入需要事务的业务方法中


    
    
    

十四、继承Junit

14.1 加入依赖


    org.springframework
    spring-test
    4.3.6.RELEASE


    junit
    junit
    4.12

14.2 编码

  • 可以免去工厂的创建过程
  • 可以直接将要测试的组件注入到测试类
//由SpringJUnit4ClassRunner启动测试
@RunWith(SpringJUnit4ClassRunner.class)

//spring的配置文件位置
@ContextConfiguration("classpath:applicationContext.xml") 

//当前测试类也会被纳入工厂中,所以其中属性可以注入
public class SpringTest{

    @Autowired // 注入要测试的组件
    @Qualifier("userDAO")
    private UserDAO userDAO;

    @Test
    public void test(){
        // 测试使用userDAO
        userDAO.queryUser();
        ....
    }
}

你可能感兴趣的:(企业级框架,spring,java,面试)