栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring

1.Spring 1.1、Spring简介

2002年,首次推出了Spring的雏形:interface21框架

2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版本。

Spring框架的创始人,同时也是SpringSource的联合创始人。他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。

Spring是面向切面编程(AOP)和控制反转(IoC)的容器框架。

Spring:使现有的技术更加容易使用,它本身是一个大杂烩,整合了现有的技术框架

1.2、相关信息

官网:https://spring.io/

Spring下载链接:https://repo.spring.io/ui/native/release/org/springframework/spring

GitHub:https://github.com/spring-projects/spring-framework

1.3、Spring maven包

    org.springframework
    spring-webmvc
    5.3.16





    org.springframework
    spring-jdbc
    5.3.16

1.4、Spring 优点

Spring是一个开源的免费的框架!

Spring是轻量级的,非入侵式的框架!

控制反转(IOC),面向切面编程(AOP)!

支持事务的处理,对框架整合的支持!

2、Spring组成

Spring

Spring Boot

一个快速开发的脚手架。基于SpringBoot可以快速开发单个微服务约定大于配置! Spring Cloud

Spring Cloud是基于Spring Boot实现的。

弊端:Spring发展了太久之后,违背了原来的理念!配置十分繁琐,人称:“配置地狱”。

3、IOC理论 3.1、IOC理论推导

我们先写一个简单的原始项目

UserDao接口

package dao;

public interface UserDao {
    void getUser();
}

UserDaoimpl实现类

package dao;

public class UserDaoImpl implements UserDao{
    public void getUser(){
        System.out.println("默认配置");
    }
}

UserService接口

package service;

public interface UserService {
    void getUserService();
}

UserServiceImpl实现类

package service;

import dao.UserDao;
import dao.UserDaoImpl;

public class UserServiceImpl {
    UserDao userdao=new UserDaoImpl();

    void getUserService(){
        userdao.getUser();
    }
}

这里注意一下new的那个UserDaoImpl对象,这是为了什么,是不是为了我们能够使用UserDao中的方法,但是这里会出现一个问题,下面我们看看是什么问题。

我们现在有一个新需求需要添加功能,假设这是个Mysql的功能,然后我们为UserDao添加了这个实现类

UserMysqlImpl实现类

package dao;

public class UserMysqlImpl {
    void getUser(){
        System.out.println("Mysql配置");
    }
}

现在问题来了,我们刚刚new出来的对象是哪个类的?是UserDaoimpl类的,但是我们这里想要使用这个新的实现类的话我们要怎么办,是不是要去修改我们的业务层代码

package service;

import dao.UserDao;
import dao.UserDaoImpl;

public class UserServiceImpl {
    UserDao userdao=new UserMysqlImpl();

    void getUserService(){
        userdao.getUser();
    }
}

变成了这样之后我们思考一下,假设我们后面还要添加很多很多的实现类呢,我们每一次都要去更改原来的代码,是不是维护成本就增加了,那么怎么解决呢,答案是”不要将Userdao的对象写死,而是通过Set注入对象的方式来完成“

package service;

import dao.UserDao;
import dao.UserDaoImpl;

public class UserServiceImpl {
    UserDao userdao;

    public void setUserdao(UserDao userdao) {
        this.userdao = userdao;
    }

    void getUserService(){
        userdao.getUser();
    }
}

这里就完成了一个非常大的改变,这意味着这个对象的控制权不在属于程序员本身,而是由用户来决定,大大降低了程序的耦合性。

3.2、IOC本质

控制反转IOC(Inversion of Control)是一种设计思想,DI(依赖注入)是实现IOC的一种方法,也有人认为DI只是IOC的另一种说法,没有IOC的程序中,我们使用面向对象编程,对象的创建和对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给了第三方也就是用户,个人认为所谓控制反转就是:获得依赖对象的方式反转了。

采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到零配置的目的。

控制反转是一种通过描述XML或注解并通过第三方去生产或获取特定对象的方式,在Spring中实现控制反转的是IOC容器,其实现方法是依赖注入(Dependency Injection,DI)。

4、使用Spring实现IOC



    
    
        
    

