spring learning code 4

Thu, Apr 16, 2020 7-minute read

This is my learning note about how Spring core features like IoC, AOP works, with Code Examples .

from Servlet to ApplicatoinContext

IoC

  • Map 容器

  • BeanFactory 工厂

  • ApplicationContext 上下文:持有BeanFactory引用, 门面模式

  • BeanDefinitionReader解析器:负责解析所有的配置文件

  • BeanDefinition元信息,配置(保存各种配置信息) xml, yml, annotation, properties

  • Bean实例,反射实例化Object 原生Bean,代理Bean

  • BeanWrapper包装器模式:缓存到了Ioc容器,缓存 持有Bean引用

IOC 顶层设计, ListableBeanFactory为例

用户通过 => ApplicationContext => 调用 getBean()方法, 底层各种factory方法listable等,创建factory对象, 所以要调用BeanDefinitionReader, 读取bean配置文件,创建 BeanDefinition = > 换存到容器里就是BeanWrapper对象, 所以getBean()实际拿到的就是BeanWrapper对象

init() {
    new ApplicationContext() {
        reader = new BeanDefinitionReader();
        reader.loadBeanDefinitions();

        factory.doRegistryBeanDefinition() {
            beanDefinitionMap.put()
        }

        doLoadInstance() {
            getBean() //循环调用
        }
    }
}

getBean() {
    BeanDefinition beanDefinition = registry.beanDefinitionMap.get(beanName);

    Object instance = instantiateBean();

    BeanWrapper beanWrapper = new BeanWrapper(instance);

    factoryBeanInstanceCache.put(beanWrapper);

    populateBean(); //依赖注入
}

1.1 the most basic bean container define a simple bean container: BeanFactory, contains a map to store bean, owns registry and get Bean two methods.

public class BeanFactory {
	private Map<String, Object> beanMap = new HashMap<>();

	public void registerBean(String name, Object bean) {
		beanMap.put(name, bean);
	}

	public Object getBean(String name) {
		return beanMap.get(name);
	}
}

public class SimpleBeanContainerTest {

	@Test
	public void testGetBean() throws Exception {
		BeanFactory beanFactory = new BeanFactory();
		beanFactory.registerBean("helloService", new HelloService());
		HelloService helloService = (HelloService) beanFactory.getBean("helloService");
		assertThat(helloService).isNotNull();
		assertThat(helloService.sayHello()).isEqualTo("hello");
	}

	class HelloService {
		public String sayHello() {
			System.out.println("hello");
			return "hello";
		}
	}
}

BeanDefinition & BeanDefinitionRegistry p

image

click here code download

…beans.factory.config.SingletonBeanRegistry.java

…beans.factory.config.BeanDefinition.java

…beans.factory.support.DefaultSingletonBeanRegistry.java

…beans.factory.BeanFactory.java

…beans.factory.support.AbstractAutowireCapableBeanFactory.java

…beans.factory.support.AbstractBeanFactory.java

…beans.factory.support.BeanDefinitionRegistry.java

…beans.factory.support.DefaultListableBeanFactory.java

package org.springframework.beans;
public class BeanDefinitionAndBeanDefinitionRegistryTest {

	@Test
	public void testBeanFactory() throws Exception {
		DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
		BeanDefinition beanDefinition = new BeanDefinition(HelloService.class);
		beanFactory.registerBeanDefinition("helloService", beanDefinition);

		HelloService helloService = (HelloService) beanFactory.getBean("helloService");
		helloService.sayHello();
	}
}

class HelloService {
	public String sayHello() {
		System.out.println("hello");
		return "hello";
	}
}


InstantiationStrategy

now we instantiat bean by method : beanClass.newInstance() in AbstractAutowireCapableBeanFactory.doCreateBean. And this only used in NoArgsConstructo

now create InstantiationStrategy interface with implemented methods image

  • SimpleInstantiationStrategy: use bean construction
  • CglibSubclassingInstantiationStrategy,use CGLIB

基础java知识

servlet(模板模式)

  • HttpServlet已经实现了service方法:HttpServlet是一个抽象类

一个类声明成抽象方法,一般有两个原因: 有抽象方法 OR 没有抽象方法,但是不希望被实例化

HttpServlet做成抽象类,仅仅是为了不让new

如何写一个Servet?

