IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring IOC与DI注解及代理模式 -> 正文阅读

[Java知识库]Spring IOC与DI注解及代理模式

DI 注入注解

因为之前 XML 配置方式比较繁琐。

<bean id="person" class="cn.XXX._01_hello.Person">
    <property name="name" value="大罗"/>
</bean>

注入注解分两类,Spring 的 Autowire,JavaEE 的 Resource,两者作用是一样,完成属性或字段的注入,注入是 bean(取代 XML property ref 元素)。而 Spring 的 Value 注解也是完成属性或字段的注入,注入是常量值(取代 XML property value 元素)。

Autowired 和 Value注解使用

编写类

public class Dog {
	@Value("黄色")
    private String color;
	
	@Override
	public String toString() {
		return "Dog [color=" + color + "]";
	}
}
public class Person {
	@Autowired
	private Dog dog;

	@Override
	public String toString() {
		return "Person [dog=" + dog + "]";
	}
}

编写配置文件

<bean id="dog" class="cn.xxx._04_anno.Dog"/>

<bean id="person" class="cn.xxx._04_anno.Person"/>

<!-- 配置 DI 注解解析器,但其实可以不配置 -->
<context:annotation-config/>

Autowired 注解细节

  • 可以让 Spring 自动的把属性或字段需要的对象找出来,并注入到属性上或字段上。
  • 可以贴在字段或者 setter 方法上面。
  • 可以同时注入多个对象。
  • 可以注入一些 Spring 内置的重要对象,比如 BeanFactory,ApplicationContext 等。
  • 默认情况下 Autowired 注解必须要能找到对应的对象,否则报错。通过 required=false 来避免这个问题:@Autowired(required=false),会给该字段赋值为null
  • 第三方程序:Spring3.0 之前需要手动配置 Autowired 注解的解析程序:< context:annotation-config/>,在 Spring 的测试环境可以不配置,在 Web 开发中换一种配置< context:component-scan base-package=“cn.xxx.扫描的包路径”/> 解决。
  • Autowired 注解寻找 bean 的方式:
    • 首先按照依赖对象的类型找,若找到,就是用 setter 或者字段直接注入。
    • 如果在 Spring 上下文中找到多个匹配的类型,再按照名字去找,若没有匹配报错。
    • 可以通过加一个注解 @Qualifier(“xml配置bean的id”) 标签来规定依赖对象按照 bean 的 id 和 类型的组合方式去找。

Resource 注解使用

添加依赖

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

使用

修改上面代码的 Person 类使用 Resource 注解,再重新测试即可。

@Component
public class Person {
	@Resource
	private Dog dog;

	@Override
	public String toString() {
		return "Person [dog=" + dog + "]";
	}
}

Resource 注解细节

  • 可以让 Spring 自动的把属性或字段需要的对象找出来,并注入到属性上或字段上。
  • 可以贴在字段或者 setter 方法上面。
  • 可以注入一些 Spring 内置的重要对象,比如 BeanFactory,ApplicationContext 等。
  • Resource 注解必须要能找到对应的对象,否则报错。
  • 第三方程序:Spring3.0 之前需要手动配置 Autowired 注解的解析程序:<context:annotation-config/>,在 Spring 的测试环境可以不配置, Web 开发中换一种配置解决。
  • Resource 注解寻找 bean 的方式:
    • 首先按照名字去找,如果找到,就使用 setter 或者字段注入。
    • 若按照名字找不到,再按照类型去找,但如果找到多个匹配类型,报错。
    • 可以直接使用 name 属性指定 bean 的名称(@Resource(name=“”));但若指定的 name,就只能按照 name 去找,若找不到,就不会再按照类型去。

IoC 注解

XML 配置问题

Spring 的 XML 配置文件中还有很多 bean 的配置,那能不能让 Spring 通过什么方式来简化配置呢?

<bean id="dog1" class="cn.xxx._04_anno.Dog">
    <property name="color" value="黄色"/>
</bean>

<bean id="dog2" class="cn.xxx._04_anno.Dog">
    <property name="color" value="黑色"/>
</bean>

IoC 注解

四个注解的功能是相同的,贴在类上 , 都是将某个类的对象交给spring管理 , 只是用于标注不同类型的类上:

  • @Repository:用于标注数据访问组件,即 DAO 实现类上。
  • @Service:用于标注业务层实现类上。
  • @Controller:用于标注控制层类上(如 SpringMVC 的 Controller)。
  • @Component:当不是以上的这几种分类含义的类,可以使用这个注解进行标注。

