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

Spring 笔记

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

Spring 笔记

Spring-Study

Kuang shen Spring study in bilibili

1. 简介

spring理念:是现有的技术更加容易使用,本身是一个大杂烩。

  • SSH:Struct2 + Spring + Hibernate

  • SSM: SpringMVC + Spring + Mybatis

官网: Spring framework

官方下载: JFrog

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

Spring Web MVC » 5.2.5.RELEASE


    org.springframework
    spring-webmvc
    5.2.5.RELEASE

    org.springframework
    spring-jdbc
    5.2.3.RELEASE
1.1、组成

组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

  • 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。

  • Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。

  • Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。

  • Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。

  • Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。

  • Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。

  • Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。

1.2、拓展

Spring Boot与Spring Cloud

  • Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务;

  • Spring Cloud是基于Spring Boot实现的;

  • Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架;

  • Spring Boot使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置 , Spring Cloud很大的一部分是基于Spring Boot来实现,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。

  • SpringBoot在SpringClound中起到了承上启下的作用,如果你要学习SpringCloud必须要学习SpringBoot。

2.IOC理论

新建一个空白的maven项目

2.1、分析实现(非常重要!!IOC底层原理)

我们先用我们原来的方式写一段代码 .

1、先写一个UserDao接口

public interface UserDao {
   public void getUser();
}

2、再去写Dao的实现类

public class UserDaoImpl implements UserDao {
   @Override
   public void getUser() {
       System.out.println("获取用户数据");
  }
}

3、然后去写UserService的接口

public interface UserService {
   public void getUser();
}

4、最后写Service的实现类

public class UserServiceImpl implements UserService {
   private UserDao userDao = new UserDaoImpl();
​
   @Override
   public void getUser() {
       userDao.getUser();
  }
}

5、测试一下

@Test
public void test(){
   UserService service = new UserServiceImpl();
   service.getUser();
}

这是我们原来的方式 , 开始大家也都是这么去写的对吧 . 那我们现在修改一下 .

把Userdao的实现类增加一个 .

public class UserDaoMySqlImpl implements UserDao {
   @Override
   public void getUser() {
       System.out.println("MySql获取用户数据");
  }
}

紧接着我们要去使用MySql的话 , 我们就需要去service实现类里面修改对应的实现

public class UserServiceImpl implements UserService {
   private UserDao userDao = new UserDaoMySqlImpl();
​
   @Override
   public void getUser() {
       userDao.getUser();
  }
}

在假设, 我们再增加一个Userdao的实现类 .

public class UserDaoOracleImpl implements UserDao {
   @Override
   public void getUser() {
       System.out.println("Oracle获取用户数据");
  }
}

那么我们要使用Oracle , 又需要去service实现类里面修改对应的实现 . 假设我们的这种需求非常大 , 这种方式就根本不适用了, 甚至反人类对吧 , 每次变动 , 都需要修改大量代码 . 这种设计的耦合性太高了, 牵一发而动全身 .

那我们如何去解决呢 ?

我们可以在需要用到他的地方 , 不去实现它 , 而是留出一个接口 , 利用set , 我们去代码里修改下 .