不用实现javax.servlet接口

不用继承GenericServlet抽象类

只需继承HttpServlet并重写doGet()/doPost()

父类把能写的逻辑都写完,把不确定的业务代码抽成一个方法,调用它。当子类重写该方法,整个业务代码就活了。这就是模板方法模式

父类: 子类

service() {                                                        service() {
    doXxx(); //具体业务代码,但是父类无法知道子类具体业务逻辑,  <= 继承  doXxx();
    //所以后抽象惩罚让子类重写                                         }
}

protected void doXxx() {                                   <= 覆盖 void doXxx() {
    //空实现,或者默认实现                                          //具体实现
}                                                                   }

ServletContext

map,服务器会为每个应用创建一个ServletContext对象

ServletContext对象的作用是在整个Web应用的动态资源(Servlet/JSP)之间共享数据

这种用来装载共享数据的对象,在JavaWeb中共有4个,而且更习惯被成为“域对象”:

ServletContext域(Servlet间共享数据)

Session域(一次会话间共享数据,也可以理解为多次请求间共享数据)

Request域(同一次请求共享数据)

Page域(JSP页面内共享数据)

它们都可以看做是map,都有getAttribute()/setAttribute()方法。

Java泛型Generics

  • 泛型类 <T> Type Parameter
public class ArrayList<T> {
    private T[] array;
    private int size;
    public void add(T e) {...}
    public void remove(int index) {...}
    public T get(int index) {...}
}
  • 对变量类型进行抽取
public User getUser(T t){...}
  • 获取构造函数的参数

获取到构造函数的对象之后,可以通过getParameterTypes()获取到构造函数的参数。

Constructor constructors = birdClass.getConstructor(new Class[]{String.class});
            Class[] parameterTypes = constructors.getParameterTypes();

反射

  • JVM是如何构建一个实例的 A a = new A();

Step1: ClassLoader加载.class文件到内存(jvm内存;执行静态代码块和静态初始化语句

Step2: 执行new,申请一个内存空间

Step3:调用构造器,创建一个空白对象

step4:子类调用父类构造器

step5:构造器执行: 执行构造代码块和初始化语句; 构造器内容

  • Class

Class类对象就相当于B超的探头,将一个类的方法、变量、接口、类名、类修饰符等信息告诉运行的程序。

  • 获取构造函数Constructor

获取构造函数的方法:

Class birdClass = Bird.class;
Constructor[] constructors = birdClass.getConstructors();

一个类会有多个构造函数,getConstructors()返回的是Constructor[]数组,包含了所有声明的用public修饰的构造函数。

如果你已经知道了某个构造的参数,可以通过下面的方法获取到回应的构造函数对象:

public class Alunbar {
    public static void  main(String arts[]){

        Class birdClass = Bird.class;
        try{
            Constructor constructors = birdClass.getConstructor(new Class[]{String.class});
        }catch(NoSuchMethodException  e){

        }
    }

    private class Bird {
        public Bird(){

        }

        public Bird(String eat){

        }
    }
}
  • 类加载器

loadClass(),告诉它需要加载的类名,它会帮你加载

    // 子类应该重写该方法
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

加载.class文件大致可以分为3个步骤:

检查是否已经加载,有就直接返回,避免重复加载 当前缓存中确实没有该类,那么遵循父优先加载机制,加载.class文件 上面两步都失败了,调用findClass()方法加载 需要注意的是,ClassLoader类本身是抽象类,而抽象类是无法通过new创建对象的。所以它的findClass()方法写的很随意,直接抛了异常,反正你无法通过ClassLoader对象调用。也就是说,父类ClassLoader中的findClass()方法根本不会去加载.class文件。

正确的做法是,子类重写覆盖findClass(),在里面写自定义的加载逻辑。比如:

@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
	try {
		/*自己另外写一个getClassData()
                  通过IO流从指定位置读取xxx.class文件得到字节数组*/
		byte[] datas = getClassData(name);
		if(datas == null) {
			throw new ClassNotFoundException("类没有找到:" + name);
		}
		//调用类加载器本身的defineClass()方法,由字节码得到Class对象
		return defineClass(name, datas, 0, datas.length);
	} catch (IOException e) {
		e.printStackTrace();
		throw new ClassNotFoundException("类找不到:" + name);
	}
}

defineClass()是ClassLoader定义的方法,目的是根据.class文件的字节数组byte[] b造出一个对应的Class对象。我们无法得知具体是如何实现的,因为最终它会调用一个native方法:

  • 反射API

创建实例

clazz.newInstance()底层还是调用Contructor对象的newInstance()。所以,要想调用clazz.newInstance(),必须保证编写类的时候有个无参构造。

为什么根据Class对象获取Method时,需要传入方法名+参数的Class类型?

调用Class对象的getMethod()方法时,内部会循环遍历所有Method,然后根据方法名和参数类型匹配唯一的Method返回。

调用method.invoke(obj, args);时为什么要传入一个目标对象?

把Method理解为方法执行指令吧,它更像是一个方法执行器,必须告诉它要执行的对象(数据)。

反射调用方法

Java 动态代理

  • 问题: 原有代码:
public class Calculator {

	//加
	public int add(int a, int b) {
		int result = a + b;
		return result;
	}

	//减
	public int subtract(int a, int b) {
		int result = a - b;
		return result;
	}

	//乘法、除法...
}

现有一个需求:在每个方法执行前后打印日志.

直接修改


public class Calculator {

	//加
	public int add(int a, int b) {
		System.out.println("add方法开始...");
		int result = a + b;
		System.out.println("add方法结束...");
		return result;
	}

	//减
	public int subtract(int a, int b) {
		System.out.println("subtract方法开始...");
		int result = a - b;
		System.out.println("subtract方法结束...");
		return result;
	}

	//乘法、除法...
}

上面的方案是有问题的:

直接修改源程序,不符合开闭原则。应该对扩展开放,对修改关闭 如果Calculator有几十个、上百个方法,修改量太大 存在重复代码(都是在核心代码前后打印日志) 日志打印硬编码在代理类中,不利于后期维护:比如你花了一上午终于写完了,组长告诉你这个功能取消,于是你又要打开Calculator花十分钟删除日志打印的代码!

  • 静态代理,通过代理访问目标对象

静态代理的实现比较简单:编写一个代理类,实现与目标对象相同的接口,并在内部维护一个目标对象的引用。通过构造器塞入目标对象,在代理对象中调用目标对象的同名方法,并添加前拦截,后拦截等所需的业务功能。

  • 将Calculator抽取为接口

/**
 * 目标对象实现类,实现Calculator接口
 */
public class CalculatorImpl implements Calculator {

	//加
	public int add(int a, int b) {
		int result = a + b;
		return result;
	}

	//减
	public int subtract(int a, int b) {
		int result = a - b;
		return result;
	}

	//乘法、除法...
}

  • 创建代理类CalculatorProxy实现Calculator

/**
 * 代理对象实现类,实现Calculator接口
 */
public class CalculatorProxy implements Calculator {
        //代理对象内部维护一个目标对象引用
	private Calculator target;
        
        //构造方法,传入目标对象
	public CalculatorProxy(Calculator target) {
		this.target = target;
	}

        //调用目标对象的add,并在前后打印日志
	@Override
	public int add(int a, int b) {
		System.out.println("add方法开始...");
		int result = target.add(a, b);
		System.out.println("add方法结束...");
		return result;
	}

        //调用目标对象的subtract,并在前后打印日志
	@Override
	public int subtract(int a, int b) {
		System.out.println("subtract方法开始...");
		int result = target.subtract(a, b);
		System.out.println("subtract方法结束...");
		return result;
	}

	//乘法、除法...
}

  • 使用代理对象完成加减乘除,并且打印日志

public class Test {
	public static void main(String[] args) {
		//把目标对象通过构造器塞入代理对象
		Calculator calculator = new CalculatorProxy(new CalculatorImpl());
		//代理对象调用目标对象方法完成计算,并在前后打印日志
		calculator.add(1, 2);
		calculator.subtract(2, 1);
	}
}

没解决重复代码,如果有很多类,没解决修改量太大的问题, 所以我们要代理的不是代理类,而是代理对象,根据接口自动生成代理对象

  • 动态代理

Proxy有个静态方法:getProxyClass(ClassLoader, interfaces),只要你给它传入类加载器和一组接口,它就给你返回代理Class对象。

  • 使用代理对象完成加减乘除,并且打印日志

public class ProxyTest {
	public static void main(String[] args) throws Throwable {
		//Calculator的类加载器
		Class calculatorProxyClazz = Proxy.getProxyClass(Calculator.class.getClassLoader(), Calculator.class);
		//得到有参构造器
        Constructor constructor =  calculatorProxyClazz.getConstructor(InvocationHandler.class);
        //反射创建代理实例
        Calculator CalculatorProxyImpl = (Calculator)constructor.newInstance(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method, method, Object[] args) throws Exception {
                //手动new一个目标对象
                CalculatorImpl calculatorImpl = new CalculatorImpl();
                //反射执行目标对象的方法
                Object result = method.invoke(calculatorImpl, args);
                //返回目标对象执行结果
                return result;
            }
        })
		CalculatorProxyImpl.add(1, 2);
		
	}
}