ref表示引用Spring库中的一个Bean

这样配置好了将对象放到Spring中托管了之后我们就可以使用了,但是首先要获取Spring上下文对象

ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

然后是从Spring库中拿出来,拿出来了之后默认是Object类型的,这里是我自己强转了一下

UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("userServiceImpl");

然后就是正常使用

userServiceImpl.getUserService();
5、IOC创建对象 5.1、使用Spring是什么时候创建对象的?

这是一个简单的User类

package pojo;

public class User {
    private String name;

    public User() {
        System.out.println("无参构造");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void show(){
        System.out.println("name----->"+name);
    }
}

测试类

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.User;

public class test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        User user = (User) context.getBean("user");
        user.show();
    }
}

我们可以看到打印了两个语句,我们都这是正常的,因为我们的无参构造函数是在对象一创建的时候就执行的。那么你们是不是以为我们的对象是在getBean的时候创建的

下面我们改变一下测试类

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.User;

public class test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    }
}

这样子我们就只有一个获取Spring上下文对象的操作

诶!为什么还是创建了对象呢?我们明明没有getBean,其实啊对象是在编写beans.xml的时候创建然后存入Spring的

下面我们验证我们从Spring中获取出来的对象是否是同一个

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.User;

public class test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User) context.getBean("user");
        User user2 = (User) context.getBean("user");
        System.out.println(user==user2);
    }
}

总结:在配置文件加载的时候,我们托管到Spring容器中管理的对象就初始化了

5.2、托管只有”有参构造“的对象

现在我们知道了在加载配置文件的时候就创建了,但是默认使用的是无参构造,如果是对象的构造函数是有参的呢?我们该怎么写配置文件呢?既然有参那就肯定要传参嘛,下面我们看看几种传参的方式

    使用索引赋值

    

    使用类型赋值,这种方法在有两个相同类型的时候就无效了,所以不推荐使用

    

    使用参数名赋值

    

6、Spring配置 6.1、别名

第一种方法通过alias标签


    



第二种方法直接通过bean标签的name属性,推荐使用这种方法,因为这种方法更加强大,它可以同时取多个别名


    

6.2、导入其他配置文件

使用import标签


使用import标签可以将多个配置文件合并成为一个

7、DI依赖注入 7.1、什么是依赖注入?

我们需要拆开来理解

依赖------------>bean对象的创建需要依赖于容器

注入------------>bean对象的所有属性由容器注入

7.2、怎么注入? 7.2.1、构造器注入

就是在构造器里为对象赋一个确定的值,前面已经用过了

7.2.2、Set注入(重点)

想要实现这种注入,前提是对象的属性具有Set方法

我们写一个具有复杂类型的学生类Student

package pojo;

import java.util.*;

public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List friends;
    private Map card;
    private Set games;
    private String wife;
    private Properties pops;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Address getAddress() {
        return address;
    }

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

    public String[] getBooks() {
        return books;
    }

    public void setBooks(String[] books) {
        this.books = books;
    }

    public List getFriends() {
        return friends;
    }

    public void setFriends(List friends) {
        this.friends = friends;
    }

    public Map getCard() {
        return card;
    }

    public void setCard(Map card) {
        this.card = card;
    }

    public Set getGames() {
        return games;
    }

    public void setGames(Set games) {
        this.games = games;
    }

    public String getWife() {
        return wife;
    }

    public void setWife(String wife) {
        this.wife = wife;
    }

    public Properties getPops() {
        return pops;
    }

    public void setPops(Properties pops) {
        this.pops = pops;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + ''' +
                ", address=" + address +
                ", books=" + Arrays.toString(books) +
                ", friends=" + friends +
                ", card=" + card +
                ", games=" + games +
                ", wife='" + wife + ''' +
                ", pops=" + pops +
                '}';
    }
}

Address类

package pojo;

public class Address {
    private String address;