public class UserServiceImpl implements UserService {
   private UserDao userDao;
// 利用set实现
   public void setUserDao(UserDao userDao) {
       this.userDao = userDao;
  }

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

现在去我们的测试类里 , 进行测试 ;

@Test
public void test(){
   UserServiceImpl service = new UserServiceImpl();
   service.setUserDao( new UserDaoMySqlImpl() );
   service.getUser();
   //那我们现在又想用Oracle去实现呢
   service.setUserDao( new UserDaoOracleImpl() );
   service.getUser();
}

大家发现了区别没有 ? 可能很多人说没啥区别 . 但是同学们 , 他们已经发生了根本性的变化 , 很多地方都不一样了 . 仔细去思考一下 , 以前所有东西都是由程序去进行控制创建 , 而现在是由我们自行控制创建对象 , 把主动权交给了调用者 . 程序不用去管怎么创建,怎么实现了 . 它只负责提供一个接口 .

这种思想 , 从本质上解决了问题 , 我们程序员不再去管理对象的创建了 , 更多的去关注业务的实现 . 耦合性大大降低 . 这也就是IOC的原型 !

2.2、IOC本质

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

在使用Spring框架后,对象的实例不再由调用者来创建,而是由Spring容器来创建,Spring容器会负责控制程序之间的关系,而不是由调用者的程序代码直接控制。这样,控制权由应用代码转移到了Spring容器

从Spring容器的角度,Spring容器负责将被依赖对象赋值给调用者的成员变量,这相当于为调用者注入了它依赖的实例,这就是Spring的依赖注入。

IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。

Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。

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

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

3. Hello Spring 3.1、导入Jar包

注 : spring 需要导入commons-logging进行日志记录 . 我们利用maven , 他会自动下载对应的依赖项 .


   org.springframework
   spring-webmvc
   5.1.10.RELEASE
3.2 、编写代码

1、编写一个Hello实体类

public class Hello {
   private String name;

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

   public void show(){
       System.out.println("Hello,"+ name );
  }
}

2、编写我们的spring文件 , 这里我们命名为beans.xml



	
	
    
    
    
    
    
	
   
   
       
   

3、我们可以去进行测试了 .

@Test
public void test(){
   //解析beans.xml文件 , 生成管理相应的Bean对象
   ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
   //getBean : 参数即为spring配置文件中bean的id .
   Hello hello = (Hello) context.getBean("hello");
   hello.show();
}
3.2、思考
  • Hello 对象是谁创建的 ? 【hello 对象是由Spring创建的

  • Hello 对象的属性是怎么设置的 ? hello 对象的属性是由Spring容器设置的

这个过程就叫控制反转 :

  • 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的

  • 反转 : 程序本身不创建对象 , 而变成被动的接收对象 .

依赖注入 : 就是利用set方法来进行注入的.

IOC是一种编程思想,由主动的编程变成被动的接收

可以通过newClassPathXmlApplicationContext去浏览一下底层源码 .

3.3、修改案例一

我们在案例一中, 新增一个Spring配置文件beans.xml




   
   

   
       
       
       
   

测试!

@Test
public void test2(){
   ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
   UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("ServiceImpl");
   serviceImpl.getUser();
}

OK , 到了现在 , 我们彻底不用再程序中去改动了 , 要实现不同的操作 , 只需要在xml配置文件中进行修改 , 所谓的IoC,一句话搞定 : 对象由Spring 来创建 , 管理 , 装配 !

4. IOC创建对象的方式 4.1、通过无参构造方法来创建

1、User.java

public class User {

   private String name;

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

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

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

}

2、beans.xml




   
       
   

3、测试类

@Test
public void test(){
   ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
   //在执行getBean的时候, user已经创建好了 , 通过无参构造
   User user = (User) context.getBean("user");
   //调用对象的方法 .
   user.show();
}

结果可以发现,在调用show方法之前,User对象已经通过无参构造初始化了!

4.2、通过有参构造方法来创建

1、UserT . java

public class UserT {

   private String name;

   public UserT(String name) {
       this.name = name;
  }

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

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

}

2、beans.xml 有三种方式编写


   
   



   
   



   

3、测试

@Test
public void testT(){
   ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
   UserT user = (UserT) context.getBean("userT");
   user.show();
}

结论:在配置文件加载的时候。其中管理的对象都已经初始化了!

5. Spring配置 5.1、别名

alias 设置别名 , 为bean设置别名 , 可以设置多个别名

 
5.2、Bean的配置 



   
5.3、import

团队的合作通过import来实现 .

6. DI依赖注入 6.1、概念
  • 依赖注入(Dependency Injection,DI)。

  • 依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .

