Spring学习笔记
以前我们调用对象时,对象通常是调用者主动new出来
而现在是IOC容器来完成,就像哆啦A梦的百宝箱 负责管理Bean对象。
一:Spring的基本概述IOC容器
1.什么是IOC容器?
Spring => 2003年开始流行框架
Spring = IOC + AOP
IOC = Inversion of Controll => 控制反转
控制反转 到底翻转了什么?
依赖对象的创建 和 依赖关系的形成
IOC的核心 DI => 依赖注入 dependency injection
感受一下Spring存在的感觉~
2.Spring框架IOC容器当中创建对象的三种方式:
1> -> 无参构造方法
2> 静态工厂 -> factory-method + class
3> 非静态的实例工厂 -> factory-bean + factory-method
3.Bean元素的常见属性:
id : 对象在IOC容器当中的唯一标识
class : 指定这是哪个类型的实例 (factory-method)
scope : 指定一个bean作为范围 声明周期
singleton : 默认 单一实例Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域
prototype : 原型 (每次都get()一个不一样的出来)Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。
request : 每个请求一个不同实例
session : 整个会话一个实例
global-session : 全局会话共享一个实例
lazy-init : 指定当前实例是否延迟加载(延迟到getBean的时候)
*:prototype本身就是延迟加载的~
init-method : 初始化bean的时候要执行的方法
destroy-method : 销毁一个bean的时候要执行的方法
4.Bean的声明周期
Bean的生命周期可以表达为:Bean的定义――Bean的初始化――Bean的使用――Bean的销毁
5.如何DI依赖注入
如何给属性赋值==如何完成DI
普通属性 --复杂属性【数组 集合】 引用类型
依赖注入 Dependency Injection
第一种方式:依赖于Setter方法
第二种方式:依赖于构造方法实现
第三种方式:注解的方式
详细案例:
public class TestSpring {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
Student stu = ac.getBean(Student.class);
//检查数组有没有注入成功
int[] data = stu.getPocket();
for(int x : data){
System.out.println(x);
}
//检查List集合有没有注入成功
//stu.getBooks().forEach(System.out::println);
stu.getBooks().forEach((x) -> System.out.println("《"+x+"》"));
//检查Set集合有没有注入成功
stu.getFriends().forEach(System.out::println);
//检查Map集合有没有注入成功
stu.getHobbies().forEach((k,v) -> System.out.println(k+" : " + v));
//检查Properties集合有没有注入成功
stu.getReports().forEach((k,v) -> System.out.println(k+" : " + v));
Teacher tea = ac.getBean(Teacher.class);
System.out.println(tea);
}
public class Student {
private String name;//字符串
private int age;//基本数据类型int
private double salary;//基本数据类型double
private char gender;//基本数据类型char
private int[] pocket;//钱包 数组
private List<String> books;
private Set<String> friends;//朋友
private Map<String,String> hobbies;
private Properties reports;//成绩单
public Properties getReports() {
return reports;
}
public void setReports(Properties reports) {
this.reports = reports;
}
public Map<String, String> getHobbies() {
return hobbies;
}
public void setHobbies(Map<String, String> hobbies) {
this.hobbies = hobbies;
}
public Set<String> getFriends() {
return friends;
}
public void setFriends(Set<String> friends) {
this.friends = friends;
}
public List<String> getBooks() {
return books;
}
public void setBooks(List<String> books) {
this.books = books;
}
public int[] getPocket() {
return pocket;
}
public void setPocket(int[] pocket) {
this.pocket = pocket;
}
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;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", salary=" + salary
+ ", gender=" + gender + "]";
}
}
public class Teacher {
private String name;
private int age;
private double salary;
private char gender;
private int[] pocket;
private List<String> books;
private Set<String> friends;
public Teacher(String name, int age, double salary, char gender,
int[] pocket, List<String> books, Set<String> friends) {
super();
this.name = name;
this.age = age;
this.salary = salary;
this.gender = gender;
this.pocket = pocket;
this.books = books;
this.friends = friends;
}
@Override
public String toString() {
return "Teacher [name=" + name + ", age=" + age + ", salary=" + salary
+ ", gender=" + gender + ", pocket=" + Arrays.toString(pocket)
+ ", books=" + books + ", friends=" + friends + "]";
}
}
Spring.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<bean id="stu" class="com.etoak.Student">
<!-- 依赖于Student类的setter方法完成属性的注入 -->
<property name="name" value="JayZhou" />
<property name="age" value="32" />
<property name="salary" value="22222" />
<property name="gender" value="女" />
<property name="pocket">
<array>
<value>100</value>
<value>100</value>
<value>50</value>
</array>
</property>
<property name="books">
<list>
<value>Core Java</value>
<value>Effective Java</value>
<value>Thinking in Java</value>
</list>
</property>
<property name="friends">
<set>
<value>大白</value>
<value>小黑</value>
<value>小白</value>
</set>
</property>
<property name="hobbies">
<map>
<entry key="牙膏" value="黑人" />
<entry key="包子" value="长清大素包" />
<entry key="烧饼" value="莱芜烧饼" />
</map>
</property>
<property name="reports">
<props>
<prop key="语文" >150</prop>
<prop key="数学" >149</prop>
<prop key="英语" >49</prop>
</props>
</property>
</bean>
<bean id="tea" class="com.etoak.Teacher">
<!-- 依赖于Teacher类的构造方法 -->
<constructor-arg index="3" value="女" />
<constructor-arg index="0" value="JayZhou" />
<constructor-arg index="2" value="22222" />
<constructor-arg index="1" value="32" />
<constructor-arg index="4">
<array>
<value>50</value>
<value>20</value>
<value>20</value>
<value>5</value>
<value>2</value>
<value>2</value>
</array>
</constructor-arg>
<constructor-arg index="5">
<list>
<value>哈姆雷特</value>
<value>梁祝</value>
</list>
</constructor-arg>
<constructor-arg index="6">
<set>
<value>GayZhou</value>
<value>Joshua</value>
</set>
</constructor-arg>
</bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<!-- 学会使用p命名空间 简化property -->
<bean id="stu" class="com.etoak.Student"
p:name="JayZhou" p:age="32"
p:salary="22222" p:gender="女" />
<!-- 学会使用c命名空间 简化constructor-arg -->
<bean id="tea" class="com.etoak.Teacher"
c:_0="JayZhou" c:_3="女" c:_2="22222" c:_1="32" />
</beans>
1.注意点:
其实上面的index和name完全可以不写 因为…构造方法的参数是有顺序的
这意味着…如果我们按顺序提供则name和index都可以不指定
2:学会使用p命名空间和c命名空间简化配置文件
导入对应的命名空间:
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
**p:**属性名=“值” p:属性名-ref="另一个bean的id"
**c:_0="**值" c:_0-ref="另一个bean的id"
3.注解的方式
@Autowired @Resource 代表自动注入
需要使用Context命名空间 配置自动扫描包
<context:component-scan base-package="com"></context:component-scan>
如果只需要扫描Controller
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
排除【不扫描】所有Controller
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
@Autowired:优先按照类型进行匹配 如果类型有多个匹配,则按照注入的属性名字匹配
如果连名字也匹配不上 需要在被注入的属性上加@Qualifier(“名字”)
@Resource 优先按照名字进行匹配 如果名字匹配不上则按照类型匹配如果类型也有多个,需要在被注入的属性上加:@Resource(name=“userService”)
使用案例:
Spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="com" />
</beans>
@Controller
@Scope("prototype")
public class RoomAction {
@Autowired//优先 按照 类型匹配 如果类型 匹配不上 则按照名字匹配
@Qualifier("roomServiceEmail")//要调用的service 名字 首字母小写
private RoomService service;
public String execute(){
System.out.println("action~~~");
service.testService();
return "SUCCESS";
}
}
public interface RoomService {
void testService();
}
@Service
public class RoomServiceSMS implements RoomService {
@Autowired
@Qualifier("roomDaoOracle")
private RoomDao dao;
@Override
public void testService() {
System.out.println("RoomServiceSMS Implement");
dao.testDao();
}
}
@Service
public class RoomServiceEmail implements RoomService {
@Resource(name="roomDaoMySQL")//优先按照名字进行匹配
private RoomDao dao;
@Override
public void testService() {
System.out.println(" RoomServiceEmail implements RoomService ");
dao.testDao();
}
}
public interface RoomDao {
void testDao();
}
@Repository
public class RoomDaoOracle implements RoomDao {
@Override
public void testDao() {
System.out.println("oracle dao");
}
}
@Repository
public class RoomDaoMySQL implements RoomDao {
@Override
public void testDao() {
System.out.println("mysql dao");
}
}
二:反射基础学习
关于反射:(一个类照镜子)
一种在Java运行时动态解析一个类当中内容的技术
(.class) 得到 (类当中的各种重要信息)
Class 类 获得类(class)对象的三种方式
Class c1 = Student.class;
Class c2 = Class.forName("Student");
Class c3 = stu.getClass();
得到类类型的对象就是为了进一步的解析这个类当中的内容~
getConstructors(); 得到该类的所有构造方法
getConstructor(参数列表); 指定某个参数的构造方法
getDeclaredFields(); 得到所有的属性字段
getDeclaredField("属性名"); 得到指定的属性
getDeclaredMethods(); 得到所有的方法
getDeclaredMethod("方法名",参数列表); 得到 指定的方法
Constructor 构造方法
得到构造方法本质的目的就是为了: 创建对象
newInstance();
Object obj=c1.newInstance();
Field 属性
得到属性本质的目的就是为了: 设置它 or 得到它
set() get()
Method 方法
反射在实际中的应用主要是动态创建对象,动态调用对象的方法
1.创建对象
调用Class类的newInstance()方法动态创建目标类的对象。
反射案例解读
public class Student{
private String name;
private int age;
private double salary;
public Student(String name,int age,double salary){
this.name = name;
this.age = age;
this.salary = salary;
}
public void study(){
System.out.println("GooDGooDStuDy DayDayUp");
}
public static void cleanClassroom(){
System.out.println("ϴˢˢ ϴˢˢ ŶŶ~");
}
@Override
public String toString(){
return name + " : " + age + " : " + salary;
}
}
import java.lang.reflect.*;
public class TestReflect{
public static void main(String[] args)throws Exception{
Class c = Class.forName("Student");
//Object obj = c.newInstance();//必须依赖于无参的构造方法
/*
Constructor[] ccs = c.getConstructors();
for(Constructor cc : ccs){
System.out.println(cc);
}
*/
Constructor cc = c.getConstructor(String.class,int.class,double.class);
Object obj = cc.newInstance("JayZhou",32,99.99D);
/*
Field[] fs = c.getDeclaredFields();//得到定义的所有属性
for(Field f : fs){
System.out.println(f);
}
*/
System.out.println(obj);
/*
Field f = c.getDeclaredField("name");
f.setAccessible(true); //破封装
f.set(obj,"GayZhou"); //obj.name="GayZhou";
System.out.println(obj);
Field f2 = c.getDeclaredField("salary");
f2.setAccessible(true);//踹裆
Object value = f2.get(obj);
System.out.println(value);
*/
/*
Method[] ms = c.getDeclaredMethods();
for(Method m : ms){
System.out.println(m);
}
*/
Method m = c.getDeclaredMethod("cleanClassroom");
m.invoke(null); //obj.fangfaming()
Method mm = c.getDeclaredMethod("study");
mm.invoke(obj);
}
}
import java.lang.reflect.*;
import java.util.*;
public class TestReflect2{
public static void main(String[] args)throws Exception{
List<Integer> list = new ArrayList<>(3);
Collections.addAll(list,11,22,33,44);
list.add(55);
Collections.addAll(list,66,77);
Class c = list.getClass();//Class.forName("java.util.ArrayList");
Field f = c.getDeclaredField("elementData");
f.setAccessible(true);
Object obj = f.get(list);
Object[] data = (Object[])obj;
System.out.println(data.length);
}
}
利用反射技术完成DI
public class AC {
String fileName;//配置文件的路径和名字
public AC(String fileName){
this.fileName = fileName;
}
public Object getBean(String id)throws Exception{
SAXReader reader = new SAXReader();//准备读取xml文件
Document doc = reader.read(this.getClass().getResourceAsStream("../../../"+fileName));//从.class存在的位置出发 读取那个指定的xml 并且得到文档树对象
Element root = doc.getRootElement();//<beans>
List<Element> bs = root.elements();//一堆<bean>
for(Element b : bs){//这里的每个b 都代表配置文件当中的一个<bean>..
String beanId = b.attributeValue("id");
if(beanId.equals(id)){
String beanClass = b.attributeValue("class");
Class c = Class.forName(beanClass);
Object obj = c.newInstance();//利用无参构造方法得到一个实例
List<Element> ps = b.elements();//里面都是<property>
for(Element p : ps){//这里的每个p 都代表配置文件当中的一个<property>
String propertyName = p.attributeValue("name");//得到属性的名字
String propertyValue = p.attributeValue("value");//得到属性的值
//这是setter方法的名字!
String setterName = "set" + propertyName.substring(0,1).toUpperCase() + propertyName.substring(1);
Field field = c.getDeclaredField(propertyName);
//这是setter方法的参数类型
Class fieldType = field.getType();
String fieldTypeName = fieldType.getSimpleName();
Method setter = c.getDeclaredMethod(setterName,fieldType);
if("String".equals(fieldTypeName))
setter.invoke(obj,propertyValue);
else if("int".equals(fieldTypeName))
setter.invoke(obj,Integer.parseInt(propertyValue));
else if("double".equals(fieldTypeName))
setter.invoke(obj,Double.parseDouble(propertyValue));
/*
Field field = c.getDeclaredField(propertyName);
field.setAccessible(true);
Class fieldType = field.getType();//得到属性的类型
String fieldTypeName = fieldType.getSimpleName();
if("String".equals(fieldTypeName))
field.set(obj,propertyValue);
else if("int".equals(fieldTypeName))
field.set(obj,Integer.parseInt(propertyValue));
else if("double".equals(fieldTypeName))
field.set(obj,Double.parseDouble(propertyValue));
*/
}
return obj;
}
}
return null;
}
}
实体类:
public class Student {
private String name;
private int age;
private double salary;
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;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", salary=" + salary
+ "]";
}
}
public class Teacher {
private String name;
private int age;
private double salary;
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;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Teacher [name=" + name + ", age=" + age + ", salary=" + salary
+ "]";
}
}
xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="stu" class="com.etoak.entity.Student">
<property name="name" value="JayZhou" />
<property name="age" value="32" />
<property name="salary" value="99.99" />
</bean>
<bean id="tea" class="com.etoak.entity.Teacher">
<property name="name" value="GayZhou" />
<property name="age" value="52" />
<property name="salary" value="19999.99" />
</bean>
</beans>
测试类:
public class TestOurSpring {
public static void main(String[] args) throws Exception{
AC ac = new AC("etoak.xml");
Student stu = (Student)ac.getBean("stu");
Teacher tea = (Teacher)ac.getBean("tea");
System.out.println(stu);
System.out.println(tea);
}
}
三:动态代理技术学习
当我们不想访问,或者不能直接访问一个对象的时候,我们就需要用到代理模式。代理模式一般涉及到委托类和代理类两个概念,代理类用于为委托类处理一些事务,代理类对象常常与委托类对象相关联。代理模式可以分为静态代理模式和动态代理模式
静态代理需要为每一个委托类实现一个代理类,程序在运行之前.class文件就已经存在,而动态代理则 是在运行时利用反射 机制动态生成的。
静态代理模式: 需要给被代理的目标类型和代理类型设计一个统一的接口
代理类型当中应当持有一个被代理的目标类型的对象
代理类型只关注非核心业务
被代理的目标类型关注核心业务
在代理类执行核心方法的时候 调用目标类型的方法即可。
进一步思考大家就会发现
使用静态代理 类的开发会几何倍数的增长
而且如果要代理的是一个类型的n个方法
编码也会非常冗余 复杂~
所以我们需要动态代理~
其核心原理 就是在程序运行时 动态的生成一个代理类的 .class文件
然后重新交给类加载器加载
动态代理的两种常用实现:
1.JDK提供的:Proxy.newProxyInstance(a,b,c)
a>类加载器对象
b>要实现的接口
c>调用执行器 InVocationHandler
2.CGLIB提供的:Enhancer
setSuperclass
setCallback();
create();
Spring AOP的底层 就是靠着这两种动态代理的实现 来完成功能的~
原理区别:java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler 来处理。而cgLib动态代理 是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
主要应用:
动态代理在 Java 开发中是非常常见的,在日志,监控,事务中都有着广泛的应用,同时在大多主流框架中的核心组件中也是少不了使用的
静态代理案例实现:
静态代理就是在程序运行之前,代理类字节码.class就已经编译好,通常一个静态代理类
也只代理一个目标类,代理类和目标类都实现相同的接口。
public interface Animal {
void call();
}
/**
* 类描述:目标类Cat 实现Animal接口
*
* @author :DaShu
* @version 1.0
* @date 2021/8/28 23:03
*/
public class Cat implements Animal {
@Override
public void call() {
System.out.println("喵喵喵~~~~");
}
}
package com.huadian.Proxy;
/**
* 类描述:静态代理类
*
* @author :DaShu
* @version 1.0
* @date 2021/8/28 23:04
*/
public class StaticProxyAnimal implements Animal{
private Animal impl;
public StaticProxyAnimal(Animal impl){
this.impl=impl;
}
@Override
public void call() {
System.out.println("小猫很饥饿");
impl.call();
}
}
package com.huadian.Proxy;
/**
* 类描述:测试代理类
*
* @author :DaShu
* @version 1.0
* @date 2021/8/28 23:07
*/
public class TestProxy {
public static void main(String[] args) {
Animal staticProxy=new StaticProxyAnimal(new Cat());
staticProxy.call();
//小猫很饥饿
//喵喵喵~~
}
}
静态代理的缺点:
代理类通过持有目标类的对象,然后通过调用目标类的方法,实现静态代理。静态
代理虽然实现了代理,但是:
1.如果接口中增加方法,不仅实现类Cat需要新增该方法的实现,代理类实现了Animal接口
所以代理类也必须实现新增的方法,不利于项目维护
2.代理类实现的Animal#call是针对Cat的目标类对象设置,如果再需要添加Dog目标类的代理,
那么必须再针对Dog类实现一个对应的代理类,这样为一个目标类,生成一个代理类,代理类会增长很快。
动态代理案例实现:
public class ProxyFactory {
public static Object createProxyWithJDK(Object tar){
return Proxy.newProxyInstance(
tar.getClass().getClassLoader(),
tar.getClass().getInterfaces(),
new InvocationHandler(){
@Override
public Object invoke(Object obj, Method m, Object[] args)
throws Throwable {
System.out.println("==前置非核心操作:收手机==");
Object returnValue = m.invoke(tar,args);
System.out.println("==后置非核心操作:看自习==");
return returnValue;
}
});
}
public static Object createProxyWithCGLIB(Object tar){
/*setSuperclass();
setCallback();
create();*/
Enhancer en = new Enhancer();//来指定要代理的目标对象
en.setSuperclass(tar.getClass());
en.setCallback(new MethodInterceptor(){
@Override
public Object intercept(Object obj, Method m, Object[] args,
MethodProxy mp) throws Throwable {
System.out.println("==前置非核心操作==");
Object returnValue = m.invoke(tar,args);
System.out.println("==后置非核心操作==");
return returnValue;
}
});
return en.create();//得到代理对象
}
}
//intercep()方法里我们可以加入任何逻辑
public class JayZhou implements Teacher{
@Override
public void teach(){
System.out.println("使劲上课~");
}
}
public interface Teacher {
void teach();
}
public class TestCGLIBDynamicProxy {
public static void main(String[] args) {
JayZhou jay = new JayZhou();//被代理的目标对象
JayZhou xj = (JayZhou)ProxyFactory.createProxyWithCGLIB(jay);//返回的是一个代理对象
xj.teach();
}
}
public class TestJDKDynamicProxy {
public static void main(String[] args) {
//要被代理的目标对象
JayZhou jay = new JayZhou();
/*
1st.类加载器对象
jay.getClass().getClassLoader();
谁加载的JayZhou.class谁加载生成的代理类的.class
2nd.代理类型要实现哪些接口
jay.getClass().getInterfaces();
JayZhou.class实现哪些接口 那么代理类也实现相同的接口
3rd.InvocationHandler => 调用控制器
非核心操作的载体 要帮你注入哪些操作啊?
*/
Teacher tea = (Teacher)ProxyFactory.createProxyWithJDK(jay);
tea.teach();
}
}
上述代码的关键是Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler handler)方法,该方法会根据指定的参数动态创建代理对象。三个参数的意义如下:
- loader,指定代理对象的类加载器;
- interfaces,代理对象需要实现的接口,可以同时指定多个接口;
- handler,方法调用的实际处理者,代理对象的方法调用都会转发到这里(*注意1)。
newProxyInstance()会返回一个实现了指定接口的代理对象,对该对象的所有方法调用都会转发给InvocationHandler.invoke()方法。理解上述代码需要对Java反射机制有一定了解。动态代理神奇的地方就是:
- 代理对象是在程序运行时产生的,而不是编译期;
- 对代理对象的所有接口方法调用都会转发到InvocationHandler.invoke()方法,在invoke()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;之后我们通过某种方式执行真正的方法体,示例中通过反射调用了Hello对象的相应方法,还可以通过RPC调用远程方法。
如果对象没有实现接口 就需要CGLIB登场
动态代理的另一种代码写法:
JDK动态代理:
实现 InvocationHandler 接口
创建JDK动态代理
执行动态代理
/**
* 类描述:创建 JDK 动态代理类
*
* @author :DaShu
* @version 1.0
* @date 2021/8/28 23:39
*/
public class DynamicProxyAnimal {
public static Object getProxy(Object target)throws Exception{
Object proxy= Proxy.newProxyInstance(
target.getClass().getClassLoader(),//指定目标类的类加载
target.getClass().getInterfaces(),//代理需要实现的接口,可指定多个
new TargetInvoker(target)//代理对象处置器
);
return proxy;
}
}
/*
* Proxy#newProxyInstance中的三个参数(ClassLoader loader、Class[] interfaces、InvocationHandler h):
loader 加载代理对象的类加载器
interfaces 代理对象实现的接口,与目标对象实现同样的接口
* h 处理代理对象逻辑的处理器,即上面的 InvocationHandler 实现类。
*/
/**
* 类描述:实现 InvocationHandler 接口
*
* @author :DaShu
* @version 1.0
* @date 2021/8/28 23:33
*/
public class TargetInvoker implements InvocationHandler {
//代理中持有的目标类
private Object target;
public TargetInvoker(Object target){
this.target=target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("jdk 代理执行前 前置非核心操作");
Object result=method.invoke(target,args);
System.out.println("JKD 代理执行后 非核心操作");
return result;
}
}
/*参数 proxy :代理目标对象的代理对象,它是真实的代理对象
method:执行目标类的方法
args:执行目标类的方法参数*/
/**
* 类描述:实现执行 DynamicProxyAnimal 动态代理
*
* @author :DaShu
* @version 1.0
* @date 2021/8/28 23:45
*/
public class DoProxy {
public static void main(String[] args) throws Exception{
Cat cat=new Cat();
Animal proxy=(Animal) DynamicProxyAnimal.getProxy(cat);
proxy.call();
}
}
/* jdk 代理执行前 前置非核心操作
喵喵喵~~~~
JKD 代理执行后 非核心操作*/
CGLIB动态代理:
实现 MethodInterceptor 接口
创建 CGLIB 动态代理类
实现 CGLIB 动态代理调用:
/**
* 类描述:实现 MethodInterceptor 接口
*相比于 JDK 动态代理的实现,CGLIB 动态代理不需要实现与目标类一样的接口,而是通过方法拦截的方式实现代理
* @author :DaShu
* @version 1.0
* @date 2021/8/28 23:49
*/
public class TargetInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("CGLIB 调用前");
Object result= methodProxy.invokeSuper(o,objects);
System.out.println("CGLIB 调用后");
return result;
}
}
/* intercept 方法里面有四个参数:
obj 代理类对象
method 当前被代理拦截的方法
args 拦截方法的参数
proxy 代理类对应目标类的代理方法*/
/**
* 类描述:创建 CGLIB 动态代理类
*
* @author :DaShu
* @version 1.0
* @date 2021/8/28 23:59
*/
public class CglibProxy {
public static Object getProxy(Class<?> clazz){
Enhancer enhancer=new Enhancer();
//设置类加载
// enhancer.setClassLoader(clazz.getClassLoader());
//设置被代理类
enhancer.setSuperclass(clazz);
//设置方法拦截器
enhancer.setCallback(new TargetInterceptor());
//创建代理类
return enhancer.create();
}
}
/**
* 类描述:实现 CGLIB 动态代理调用:
*
* @author :DaShu
* @version 1.0
* @date 2021/8/29 0:02
*/
public class DoCGLIB {
public static void main(String[] args) {
Animal cat=(Animal) CglibProxy.getProxy(Cat.class);
cat.call();
}
}
四:SpringAOP学习
- AOP的简介:
SpringAOP(Aspect Oriented Programming面向切面编程 将核心业务和非核心业务解耦)
AOP关注的是非核心业务,比如事务管理,权限控制,缓存控制,日志打印。等等。
AOP把软件分为两个部分 :核心关注点和横切关注点,业务处理的主要功能为核心关注点,而非核心、需要拓展的功能为横切关注点。
AOP要做的事情:生成代理对象,然后织入。
在运行过程中对已有的类或方法的功能进行增强同时又不改变原有类的代码,这妥妥的代理模式,代理模式可以在运行期间对方法进行增强,很好的实现了我们的需要
2:使用AOP的好处
集中处理某一关注点/横切逻辑
可以很方便的添加/删除关注点
侵入性少,增强代码可读性及可维护性 因此当想打印请求日志时很容易想到切面,对控制层代码0侵入
3.AOP的一些概念
通知(Advice):通知定义了一个切面在什么时候需要完成什么样的功能,通知定义了一个切面在什么时候需要完成什么样的功能
切点(pointCut): 切点定义了切面需要作用在什么地方
切面(Aspect):是通知和切点的组合,表示在指定的时间点对指定的地方进行一些额外的操作。
链接点(join Points):连接点表示可以被选择用来增强的位置,链接点是一组集合,在程序运行中的整个周期中都存在。
织入(Weaving): 在不改变原类代码的前提下 对功能进行增强。
4.AOP常用的主键
@Aspect => 声明该类为一个注解类
切点注解:
@Pointcut => 定义一个切点,可以简化代码
通知注解:
@Before => 在切点之前执行代码
@After => 在切点之后执行代码
@AfterReturning => 切点返回内容后执行代码,可以对切点的返回值进行封装
@AfterThrowing => 切点抛出异常后执行
@Around => 环绕,在切点前后执行代码
1.如何找到我们要增强的方法呢?
当我们确定好有哪些类的方法需要增强,后面就需要考虑我们如何获取到这些方法(对方法增强需要获取到具体的方法)
2.有了表达式我们可以确定具体的类和方法,表达式只是定义了相对的路径,如何根据相对路径获取Class文件地址?
对bean实例的增强是在初始化的时候完成的,初始化的时候判断如果需要增强,则通过代理生成代理对象,在返回时由
该代理对象代替原实例被注册到容器中。
3.Class文件有了,怎样取到类中的方法?
使用Class对象即可获取所有的非私有方法。在实际调用被增强方法时,将该方法与所有的advice进行匹配,
如果有匹配到advice,则执行相应的增强。当然并不需要每一次都需要遍历获取,为了效率可以执行对方法和增强的advice进行缓存。
用户要实现AOP需要提供什么呢?
首先必须提供一个Advice(通知)来增强功能,一个expression表达式来定义增强那些方法,实际上还要指定使用哪个解析器解析传入
的表达式,(正则,AspectJ…)如果单独提供这些东西对用户来说还是比较麻烦的,而框架的作用是帮用户简化开发过程。
Weaving
现在我们已经有了切面,用户也已经能够比较简单的来定义如何使用切面,最重要的一步到了,那就是我们应该如何对需要增强
的类进行增强呢?什么时候增强?
上文已经说了
对类和方法进行增强就是使用代理模式来增强。那么我们作为框架改在什么时候来增强呢?
两个时机:
一种是在启动容器初始化bean的时候来进行增强,然后容器中存放的不是bean的实例,而是bean的代理实例。
二是在每一次使用bean
的时候判断一次是否需要增强需要就对其增强,然后返回bean的代理实例。这两种方法很明显第一种比较友好,只是让容器的启动时间稍长了一点,而第二种在运行时判断,会使得用户的体验变差。
https://juejin.cn/post/6844903744530808840
5.切点表达式
五:Spring模块总结:
Spring~~~~ 控制反转 依赖对象的创建 依赖关系的形成
一. IOC 容器
bean 对象的创建 三种方式
1.无参构造(经常用)
2. 静态工厂(静态方法)
3.实例工厂
二.DI 依赖注入
给属性赋值
依赖于set方法
依赖于 构造方法
使用注解 (例如 action service dao)
三.AOP(Aspect Oriented Programming面向切面编程 将核心业务和非核心业务解耦)
静态代理
动态代理
JDK CGLIB
Aop 的几个概念
pointCut:切入点
Advice:通知 增强
before:MethodBeforeAdvice
after :AfterReturningAdvice
around :MethodInterceptor
throwException:ThrowsAdvice
Advisor:描述 切入点 与通知 之间的关系
四:常用注解
AOP IOC
@Aspect @Component
@Pointcut @Scope
@Before @Controller
@After @Service
@AfterReturning @Autowired
@AfterThrowing @Resource
@Around
六:Spring常见的面试题
1.Spring事务
2.Spring循环依赖
3.谈谈你对Spring的理解
4.Spring Bean初始化的过程
5.Spring中的设计模式
aop中有 代理模式 观察者模式 责任链模式