    public String getAddress() {
        return address;
    }

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

下面看一下我们的应该怎么为这个类注入所有的属性值呢?、




    

    
        
        

        
        

        
        
            
                红楼梦
                西游记
                水浒传
                三国演义
            
        

        
        
            
                小红
                小绿
                小灯
            
        

        
        
            
                
                
            
        

        
        
            
                LOL
                CF
                DNF
            
        

        
        
            
        

        
        
            
                kasjdkasjdkasjd
                database
                root
                000000
            
        
    

7.2.3、其他注入

我们还可以使用p命名空间和c命名空间来进行注入,但是个人觉得本质还是通过上面的两种方式来进行注入

在使用之前都必须导入对应的依赖

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

需要哪个导入哪个

下面是他们的使用

实体类

package pojo;

public class User {
    private String name;
    private int age;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}

配置文件




    
    
    

测试类

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.User;

public class test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
        User user = context.getBean("user", User.class);
        System.out.println(user);

        User user2 = context.getBean("user2", User.class);
        System.out.println(user2);
    }
}

运行

8、bean的作用域

官网给出的六种作用域,其实只有五种,因为最后两个其实是一种

我们简单来测试一下,下面我往容器中托管了一个对象




    

singleton(默认配置):单例模式,我们从容器中取出的多个对象其实都是同一个


import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.User;

public class test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
        User user = context.getBean("user", User.class);
        User user2 = context.getBean("user", User.class);
        System.out.println(user==user2);
    }
}

prototype:原型模式,每次get的时候都会创建不同的对象


import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.User;

public class test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
        User user = context.getBean("user", User.class);
        User user2 = context.getBean("user", User.class);
        System.out.println(user==user2);
    }
}

剩下的request、session、application,只能在web开发中使用

request--------->在一次请求中有效,指的是在容器中的对象

session--------->在一个会话中有效,指的是在容器中的对象

application--------->全局有效,也就是只要服务器没有关闭就一直有效,指的是在容器中的对象

9、bean自动装配

在Spring中有三种装配的方法

    第一种是在配置文件中显示的装配,也就是我们一直都在使用的这种方式使用java装配,这个后面再说隐式的自动装配
9.1、测试

猫类

package pojo;

public class Cat {
    public void should(){
        System.out.println("喵~");
    }
}

狗类

package pojo;

public class Dog {
    public void should(){
        System.out.println("汪~");
    }
}

人类

package pojo;

public class People {
    private Cat cat;
    private Dog dog;

    public Cat getCat() {
        return cat;
    }

    public void setCat(Cat cat) {
        this.cat = cat;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String toString() {
        return "People{" +
                "cat=" + cat +
                ", dog=" + dog +
                '}';
    }
}

测试类,以下的九章节部分都是使用这个测试类

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.People;

public class test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        People people = context.getBean("people", People.class);
        people.getCat().should();
        people.getDog().should();
    }
}
9.2、通过byName自动装配



    
    

    

这种方式就是在容器中寻找和对象的属性名相同的对象来进行自动装配

人的类里面有一个名为dog的属性,然后就会去容器里面寻找id为dog的对象来进行装配。

但是这种方式需要属性名和容器中的对象的id一致

9.3、通过byType自动装配



    
    

    

这种方式就是在容器中寻找和对象的类型相同的对象来进行自动装配

人的类里面有一个类型为Dog的属性,然后就会去容器里面寻找类型为Dog的对象来进行装配。

优点:我们在将对象托管到Spring容器的时候可以不指定对象的id

缺点:我们必须保证这个类型的对象在Spring容器中是唯一的

9.4、使用注解实现自动装配

@Autowired

我们可以使用@Autowired注解来完成自动装配,使用之前首先要导入依赖和开启注解支持



    
    

然后我们可以将这个注解使用在属性上,也可以将它使用在Set方法上

package pojo;

import org.springframework.beans.factory.annotation.Autowired;

public class People {
    private Cat cat;
    @Autowired
    private Dog dog;

    public Cat getCat() {
        return cat;
    }
    