  • 注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 .

依赖注入的作用就是在使用Spring框架创建对象的时候,动态地将其所依赖的对象注入 Bean 组件中,其实现方式通常有两种,一种是属性setter方式注入,另一种是构造方法注入。

6.2、构造器注入

我们在之前的案例已经讲过了

6.3、Set 注入 (重点)

要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 , 如果属性是boolean类型 , 没有set方法 , 是 is .

测试pojo类 :

Address.java

public class Address {
 
     private String address;
 
     public String getAddress() {
         return address;
    }
 
     public void setAddress(String address) {
         this.address = address;
    }
 }

Student.java

package com.king.pojo;
 
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 
 public class Student {
 
     private String name;
     private Address address;
     private String[] books;
     private List hobbys;
     private Map card;
     private Set games;
     private String wife;
     private Properties info;
 
     public void setName(String name) {
         this.name = name;
    }
 
     public void setAddress(Address address) {
         this.address = address;
    }
 
     public void setBooks(String[] books) {
         this.books = books;
    }
 
     public void setHobbys(List hobbys) {
         this.hobbys = hobbys;
    }
 
     public void setCard(Map card) {
         this.card = card;
    }
 
     public void setGames(Set games) {
         this.games = games;
    }
 
     public void setWife(String wife) {
         this.wife = wife;
    }
 
     public void setInfo(Properties info) {
         this.info = info;
    }
 
     public void show(){
         System.out.println("name="+ name
                 + ",address="+ address.getAddress()
                 + ",books="
        );
         for (String book:books){
             System.out.print("<<"+book+">>t");
        }
         System.out.println("n爱好:"+hobbys);
 
         System.out.println("card:"+card);
 
         System.out.println("games:"+games);
 
         System.out.println("wife:"+wife);
 
         System.out.println("info:"+info);
 
    }
 }

1、常量注入


     
 

测试:

 @Test
 public void test01(){
     ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
 
     Student student = (Student) context.getBean("student");
 
     System.out.println(student.getName());
 
 }

2、Bean注入

注意点:这里的值是一个引用,ref


     
 
 
 
     
     
 

3、数组注入


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

4、List注入

 
     
         听歌
         看电影
         爬山
     
 

5、Map注入


     
         
         
     
 

6、set注入


     
         LOL
         BOB
         COC
     
 

7、Null注入

 

8、Properties注入


     
         20190604
         
         小明
     
 

p命名和c命名注入

User.java :【注意:这里没有有参构造器!】

 public class User {
     private String name;
     private int age;
 
     public void setName(String name) {
         this.name = name;
    }
 
     public void setAge(int age) {
         this.age = age;
    }
 
