java进阶二

Java进阶第二部分

多线程

一个简单的线程

通过Thread.currentThread().getName();获取当前运行的线程

public class firstThread {
    public static void main(String[] args) throws InterruptedException {
        String string = "好好学习,天天向上,你是最棒的\n";
        System.out.println(Thread.currentThread().getName());
        printSlowly(string,400);
    }
    public static void printSlowly(String string,long interval) throws InterruptedException {
        for(char ch:string.toCharArray()){
            Thread.sleep(interval);
            System.out.print(ch);
        }
    }
}

创建线程

用于共享命令行资源,因此此多线程Terminal输出会杂乱

thread.setDaemon(true);设置守护线程

thread.setPrority(Thread.MAX_PRORITY);设置线程优先级

TimeUnit.SECONDS.toMillis(xx)将时间按单位转换

public class secondThread {
    private static final String string = "好好学习,天天向上,你是最棒的\n";
    public static void main(String[] args) {
        for (int i = 1; i <= 2; i++) {
            Thread thread = new Thread(new PrintSome(string, 200 * i), "我的线程" + i);
            // 设置守护线程,注意。是在需要截取其他线程的当前线程设置
            thread.setDaemon(true);
            // 设置优先级
//            thread.setPriority(Thread.MAX_PRIORITY);
            thread.start();
        }
        // 时间单位转换器
        Thread.sleep(TimeUnit.SECONDS.toMillis(3));
        System.out.println("end thread:" + Thread.currentThread().getName());
    }
}
class PrintSome implements Runnable {
    private final String text;
    private final long interval;
    public PrintSome(String text, long interval) {
        this.text = text;
        this.interval = interval;
    }
    @Override
    public void run() {
        try {
            System.out.println("thread name:" + Thread.currentThread().getName());
            firstThread.printSlowly(text, interval);
            System.out.println("end thread:" + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程的InterruptedException

在当前线程sleep,wait和join时,如果被其创建的线程中断(或主动执行Interrupted方法)会抛出此异常,可以在catch此异常并进行相关操作

synchronized

同步锁,类似于python中的lock,js中的sync。不详细解释

wait notify

注意:sleep表示当前线程被阻塞,并不会释放资源。当被阻塞的线程被调用时,会抛出interrupted异常;当阻塞的线程sleep时间满后,如果其资源被其他线程锁定,就并不会立即执行,除非其具有更高的优先级

wait表示线程挂起,会释放资源让其他线程进入,使用notify会唤醒被挂起的线程,其使用必须在sync代码块中(因为必须是同步的线程才允许挂起,异步的资源无法准确释放),且并不会主动唤醒,会一直处于挂起状态

wait类似于一个标识,其他线程可以标识某个线程挂起,而阻塞只能由当前线程标识为阻塞状态

public class forthThread {
    public static void main(String[] args) {
        Object lock = new Object();
        int workSec = 2;
        int threadCount = 5;
        System.out.println("--------------------invoke thread start sleep-----------------");
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                System.out.println("thread working:" + getThreadName());
                // // 当前线程被阻塞,但持有的资源并不释放,注意,这里被阻塞2s(模拟工作),main线程继续执行
                sleepSec(workSec);
                synchronized (lock) {
                    // 利用阻塞模拟线程锁定并正在运行的状态,这里表示五个线程均在运行2s
                    System.out.println("thread begin to wait:" + getThreadName());
                    try {
                        // wait表示线程挂起,相关资源释放,其他线程可以访问,这里表示其他四个线程可以进入
                        lock.wait();
                        System.out.println("thread go on:" + getThreadName());
                        // 模拟被唤醒的线程又运行了2s
                        sleepSec(1);
                        System.out.println("thread end:" + getThreadName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "thread" + i).start();
        }
        sleepSec(workSec + 1);
        System.out.println("--------------------invoke thread end sleep-----------------" + getThreadName());
        synchronized (lock) {
            System.out.println("--------------------invoke all thread-----------------");
            lock.notifyAll();
        }
    }
    private static void sleepSec(int sec) {
        try {
            Thread.sleep(TimeUnit.SECONDS.toMillis(sec));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static String getThreadName() {
        return Thread.currentThread().getName();
    }
}

join

join会判断所需join的线程是否为alive,如果在对应线程start或end后添加join,则该线程会立即返回,join的作用是将所需join的线程插入到当前运行的线程之前

for (Thread thread : threads) {
    try {
        String name = thread.getName();
        System.out.println(" --- 主线程开始join " + name + " --- ");
        // 这里仅仅是插入到当前线程,threads队列之间的线程还是独立的
        thread.join();
        System.out.println(" ---主线程join " + name + " 结束 --- ");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

解耦的思路:

1.通过反射来创建对象,避免使用new关键字

2.通过读取配置文件来获取要创建的对象的限定类名

Spring

一个小例子

使用jdbc与com.mysql依赖进行数据库读取

实际开发中应该左到:编译器不依赖,运行时才依赖

public class day1 {
    public static void main(String[] args) throws SQLException {
        // 1.注册驱动 2.获取连接 3.获取预处理对象 4.执行sql 5.遍历结果集 6.释放资源
        DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
        // 通过反射调用,实现在运行时依赖
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://192.168.43.217:3306/demo1?serverTimezone=Asia/Shanghai", "root", "123456");
        PreparedStatement preparedStatement = conn.prepareStatement("select * from customer");
        ResultSet set = preparedStatement.executeQuery();
        while (set.next()){
            System.out.println(set.getString("CUSTOMER_EMAIL"));
        }
        set.close();
        preparedStatement.close();
        conn.close();
    }
}

maven

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.19</version>
    <!--设置为运行时依赖 -->
     <scope>runtime</scope>
</dependency>

Bean

ApplicationContext与BeanFactory

  • 模拟一个表现层,用于调用业务层
  • 获取spring 容器的IOC,并根据id获取对象
  • ApplicationContext 常用的三个实现类:
    •  ClassPathXmlApplicationContext:加载类路径下的config,配置文件必须在类路径下
      
    •  FileSystemXmlApplicationContext:加载磁盘任一路径下的配置文件
      
    •  AnnotationConfigApplicationContext:读取注解创建容器
      
  • 核心容器的两个接口
    • ApplicationContext: 单例对象使用(多),构建容器时,采用立即加载的方式(只要读取配置文件就创建)
    • BeanFactory:多例对象使用,创建对象采用延时加载,即创建对象时加载
import com.learn2.dao.IAccountDao;
import com.learn2.service.IAccountSer;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
public class Client {
    public static void main(String[] args) {
        // 1.获取核心对象(ApplicationContext和BeanFactory两种方式)
        useApplicationContext();
//        ApplicationContext ac = useApplicationContext();
        BeanFactory ac = useBeanFactory();
        // 2.根据bean id获取对象(两种方式)
        IAccountSer as = (IAccountSer)ac.getBean("accountService");
        IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);
        System.out.println(as);
        System.out.println(adao);
    }
    public static ApplicationContext useApplicationContext(){
        //        IAccountSer as = new AccountServiceImpl();
        return new ClassPathXmlApplicationContext("bean.xml");
    }
    public static BeanFactory useBeanFactory(){
        Resource resource = new ClassPathResource("bean.xml");
        return new XmlBeanFactory(resource);
    }
}

Bean对象的使用

  1. 创建Bean的三种方式

    • 默认构造函数创建Bean对象(此方法对应的类中必须存在默认构造函数)

    • <?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.xsd">
          <bean id="accountService" class="com.learn2.service.impl.AccountServiceImpl"/>
          <bean id="accountDao" class="com.learn2.dao.impl.AccountDaoImpl"/>
      </beans>
      
    • 使用工厂设计模式创建对象

      <bean id="instanceFactory" class="com.learn2A.factory.InstanceFactory"/>
      <bean id="accountService_2A" factory-bean="instanceFactory" factory-method="getAccountService"/>
      
    • 类的静态方式,将上面两句结合起来就好(静态方法是属于类的,不是对象的,所以可以合在一起,但对应的工厂方法必须为静态的)

  2. Bean的作用范围

    1. bean默认为单例模式 scope:singleton
    2. prototype:多例的
    3. request作用于web应用的请求范围,session->web应用的会话范围,global-session集群的全局会话范围
  3. Bean对象的生命周期

    注意:scope为singleton时模式为默认的ApplicationContext,即在加载配置时就创建bean,而模式为prototype时为BeanFactory模式,即在获取对应对象时才创建

    1. 单例对象:生命周期随容器相同

    2. 多例对象:生命周期随对象相同,但回收时由GC回收

      <bean id="accountSer" class="com.learn2A.service.impl.AccountServiceImpl" init-method="init"
            destroy-method="destroy" scope="prototype"/>
      
public class Client {
    public static void main(String[] args) {
        ApplicationContext applicationContext = useBeanMethod();
        IAccountSer iAccountSer = applicationContext.getBean("accountSer", AccountServiceImpl.class);
        iAccountSer.saveAccount();
        // 注意,父类ApplicationContext没有close方法,向上转型无法使用,所以向下转型后调用close;
        ClassPathXmlApplicationContext classPathXmlApplicationContext = (ClassPathXmlApplicationContext)applicationContext;
        classPathXmlApplicationContext.close();
    }
    public static ApplicationContext useBeanMethod(){
        return new ClassPathXmlApplicationContext("bean.xml");
    }
}

依赖注入

如果是经常变化的数据,并不适用于注入的方式

构造函数注入

注意:必须存在对应构造函数才能注入

<!-- name:args ; type:java.lang.String ; value:value ;  index:args.indexOf  ref:other bean plugin -->
<bean id="accountSer" class="com.learn2A.service.impl.AccountServiceImpl" init-method="init"
destroy-method="destroy">
    <constructor-arg name="name" value="你猜我是谁"/>
    <constructor-arg name="age" value="18"/>
    <constructor-arg name="birthday" ref="now"/>
</bean>
<bean id="now" class="java.util.Date"/>

SetGet方法注入

<property name="name" value="Test"/>
<property name="age"  value="1"/>
<property name="birthday" ref="now"/>

复杂类型注入

<property name="myList">
    <list>
        <value>AAA</value>   <value>bbb</value>   <value>ccc</value>
    </list>
</property>
<property name="myStrs">
    <array>
        <value>null</value>  <value>0000</value>
    </array>
</property>
<property name="myMap">
    <map>
        <entry key="testA" value="testAA"/>   <entry key="testB" value="testBB"/>
    </map>
</property>
<property name="myProps">
    <props>
        <prop key="123">123</prop>   <prop key="2233">2233</prop>
    </props>
</property>

注解创建

若要读取到注解,需要在xml文件里写入

<!--告知spring创建容器时要扫描的包,在context空间约束中使用-->
<context:component-scan base-package="learnA.service"/>
  • @Component 当前类对象存入容器,不写value时,默认类名为小写
  • @Controller一般用于表现层
  • Service一般用于业务层
  • @Repository一般用于持久层

注入类型

  • @Autowired自动按照类型注入,只要容器中由唯一的一个bean对象类型和注入的变量类型匹配,就能成功,可以为变量,也可以为方法

    如果IOC容器中有多个类型匹配时(先按照类型匹配,类型一致时按照变量名与bean名寻找)

  • @Qualifier 按照类注入的基础上再按照名称注入,必须与Autowired配合使用,用于处理多个类型匹配的问题,value值为类bean的名称

  • @Resource直接按照类的id注入 name:用于指定类的id

    // IAccountDao为接口,存在多个实现类时一下方法注入
    // @Autowired
    // @Qualifier(value = "accountDao1")
    @Resource(name = "accountDao1")
    private IAccountDao accountDao;
    

以上为注入类型,而基本数据与String无法注入,且集合也无法注入

注入数据

  • @Value 用于注入基本类型和String类型的数据,value指定数据的值,可以使用EL表达式:${}
  • @scope用于改变作用范围 value :singleton prototype
  • @PreDestroy 指定销毁方法
  • @PostConstruct 指定初始化方法

使用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.xsd">
    <!--创建业务对象-->
    <bean id="accountService" class="com.smallPro.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>
    <!--创建持久层对象-->
    <bean id="accountDao" class="com.smallPro.dao.impl.AccountDaoImpl">
        <property name="runner" ref="runner"/>
    </bean>
    <!--创建连接池-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <constructor-arg name="ds" ref="dataSource"/>
    </bean>
    <!--创建数据源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://192.168.43.217:3306/eesy?serverTimezone=UTC"/>
        <property name="user" value="root"/>
        <property name="password" value="123456"/>
    </bean>
</beans>
// 业务层中别忘了 private QueryRunner runner;
public class AccountServiceTest {
    public static IAccountService accountService = null;
    @Test
    public void findAll() {
        List<Account> accounts = accountService.findAllAccount();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }
    @BeforeClass
    public static void before() throws Exception {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        AccountServiceTest.accountService = applicationContext.getBean("accountService", IAccountService.class);
    }

    @Test
    public void findId() {
        Account account = accountService.findAccountById(1);
        System.out.println(account);
    }
    @Test
    public void insertValue() {
        Account account = new Account();
        account.setName("name1");
        account.setMoney((float) 2233);
        accountService.saveAccount(account);
        findAll();
    }
    @Test
    public void UpdateValue() {
        Account account = new Account();
        account.setMoney((float) 999);
        account.setName("some");
        account.setId(4);
        accountService.updateAccount(account);
        findAll();
    }
    @Test
    public void deleteValue() {
        accountService.deleteAccount(2);
        findAll();
    }
}

注解方式:略,记着bean.xml里面加上<context:component-scan base-package="com.smallPro"/>

自动配置

@Configuration指定当前类是一个配置类,注意:当AnnotationConfigApplicationContext(SpringConfiguration.class);中的class为配置类时,此注解可以不用加,会自动到指定目录寻找,多个配置类之间可以用空格隔开,扫描包也可以不用啦

@ComponentScan指定spring在创建容器时要扫描的包 用value指定

@Bean用于把当前方法的返回值存入容器当中 name:指定bean的id,默认值为当前方法的名称

@Configuration
@ComponentScan(value = "com.smallPro")
public class SpringConfiguration {
    /**
     * 创建一个runner对象
     * @param dataSource 数据源
     * @return runner对象
     */
    @Bean(name = "runner")
    public QueryRunner createRunner(DataSource dataSource){
        return new QueryRunner(dataSource);
    }
    /**
     * 创建数据源对象
     * @return 数据源
     */
    @Bean
    public DataSource createDataSourceA(){
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        try {
            comboPooledDataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
            comboPooledDataSource.setJdbcUrl("jdbc:mysql://192.168.43.217:3306/eesy?serverTimezone=UTC");
            comboPooledDataSource.setUser("root");
            comboPooledDataSource.setPassword("123456");
            return comboPooledDataSource;
        } catch (PropertyVetoException e) {
            throw new RuntimeException(e);
        }
    }
}
// to use
@BeforeClass
public static void before() throws Exception {
    //  ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
    AccountServiceTest.accountService = applicationContext.getBean("accountService", IAccountService.class);
}
// 默认为单例模式,scope设置为prototype时,变为多例
public class QueryRunnerTest {
    @Test
    public void testQueryRunner() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        QueryRunner queryRunner = ac.getBean("runner",QueryRunner.class);
        QueryRunner queryRunner1 = ac.getBean("runner",QueryRunner.class);
        assert queryRunner!=queryRunner1;
    }
}

@import用于导入其他的配置类,value参数值为对应配置类的.class类,注意:这个一般用于在总配置文件上导入其他子配置文件,总配置文件还是建议注解Configuration与componentScan

@PropertySource("classpath:xxx.properties")指定properties文件的路径。为了打jar包之后也能找到,因此需要加上classpath,表示类路径下

jdbc.driver = com.mysql.cj.jdbc.Driver
jdbc.url = jdbc:mysql://192.168.43.217:3306/eesy?serverTimezone=UTC
jdbc.username = root
jdbc.password = 123456
@PropertySource("classpath:jdbcConfig.properties")
 @Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;

Junit单元测试与Spring整合

@Runwith告知spring的运行器

@ContextConfiguration locations指定xml文件的位置,加上classpath关键字,表示类路径下,classes:指定注解类所在位置

然后通过Autowired就可以自动注入了

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
    @Autowired
    public IAccountService accountService;
posted @ 2020-12-04 11:42  WheelCode  阅读(145)  评论(0)    收藏  举报