    @Autowired
    public void setCat(Cat cat) {
        this.cat = cat;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String toString() {
        return "People{" +
                "cat=" + cat +
                ", dog=" + dog +
                '}';
    }
}

并且如果我们将这个注解使用在属性上,我们甚至可以不用写Set方法,因为这个注解是通过反射来实现的

@Qualifier

这个注解是首先通过byType来实现的,所以当我们的Spring容器中有两个相同类型且没有和属性相同id的对象的时候就会失效




    

    
    
    
    

    

这种情况我们可以通过另外一个注解来搭配使用,也就是@Qualifier,我们可以看看它的源码

在它这个注解里面有一个属性叫value,默认为空,我们可以手动给他赋一个值

@Autowired
@Qualifier(value="cat1")
private Cat cat;

这样就可以帮助@Autowired找到对应的对象来实现自动装配,需要注意的是一定要对应类型才可以装配

@Nullable

我们可以使用@Nullable来标注一个形参可以为空且不会报错(这个不会报错仅仅只是不会出现红色下划线,但是实际编译的时候还是会报错)

public People(@Nullable Cat cat) {
    this.cat = cat;
}

这是一个构造函数,我们使用@Nullable注解标记了cat参数,则表示cat可以为空

还有另外一种方式,我们看看@Autowired注解的源码

没错就是这个属性,我们可以将它显示的赋为false

@Autowired(required = false)

@Resource

这个注解可以实现和@Autowired注解一样的功能但是有一些差别,它是java的注解,但是使用也是需要导包的


    javax.annotation
    javax.annotation-api
    1.3.2

@Resource和@Autowired的区别是,@Resource是先去找是否有id跟属性名相同的对象,如果没有才会去找类型一样的

使用这个注解的时候我们配置bean的时候需要注意

要么是这样,也就是有id和属性名相同的对象





或者这样,保证类型唯一



10、Spring注解开发 10.1托管Bean

在前面我们使用的都是配置文件的形式来开发,但是在实际开发中使用最多的还是注解开发,当然复杂的时候还是使用配置文件

首先一定要保证aop的包导入了,我们上面导入的那个spring-mvc的包会包括这个

然后一样要导入依赖和开启注解支持,这里还要多一步扫描包




    
    
    

扫描了才能使下面的那些注解生效

@component:说明这个类被Spring容器管理了,也就是bean!

​ @Repository:dao层的类使用

​ @Service:service层的类使用

​ @Controller:Controller层的类使用

上面的四个注解功能上没有什么不同,只是因为在web开发中要按照MVC三层架构,所以才会出现不同

@Value:用来注入值,但是复杂类型的值注入还是使用配置文件比较好

10.2、自动装配

@Autoiwred
@Qualifier
@Resource
@Nullable

10.3、作用域

@Scope

10.4、小结

xml和注解:

xml更加万能,适用于任何场景,维护简单方便

注解只能使用在自己的类上,也就是说其他的bean不能引用,维护成本高且复杂

建议:

XML用来管理我们的Bean

注解只用来完成属性的注入

11、使用JavaConfig实现配置

在Spring4以后Spring开始支持JavaConfig来配置,这使得我们可以抛弃XML使用JavaConfig来进行配置,下面我们来看看怎么使用

package orange;

import orange.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("orange.pojo")
public class orangeConfig {

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

@Configuration:用来标注这是一个配置类

@Bean:用来向Spring容器托管一个Bean,等价于XML配置中的bean标签,方法名相当于bean标签的id

@ComponentScan:用来扫描一个包,当然使用这个注解也需要给我们需要托管的类加上@Component注解

使用这种方式更加灵活,我们可以直接在方法中为对象注入值

@Bean
public User user(){
    User user=new User();
    user.setName("orange");
    return user;
}

同样的我们也可以通过@import注解来导入其他的配置类,但是需要注意的是一定要是类的class

@import(orangeConfig2.class)

@Scope作用域,singleton单例,prototype原型

@Scope("singleton")
12、代理模式

我们不使用代理的时候,我们需要直接去找房东,但是这是一个很麻烦的过程,不仅我们很难找到房东,房东也很难找到租户,这个时候就需要中介也就是我们的代理角色,用来代理真实角色,我们不在需要去找房东,房东也不再需要找租户,房东只需要跟中介说我要出租房子,我们只需要去中介那里跟中介说自己需要怎样的房子。我们不再需要直接面向抽象角色,而是面向代理角色,代理角色会帮我们做一些事。但是代理角色所完成的事必须跟抽象角色完成的事情一样,也就是说我们租房子找中介租和房东和租都是一样的。除此代理角色还会有一些附属操作,比如中介在帮我们租房子的时候还会收中介费等等

12.1、静态代理模式

角色分析:

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

代码:

1.接口,具体做的事

package orange.demo1;

public interface Rent {
    public void rent();
}

2.真实角色

package orange.demo1;

public class fangDong implements Rent{
    public void rent(){
        System.out.println("房东要出租房子");
    }
}

3.代理角色

package orange.demo1;

public class proxyRole implements Rent{
    private fangDong fd;

