spring笔记

Bean的管理

获取

注入

  • BeanFactory 是 Spring的一个Bean 的工厂,使用简单工厂模式,是 ApplicationContext 的父类,IOC 容器的容器顶级接口,负责生产、实例化、配置Bean对象以及建立这些Bean对象间的依赖。BeanFactory 实例化后并不会自动去实例化 Bean(是说他生产的bean),只有当 Bean 被使用时才实例化并装配依赖关系,属于延迟加载,适合多例模式。
    • 可以通过BeanFactory.getBean(“”)获取Bean
    • 范围可以是一个xml文件
  • FactoryBean 则是一个Bean,使用了工厂方法模式,作用是生产其他 Bean 实例,可以通过实现该接口,提供一个工厂方法来自定义实例化 Bean 的逻辑。FactoryBean 接口由 BeanFactory 中配置的对象实现,这些对象本身就是用于创建对象的工厂,如果一个 Bean 实现了这个接口,那么它就是创建对象的一个工厂 Bean,而不是 Bean 实例本身。
    • FactoryBean是默认是单例的,其default方法 isSingleton()默认直接返回true
  • 为什么Spring想到注入?
  • 注入解决了什么问题?Spring是在什么情况下想到工厂方式注入bean的?可以总结下当作设计模式的案例。

动态代理

jdk动态代理

一切皆对象真是好呐,类的信息有Class对象,方法有Method对象来直接调用
jdk动态代理需要基于接口实现(如果使用继承某个类来实现的话,会受到java只允许单继承机制的限制)
jdk动态代理会为目标对象生成一个继承了Proxy类的代理类对象。
下文一个AI生成的案例:利用反射原理,通过一个目标对象,获取其代理对象
欸!像这样↑开头先说清楚入参出参就好办了
getProxyInstance方法源码

    private Object target;

    public Object getProxyInstance() {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),		// 用于加载被代理类
                target.getClass().getInterfaces(),	//Class<?>[] interfaces数组
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("前置");
                        Object result = method.invoke(target, args);
                        System.out.println("后置");
                        return result;
                    }
                }
        );

通过目标对象获取其类的ClassLoader和接口,据此生成(代理类的字节码cl?)
然后获取其带有InvocationHandler的构造器,(可代理类的父类Proxy里带有。可能基于接口代理是因为生成的代理类必须在子级,但是又必须继承这个Proxy?那他咋恁贱类,非得继承?可能有单例的实例化吧。),并据此创建代理对象
在生成的代理类中,会生成和原对象同名的方法(根据接口?),而方法的实现则是调用我们传入的InvocationHandler 的invoke方法中,我们自己实现的、包装了原方法的逻辑。

//反编译生成的代理类字节码文件,可以看到:代理类继承了Proxy类,实现了Singer接口
 public final class $Proxy0 extends Proxy implements 目标对象类{
  public final boolean 原方法名(Object paramObject)
    throws {
    try
    {		// m1,m2,m3:反编译得来private static Method 属性,既被代理的原方法。
  		this.h.invoke(this, m3, new Object[] { paramString });
    }
      // ...
  }
}

再通过Spring的IOC管理,实际使用时自动注入代理对象给接口,而调用代理对象的方法,自然是代理生成的包装后的方法。

/**
* 这也是多态特性的一个体现:其实这里给接口注入的是同样实现了接口的代理对象,而不是目标对象;
* 因为接口可以引用任意实现类,这一操作自然可以实现。当我们后面调用userService方法的时候,实际上是在调用代理类中同名的方法。
*/
@Resource("userService")
IUserService userService;	

拓展:Proxy#newProxyInstance 源码1

可作为工厂模式案例
其实并没有挖出什么东西,,挖到defineClass0就停了,了解到的仍然还是"ClassLoader+接口获取代理类字节码。可能这其中,因为有接口之类的,所以生成了接口中的方法吧。"
通过ClassLoader

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException{
  		// ...
        // 将接口类对象数组clone一份。
        final Class<?>[] intfs = interfaces.clone();

        //执行权限检查
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
         // 查找或者是生成一个特定的代理类对象? todo 这里是字节码吧
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            // 是static final 修饰的,源码: private static final Class<?>[] constructorParams ={ InvocationHandler.class };
            // 从代理类对象中查找参数为InvocationHandler的构造器
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            // 检测构造器是否是Public修饰,如果不是则强行转换为可以访问的。
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            // 通过反射,将InvocationHandler 作为参数,实例化代理类,返回代理类实例。
            return cons.newInstance(new Object[]{h});
        }
            // ...
    }

getProxyClass0代码嵌套了两层缓存用的内部子类的map,终于到了ProxyClassFactory