代理对象改变,invoke方法要改,所以把目标对象当参数传进来


public class ProxyTest {
	public static void main(String[] args) throws Throwable {
        //传入目标对象
        CalculatorImpl target = new CalculatorImpl();
        //根据它实现的接口生成代理对象, 代理对象调用目标对象方法
		Calculator calculatorProxy = (Calculator)getProxy(target);
        calculatorProxy.add(1, 2);
        calculatorProxy.substract(2, 1);
    }

    private static Object getProxy(final Object target) throws Exception {
		Class ProxyClazz = Proxy.getProxyClass(target.getclass().getClassLoader(), target.getclass().getInterfaces());
		//得到有参构造器
        Constructor constructor =  ProxyClazz.getConstructor(InvocationHandler.class);
        //反射创建代理实例
        Object proxy = constructor.newInstance(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method, method, Object[] args) throws Exception {
                System.out.println(method.getName() + "方法开始执行。。");
                Object result = method.invoke(target, args);
                //反射执行目标对象的方法
                
                return result;
            }
        });
		return proxy;
		
	}
}

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

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


public class ProxyTest {
	public static void main(String[] args) throws Throwable {
        //传入目标对象
        CalculatorImpl target = new CalculatorImpl();
        //根据它实现的接口生成代理对象, 代理对象调用目标对象方法
		Calculator calculatorProxy = (Calculator)getProxy(target);
        calculatorProxy.add(1, 2);
        calculatorProxy.substract(2, 1);
    }