     @Override
     public String toString() {
         return "User{" +
                 "name='" + name + ''' +
                 ", age=" + age +
                 '}';
    }
 }

1、P命名空间注入 : 需要在头文件中加入约束文件

 
 
 

2、c 命名空间注入 : 需要在头文件中加入约束文件

 
 
 

发现问题:爆红了,刚才我们没有写有参构造!

解决:把有参构造器加上,这里也能知道,c 就是所谓的构造器注入!

测试代码:

 @Test
 public void test02(){
     ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
     User user = (User) context.getBean("user");
     System.out.println(user);
 }

7. Bean的自动装配
  • 自动装配是Spring是满足bean依赖的一种方式

  • Spring会在上下文自动寻找,并自动给bean装配属性

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

  1. 在xml中显示配置

  2. 在java中显示配置

  3. 隐式的自动装配bean


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

  2. Byname自动装配:byname会自动查找,和自己对象set对应的值对应的id

    保证所有id唯一,并且和set注入的值一致

  3. Bytype自动装配:byType会自动查找,和自己对象属性相同的bean

    保证所有的class唯一

public class Cat {
    public void jiao(){
        System.out.println("miao");
    }
}
public class Dog {

    public void jiao(){
        System.out.println("wow");
    }

}
package com.pojo;


public class People {

    private Cat cat;
    private Dog dog;
    private String name;

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

    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;
    }

    public String getName() {
        return name;
    }

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



    
    
    
    
        
    
    
    
        
    

使用注解自动装配

jdk1.5支持的注解,spring2.5支持的注解

The introduction of annotation-based configuration raised the question of whether this approach is “better” than XML.

导入context约束




    

@Autowire

在属性上个使用,也可以在set上使用

我们可以不用编写set方法了

public class People {
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;
    private String name;
}
@Nullable 字段标志的注解,说明这个字段可以为null

如果@Autowired自动装配环境比较复杂。自动装配无法通过一个注解完成的时候

我们可以使用@Qualifier(value = "dog")去配合使用,指定一个唯一的id对象

public class People {
    @Autowired
    private Cat cat;
    @Autowired
    @Qualifier(value = "dog")
    private Dog dog;
    private String name;
}

@Resource(name="dog")也可以

区别:

  • @autowire通过byType实现,而且必须要求这个对象存在

  • @resource默认通过byName实现,如果找不到,通过byType实现

8. 使用注解开发

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

使用注解需要导入contex的约束




    

1.属性如何注入
@Component
public class User {
    
    @Value("dong")
    private String name;

    public String getName() {
        return name;
    }


}

@Component:组件,放在类上,说明这个类被Spring管理了,就是bean

@Component
public class User{
	public String name
}
 等价于

@Value:

public class User{
	@Value("King")
	public String name;
    
        /// 也可以注入到set方法上
        @Value("King")
        public void setName(String name) {
        	this.name = name;
        }
}
 等价于
2.衍生的注解

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

  • dao (@Repository)x

  • service(@Service)

  • controller(@Controller)

这四个注解功能一样的,都是代表将某个类注册到容器中

3.自动装配

@Autowired : 自动装配类型。名字 如果Autowired 不能唯一自动装配,则需要 @Qualifier(value="xxx") - @Nullable "xxx"表示的是bean-id ,字段标记了这个注解,说明这个字段可以用NULL @Resource : 自动装配通过名字。类型 (和上面一样是有顺序性)

3.作用域
@Scope("singleton")

@Component
@Scope("prototype")
public class User {

    @Value("dong")
    private String name;

    public String getName() {
        return name;
    }

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

小结:

xml与注解

  • xml更加万能,维护简单

  • 注解,不是自己的类,使用不了,维护复杂

最佳实践:

  • xml用来管理bean

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




    
    
    

9. 使用java方式配置spring(纯java,不用Spring)

我们现在要完全不使用Spring的xml配置了,全权交给java来做

JavaConfig是Spring的一个子项目,在spring4之后,他成为了核心功能

配置文件

 // 这个也会被spring容器托管,注册到容器中,因为他本来就是一个@Component
// @Configuration代表这是一个配置类,就和我们之前看的beans.xml一样
@Configuration
@ComponentScan("com.pojo")	// 扫描包
@import(Config2.class)
public class MyConfig {

    // 注册一个bean,就相当于我们之前写的一个bean标签
    // 他们函数名,就相当于 bean.id
    // 他的返回值,就相当于bean标签中的class属性
    @Bean
    public User getUser(){
        return new User();
    }

}

实体类

@Component
public class User {

    @Value("dong")
    private String name;

    public String getName() {
        return name;
    }

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

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

测试类

public class MyTest { 
    public static void main(String[ ] args) { 
    //如果完全使用了配置类方式去做,我们就只能通过 Annotationconfig 上下文来获取容器,通过配置类的class对象加载! 
    ApplicationContext context = new AnnotationConfigApplicationContext(KuangConfig.Class); //class对象
    User getUser =(User)context.getBean( "getUser"); //方法名getUser
    System.out.Println(getUser.getName()); 
    } 
}

这种纯java配置方式

在springboot中,随处可见

10. 代理模式(动态代理)

为什么要学习代理模式?

因为这是SpringAOP的底层


代理模式的分类:

  • 静态代理

  • 动态代理

10.1、静态代理

代理模式的好处:

  • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务!

  • 公共也就交给代理角色!实现了业务的分工!

  • 公共业务发生拓展的时候,方便拓展管理。

缺点:

  • 一个真实角色就又会产生一个代理角色,代码量会翻倍,开发效率变低(解决这个的办法,就是动态代理)

代码步骤

  1. 接口(抽象角色)

    public interface Rent {
    	public void rent();
    }

  2. 真实角色

public class Host implements Rent {
    public void rent() {
        System.out.println("房东要出租房子");
    }
}
  1. 代理角色

public class Proxy {

	public Host host;
	
	public Proxy() {
	}
	
	public Proxy(Host host) {
		super();
		this.host = host;
	}
	
	public void rent() {
		seeHouse();
		host.rent();
		fee();
		sign();
	}
        // 自己找房东看房子就只有1套,但是中介可以带你看很多很多套
	//看房
	public void seeHouse() {
		System.out.println("看房子");
	}
	//收费
	public void fee() {
		System.out.println("收中介费");
	}
	//合同
	public void sign() {
		System.out.println("签合同");
	}		
}
  1. 客户端访问代理角色

        
        
        ProxyInvocation proxyInvocation = new ProxyInvocation();

import pojo.Host;
import pojo.HostMaster;
import pojo.Proxy;

public class My {

	public static void main(String[] args) {
		//房东要出租房子
		Host host = new HostMaster();
		//中介帮房东出租房子,但也收取一定费用(增加一些房东不做的操作)
        	/// 代理角色一般有很多附属操作
      	      // 代理角色, 中介帮房东租房子
		Proxy proxy = new Proxy(host);
		//看不到房东,但通过代理,还是租到了房子
		proxy.rent();
		
	}
}
10.2、加深理解

如果我们要新增一个log日志功能,通常情况下,我们是直接在真实角色的源代码中直接cout,但是我们采用的是在代理角色中,新增一个方法,进行横向的修改。

这样就不会影响原有的代码逻辑!

在公司中,你就不需要改别人的代码实现系统功能的更新

  1. 接口(抽象角色)

    // 抽象角色
    public interface UserService {
        public void add();
        public void delete();
        public void update();
        public void query();
    }

  2. 真实角色

// 真实对象
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("查询了一个用户");
    }
}
  1. 代理角色

// 代理角色
public class UserServiceProxy implements UserService{
    UserServiceImpl userService = new UserServiceImpl();

    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 msg){
        System.out.println("[Debug]使用了"+msg+"方法");
    }

    // 1.改动原有代码在公司中是大忌,所以就多一层多一个方法

}
  1. 客户端访问代理角色

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

        UserServiceProxy proxy = new UserServiceProxy();
        proxy.setUserService(userService);

        proxy.add();
    }
}
10.3、动态代理

解决静态代理的缺点,既想让分工明确,又不想让代码量增加(上个demo中每个函数还是加了log函数)


  • 动态代理和静态代理,角色一样。

  • 动态代理的类,是动态生成的,不是我们直接写好的。

  • 动态代理分为两大类:①基于接口的动态代理,②基于类的动态代理

    ①基于接口:JDK 动态代理【我们在这里使用】

    ②基于类:cglib

    ③java字节码实现:javasist(但是不用用于Tomcat,而是JBoss服务器)

需要两个类:①Proxy:代理,②InvocationHandler:调用处理程序


InvocationHandler:调用处理程序

Proxy:代理

Object invoke(Object proxy, Method method, Object[] args);

参数: proxy: 调用该方法的代理实例

method:所述方法对应于调用代理实例上的接口方法的实例(要运行的方法)。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。

args:包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。


Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this);

代码实现

抽象角色

public interface Rent {
    public void rent();

}

真实角色

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

代理角色

// 等会我们会用这个类,动态生成代理类!
public class ProxyInvocationHandler implements InvocationHandler {

    // 被代理的接口(被代理的方法,比如说是增删改查的某一种)
    private  Rent rent;

    public void setRent(Rent rent) {
        this.rent = rent;
    }

    // 1.生成得到代理类,固定的代码,只需要改Rent
    public Object getProxy(){
        //                                          加载到类在那个位置,要代理的接口是哪一个接口         调用他自己来处理
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this);
    }

    // 2.处理代理实例,并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 用invoke 来执行反射方法
        // 动态代理的本质,就是使用反射机制来实现!
        Object result =  method.invoke(rent,args);  // 为什么会执行rent方法,就是因为在这里调用自己

        // 新加的方法直接在后面加上就好了,直接放到反射的类里面处理就好了
        seeHouse();
        fare();
        return result;
    }

    // 新加一个方法
    public void seeHouse(){
        System.out.println("中介带看房子");
    }

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

}

访问客户

public class Client {

    public static void main(String[] args) {
        // 真实角色
        Host host = new Host();

        // 代理角色:现在没有
        ProxyInvocationHandler pih = new ProxyInvocationHandler();  // 创建代理
        // 通过调用程序处理角色来处理我们要调用的接口
        pih.setRent(host);

        Rent proxy = (Rent) pih.getProxy(); // 这里的proxy是动态生成的,我们并没有写

        proxy.rent();   // 这里进入 invoke

    }
}

之后我们可以把代理角色公式化

// 写成活的,
public class ProxyInvocationHandler implements InvocationHandler {

    // private Rent rent;
    // 我们之前代理的是一个死的接口,现在我们可以把它变活
    private Object target;

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

    // 1.生成得到代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }

    // 2.处理代理实例,并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 用invoke 来执行反射方法
        // 动态代理的本质,就是使用反射机制来实现!
        Object result =  method.invoke(target,args);  // 为什么会执行rent方法,就是因为在这里调用自己
        log(method.getName());  // 通过反射来得到函数名字
        /// 新加的方法直接在后面加上就好了,直接放到反射的类里面处理就好了

        return result;
    }

    // 新加一个方法,加一个日志的功能

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

}

客户

public class Client {
    public static void main(String[] args) {
        /// 真实角色
        UserServiceImpl userService = new UserServiceImpl();
        /// 代理角色,不存在
        ProxyInvocationHandler pih = new ProxyInvocationHandler();

        /// 设置要代理的对象
        pih.setTarget(userService);
        /// 动态生成代理类
        UserService proxy = (UserService) pih.getProxy();
        proxy.delete();
    }
}

静态代理有的它都有,静态代理没有的,它也有!

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .

  • 公共的业务由代理来完成 . 实现了业务的分工 ,

  • 公共业务发生扩展时变得更加集中和方便 .

  • 一个动态代理 , 一般代理某一类业务

  • 一个动态代理可以代理多个类,代理的是接口!

11.AOP 11.1、什么是AOP

AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

11.2、Aop在Spring中的作用

==提供声明式事务;允许用户自定义切面==

以下名词需要了解下:

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 ....

  • 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。

  • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。

  • 目标(Target):被通知对象。

  • 代理(Proxy):向目标对象应用通知之后创建的对象。

  • 切入点(PointCut):切面通知 执行的 “地点”的定义。

  • 连接点(JointPoint):与切入点匹配的执行点。

SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:

即 Aop 在 不改变原有代码的情况下 , 去增加新的功能。

11.3、使用Spring实现Aop

【重点】使用AOP织入,需要导入一个依赖包!


    
        org.aspectj
        aspectjweaver
        1.9.4
    

方法一:

使用Spring API接口 【主要是Spring API接口实现】

首先编写我们的业务接口和实现类

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void select();
}
/// 真实对象
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 select() {
        System.out.println("查询了一个用户");
    }
}

然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强

// 前置日志
public class Log implements MethodBeforeAdvice {

    // method : 要执行的目标对象方法
    // object : 参数
    // target : 目标读写
    public void before(Method method, Object[] objects, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了" );
    }
}
// 后置日志
public class AfterLog implements AfterReturningAdvice {

    // returnValue : 返回值
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println( "执行了" + method.getName() + "方法,返回结果为:" + returnValue );
    }
}

最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 .




    
    
    
    

    
    
    
        
        

        
        
        
    

测试

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

        /// 动态代理代理的是接口:
        UserService userService = (UserService)context.getBean("userService");
        userService.add();
        userService.select();
    }

}

Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块 .

Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .

方法二:

自定义来实现AOP 【主要是切面定义】

目标业务类不变依旧是userServiceImpl

第一步 : 写我们自己的一个切入类

class DiyPointcut {

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

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

}

第二步:去spring中配置

 
    

    
        
        
            
            
            
            
            
        

    

方法三:

注解方式

第一步:编写一个注解实现的增强类

// 如果不写xml,直接在这里 @Component
@Aspect // 标注这个类是一个切面
public class AnnotationPointcut {

    @Before("execution(* com.king.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("---------方法执行前---------");
    }

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

    /// 在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点

    @Around("execution(* com.hoppz.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        System.out.println("签名:"+jp.getSignature());
        //执行目标方法proceed
        Object proceed = jp.proceed();
        System.out.println("环绕后");
        System.out.println(proceed);

}

第二步:在Spring配置文件中,注册bean,并增加支持注解的配置



12. 整合mybatis

文档: mybatis-spring –

12.1、回忆Mybatis


    
        Spring2
        org.example
        1.0-SNAPSHOT
    
    4.0.0

    spring-mybaits

    
        8
        8
    

    
        
            mysql
            mysql-connector-java
            5.1.47
        

        
            org.mybatis
            mybatis-spring
            2.0.6
        

        
            org.springframework
            spring-jdbc
            5.3.9
        


        
            org.mybatis
            mybatis
            3.5.7
        

        
        
            org.aspectj
            aspectjweaver
            1.9.7
        

        
            org.projectlombok
            lombok
            1.18.20
        
    


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






    
        
    

    
        
            
            
                
                
                
                
            
        
    

    
        
    





    

public interface UserMapper {
    List selectUser();
}
12.2、MyBatis-Spring学习

引入Spring之前需要了解mybatis-spring包中的一些重要类;

mybatis-spring –

什么是 MyBatis-Spring?

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。

要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。

在 MyBatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:


    
    
    
    
    

注意:SqlSessionFactory需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。


    
    
    
    

在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。

在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。

SqlSessionFactory有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。

一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 < settings> 或 < typeAliases>元素。

需要注意的是,这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(),数据源()和 MyBatis 的事务管理器()都会被忽略。SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值。

SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。

模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。

可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。


    
    

现在,这个 bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性,就像下面这样:

public class UserMapperImpl implements UserMapper{
    /// 我们所有的操作,都是用SQLSession来执行在原来,现在都是SqlSessionTemplate
    private SqlSessionTemplate sqlSession;

    public void setSqlSession(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }

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

按下面这样,注入 SqlSessionTemplate:


    

整合方式1:

  1. 编写数据源配置,以及mybati-config

  2. sqlSessionFactory

  3. sqlSessionTempla

  4. 需要给接口加实现类

  5. 将实现类注册到Spring容器中

  6. 测试

(代码详见Spring-mybaits)

整合方式2:

  1. 将我们上面写的UserDaoImpl修改一下(新加一个继承)

public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper {
    public List selectUser() {
        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}
  1. 修改bean的配置


    
  1. 测试

@Test
public void Test() throws IOException {

    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

    UserMapper userMapper = context.getBean("userMapper",UserMapper.class);
    for(User user : userMapper.selectUser()){
        System.out.println(user);
    }

}

13. 声明式事务 13.1、回顾事务
  • 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!

  • 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。

  • 事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。

事务四个属性ACID

  1. 原子性(atomicity)

    事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用

  2. 一致性(consistency)

    一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中

  3. 隔离性(isolation)

    可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏

  4. 持久性(durability)

    事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/694213.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号