修改配置文件

<!-- 配置注解的第三方程序  解析 IoC 注解 DI 注解,让这些注解起作用-->
<context:component-scan base-package="cn.xxx.扫描的包路径"/>

Scope 和 PostConstruct 以及 PreDestroy 注解

问题

比如之前使用 XML 方式配置 bean 的作用域等能不能也使用注解的方式?

注解说明

  • @Scope:贴在类上,标明 bean 的作用域。
  • @PostConstruct:贴在方法上,标明 bean 创建完后调用此方法。
  • @PreDestroy:贴在方法上,标明容器销毁时调用此方法。

编写配置文件

配置文件内容不变,还是跟上面一样。

控制事务繁琐

问题

考虑一个应用场景:需要对系统中的某些业务方法做事务管理,拿简单的 save 操作举例。没有加上事务控制的代码如下

public class EmployeeServiceImpl implements IEmployeeService {
    public void save(Employee employee){
        // 保存业务操作
    }
}

修改源代码,加上事务控制之后:

public class EmployeeServiceImpl implements IEmployeeService {
    public void save(Employee employee){
        // 打开资源
        // 开启事务
        try {
            // 保存业务操作
            // 提交事务
        }catch (Exception e){
            // 回滚事务
        }finally{
            // 释放资源
        }
    }
}

上述问题:在我们的业务层中每一个业务方法都得处理事务(繁琐的 try-catch),这样设计上存在两个很严重问题:

  • 代码结构重复:在开发中不要重复代码,重复就意味着维护成本增大;
  • 责任不分离:业务方法只需要关心如何完成该业务功能,不需要去关系事务管理、日志管理、权限管理等等。

租房案例

在这里插入图片描述
问题:此时若有人来整房东,派很多人来找房东假租房,这会导致房东一天到晚都忙且没收获。带来这个问题就是:重复,且责任不分离,其实房东最关心的就是签合同和收房租。
在这里插入图片描述
后面我们就借鉴上面生活的例子的思想来之前事务繁琐的问题。

代理模式

好处

客户端(租客)直接使用的都是代理对象(中介),不知道真实对象(房东)是谁,此时代理对象(中介)可以在客户端(租客)和真实对象(房东)之间起到中介的作用。

  • 代理对象完全包含真实对象(中介存着房东联系方式),客户端(租客)使用的都是代理对象(中介)的方法,和真实对象没有直接关系;
  • 代理模式的职责:把不是真实对象(房东)该做的事情(比如看房子,谈价格等等)从真实对象(房东)上撇开—职责分离。

代理分类

  • 静态代理:在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了。(即代理类及对象要我们自己创建)
  • 动态代理:代理类是在程序运行期间由 JVM 通过反射等机制动态的生成的,所以不存在代理类的字节码文件,动态生成字节码对象,代理对象和真实对象的关系是在程序运行时期才确定的。(即代理类及对象不要我们自己创建)

动态代理实现方式

  • 针对真实类有接口使用 JDK 动态代理;
  • 针对真实类没实现接口使用 CGLIB 或 Javassist 组件。

动态代理实现机制

由于 JVM 通过字节码的二进制信息加载类的,若我们在运行期系统中,遵循 Java 编译系统组织 .class 文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类。如此,就完成了在代码中动态创建一个类的能力了。
在这里插入图片描述

静态代理

给业务类的方法增加模拟的事务。

类体系图

在这里插入图片描述

新建一个maven项目,设置编译版本及添加依赖

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
</properties>

<dependencies>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>5.0.8.RELEASE</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-test</artifactId>
		<version>5.0.8.RELEASE</version>
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.12</version>
		<scope>test</scope>
	</dependency>
</dependencies>

编写业务接口及实现

public interface IEmployeeService {
	void save(String username, String password);
}
// 房东
// 真实类,其对象为真实对象
public class EmployeeServiceImpl implements IEmployeeService {
	@Override
	public void save(String username, String password) {
		System.out.println("保存:" + username + ":" + password);
	}
}

编写代理类

// 中介
// 代理类,其对象为代理对象
public class EmployeeServiceProxy implements IEmployeeService {

	// 房东引用,真实对象引用
	private IEmployeeService target;
	
	public void setTarget(IEmployeeService target) {
		this.target = target;
	}
	
	//拥有事务功能的对象
	private MyTransactionManager tx;
	
	public void setTx(MyTransactionManager tx) {
		this.tx = tx;
	}
	
