Spring注解开发

  • 注解设置bean
  • 注解改造spring配置文件
  • Bean作用范围设置以及有关线程安全问题
  • bean生命周期管理
  • 注解依赖注入
    • 按照类型注入
    • 按照名称注入
    • 按照数据类型注入
  • 注解读取properties 文件

从注解开发开始,这里的一切都变得十分简化。
包括后面的大量开发实战,我们的注解开发都会派上大的用处。

注解设置bean

那么我们的bean就不再xml文件里面进行定义了。具体怎么做呢?

我们可以这样举个例子。先创建一个接口,放在dao层,然后创建sevice包,在这里我们创建接口service继承dao,然后在改包下创建impl,里面创建类来实现sevice接口类。


具体如下

package com.jgdabc.dao;

public interface Bookdao {
    void save();

}

package com.jgdabc.service;

import com.jgdabc.dao.Bookdao;

public interface BookService extends Bookdao {
    @Override
    void save();
}

package com.jgdabc.service.impl;

import com.jgdabc.service.BookService;

public class BookDaoServiceImp implements BookService {
    @Override
    public void save() {

    }
}

我们然后这样操作这个实现类

package com.jgdabc.service.impl;

import com.jgdabc.dao.Bookdao;
import com.jgdabc.service.BookService;
import org.springframework.stereotype.Component;

@Component("bookDao")
public class BookDaoServiceImp implements BookService {


    @Override
    public void save() {
        System.out.println("bookdaoservice impl is runnning");

    }
}

非常重要的一件事,在BookDaoImpl类上添加@Component 注解。到底是什么用处呢?
例如这样,我们上面的代码与此类似

然后呢,我们还是需要去spring 的配置文件配置一下。你要开启包扫描

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework/schema/beans"
       xmlns:context="http://www.springframework/schema/context"
       xmlns:xsi="http://www.w3/2001/XMLSchema-instance" xmlns:aop="http://www.springframework/schema/aop"
       xsi:schemaLocation="http://www.springframework/schema/beans http://www.springframework/schema/beans/spring-beans.xsd
                           http://www.springframework/schema/context http://www.springframework/schema/context/spring-context.xsd http://www.springframework/schema/aop https://www.springframework/schema/aop/spring-aop.xsd">


    <!--    bean标签表示配置配置bean-->
    <!--    id属性表示给bean起名字-->
    <!--    class属性表示给bean定义类型-->
    <!--    context是我开启的新的命名空间-->
    <!--  在xml文件配置了<context:component-scan>标签后,
    spring容器可以自动去扫描base-pack所指定的包或其子包下面的java类文件,
    如果扫描到有@Component、@Controller、@Service 、@Repository等注解修饰的Java类,
    则将这些类注册为spring容器中的bean。
      -->
  <context:component-scan base-package="com.jgdabc">

  </context:component-scan>
</beans>


然后运行

但是包括包扫描,开启命名空间,其实都需要在配置文件上添加配置,比较容易出现错误。我们后面可以将配置文件也按照注解开发。

component-scan
component:组件,Spring将管理的bean视作自己的一个组件scan:扫描
base-package指定Spring框架扫描的包路径,它会扫描指定包及其子包中的所有类上的注解。包路径越多[如:com.jgdabc.dao.impl],扫描的范围越小速度越快
包路径越少[如:com.jgdabc],扫描的范围越大速度越慢
一般扫描到项目的组织名称即Maven的groupId下[如:com.jgdabc]即可。

注意

@Component注解如果不起名称,会有一个默认值就是当前类名首字母小写,所以也可以按照名称获取

对于@Component注解,还衍生出了其他三个注解@Controller 、@Service 、@Repository

这几个注解的功能是一样的,只是设定这样的名字便于我们区分具体功能。
方便我们后期在编写类的时候能很好的区分出这个类是属于表现层、业务层还是数据层的类。

注解改造spring配置文件

首先将原来的配置文件删掉。因为实在太麻烦。或者你可以注销掉。我们使用注解的方式。

这样这个之前的配置文件就不会再起作用了。


然后我们创建一个config的目录。下面创建配置类。


难道你创建一个配置类,spring就会认为你这个类就是它的配置类?不会的。

我们用注解告诉他

上次我们用的是再xml配置扫描,现在我们用注解扫描bean。

现在我们运行App测试类,照样也可以运行出来。这个就真的方便太多了。

总结以上的过程


注解扫描的话,可以扫描多级。需要这样去写,举个例子

之前我们读取配置方式改一下