    public proxyRole() {
    }

    public proxyRole(fangDong fd) {
        this.fd = fd;
    }

    public void rent() {
        fd.rent();
        hetong();
        shoufei();
    }

    public void hetong(){
        System.out.println("租赁合同");
    }

    public void shoufei(){
        System.out.println("收中介费!");
    }
}

4.客户

package orange.demo1;

public class client {
    public static void main(String[] args) {
        fangDong fd = new fangDong();
        proxyRole proxyrole = new proxyRole(fd);
        proxyrole.rent();
    }
}

代理模式的优点:

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

缺点:

一个真实角色就会产生一个代理角色;代码量会翻倍,开发效率会降低 12.2、静态代理模式再理解

接口

package orange.demo2;

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

真实角色

package orange.demo2;

public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("添加一个用户");
    }

    @Override
    public void delete() {
        System.out.println("删除一个用户");
    }

    @Override
    public void update() {
        System.out.println("更新一个用户");
    }

    @Override
    public void query() {
        System.out.println("查询一个用户");
    }
}

客户

package orange.demo2;

public class client {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        userService.add();
    }
}

比如我们现在有一个需求,需要做增删改查,但是现在公司要求我们增加一个日志功能,但是我们知道在实际开发中我们不能去改变原有的代码,因为如果一个项目本来能跑的,但是你给改成不能跑了不就完蛋了,这个时候我们就需要使用到代理

代理角色

package orange.demo2;

public class UserServiceImplProxy implements UserService{
    private UserServiceImpl userService;

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

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

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

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

    @Override
    public void query() {
        log("query");
        userService.query();
    }

    public void log(String f){
        System.out.println("[debug] 调用了"+f+"方法");
    }
}

客户就变成这样了

package orange.demo2;

public class client {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        UserServiceImplProxy userServiceImplProxy = new UserServiceImplProxy();
        userServiceImplProxy.setUserService(userService);
        userServiceImplProxy.add();
    }
}
12.3、动态代理

我们梳理静态代理,我们就可以发现我们的代理类实在编译之前就确定的,是我们写死的。

而动态代理与静态代理之间不同的是,动态代理是在运行过程中创建的,且不在是写死的。

JDK提供了两个类来帮助我们实现动态代理,java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler

两个包都来自于反射包下,而他们的底层也是通过反射来实现的。

Proxy提供了创建动态代理类和实例的静态方法,在Proxy中有一个getProxyClass()方法,这个方法你只需要传入类加载器和需要代理的接口就可以给你返回一个对应的代理对象。

这里需要了解一下类加载器的过程

而我们创建对象又是通过Class对象来的,所以最重要的就是得到Class对象,而这可以通过反射来得到。

拿到了Class对象之后,getProxyClass()就会根据Class来返回一个对象的Class代理对象,并且这个Class代理对象带有构造器,构造器参数是InvocationHandler。简单点说就是以Class对象复制一个带构造器的Class对象。

然后有了这个新的Class对象之后就可以创建我们的代理对象。并且每次调用代理对象的方法的时候都会被JVM引导到InvocationHandler的invoke()方法。

我们通过代理Class的构造器创造对象的时候,需要传入InvocationHandler。使用构造器传入一个引用的时候,在其内部必定会有一个变量去接收,没错,在代理对象的内部确实有个成员变量,且代理对象的每一个方法内部都会调用InvocationHandler的invoke()方法,不再想静态代理那样直接,InvocationHandler成了代理对象和真实对象之间的桥梁。