	@Override
	public void save(String username, String password) {
		try {
			tx.begin();
			// 保存的业务操作,找房东来做,找真实对象来做
			target.save(username, password);
			tx.commit();
		}catch (Exception e) {
			e.printStackTrace();
			tx.rollback();
		}
	}
}

编写事务模拟类

public class MyTransactionManager {
	public void begin() {
		System.out.println("开启事务");
	}
	public void commit() {
		System.out.println("提交事务");
	}
	public void rollback() {
		System.out.println("回滚事务");
	}
}

使用xml或注解配置

<!-- 配置事务管理器对象 -->
<bean id="tx" class="cn.wolfcode.tx.MyTransactionManager"/>
	
<!-- 配置代理对象 -->
<bean id="employeeServiceProxy" class="cn.wolfcode.service.impl.EmployeeServiceProxy">
    <property name="target">
        <!-- 配置真实对象, 藏起来,避免在使用接口对象注入时找到2个实现类,如果使用注解配置则指定id名称 -->
        <bean class="cn.wolfcode.service.impl.EmployeeServiceImpl"/>
    </property>
    <property name="tx" ref="tx"></property>
</bean>

优缺点

优点

  • 业务类只需要关注业务逻辑本身,保证了业务类的重用性。
  • 把真实对象隐藏起来了,保护真实对象(用了 Spring )。

缺点

  • 代理对象的某个接口只服务于某一种类型的对象,也就是为每个真实类创建一个代理类,比如项目还有其他业务类呢。
  • 若需要代理的方法很多,则要为每一种方法都进行代理处理。
  • 若接口增加一个方法,除了所有实现类需要实现这个方法外,代理类也需要实现此方法。

JDK 动态代理

给业务类的方法增加模拟的事务。

java.lang.reflect.Proxy

Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
主要方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler hanlder)
  • 方法职责:根据指定的类加载器、一组需要代理的接口及方法调用处理器生成动态代理类实例。
  • 参数:
    • loader :类加载器,一般给真实对象的类加载器;
    • interfaces:代理类需要实现的接口,代理这个接口中的所有方法;
    • handler:调用处理器,就是生成的代理对象在执行方法时是执行这个对象里我们定义的调用规则。
  • 返回:创建好的代理对象。

java.lang.reflect.InvocationHandler 调用处理器

主要方法:

public Object invoke(Object proxy, Method method, Object[] args)
  • 方法职责:负责处理动态代理类上的所有方法调用,让使用者自定义做什么事情,对原来方法增强(加什么功能)。
  • 参数:
    • proxy :生成的代理对象;
    • method:当前需要代理的方法;
    • args :当前调用方法的实参。
  • 返回:代理逻辑和真实对象调用方法之后想要返回的结果。

JDK动态代理代码实现

编写 TransactionInvocationHandler 类

该类实现 InvocationHandler 接口,实现 invoke 方法,实现增强操作。

public class TransactionInvocationHandler implements InvocationHandler {
	//真实对象引用,Object类型,为了方便存任意真实对象,为多个类做代理时通用
	private Object target;
	public void setTarget(Object target) {
		this.target = target;
	}
	public Object getTarget() {
		return target;
	}

	private MyTransactionManager tx;
	public void setTx(MyTransactionManager tx) {
		this.tx = tx;
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("到此一游");
		Object retVal = null;
		try {
			tx.begin(); // 调用事务管理器对象的方法
			retVal= method.invoke(target, args); // 调用真实对象的方法
			tx.commit(); // 调用事务管理器对象的方法
		}catch (Exception e) {
			tx.rollback(); // 调用事务管理器对象的方法
		}
		return retVal; // 返回方法调用的结果
	}
}

修改 applicationContext.xml

配置 TransactionInvocationHandler、MyTransactionManager、EmployeeServiceImpl,让 Spring 帮我们创建这些对象组装依赖,也可以不用spring帮我们进行管理。

<!-- 配置事务管理器对象 -->
<bean id="tx" class="cn.wolfcode.tx.MyTransactionManager"/>

<!-- 配置调用处理器对象 -->
<bean id="transactionInvocationHandler" class="cn.wolfcode.handler.TransactionInvocationHandler">
    <property name="target">
        <bean class="cn.wolfcode.service.impl.EmployeeServiceImpl"/>
    </property>
    <property name="tx" ref="tx"/>
</bean>

测试类