    private static Object getProxy(final Object target) throws Exception {
		Object proxy = Proxy.newProxyInstance(target.getclass().getClassLoader(), target.getclass().getInterfaces(),
		new InvocationHandler() {
            
            public Object invoke(Object proxy, Method, method, Object[] args) throws Exception {
                System.out.println(method.getName() + "方法开始执行。。");
                Object result = method.invoke(target, args);
                //反射执行目标对象的方法
                
                return result;
            }
        }
        );
		return proxy;
		
	}
}

Spring DI

组合复合原则(怎么给对象自动赋值,循环依赖注入)

Spring MVC

委派,策略,解释器原则(用户输入URL怎么样和java代码关联)

Spring AOP

责任,动态代理

我们需要一个通知类(TransactionManager)执行事务,一个代理工厂帮助生成代理对象,然后利用动态代理将事务代码织入代理对象的各个方法中。

UserService {
    public void test() {
        //开启事务
        userDao.add();
    }
}
BrandService {
    public void test() {
        //开启事务
        brandDao.add();
    }
}
CategoryService {
    public void test() {
        //开启事务
        categoryDao.add();
    }
}

希望最终达到的效果是,我加了个@MyTransactional后,代理工厂给我返回一个代理对象:

UserService –》 入参 |代理工厂| 出参 –》 UserServiceProxy

细节分析:

UserServiceProxy.test() –> //+1 +8

|代理工厂| 
  代理对象 {
    //1.开启事务
    txManager.beginTransaction(); //+2
    //2.执行事务
    rtValue = method.invoke(target, args) --> 调用代理对象同名方法+3 +5
       //3.提交事务
    txManager.commit(); //+6
    //3.返回结果
    return rtValue; //+7
}
UserService {
    public void test() { //+4
        //开启事务
        userDao.add();
    }
}

代理对象方法 = 事务 + 目标对象方法。

事务操作,必须使用同一个Connection对象。如何保证?第一次从数据源获取Connection对象并开启事务后,将它存入当前线程的ThreadLocal中,等到了DAO层,还是从ThreadLocal中取,这样就能保证开启事务和操作数据库使用的Connection对象是同一个。