只要你把实例传进来,getProxy()都能给你返回对应的代理对象。就这样,我们完美地跳过了代理类,直接创建了代理对象!

不过实际编程中,一般不用getProxyClass(),而是使用Proxy类的另一个静态方法:Proxy.newProxyInstance(),直接返回代理实例,连中间得到代理Class对象的过程都帮你隐藏:

package orange.demo3;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyInvocationHandler implements InvocationHandler {
    private Rent rent;//代理对象

    //用来接收传入的代理对象的实例,用构造器也可
    public void setRent(Rent rent) {
        this.rent = rent;
    }

    public Object getProxy(){
        //参数1:当前类的类加载器
        //参数2:需要代理的类的接口
        //参数3:一个InvocationHandler,因为我们实现了InvocationHandler接口,所以我们自己也是一个InvocationHandler类
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Rent result = (Rent) method.invoke(rent, args);
        return result;
    }
}

参考博文:https://www.zhihu.com/question/20794107

13、AOP

横切关注点:跨越应用程序多个模块的方法或功能,即是与我们业务逻辑无关的,但是我们 需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等切面(ASPECT):横切关注点,被模块化的特殊对象,即,他是一个类。通知(Advice):切面必须要完成的工作,即,他是类中的一个方法。目标(Target):被通知对象。也就是我们所谓的真实对象。代理(Proxy):向目标对象应用通知之后创建的对象。切入点(PointCut):切面通知 执行的“地点”的定义。在哪个类里面执行。连接点(JoinPoint):与切入点匹配的执行点。在哪个类的哪个方法执行。

我们要使用aop首先要导包


    org.aspectj
    aspectjweaver
    1.9.8


其次是导入aop的依赖





然后就可以开始编写我们的aop了

13.1、方式一:SpringApi实现

首先是UserService接口

package service;

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

UserServiceImpl实现类

package service;

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("查询了一个用户");
    }
}

然后是我们的横切类,假如我们要实现log功能,在org.springframework.aop包下提供了很多功能类

package log;

//返回之后
import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

public class AfterReturnLog implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println(method.getName()+"执行返回了"+returnValue+"结果");
    }
}
package log;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

//方法执行之前
public class BeforeMethodLog implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(method.getName()+"开始执行!");
    }
}

然后是我们的配置文件




    
    
    

    
        
        
        
        
        
        
        

        
        
    

测试类

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.UserService;

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        UserService userServiceImpl = (UserService) context.getBean("userService");
        userServiceImpl.add();
    }
}

这种方式有一种弊端,那就是假如说我们的类没有实现SpringAop接口的话,就没有办法找到了

13.2、方式二:自定义类实现Aop

UserService接口

package demo1.service;

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

UserServiceImpl实现类

package demo1.service;

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("查询了一个用户");
    }
}

我们自定义的日志类,这里我们不再需要去使用Spring的API

package demo1.log;

public class diyLog {
    public void beforeLog(){
        System.out.println("-------->方法执行前<--------");
    }
    public void afterLog(){
        System.out.println("-------->方法执行后<--------");
    }
}

配置文件



    
    

    
        
        
            
            

            
            
        
    

13.3、使用注解实现
package log;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

//该注解用于将一个类定义为一个切面
@Aspect
public class diyLog {
    //方法前通知
    @Before("execution(* service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("------>方法执行前<------");
    }

    //方法前通知
    @After("execution(* service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("------>方法执行前<------");
    }
}

我们可以在类中直接定义一个切面和环绕通知

这种方式我们需要在配置文件中开启注解支持


这种方式的缺点就是我们需要去重复的写切入点

14、整合Mybatis 14.1、回顾Mybatis
    导包和解决静态资源导出问题

    
    
        junit
        junit
        4.13.2
        test
    


    
    
        mysql
        mysql-connector-java
        8.0.27
    

    
        org.springframework
        spring-webmvc
        5.3.16
    

    
        org.mybatis
        mybatis-spring
        2.0.7
    

    
    
        org.mybatis
        mybatis
        3.5.9
    

    
        org.springframework
        spring-jdbc
        5.3.16
    

    
        org.aspectj
        aspectjweaver
        1.6.11
    

    
    
        org.projectlombok
        lombok
        1.18.22
        provided
    
    
        junit
        junit
        4.11
        test
    




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

    mybatis核心配置文件



    
        
    
    
    
        
            
            
                
                
                
                
            
        
    
    
        
    

    连接数据库编写实体类