注入类型 InvocationHandler 的 bean,在测试方法中手动使用 Proxy 中的方法创建代理对象,调用代理对象的方法

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class EmployeeServiceTest {
    @Autowired
    //调用处理器对象
    private TransactionInvocationHandler transactionInvocationHandler;
    
    @Test
    public void testSave() {
        // 动态生成代理类,并创建代理对象
        IEmployeeService proxy = (IEmployeeService)Proxy.newProxyInstance(
        	//获取类加载器对象,从调用处理器中获取,因为里面我们存放了
            transactionInvocationHandler.getTarget().getClass().getClassLoader(),
            // 获取真实类实现的接口,生成的代理类也是实现这些接口
            transactionInvocationHandler.getTarget().getClass().getInterfaces(),
            // 将调用处理器放进去,通过这个参数告诉 API 生成代理对象具体做什么
            transactionInvocationHandler);
        proxy.save("罗老师", "666");
    }

}

优缺点

优点

对比静态代理,发现不需手动地提供那么多代理类。

缺点

  • 真实对象必需实现接口(JDK 动态代理特有);
  • 动态代理的最小单位是类(类中方法都会被代理),如果只想拦截一部分方法,可以在 invoke 方法中对要执行的方法名进行判断;
  • 对多个真实对象进行代理的话,要手动写代码调用api创建代理对象,用起来麻烦。

JDK 动态代理原理

获取代理类字节码文件

ProxyGenerator 在 JDK 11 已经不是公有的了。

通过反编译工具查看字节码文件

使用 IDEA 就可以反编译。观察:save 方法,发现底层其实依然在执行 InvocationHandler 中的 invoke 方法。
在这里插入图片描述

CGLIB 动态代理

JDK 动态代理的问题

JDK 动态代理要求真实类必须实现接口。而 CGLIB 与 JDK 动态代理不同是,真实类不用实现接口,生成代理类的代码不一样且代理类会继承真实类。

org.springframework.cglib.proxy.Enhancer

类似 JDK 中 Proxy,用来生成代理类创建代理对象的。

org.springframework.cglib.proxy.InvocationHandler

类似 JDK 中 InvocationHandler,让使用者自定义做什么事情,对原来方法增强。

CGLIB 动态代理代码实现

改用 Enhancer API 来生成代理类创建代理对象。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class EmployeeServiceTest {
	
	@Autowired
	//调用处理器对象
	private TransactionInvocationHandler transactionInvocationHandler;
	
	@Test
	public void testSave() {
		//创建增强器对象
		Enhancer enhancer = new Enhancer();
		// 设置要生成代理类 继承的类
		enhancer.setSuperclass(transactionInvocationHandler.getTarget().getClass());
		// 设置生成代理类对象的调用处理器
		enhancer.setCallback(transactionInvocationHandler);
		// 生成代理类,并创建代理对象
		EmployeeServiceImpl proxy = (EmployeeServiceImpl)enhancer.create();
		proxy.save("罗老师", "666");
	}

}

动态代理总结

JDK 动态代理总结

  • Java 动态代理是使用 java.lang.reflect 包中的 Proxy 类与 InvocationHandler 接口这两个来完成的。
  • 要使用 JDK 动态代理,真实类必须实现接口,并只能代理这个接口中的方法。
  • JDK 动态代理将会拦截所有 pubic 的方法(因为只能调用接口中定义的方法),这样即使在接口中增加了新的方法,不用修改代码也会被拦截。
  • 动态代理的最小单位是类(类中某些方法都会被处理),如果只想拦截一部分方法,可以在 invoke 方法中对要执行的方法名进行判断。
  • 特征 : 代理类与真实类共同实现相同的接口

CGLIB 动态代理总结

  • CGLIB 可以生成真实类的子类,并重写父类非 final 修饰符的方法。
  • 要求真实类不能是 final 的,要拦截的方法要是非 final、非 static、非 private 的。
  • 动态代理的最小单位是类(类中某些方法都会被处理),如果只想拦截一部分方法,可以在 invoke 方法中对要执行的方法名进行判断。
  • 特征 : 代理类是继承真实类

两种代理的选用

JDK 动态代理是基于实现接口的,CGLIB 和 Javassit 是基于继承真实类的。

从性能上考虑:Javassit > CGLIB > JDK。

选用如下:

  • 若真实类实现了接口,优先选用 JDK 动态代理。(因为会产生更加松耦合的系统,也更符合面向接口编程)
  • 若真实类没有实现任何接口,使用 Javassit 和 CGLIB 动态代理。
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-09-30 00:38:26  更:2022-09-30 00:41:04 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年3日历 -2025/3/10 15:57:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码