开启事务后,Controller并不是直接调用Service,而是Spring提供的代理对象

Service和Dao关系也是i如此

AOP事务具体代码实现

ConnectionUtils工具类

package com.demo.myaopframework.utils;

import org.apache.commons.dbcp.BasicDataSource;

import java.sql.Connection;

/**
 * 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
 */
public class ConnectionUtils {

    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

    private static BasicDataSource dataSource = new BasicDataSource();

    //静态代码块,设置连接数据库的参数
    static{
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
    }


    /**
     * 获取当前线程上的连接
     * @return
     */
    public Connection getThreadConnection() {
        try{
            //1.先从ThreadLocal上获取
            Connection conn = tl.get();
            //2.判断当前线程上是否有连接
            if (conn == null) {
                //3.从数据源中获取一个连接,并且存入ThreadLocal中
                conn = dataSource.getConnection();
                tl.set(conn);
            }
            //4.返回当前线程上的连接
            return conn;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    /**
     * 把连接和线程解绑
     */
    public void removeConnection(){
        tl.remove();
    }
}

AOP通知(事务管理器)

package com.demo.myaopframework.utils;

/**
 * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
 */
public class TransactionManager {

    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    /**
     * 开启事务
     */
    public  void beginTransaction(){
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 提交事务
     */
    public  void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 回滚事务
     */
    public  void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        }catch (Exception e){
            e.printStackTrace();
        }
    }


    /**
     * 释放连接
     */
    public  void release(){
        try {
            connectionUtils.getThreadConnection().close();//还回连接池中
            connectionUtils.removeConnection();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

自定义注解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTransactional {
}

Service

public interface UserService {
	void getUser();
}

 
public class UserServiceImpl implements UserService {
	@Override
	public void getUser() {
		System.out.println("service执行...");
	}
}

实例工厂

public class BeanFactory {

	public Object getBean(String name) throws Exception {
		//得到目标类的Class对象
		Class<?> clazz = Class.forName(name);
		//得到目标对象
		Object bean = clazz.newInstance();
		//得到目标类上的@MyTransactional注解
		MyTransactional myTransactional = clazz.getAnnotation(MyTransactional.class);
		//如果打了@MyTransactional注解,返回代理对象,否则返回目标对象
		if (null != myTransactional) {
			ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
			TransactionManager txManager = new TransactionManager();
			txManager.setConnectionUtils(new ConnectionUtils());
			//装配通知和目标对象
			proxyFactoryBean.setTxManager(txManager);
			proxyFactoryBean.setTarget(bean);
			Object proxyBean = proxyFactoryBean.getProxy();
			//返回代理对象
			return proxyBean;
		}
		//返回目标对象
		return bean;
	}
}

代理工厂

public class ProxyFactoryBean {
	//通知
	private TransactionManager txManager;
	//目标对象
	private Object target;

	public void setTxManager(TransactionManager txManager) {
		this.txManager = txManager;
	}

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

	//传入目标对象target,为它装配好通知,返回代理对象
	public Object getProxy() {
		Object proxy = Proxy.newProxyInstance(
				target.getClass().getClassLoader(),/*1.类加载器*/
				target.getClass().getInterfaces(), /*2.目标对象实现的接口*/
				new InvocationHandler() {/*3.InvocationHandler*/
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						try {
							//1.开启事务
							txManager.beginTransaction();
							//2.执行操作
							Object retVal = method.invoke(target, args);
							//3.提交事务
							txManager.commit();
							//4.返回结果
							return retVal;
						} catch (Exception e) {
							//5.回滚事务
							txManager.rollback();
							throw new RuntimeException(e);
						} finally {
							//6.释放连接
							txManager.release();
						}

					}
				}
		);
		return proxy;
	}

}

AOPTest,

BeanFactory beanFactory = new BeanFactory();
try {
    Object bean = beanFactory.getBean("com.demo.mya.service.UserServiceImpl");
    Sout(bean.getClass().getName());
}

com.demo.mya.service.UserServiceImpl

给UserServiceImpl添加@MyTransactional注解,得到代理对象:

com.sun.proxy.$Proxy2


Ok, It’s time for spring1

Ok, It’s time for spring2

Ok, It’s time for spring mvc