package pojo;

import lombok.Data;

@Data
public class User {
    private int id;
    private String name;
    private String password;
}
    编写Mapper接口
package mapper;

import pojo.User;

import java.util.List;

public interface UserMapper {
    //查询所有用户
    List queryUser();
}
    Mapper配置文件



    
        select * from `user`
    

mybatis-config.xml配置文件




    
        
    

spring配置文件



    
    
        
        
        
        
    

    
    
        
        
        
        
        
    
    
    
        
    
    
    
        
    

我们首先要配置一个spring数据源,也就是利用spring-jdbc包下提供的DriverManagerDataSource类,具体的参数可以看看它的源码下来之后就是创建sqlSessionFactory实例,使用的是我们上面创建的数据源,然后绑定一下绑定Mybatis的核心配置文件,也可以不绑定,但是我个人喜欢用Mybatis的核心配置文件来进行别名和设置的管理。接着我们需要配置Mapper然后要生产SqlSession了,这里我们需要使用Spring提供的SqlSessionTemplate,本质没有什么区别。

然后我们需要来写一个实现类,这里就不在需要Mybatis了,全部使用spring来完成。

package mapper;

import org.mybatis.spring.SqlSessionTemplate;
import pojo.User;

import java.util.List;

public class UserMapperImpl implements UserMapper{
    private SqlSessionTemplate sqlSession;

    public UserMapperImpl(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }

    @Override
    public List queryUser() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.queryUser();
    }
}

接着就是将这个类托管到Spring,这里不多说,上面的Spring配置文件已经写了

测试类

import mapper.UserMapperImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.User;

import java.io.IOException;
import java.util.List;

public class test {
    @Test
    public void test() throws IOException {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
        UserMapperImpl userMapperImpl = context.getBean("userMapperImpl", UserMapperImpl.class);
        List users = userMapperImpl.queryUser();
        for (User user : users) {
            System.out.println(user);
        }
    }
}
14.3、整合Mybatis方式二

spring提供了一个SqlSessionDaoSupport类,使用该类之后我们可以通过getSqlSession()方法就可以获得一个SqlSessionTemplate

我们来看看如何使用

package mapper;

import org.mybatis.spring.support.SqlSessionDaoSupport;
import pojo.User;

import java.util.List;

public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{
    @Override
    public List queryUser() {
        return getSqlSession().getMapper(UserMapper.class).queryUser();
    }
}

这是我们的接口实现类,可以看到我们根本没有注入SqlSessionTemplate,我们直接使用getSqlSession就获得了一个SqlSessionTemplate

使用这种方法不需要在注入SqlSessionTemplate了,但是需要注入sqlSessionFactory,因为SqlSessionDaoSupport类需要sqlSessionFactory


    

测试类

import mapper.UserMapperImpl;
import mapper.UserMapperImpl2;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.User;

import java.io.IOException;
import java.util.List;

public class test {
    @Test
    public void test() throws IOException {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
        UserMapperImpl2 userMapperImpl2 = context.getBean("userMapperImpl2", UserMapperImpl2.class);
        List users = userMapperImpl2.queryUser();
        for (User user : users) {
            System.out.println(user);
        }
    }
}

建议初学者还是使用方式一,了解原理。

15、声明式事务 15.1、事务回顾

我们来回忆一下事务的四个原则

原子性一致性隔离性持久性

我们都知道只是为了完成一句话,那就是“事务要么都成功要么都失败”。

那么我们下面看一个例子

这是我们现在的数据库

这是我们的功能,一个查询全部用户,一个添加用户,一个删除用户

UserMapper接口

package mapper;

import pojo.User;

import java.util.List;

public interface UserMapper {
    List queryUser();

    int addUser(User user);

    int deleteUser(int id);
}

UserMapper.xml配置文件