学习spring框架的笔记-两大核心以及常见注解
SpringSpring框架概述什么是SpringSpring是一个 开源 的 轻量级 框架。可以使我们的开发更加方便和快捷的开发。Spring可以解决企业应用开发的复杂性。Spring有两个核心部分:IOC控制反转(DI注入)把创建对象的过程交给 Spring进行管理。AOP面向切面编程不修改源代码的情况下,可以进行功能的增强Spring的特点方便解耦,简化开发Aop编程的支持方便程序的测试方便
Spring
Spring框架概述
什么是Spring
-
Spring是一个 开源 的 轻量级 框架。可以使我们的开发更加方便和快捷的开发。
-
Spring可以解决企业应用开发的复杂性。
-
Spring有两个核心部分:
-
IOC控制反转(DI注入)
把创建对象的过程交给 Spring进行管理。
-
AOP面向切面编程
不修改源代码的情况下,可以进行功能的增强
-
Spring的特点
- 方便解耦,简化开发
- Aop编程的支持
- 方便程序的测试
- 方便整合其他框架
- 方便进行事务的操作
- 降低来API的开发难度
IOC容器
- IOC底层原理
- IOC接口(BeanFactory)
- IOC操作Bean管理(基于xml)
- IOC操作Bean管理(基于注解)
IOC概念和底层原理
什么是IOC
IOC:控制反转,是面向对象设计的一种设计原则,可以降低代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection DI)。
通俗的说:以前我们说通过 new的方式创建对象来进行引用,现在我们把对象的创建和对象之间的调用过程都交给 Spring进行管理。
使用IOC的目的
为了降低代码之间的耦合度
IOC底层原理
-
XML解析
-
工厂模式
-
反射
IOC(接口)
-
IOC思想基于IOC容器完成,IOC底层就是对象工厂。
-
Spring提供了两种IOC容器的实现方式:(两个接口)
-
BeanFactory
IOC容器的基本实现,是Spring内部的使用接口,不提供开发人员进行使用
加载配置文件(applicationContext.xml)的时候,不会创建对象,而在获取或者使用配置文件里面配置的对象的时候,才会创建该对象。
-
ApplicationContext:
BeanFactory接口的子接口,提供更多更强大的功能,一般开发人员进行使用。
在加载配置文件的时候,就会直接创建配置文件里面配置的对象
-
ApplicationContext的实现类:主要是最后两个
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gHkwbv03-1623675729318)(spring.assets/截屏2021-05-11 下午1.35.34.png)]
IOC操作Bean管理
什么是Bean管理
bean管理指的是两个操作
- Spring创建对象
- Spring注入属性
Bean管理操作的两种方式
- 基于 xml 配置文件
- 基于 注解 方式进行实现
基于xml方式创建对象
<!--配置User对象创建-->
<!-- 默认是单例模式,也就是只有一个User类对象 -->
<!-- 执行的是User类的无参构造 -->
<bean id="user" class="com.mao.bean.User"></bean>
在 Spring的配置文件中,使用 bean标签,在标签里面添加相应的属性,就可以实现对象的创建
**在bean标签里面有很多属性**
- id:对象别名,或者说是唯一标识,根据这个名字可以找到这个对象。
- class: 类的全限定名称
**<span style="color:red">注意:spring管理的对象默认情况下都是单例的。所以根据id标识多次获取同一个类的实例其实都是同一个对象。</span>**
```java
public class User {
public void add(){
System.out.println("add........");
}
}
@Test
public void test(){
// 加载Spring的配置文件
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取配置创建的对象,参数1: 配置文件里面的对象 id 参数2: 对象的类型
User user = ac.getBean("user", User.class);
User user2 = ac.getBean("user", User.class);
System.out.println(user == user2);// true
user.add();
}
基于xml方法注入属性
DI:依赖注入,就是注入属性。在创建对象的基础上进行完成。是IOC的一种具体实现
使用 set方法 方式进行注入
创建类:
package com.mao.bean;
/**
* @Description 使用set方式进行属性的注入
* @Author 毛毛
* @CreateDate 2021/05/11/周二 1:56 下午
*/
public class Book {
// 在类中需要属性,以及属性的set方法
private String bookName;
private String bookAuthor;
public String getBookAuthor() {
return bookAuthor;
}
public void setBookAuthor(String bookAuthor) {
this.bookAuthor = bookAuthor;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
}
在配置文件中进行相关配置
配置对象的创建,以及属性的注入(类属性必须有set方法)
<!-- 配置Book对象-->
<!-- 使用set方法注入属性-->
<bean id="book" class="com.mao.bean.Book">
<!-- 属性的注入,在bean 标签里面使用property标签完成属性的注入 -->
<!-- name 属性名(就是setXxx方法后面的Xxx的首字母小写) value 属性值 ref 引用类型属性的赋值 -->
<property name="bookName" value="java入门"/>
<property name="bookAuthor" value="毛毛"/>
</bean>
测试:发现对象具有了属性值。
@Test
public void test2(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Book book = ac.getBean("book", Book.class);
System.out.println(book.getBookName()+"---------"+book.getBookAuthor());
}
使用 有参构造方法 进行 注入
创建类
package com.mao.bean;
/**
* @Description 使用构造方法的形式注入属性
* @Author 毛毛
* @CreateDate 2021/05/11/周二 4:25 下午
*/
public class Order {
// 订单名称和订单数量
private String name;
private int age;
public Order(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Order{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
在配置文件中进行相关配置
<!-- 构造方法的形式注入属性-->
<bean id="order" class="com.mao.bean.Order">
<!-- 使用 constructor-arg 标签进行构造方法的赋值 -->
<!-- 可以按照 index 来填写属性,是按照参数的位置来的,其依据就是构造函数中行参的顺序 -->
<!-- 可以不写基本类型 -->
<constructor-arg name="name" value="大订单" type="java.lang.String"/>
<constructor-arg name="age" value="10"/>
</bean>
测试:
@Test
public void test3(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Order order = ac.getBean("order", Order.class);
System.out.println(order);
}
P 名称空间注入(了解)
使用 p 名称空间注入,可以简化基于 xml 配置的方式
-
在 配置文件的 beans标签上 添加 p 名称空间约束(idea中不需要手动添加的,使用 p 名称空间的时候会自动给我们加上)
xmlns:p="http://www.springframework.org/schema/p"
-
进行属性的注入(在 bean标签上)
<!-- p 名称空间的形式进行注入(set方式的简化,需要具有属性的set方法)-->
<bean id="book2" class="com.mao.bean.Book" p:bookName="哈哈哈" p:bookAuthor="我是p名称空间形式" />
IOC操作Bean管理(xml注入其他类型属性)
1. 字面量(固定值)
-
null 值
<!-- set方法注入属性,让其字面量的值为空值 null--> <bean id="bookTow" class="com.mao.bean.BookTow"> <!-- 设置bookName属性值为null,在property标签上不使用value属性,在其内部使用null标签就好了 --> <property name="bookName"> <null/> </property> </bean>
-
属性值包含特殊符号
<bean id="bookTow" class="com.mao.bean.BookTow"> <!-- 设置bookName属性值为null,在property标签上不使用value属性,在其内部使用null标签就好了 --> <property name="bookName"> <null/> </property> <!-- 设置address属性值包含特殊符号,比如 < > = 这些--> <!-- 第一种方式,对特殊符合进行转义--> <property name="address" value="<hah>"></property> <!-- 第二种方式,把特殊符号对内容写到 CDATA里面(xml的写法) --> <property name="bookAuthor"> <!--value标签最好不要换行--> <value><![CDATA[<><哈哈哈>=]]></value> </property> <!-- 现在可以直接使用某些特殊符号了,比如大于小于 > < --> </bean>
2. 注入属性—外部 bean
-
创建两个类 service 和 dao 类
package com.mao.service; /** * @Description * @Author 毛毛 * @CreateDate 2021/05/11/周二 5:16 下午 */ public class UserService { public void add(){ System.out.println("service add........."); } } package com.mao.dao; /** * @Description * @Author 毛毛 * @CreateDate 2021/05/11/周二 5:17 下午 */ public interface UserDao { public void update(); } package com.mao.dao.impl; import com.mao.dao.UserDao; /** * @Description * @Author 毛毛 * @CreateDate 2021/05/11/周二 5:18 下午 */ public class UserDaoImpl implements UserDao { @Override public void update() { System.out.println("dao update........"); } }
-
在 service 类中调用 dao 里面的方法
public class UserService { // 创建 UserDao类的属性,生成 set方法 private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void add(){ System.out.println("service add........."); userDao.update(); } }
-
在 Spring的配置文件中配置注入引用类型
<!-- service和dao对象的创建--> <bean id="userService" class="com.mao.service.UserService"> <!-- 注入 userDao对象, ref 的属性值就是创建userDao对象bean标签的id值(唯一标识) 引用数据类型的注入,--> <property name="userDao" ref="userDao"/> </bean> <bean id="userDao" class="com.mao.dao.impl.UserDaoImpl"></bean>
-
测试
@Test public void test(){ ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext2.xml"); UserService userService = ac.getBean("userService", UserService.class); userService.add(); }
3. 注入属性—内部bean和级联赋值
-
一对多的关系: 例如 部门和员工
一个部门可以有多个员工,一个员工属于一个部门
-
在实体类之间表示一对多的关系
package com.mao.bean;
/**
* @Description 部门类
* @Author 毛毛
* @CreateDate 2021/05/12/周三 12:10 下午
*/
public class Dept {
private String deptName;
public void setDeptName(String deptName) {
this.deptName = deptName;
}
}
package com.mao.bean;
/**
* @Description 员工类
* @Author 毛毛
* @CreateDate 2021/05/12/周三 12:11 下午
*/
public class Emp {
private String empName;
private String gender;
// 员工属于那个部门
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public void setGender(String gender) {
this.gender = gender;
}
}
<!-- 内部bean -->
<bean id="emp" class="com.mao.bean.Emp">
<!-- 普通属性的注入-->
<property name="empName" value="员工1"/>
<property name="gender" value="男"/>
<!-- 对象类型(引用类型的注入)-->
<property name="dept">
<!-- 采用内部bean的方式进行赋值(有点内部类的味道),该内部定义的bean 无法被外界其他对象再次引用,只能在内部进行使用 -->
<bean id="dept" class="com.mao.bean.Dept">
<property name="deptName" value="哈哈"/>
</bean>
</property>
</bean>
<!-- <bean id="ee" class="com.mao.bean.Emp">-->
<!-- 无法访问到 dept 这个对象 -->
<!-- <property name="dept" ref="dept"/>-->
<!-- </bean>-->
注意:内部定义的bean 无法被外界其他对象再次引用,只能在内部进行使用。
4. 级联赋值
向其有关联的那些类的属性中赋值。
<!-- 级联赋值-->
<bean id="emp" class="com.mao.bean.Emp">
<!-- 普通属性的注入-->
<property name="empName" value="员工1"/>
<property name="gender" value="男"/>
<!-- 级联赋值(就是外部bean的方式通过ref引入)-->
<property name="dept" ref="dept"/>
</bean>
<bean id="dept" class="com.mao.bean.Dept">
<property name="deptName" value="111"/>
</bean>
<!-- 级联赋值写法二 :-->
<bean id="emp2" class="com.mao.bean.Emp">
<!-- 普通属性的注入-->
<property name="empName" value="员工1"/>
<property name="gender" value="男"/>
<!-- 级联赋值(就是外部bean的方式通过ref引入)-->
<property name="dept" ref="dept"/>
<!-- 采用获取对象引用的形式进行赋值,需要有相应属性的get方法,能得到dept这个对象 -->
<property name="dept.deptName" value="哈啊哈"/>
</bean>
5. xml注入集合属性
- 注入数组类型属性
- 注入List集合类型属性
- 注入Map集合类型属性
创建Stu类
package com.mao.bean.collectiontype;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @Description 集合类型属性的注入
* @Author 毛毛
* @CreateDate 2021/05/12/周三 12:44 下午
*/
public class Stu {
private String[] courses;
private List<String> list;
private Map<String, String> maps;
private Set<String> set;
public Set<String> getSet() {
return set;
}
public void setSet(Set<String> set) {
this.set = set;
}
public Stu(String[] courses, List<String> list, Map<String, String> maps, Set<String> set) {
this.courses = courses;
this.list = list;
this.maps = maps;
this.set = set;
}
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
public Map<String, String> getMaps() {
return maps;
}
public void setMaps(Map<String, String> maps) {
this.maps = maps;
}
public String[] getCourses() {
return courses;
}
public void setCourses(String[] courses) {
this.courses = courses;
}
public Stu() {
}
@Override
public String toString() {
return "Stu{" +
"courses=" + Arrays.toString(courses) +
", list=" + list +
", maps=" + maps +
", set=" + set +
'}';
}
}
对配置文件进行相关配置
<!-- 集合类型属性对注入-->
<bean id="stu" class="com.mao.bean.collectiontype.Stu">
<!-- 数组类型对注入-->
<property name="courses">
<!-- 注入数组对值,我们一般采用array标签,集合一般使用list标签-->
<array>
<!-- 每一个value标签里面的值,都代表一个数组元素,依次是第0号元素,第一号元素-->
<value>java</value>
<value>python</value>
<value>mysql</value>
</array>
</property>
<!-- list集合类型的注入-->
<property name="list">
<list>
<!-- 一个value标签内部的值,代表一个集合元素 -->
<value>张三</value>
<value>李四</value>
<value>王五</value>
</list>
</property>
<!-- Map集合类型的注入-->
<property name="maps">
<!-- 采用map标签,来完成map属性的注入-->
<map>
<!--map集合元素的注入采用entry标签 key属性表示键,value 表示值 -->
<entry key="1" value="111"></entry>
<entry key="2" value="222"></entry>
<entry key="3">
<value>333</value>
</entry>
<entry>
<!-- 键为null的时候可以这样赋值,不然只能将key作为entry标签的属性进行键的赋值-->
<key/>
<value>555</value>
</entry>
</map>
</property>
<!-- set集合类型的属性的注入-->
<property name="set">
<!-- 使用set标签注入set集合-->
<set>
<value>mysql</value>
<value>redis</value>
<value>mysql</value>
</set>
</property>
</bean>
测试:
@Test
public void test(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext4.xml");
Stu stu = ac.getBean("stu", Stu.class);
System.out.println(stu);// Stu{courses=[java, python, mysql], list=[张三, 李四, 王五], maps={1=111, 2=222, 3=333, null=555}, set=[mysql, redis]}
}
注意:
上面的数组或者集合类型,里面存储的数据都是String类型,如何在集合等类型中存储对象(引用类型的数据呢)?
上面的集合的数据都在bean对象内部,外部其他配置的对象无法再次访问,如何抽取出来作为公共部分,让每个对象都可以使用呢?
集合类型属性设置对象类型值
课程类
package com.mao.bean.collectiontype;
/**
* @Description 课程类
* @Author 毛毛
* @CreateDate 2021/05/12/周三 1:29 下午
*/
public class Course {
private String courseName;
public String getCourseName() {
return courseName;
}
@Override
public String toString() {
return "Course{" +
"courseName='" + courseName + '\'' +
'}';
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
}
在 stu类中加上 课程集合列表
// 课程属性,学生可以选多门课程
private List<Course> courseList;
public List<Course> getCourseList() {
return courseList;
}
public void setCourseList(List<Course> courseList) {
this.courseList = courseList;
}
在Spring配置文件中进行配置
<!--集合类型的属性注入,集合存储的元素是课程对象(引用类型)-->
<property name="courseList">
<list>
<!-- 引用类型集合元素的注入,采用ref标签-->
<ref bean="course1"/>
<ref bean="course2"/>
</list>
</property>
<!-- 创建多个课程对象-->
<bean id="course1" class="com.mao.bean.collectiontype.Course">
<property name="courseName" value="Java"/>
</bean>
<bean id="course2" class="com.mao.bean.collectiontype.Course">
<property name="courseName" value="mybatis"/>
</bean>
集合注入部分提取出到公共部分
新建 Book类
package com.mao.bean.collectiontype;
import java.util.List;
/**
* @Description
* @Author 毛毛
* @CreateDate 2021/05/12/周三 1:42 下午
*/
public class Book {
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
private List<String> list;
@Override
public String toString() {
return "Book{" +
"list=" + list +
'}';
}
}
在 Spring 配置文件中 引入名称空间 util(不需要记,idea可以自动引入,只要你使用util)
<util:list>
使用这个标签,然后让idea自动帮忙导入就好了。
使用 util标签完成list集合的注入
<!-- 提取list集合类型属性注入-->
<util:list id="bookList">
<!--如果集合类型的元素存储的是引用类型数据,可以使用 ref标签-->
<value>Java</value>
<value>redis</value>
<value>mysql</value>
</util:list>
<!-- 提取list集合类型属性的注入使用-->
<!-- 在标签 property上使用 ref进行集合的赋值-->
<bean id="book" class="com.mao.bean.collectiontype.Book">
<property name="list" ref="bookList"/>
</bean>
IOC 操作Bean管理(工厂Bean)
Spring中有两种bean,一种普通的bean,一种工厂类型的bean(FactoryBean)
普通bean
在配置文件中定义bean的类型(class全类名)就是返回类型。
工厂bean
在配置文件中定义bean的类型和返回的类型可能不一样。(可以不一样)
先创建类,让这个类作为工厂bean,实现一个接口,FactoryBean
然后 实现接口里面的方法,在实现的方法中定义返回的bean类型
package com.mao.factoryBean;
import com.mao.bean.collectiontype.Course;
import org.springframework.beans.factory.FactoryBean;
/**
* @Description 工厂bean的使用,创建类实现 FactoryBean接口并实现方法
* @Author 毛毛
* @CreateDate 2021/05/12/周三 2:16 下午
*/
public class MyBean implements FactoryBean<Course> {
/**
* 返回bean的实例
* @return
* @throws Exception
*/
@Override
public Course getObject() throws Exception {
return new Course();
}
/**
* bean的类型
* @return
*/
@Override
public Class<Course> getObjectType() {
return Course.class;
}
/**
* 是否是单例
* @return
*/
@Override
public boolean isSingleton() {
return false;
}
}
配置:
<!-- 工厂bean的使用-->
<bean id="myBean" class="com.mao.factoryBean.MyBean"></bean>
测试:
@Test
public void test(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext7.xml");
Course myBean = ac.getBean("myBean", Course.class);
System.out.println(myBean);
}
IOC操作bean管理(作用域)
在Spring中,可以设置创建 bean 实例是单实例还是多实例
默认情况下,创建的bean是一个单实例对象。
User user = ac.getBean("user", User.class);
User user2 = ac.getBean("user2", User.class);
System.out.println(user == user2);// true
如何配置bean是单实例还是多实例:
在 bean标签上有一个属性 scope,用来设置
scope值有4种,常用的有两种
- singleton: 默认值,表示单实例对象
- prototype: 表示多实例对象
- request:
- session:
注意:singleton和propertype区别:
- 单实例和多实例的区别
- scope属性的值为默认值(singleton)的时候,加载配置文件的时候就会创建单实例对象。如果设置值为 prototype,那么加载配置文件的时候,并不会创建该对象,而是在每次调用 getBean()方法获取该唯一id所对应的bean配置对象时,创建我们所需要的对象。说简单点也就是什么时候用,什么时候创建多实例对象。不用不会创建。
IOC操作bean管理(声明生命周期)
**生命周期:**对象从创建到对象销毁的过程
bean的生命周期:
- 通过构造器创建bean实例(无参数构造)
- 为bean的属性设置对应的值和对其他bean的引用(实际上就是调用属性的set方法)
- 调用bean的初始化的方法(需要进行配置)(init-method=“类中定义的初始化方法名”)
- bean可以使用了(对象获取到了)(测试,getBean)
- 当容器关闭的时候,调用bean的销毁的方法(需要进行配置销毁的方法)(destroy-methos=“类中定义的销毁方法名”)
**Bean的后置处理器(对所有对bean实例进行处理):**bean的生命周期有七步(类需要实现BeanPostProcessor接口)
后置处理器需要在Spring配置文件中配置一下
- 通过构造器创建bean实例(无参数构造)
- 为bean的属性设置对应的值和对其他bean的引用(实际上就是调用属性的set方法)
- 把bean实例传递bean后置处理器方法(postProcessBeforeInitialization)
- 调用bean的初始化的方法(需要进行配置)(init-method=“类中定义的初始化方法名”)
- 把bean实例传递bean后置处理器方法(postProcessAfterInitialization)
- bean可以使用了(对象获取到了)(测试,getBean)
- 当容器关闭的时候,调用bean的销毁的方法(需要进行配置销毁的方法)(destroy-methos=“类中定义的销毁方法名”)
**用途: **
可以用来对传入的数据进行封装,像表单数据的封装
IOC操作Bean管理(xml自动装配)
1. 什么是自动装配
根据指定装配规则(属性名称或者属性类型),Spring自动将匹配的属性进行自动注入
根据属性名称进行自动注入
package com.mao.autowire;
/**
* @Description
* @Author 毛毛
* @CreateDate 2021/05/13/周四 8:09 上午
*/
public class Dept {
private String name;
@Override
public String toString() {
return "Dept{" +
"name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.mao.autowire;
/**
* @Description
* @Author 毛毛
* @CreateDate 2021/05/13/周四 8:09 上午
*/
public class Dept {
private String name;
@Override
public String toString() {
return "Dept{" +
"name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Spring配置
<!-- 自动装配,在bean标签中,有一个autowire属性,自动装配-->
<!--
autowire: 自动装配
属性值:
byName: 根据属性名称注入
要自动装配的属性名必须要和配置文件中某个bean的id一致
byType: 根据属性类型注入
-->
<bean id="emp" class="com.mao.autowire.Emp" autowire="byName"></bean>
<bean id="dept" class="com.mao.autowire.Dept">
<property name="name" value="哈哈"/>
</bean>
根据属性类型进行自动注入
根据类型进行装配的时候,属性对应的属性值的bean只能在配置文件中出现一次。
<bean id="emp" class="com.mao.autowire.Emp" autowire="byType"></bean>
<!-- 根据类型进行装配的时候,属性对应的属性值的bean只能在配置文件中出现一次 -->
<!-- <bean id="dept" class="com.mao.autowire.Dept">-->
<!-- <property name="name" value="哈哈"/>-->
<!-- </bean>-->
<bean id="dept2" class="com.mao.autowire.Dept">
<property name="name" value="哈哈111"/>
</bean>
IOC操作Bean管理(外部属性文件)
-
直接配置数据库连接池
配置德鲁伊
<!-- 直接配置数据库连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <!-- 数据库驱动名称--> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <!-- 数据库地址(连接那一台主机上的mysql)--> <property name="url" value="jdbc://localhost://localhost:3306/Test"/> <!-- 数据库的用户名--> <property name="username" value="root"/> <!-- 数据库的密码--> <property name="password" value="826106MjCpf."/> </bean>
-
引入外部资源文件的方式配置数据库连接池
先创建一个properties格式的配置文件,书写jdbc的相关配置。(jdbc.properties)
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc://localhost://localhost:3306/Test
jdbc.username=root
jdbc.password=826106MjCpf.
在Spring配置文件中,需要引入一个名称空间(idea可以帮忙自动导入,我们只需要打上标签)
书写标签<context:property-placeholder>
然后换行,idea自动导入名称空间。
<!-- 引入配置文件的方法,完成数据库相关的配置-->
<!-- 引入配置文件 classpath: 表示从项目的根路径下出发 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
IOC操作Bean管理(基于注解方式)
- 什么是注解:
注解是代码里面的一些特殊标记,格式:@注解名称(属性名 = 属性值,属性名=属性值…)
注解的使用:
可以作用在类,方法,属性上。
目的:
为了简化xml配置。
- Spring针对Bean管理中创建对象提供了注解
- @Component:普通组件注解,在所有的地方都可以使用,都可以创建对象(bean级别)
- @Service:多用于Service层(业务逻辑层)
- @Controller:一般用在web层上(视图层)
- @Repository:用在dao层(持久层上)
上面的四个注解功能都是一样的,都可以用来创建bean实例。只是我们建议在不同的层用不同的注解来创建bean实例。使结构更加清晰。
基于注解的方式实现对象的创建
实现步骤
-
引入spring依赖(就是Spring的一个spring-aop的jar包。使用maven时不需要管这么多)
-
开启组件扫描器(Spring配置文件中配置)
<!-- 开启组件扫描器,告诉Spring我们那些类中(包下的类)会使用注解 --> <!-- base-package:扫描那些包下的类,多个包之间可以使用 , 进行分割 --> <!-- 也可以写要扫描包的父级目录--> <context:component-scan base-package="com.mao.bean,com.mao.service"/> <!-- <context:component-scan base-package="com.mao"/>-->
在包下的类中可以使用四种注解之一。每种注解都相当于bean标签。
// 创建bean实例的注解 value="userService" value可以省略 也可以直接只声明一个注解 @Service
// 这样相当于 bean的id(唯一表示)默认为类名的首字母小写
@Service("userService") // 等于以前的bean标签 <bean id="" class="" />
public class UserService {
public void add(){
System.out.println("service add .......");
}
}
组件扫描器的注意事项
一旦配置了<context:component-scan base-package="com.mao"/>
扫描器,就会扫描该包下所有的类。如果包下文件过多,且有的类并不是我们需要扫描的时候,效率就会降低。
此时,我们可以在这个组件扫描器中进行配置自己的过滤器,不用默认的过滤器。提高扫描效率
组件扫描器配置
<!-- 在这个包下配置过滤器,加快扫描的效率-->
<!-- use-default-filters="false" 表示不使用默认的扫描器 -->
<context:component-scan base-package="com.mao" use-default-filters="false">
<!-- 在扫描器标签内设置自己的过滤器-->
<!-- include-filter 表示该过滤器只扫描包含以下扫描到的配置 ,就是设置只扫描那些内容 -->
<!-- type="annotation" 表示根据注解进行扫描 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
</context:component-scan>
<!-- 组件扫描器配置2 -->
<!-- 使用默认的filter,扫描包下所有内容-->
<!-- exclude-filter 设置那些内容不扫描-->
<!-- <context:component-scan base-package="com.mao">-->
<!-- <context:exclude-filter expression="org.springframework.stereotype.Component" type="annotation"/>-->
<!-- </context:component-scan>-->
基于注解的方式实现属性的注入
注解
三个注解:(引用类型)
@Autowired
@Qualifier
@Resource
普通类型:
@Value:
- @Autowired: 根据属性的类型进行自动注入。
- @Qualifier: 根据属性名称进行注入。
- @Resource: 可以根据类型注入,也可以根据名称进行注入。
- @Value: 可以注入普通类型(字符串等)属性。
注入方式:
-
第一步,把 service 和 dao对象创建。在service和dao层都加上组件对象注解。
@Repository public class UserDaoImpl implements UserDao { @Override public void add() { System.out.println("dao add ........."); } }
-
在 service 里面 注入 dao属性。在 service类中添加 dao 属性,在属性上面使用注解。
/ 定义 dao 属性,不定义 set 方法 @Autowired // 根据类型进行注入 private UserDao userDao;
@Qualifier根据属性名称进行注入
该注解需要和@Autowired注解一起进行使用。
// 定义 dao 属性,不定义 set 方法
@Autowired // 根据类型进行注入,只适用于只有一个实现类注入
@Qualifier(value = "UserDaoImpl2") // 根据名称进行注入,和@Autowired注解联合使用
private UserDao userDao;
@Resource(Java扩展包中的注解)
此注解可以名称注入,也可以属性注入。注解并不是来源于 Spring提供的,而是jdk源码扩展包中提供的。javax.annotation.Resource;
// 注解 @Resource 的使用
// @Resource // 根据类型进行注入(只能有一个实现类,属性的类型实现类多于一个时出现错误。)
@Resource(name = "UserDaoImpl2") // 根据名称进行注入
private UserDao userDao;
// value注解的使用
@Value(value = "aaa")
// @Value("aaa")
private String name;
纯注解开发
-
创建配置类,替代xml配置文件
/ 告诉Spring,这个类作为配置类,替代配置文件 @Configuration @ComponentScan(basePackages = {"com.mao"}) // 开启组件扫描器的注解(basePackages的值为要扫描的包) public class SpringConfig { }
-
使用类 AnnotationConfigApplicationContext(配置文件字节码)
// 使用纯注解开发 // 加载配置类 使用 AnnotationConfigApplicationContext(配置文件类的字节码文件); ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class); UserService userService = ac.getBean("userService", UserService.class); userService.add();
Aop面向切面编程
什么是Aop
AOP:翻译过来为 面向切面编程。AOP是OOP的延续性,是函数式编程的中衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个部分之间的耦合度降低,提高程序的可重用性,同时提高开发的效率。
通俗的说:就是不通过修改源代码的方法,在主干功能里面添加新的功能。
AOP底层原理
底层采用动态代理的方式。
动态代理中,有两种情况的动态代理
-
有接口,默认使用JDK动态代理
-
无接口,使用CGLIB动态代理
JDK动态代理
需要使用Proxy这个类(代理类)。
返回值 | 方法名 | 说明 |
---|---|---|
Object | static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandle h) | 返回指定接口的代理类实例,该接口将方法调用分配给指定的调用程序处理。 |
-
调用Proxy类的静态方法 newProxyInstance方法
方法里面有三个参数:
- 类加载器
- 增强方法所在的类,这个类实现的接口,支持多个接口
- 实现这个接口InvocationHandle,创建代理对象,写增强的方法。
-
代码实现
- 创建接口,定义方法
- 创建接口实现类,实现方法
public interface UserDao {
int add(int a,int b);
String update(String id);
}
public class UserDaoImpl implements UserDao {
@Override
public int add(int a, int b) {
return a+b;
}
@Override
public String update(String id) {
return id;
}
}
使用 Proxy代理类创建接口代理对象
package com.mao.proxy;
import com.mao.dao.UserDao;
import com.mao.dao.impl.UserDaoImpl;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* @Description
* @Author 毛毛
* @CreateDate 2021/05/18/周二 1:20 下午
*/
public class TestProxy {
@Test
public void test1(){
// 创建接口实现类代理对象
UserDao userDao = (UserDao) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{UserDao.class}, new UserHandle(new UserDaoImpl()));
int add = userDao.add(1, 2);
System.out.println(add);
}
}
class UserHandle implements InvocationHandler{
// 将要创建的代理对象传递进来
private Object o;
public UserHandle(Object o){
this.o = o;
}
/**
* UserHandle类一创建,就会执行invoke方法,在 invoke 方法中写增强的逻辑。
* @param proxy jDK提供创建好的,创建的代理对象
* @param method jdk提供创建好的,要执行的对象的方法
* @param args 方法的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在方法之前进行处理
System.out.println("执行前:当前方法名称:"+method.getName());
System.out.println("执行前:传递的参数:"+ Arrays.toString(args));
// 执行该方法的对象和参数
Object result = method.invoke(o, args);
// 放行执行完成后:
System.out.println("执行后:。。。"+"执行方法的对象是:"+o);
// 在这里可以书写返回值
return result;
};
}
AOP(术语)
1. 连接点
概念:
类里面那些方法可以被增强,这些方法就被称为连接点。
2. 切入点
概念:
实际被真正增强的方法,就是切入点。
3. 通知(增强)
概念:
实际增强的逻辑部分被称为通知。也可以叫做增强。
切面的执行时间,这个执行时间在规范中叫做Advice(通知,增强)。在aspectJ框架中使用注解表示的。(也可以使用xml配置文件中的标签)
通知的类型
- 前置通知:方法前执行
- 后置通知:方法执行后执行
- 环绕通知:在方法的前面和后面都执行
- 异常通知:方法出现异常时执行
- 最终通知:类似于异常里面的finally语句,无论任何情况下都会执行
4. 切面
概念
切面是一个动作,把通知应用到 切入点的过程就称为切面。
Aop操作(准备)
在Spring框架中,我们一般都是基于AspectJ实现AOP操作。
AspectJ
不是Spring的组成部分,是一个独立的AOP框架,实际开发中,我们为了方便,将AspectJ和Spring框架一起联合使用,进行AOP操作。
基于AspectJ实现AOP操作
-
基于xml配置文件实现
-
基于注解方式实现(使用更多)
在项目中引入AOP相关依赖
切入点表达式
作用
切入点表达式的作用:知道对那个类里面的那个方法进行增强
语法结构
execution( [权限修饰符] [返回值类型] [类的全路径名称].[方法名称] ([参数列表]) )
例子
对 com.mao.dao.UserDao 类中的 add 方法进行增强
package com.mao.dao;
public class UserDao{
public int add(int x){
return x*2;
}
}
表达式书写:
execution(public void com.mao.dao.UserDao.add(…))
对所有方法进行增强
execution(* com.mao.dao.UserDao.*(…))
对包里面的所有类,所有方法,进行增强
execution(* com.mao.dao.*
.*
(…))
AOP操作(AspectJ注解)
-
创建类,在类中定义方法
public class User { public void add(){ System.out.println("User add ..."); } }
-
创建增强类(编写增强逻辑)
在增强类中,创建方法,让不同方法代表不同的通知类型
public class UserProxy { /** * 在被增强的方法之前执行 */ public void before(){ System.out.println("before ....."); } }
-
进行通知的配置
-
先在配置文件或者配置类中,开启组件扫描器
-
然后使用注解创建User和UserProxy对象
-
在增强类上面添加注解@Aspect(需要导入依赖)
<!-- aspectJ依赖--> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.6</version> </dependency>
-
在Spring配置文件中开启生成代理对象
<!-- 开启组件扫描器--> <context:component-scan base-package="com.mao.aopAnnotation"></context:component-scan> <!-- 开启AspectJ 生成代理对象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
配置不同类型的通知
在增强类里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置。
-
@Component
@Aspect
public class UserProxy {
/**
* 在被增强的方法之前执行
*/
// @Before(value = "execution(public void com.mao.aopAnnotation.User.add(..))")
@Before(value = "execution(* com.mao.aopAnnotation.User.add(..))") // * 在这里表示返回值类型为任意类型,权限修饰符可以省略
public void before(){
System.out.println("before .....");
}
}
package com.mao.aopAnnotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @Description
* @Author 毛毛
* @CreateDate 2021/05/21/周五 2:16 下午
*/
@Component
@Aspect
public class UserProxy {
/**
* 在被增强的方法之前执行
*/
// 前置通知一定会执行
// @Before(value = "execution(public void com.mao.aopAnnotation.User.add(..))")
@Before(value = "execution(* com.mao.aopAnnotation.User.add(..))") // * 在这里表示返回值类型为任意类型,权限修饰符可以省略
public void before(){
System.out.println("before .....");
}
// 最终通知(即使出现异常,也仍然会执行)
@After(value = "execution(* com.mao.aopAnnotation.User.add(..))")
public int after(){
System.out.println("after .....");
return 1;
}
// 后置通知(返回通知),在方法返回值之后进行执行,方法出现异常不执行
@AfterReturning(value = "execution(* com.mao.aopAnnotation.User.add(..))")
public void afterReturning(){
System.out.println("afterReturning .......");
}
// 异常通知,出现异常时执行
@AfterThrowing(value = "execution(* com.mao.aopAnnotation.User.add(..))")
public void afterThrowing(){
System.out.println("afterThrowing .......");
}
// 环绕通知,出现异常不会执行环绕后的增强部分
@Around(value = "execution(* com.mao.aopAnnotation.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕前 .......");
// 被增强的方法的执行
proceedingJoinPoint.proceed();
System.out.println("环绕后 。。。。。");
}
}
切入点表达式抽取
将表达式中的公共部分进行抽取
// 相同的公共点进行抽取,定义一个方法
// 在方法上使用一个 @Pointcut注解
@Pointcut(value = "execution(* com.mao.aopAnnotation.User.add(..))")
public void pointcut(){
}
// 抽取的表达式的使用
@Before(value = "pointcut()") // 直接使用抽取出来的切入点表达式,对抽出来的方法进行调用,就可以得到需要的切入点表达式
public void before(){
System.out.println("before .....");
}
增强类的优先级
如果有多个增强类对同一个方法进行增强,我们可以设置增强类的优先级。
方式:
在增强类上面添加注解 @Order(数字类型值)
,值越小,优先级越高
@Component
@Aspect
@Order(2)
public class PersonProxy {
@Before(value = "execution(* com.mao.aopAnnotation.User.add(..))")
public void before(){
System.out.println("before 222222222222222222=====");
}
}
AOP操作(AspectJ配置文件)
1. 创建增强类和被增强类
public class Book {
public void buyBook(){
System.out.println("buy ..........");
}
}
public class BookProxy {
public void before(){
System.out.println("before ............");
}
}
2. 在Spring配置文件中创建两个类对象
<!-- 采用配置文件的方式创建对象-->
<bean id="book" class="com.mao.aopxml.Book"></bean>
<bean id="bookProxy" class="com.mao.aopxml.BookProxy"/>
3. 在Spring配置文件中配置切入点
<!-- 配置aop中的增强, -->
<aop:config>
<!-- 配置切入点表达式-->
<aop:pointcut id="buybook" expression="execution(* com.mao.aopxml.Book.buyBook(..))"/>
<!-- 配置切面,切面就是将增强(通知)应用到需要增强方法上面的过程-->
<!-- ref 属性指向的增强类对象的唯一标识-->
<aop:aspect ref="bookProxy">
<!-- 配置增强作用的方法上-->
<!-- before 前置通知 method属性上增强类中进行增强的方法,pointcut-ref属性指向的是上面配置的被增强类中的被增强方法的id标识-->
<aop:before method="before" pointcut-ref="buybook"/>
</aop:aspect>
</aop:config>
Aop操作(配置类方式)
@Configuration // 告诉Spring这个类是配置类
@ComponentScan(basePackages = {"com.mao.aopAnnotation"}) // 开启组件扫描器
/**
* @EnableAspectJAutoProxy(proxyTargetClass = true) 注解 proxyTargetClass的值默认为false
* proxyTargetClass的值控制aop的具体实现方式,如果值为true,则采用cglib的方式创建代理,如果为false的话则使用jdk的默认的Proxy
* 参数 exposeProxy 控制代理的暴露方式,解决内部调用不能使用的代理场景,默认为false不解决
*/
@EnableAspectJAutoProxy(proxyTargetClass = true) // 开启AspectJ自动生成代理对象,对被代理对类对方法进行通知增强
public class SpringConfig {
}
JDBCTemplate
什么是JdbcTemplate
Spring框架对jdbc进行了封装,使用jdbcTemplate方便实现对数据库的操作。
准备工作
1. 导入依赖
数据库依赖,数据库连接池等(数据库连接池采用druid),需要导入 spring-jdbc依赖
2. 在Spring配置文件配置数据库连接池
<!-- 导入jdbc配置文件-->
<util:properties id="jdbc" location="jdbc.properties"/>
<!-- 配置数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="clone">
<property name="driver" value="#{jdbc['jdbc.driver']}"/>
<property name="url" value="#{jdbc['jdbc.url']}"/>
<property name="username" value="#{jdbc['jdbc.username']}"/>
<property name="password" value="#{jdbc['jdbc.password']}"/>
<property name="maxActive" value="#{jdbc['jdbc.maxActive']}"/>
</bean>
3. 配置JdbcTemplate对象,注入DataSource
<!-- jdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 注入dataSource数据库连接池-->
<property name="dataSource" ref="dataSource"/>
</bean>
4. 创建service类,创建dao类,在dao中注入jdbctemplate对象
public interface BookDao {
public int add();
}
@Repository
public class BookDaoImpl implements BookDao {
// 注入jdbcTemplate对象
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int add() {
return 0;
}
}
@Service
public class BookService {
// 注入dao
@Autowired
private BookDao bookDao;
}
<!-- 开启注解扫描器-->
<context:component-scan base-package="com.mao.dao;com.mao.service"/>
jdbcTemplate操作数据库
1. 创建表对应的实体类
package com.mao.bean;
/**
* @Description
* @Author 毛毛
* @CreateDate 2021/05/21/周五 5:09 下午
*/
public class User {
private int id;
private String username;
private int age;
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", age=" + age +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
2. 编写service和dao
1. 在dao中进行数据库添加操作
2. 调用 jdbcTemplate对象里面的update方法实现添加操作
返回值 | 方法名 | 说明 |
---|---|---|
Int | update(String sql, Object …args) args就是可变参数,就是sql语句中占位符的值 | 实现对数据库中表数据的添加操作 |
@Override
public int add(Book book) {
String sql = "insert into spring_book value(null,?,?)";
int result = jdbcTemplate.update(sql, book.getBookName(), book.getAuthor());
if(result == 1){// 执行成功,影响数据库行数为1
return 1;
}
return 0;
}
3. 修改和删除基本一样
JdbcTemplate操作数据库(查询)
1. 查询返回某个值
例如:查询表中有多少条数据,返回的是一个具体的值(单行单列)
代码实现(调用queryForObject方法进行实现)
返回值 | 方法名 | 说明 |
---|---|---|
T / Object | queryForObject(String sql, Class requiredType) | 执行查询功能的sql语句,其返回值是一个对象或者一个单列单行的值 |
@Override
public int selectCount() {
String sql = "select count(*) from book;";
Integer result = jdbcTemplate.queryForObject(sql, int.class);
return result;
}
2. 查询返回对象
返回值 | 方法名 | 说明 |
---|---|---|
T / Object | queryForObject(String sql, RowMapper rowMapper, Object …args) | RowMapper是接口,返回不同类型数据,使用这个接口里面的实现类完成数据的封装 |
@Override
public Book findBookInfo(int id) {
// 要注意,这里需要给表中的字段其别名,让别名和需要封装的实体类中的属性名一样
String sql = "select id,name bookName,author from book where id = ?";
Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
return book;
}
3. 查询返回集合
返回值 | 方法 | 说明 |
---|---|---|
List 集合 | query(String sql, RowMapper rowMapper, Object …args) | RowMapper是接口,返回不同类型数据,使用这个接口里面的实现类完成数据的封装 |
@Override
public List<Book> findAllBook() {
String sql = "select id,name bookName,author from book;";
List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
return bookList;
}
JDBCTemplate(批量操作数据增删改)
1. 批量添加/修改/删除 操作
返回值 | 方法 | 说明 |
---|---|---|
int[] | batchUpdate(String sql, List<Object[]> batchArgs) | 参数二:List集合,添加/修改/删除 多条记录数据 |
// 批量添加
@Override
public int[] batchAddBook(List<Object[]> batchArgs) {
String sql = "insert into book(name,author) values(?,?)";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
return ints;
}
@Test
public void test5() {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = ac.getBean("bookService", BookService.class);
List<Object[]> list = new ArrayList<>();
Object[] o1 = {"mysql","俊俊"};
Object[] o2 = {"mongodb","哈哈"};
Object[] o3 = {"redis","呵呵"};
list.add(o1);
list.add(o2);
list.add(o3);
int[] ints = bookService.batchAdd(list);
System.out.println(Arrays.toString(ints));
}
事务
事务概念
1. 什么是事务
**事务:**是数据库操作的最基本的单元,逻辑上一组操作,要么都成功;如有有一个失败则所有操作都失败。(事务的原子性)
例如:
银行转账。
2. 事务特性(ACID)
- 原子性:
- 一致性:操作前和操作后,总量不变
- 隔离性:多事务之间进行操作的时候,不会互相产生影响
- 持久性:对表的操作是永久性的
补充-动态代理
package com.mao.proxy;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @Description
* @Author 毛毛
* @CreateDate 2021/05/18/周二 1:59 下午
*/
public class ProxyTest {
@Test
public void test() {
// 多态,返回的代理对象的类型其实也是Human接口类型。是实现类该接口的子类
Human superMan = (Human) ProxyFactory.getProxyInstance(new SuperMan());
// 代理类调用类eat和belief方法,实际上也是动态的调用类被代理类的同名方法
// 通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
superMan.eat("苹果");
String belief = superMan.getBelief();
System.out.println(belief);
}
}
/**
* 人类接口
*/
interface Human {
String getBelief();
void eat(String food);
}
// 被代理类,超人
class SuperMan implements Human {
@Override
public String getBelief() {
return "我能飞。。。";
}
@Override
public void eat(String food) {
System.out.println("我喜欢吃:" + food);
}
}
// 代理类
/**
* 想要实现动态代理,需要解决什么问题
* 1. 如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象
* 2. 如何通过代理类对象调用方法时,如何动态的调用被代理类中同名方法
*/
class ProxyFactory {
/**
* 调用此静态方法,返回一个代理类的对象
*
* @param obj 被代理类的对象
* @return
*/
public static Object getProxyInstance(Object obj) {
// 创建MyInvocationHandle对象
MyInvocationHandle handle = new MyInvocationHandle(obj);
// 使用Proxy类的newProxyInstance方法创建一个代理类对象
/**
* 使用被代理对象的类加载器进行加载,
* 被代理类实现的接口,代理类我也要实现
*/
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handle);
}
}
class MyInvocationHandle implements InvocationHandler {
// 需要使用被代理类的对象进行赋值
private Object obj;
public MyInvocationHandle(Object obj) {
this.obj = obj;
}
/**
* 当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke方法
* 将被代理类要执行的方法a的功能声明在invoke方法体中
*
* @param proxy 代理类对象,就是上面使用 newProxyInstance方法返回的代理类对象
* @param method 就是代理类对象执行的方法,代理类执行什么方法,method就是那个方法(同名方法)
* @param args 代理类对象执行方法时传递的参数(调用时传递的实参)
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在这里可以加上功能的拓展内容
if (method.getName().equals("eat")){
System.out.println("方法执行前的功能拓展");
}
// 代理类对象调用的方法,此方法也就作为被代理类对象要调用的方法
Object result = method.invoke(obj, args);
// 方法的返回值,执行带被代理类对象同名方法时的返回值
return result;
}
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)