/**
     * 一个利用给定的类加载器和接口类数组进行生成、定义并返回代理类对象的工厂方法
     * A factory function that generates, defines and returns the proxy class given
     * the ClassLoader and array of interfaces.
     */
    private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
		// ...
        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

            // ...

            /*
             * Generate the specified proxy class.
             */
            //生成代理类class文件
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
            try {
                // 返回代理类对象 参数:ClassLoader, 代理类类名,代理类.class字节码信息
                return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
   				// ...
            }
        }
    }
}
  • 不知道为什么私有方法不能代理。是因为目标类不能继承所有就不搞了?是因为接口不会有有意义的私有声明?

  • spring 5.x仍然默认使用jdk动态代理

  • SpringBoot 2.x开始,AOP为了解决使用JDK动态代理可能导致的类型转换异常,而使用CGLIB。

  • 在SpringBoot 2.x中,AOP如果需要替换使用JDK动态代理可以通过配置项spring.aop.proxy-target-class=false来进行修改,proxyTargetClass配置已无效。

SPI

前置知识:

  • ClassLoader

数据

DAO支持

@Repository定位同DAO。文档中经常用or同时称呼他们

@Repository
public class JdbcMovieFinder implements MovieFinder {

	private JdbcTemplate jdbcTemplate;

	@Autowired
	public void init(DataSource dataSource) {
		this.jdbcTemplate = new JdbcTemplate(dataSource);
	}

	// ...
}
  • 为什么 @Autowired在方法上而不是直接注入?
  • 话说此处说明DAO依赖其具体实现(需要用具体实现去set)。但通过依赖注入,不用每次手动set了?
    • 那如果单例模式每次获得这个对象不是一个效果吗
@Repository
public class HibernateMovieFinder implements MovieFinder {

	private SessionFactory sessionFactory;

	@Autowired
	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	// ...
}
  • 下面这个是类似行数据入口的模式吗?顶一个DAO,然后执行方法,其内部实现,,emm,那个时候还只有jdbc这种级别吧,没牵扯到ORM。所以数据映射器后面单独说了
String lastName = this.jdbcTemplate.queryForObject(
		"select last_name from t_actor where id = ?",
		String.class, 1212L);

JDBC编程in Spring

wow,新的数据mapper方式出现了!这画中倒没说是ORM mapping

public class ActorMappingQuery extends MappingSqlQuery<Actor> {

	public ActorMappingQuery(DataSource ds) {
		super(ds, "select id, first_name, last_name from t_actor where id = ?");
		declareParameter(new SqlParameter("id", Types.INTEGER));
		compile();
	}

	@Override
	protected Actor mapRow(ResultSet rs, int rowNumber) throws SQLException {
		Actor actor = new Actor();
		actor.setId(rs.getLong("id"));
		actor.setFirstName(rs.getString("first_name"));
		actor.setLastName(rs.getString("last_name"));
		return actor;
	}
}

事务

声明式事务管理:xml 或注解方式
编程式事务管理:TransactionTemplate

  • 看来用XXXTemplate 几乎都是编程式管理了?

@Transactional 事务原理

在方法开始前开启事务
检查异常(unchecked exception)不回滚
如果想针对检查异常进行事务回滚,可以在@Transactional注解里使用 rollbackFor属性明确指定异常。

@Transactional(rollbackFor = Exception.class)

推荐做法:在业务层统一抛出异常,然后在控制层统一处理。

这样业务层一直在抛异常,就保证了事务会回滚;同时异常可以在控制层统一处理了

Transactional注解的常用属性表:

属性说明
propagation事务的传播行为,默认值为 REQUIRED。
isolation事务的隔离度,默认值采用 DEFAULT
timeout事务的超时时间,默认值为-1,不超时。如果设置了超时时间(单位秒),那么如果超过该时间限制了但事务还没有完成,则自动回滚事务。
read-only指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
rollbackFor用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。{xxx1.class, xxx2.class,……}
noRollbackFor抛出 no-rollback-for 指定的异常类型,不回滚事务。{xxx1.class, xxx2.class,……}

Controller层使用事务注解会生效吗?

1.对于Spring + SpringMvc架构中,@Transactional被Spring容器管理,而Controller则被SpringMvc容器进行管理, 所以在Spring + SpringMvc架构中,我们直接在Controller层使用@Transactional,此时事务是不生效的。
2.对于SpringBoot架构,@Transactional和Controller统一由SpringBoot容器进行管理,因此我们在Controller层使用@Transactional开启事务是可以生效的。
存疑

javax与Spring的@Transactional

jakarta.transaction.Transactional 也可以即插即用得替换Spring的事务注解。

stereotype

Spring的组件管理注解同JSR-330标准注解有些不同。

Component

@Serivce

@Component的别名

@Configuration

@Bean

@Bean注解可在@Configuration 或 @Component 注解的类中使用。
该注解标记在方法上,默认会注册一个Bean定义,其名称为方法名,ApplicationContext 为方法返回值的类型

拓展

Spring事务失效


  1. https://www.cnblogs.com/zhangchengzi/p/9713807.html 详细但内容很多的源码,颇受java“一层套一层”的荼毒。本文提取了其主要部分 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值