import com.jgdabc.config.SpringConfig;
import com.jgdabc.dao.Bookdao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
//        ApplicationContext ctx = new ClassPathXmlApplicationContext("SpringConfig.xml.bak");
//        DataSource dataSource = (DataSource) ctx.getBean("dataSource");
//        System.out.println(dataSource);
//        DataSource dataSource01= (DataSource) ctx.getBean("datasource01");
//        System.out.println(dataSource01);
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        
        Bookdao bookDao = (Bookdao) ctx.getBean("bookDao");
       bookDao.save();

    }
}

起步就这么简单。

Bean作用范围设置以及有关线程安全问题

所以bean的作用范围指的是什么?
在Spring中使用Scope来表示一个bean定义对应产生实例的类型,也可以说是对应实例的作用范围。
比如这样。像这样我们设置的话,其实就是一个非单例的模式。单例对应singleton。其实一共有五种,但是其他几种使用后在web环境中。我们目前需要了解到这两种。

singleton:表示在整个bean容器中或者说是整个应用中只会有一个实例。
prototype:多例类型,表示每次从bean容器中都会获取到一个对应bean定义全新的实例。

如何用代码更加清楚的说明?
我们这边设置一个非单例的bean


然后测试代码

import com.jgdabc.config.SpringConfig;
import com.jgdabc.dao.Bookdao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
//        ApplicationContext ctx = new ClassPathXmlApplicationContext("SpringConfig.xml.bak");
//        DataSource dataSource = (DataSource) ctx.getBean("dataSource");
//        System.out.println(dataSource);
//        DataSource dataSource01= (DataSource) ctx.getBean("datasource01");
//        System.out.println(dataSource01);
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

        Bookdao bookDao = (Bookdao) ctx.getBean("bookDao");
        Bookdao bookDao1 = (Bookdao) ctx.getBean("bookDao");
        bookDao.save();
        System.out.println(bookDao);
        System.out.println(bookDao1);

    }
}


可以看到输出的地址是不一样的。
如果设置为单例,其实可以将注解删掉,因为默认单例。



从地址上看单例的每次获取到bean的地址是一样的,非单例不一样。

Spring单例Bean与单例模式的区别在于它们关联的环境不一样,单例模式是指在一个JVM进程中仅有一个实例,而Spring单例是指一个Spring Bean容器(ApplicationContext)中仅有一个实例。
我们区分好这个是十分重要的。
参考一片完整说明文bean 作用域说明

其实这些都与线程安全的应用有关。简单提一下。

对于单例Bean,所有线程都共享一个单例实例Bean,因此是存在资源的竞争。

如果单例Bean,是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。

Spring容器本身并没有提供线程安全的策略,因此是否线程安全完全取决于Bean本身的特性。

那么什么是无状态bean和有状态bean呢?

有状态会话bean :每个用户有自己特有的一个实例,在用户的生存期内,bean保持了用户的信息,即“有状态”;一旦用户灭亡(调用结束或实例结束),bean的生命期也告结束。即每个用户最初都会得到一个初始的bean。简单来说,有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象 ,可以保存数据,是非线程安全的。

无状态会话bean :bean一旦实例化就被加进会话池中,各个用户都可以共用。即使用户已经消亡,bean 的生命期也不一定结束,它可能依然存在于会话池中,供其他用户调用。由于没有特定的用户,那么也就不能保持某一用户的状态,所以叫无状态bean。但无状态会话bean 并非没有状态,如果它有自己的属性(变量),那么这些变量就会受到所有调用它的用户的影响,这是在实际应用中必须注意的。简单来说,无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象 .不能保存数据,是不变类,是线程安全的。

其实上面这两段我们可以归结更加明白的是这两段话
无状态:表示这个实例没有属性对象,不能保存实数据,是不变的类。比如:controller、service、dao。
有状态:表示实例是有属性的对象,可以保存数据,是线程不安全的,比如:pojo。

我们这里不重点提线程安全的问题。

对应的非单例bean是线程安全的,我们可以在后面的实际应用中用到。

bean生命周期管理

用注解的话,我们需要关注两个注解。@PostConstruct 和@PreDestroy。


具体怎么用呢?
@PostConstruct //在构造方法之后执行,替换 init-method
@PreDestroy //在销毁方法之前执行,替换 destroy-method

只要注解在方法上即可。

我看来看举例

package com.jgdabc.service.impl;

import com.jgdabc.dao.Bookdao;
import com.jgdabc.service.BookService;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Component("bookDao")
//@Scope("prototype")
public class BookDaoServiceImp implements BookService {
    @PostConstruct
    public void init()
    {
        System.out.println("init方法执行");
    }
    @PreDestroy
    public void destory()
    {
        System.out.println("destory 方法执行");
    }

    @Override
    public void save() {
        System.out.println("bookdaoservice impl is runnning");

    }
}

来看测试方法

import com.jgdabc.config.SpringConfig;
import com.jgdabc.dao.Bookdao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
//        ApplicationContext ctx = new ClassPathXmlApplicationContext("SpringConfig.xml.bak");
//        DataSource dataSource = (DataSource) ctx.getBean("dataSource");
//        System.out.println(dataSource);
//        DataSource dataSource01= (DataSource) ctx.getBean("datasource01");
//        System.out.println(dataSource01);
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

        Bookdao bookDao = (Bookdao) ctx.getBean("bookDao");
        Bookdao bookDao1 = (Bookdao) ctx.getBean("bookDao");
        bookDao.save();
        System.out.println(bookDao);
        System.out.println(bookDao1);
        ctx.close();


    }
}

执行

注解依赖注入

按照类型注入

我们来举例说明
我们先定义一个接口

package com.jgdabc.dao;

public interface BookDao {
    void save();

}

然后也可以写一个接口实现类,这里我们可以加注解,将其制作为bean

package com.jgdabc.dao;

import org.springframework.stereotype.Repository;

@Repository
public class BookDaoImpl implements BookDao {
    public  void save()
    {
        System.out.println("bookdaoImpl is running");
    }

}

上面这两个都是在dao层。实际开发我们的dao层一般是不会直接去写实现。我们这里主要来看注入效果。

然后我们在service层定义一个接口

package com.jgdabc.service;

import com.jgdabc.dao.BookDao;

public interface BookService extends BookDao {
    @Override
    void save();
}

然后我们做service层的实现类


package com.jgdabc.service.impl;

import com.jgdabc.dao.BookDao;
import com.jgdabc.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

//@Component("bookDao")
//@Scope("prototype")
@Service
public class BookDaoServiceImp implements BookService {
    @Autowired
    private BookDao bookDao;
    @PostConstruct
    public void init()
    {
        System.out.println("init方法执行");
    }
    @PreDestroy
    public void destory()
    {
        System.out.println("destory 方法执行");
    }

    @Override
    public void save() {
        System.out.println("bookdaoservice impl is runnning");
        bookDao.save();


    }

}

测试

其实你可以探索注入bean的来源

你只需要输出bean调用对象,这样来做

从这里的输出我们得出什么?

我们需要搞懂这个bean的来源,以免后续不必要的疑惑。
我们这里是按照类型注入。并且这个自动注入存在一个特点就是
自动装配基于反射设计创建对象并通过暴力反射为私有属性进行设值普通反射只能获取public修饰的内容
暴力反射除了获取public修饰的内容还可以获取private修改的内容所以此处无需提供setter方法

这样注入存在的问题就是,如果一个类存在多个实现,那么就会有多个一样的类型,那么之后我们注入的话如何区分注入哪个?我们可以去给每个bean类起名称。这样就可以区分了。但是这个需要名字匹配到。

什么意思?

也就是说你这里起的名字必须对应到。不然注入不了。这样可以注入到名字匹配的那个、

但是这样其实要求名字必须一样,我们还有一种方法就是按照名称注入。

按照名称注入

按照名称注入的话,我们可以不必刻意去匹配名字。

那么我们如何实现按照名称注入呢?
我们可以使用@Qualifier来指定注入哪个bean对象。这里你最好给你设置的bean起一个名字。
@Qualifier不能独立使用,必须和@Autowired一起使用

例如

按照数据类型注入

我们之前注入的是引用数据类型,现在我们可以来看看简单数据类型

非常简单的操作

注解读取properties 文件

首先我们准备这样的一个配置文件放在resource下面,然后我们在配置类配置到它,然后再进行读取。具体这样操作。

先做一个properties的配置文件

这个是后面作为数据库连接信息的参数,我们也可以用这个数据,只要可以读取到就可以。这样我们就可以实现我们的在注解开发中的配置文件的读取。

我们使用注解加载到我们的配置文件
在配置类上添加@PropertySource 注解

然后我们尝试对配置文件中的数据进行一个读取。

这样我们可以成功读取到。

和我们的扫描一样,如股票配置文件有多个,我们也可以指定多个,这个再再在之前我们使用xml配置文件配置的时候就这样做过。

@PropertySource({“jdbc.properties”,“xxx.properties”})
但是需要主义的是,这样我们的配置文件注解属性是不支持通配符的。
像这样是不可以的。
@PropertySource({“*.properties”})

还有就是我们在加载配置文件的时候,可以指定从项目的根路径进行加载。
@PropertySource({“classpath:jdbc.properties”})

未完续更

更多推荐

java从入门到精通二十八(Spring注解开发)