2018年8月27日 星期一

@ComponentScan 排除或包括 @Component (Spring 3.x 二十六)


AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(Apple.class);
for (String s : app.getBeanDefinitionNames()) {
    System.out.println(s);
}
app.close();

※Apple.class 不管有沒有加 annotation 一定會有



※Apple.class

// @ComponentScan(excludeFilters = { @Filter(type = FilterType.ANNOTATION, classes = Component.class) })
// @ComponentScan(useDefaultFilters = false, includeFilters = { @Filter(type = FilterType.ANNOTATION, classes = Component.class) })
    
// @ComponentScan(excludeFilters = { @Filter(type = FilterType.ANNOTATION, classes = Repository.class) })
// @ComponentScan(useDefaultFilters = false, includeFilters = { @Filter(type = FilterType.ANNOTATION, classes = Controller.class) })
// @ComponentScan(useDefaultFilters = false, includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Chicken.class) })
@ComponentScan(useDefaultFilters = false, includeFilters = { @Filter(type = FilterType.CUSTOM, classes = MyTypeFilter.class) })
// @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = MyTypeFilter.class) })

※如果使用 includeFilters 時,一定要將 useDefaultFilters 設為 false,否則無效,表示只包括什麼

※第一個和第二個是有問題的,使用 excludeFilters 和 Component,結果是什麼都沒有;
使用 includeFilters 和 Component,結果是什麼都有



public class MyTypeFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
            throws IOException {
        // System.out.println(metadataReader.getAnnotationMetadata().getAnnotationTypes());
        // System.out.println(metadataReader.getClassMetadata().getClassName());//
        // package.class
        // System.out.println(metadataReader.getResource().getFilename());// name.class
        // System.out.println(metadataReaderFactory.getMetadataReader(metadataReader.getResource()).getAnnotationMetadata().getSuperClassName());
    
        Set<String> set = metadataReader.getAnnotationMetadata().getAnnotationTypes();
        if (new ArrayList<String>(set).contains("org.springframework.stereotype.Component")) {
            return false;
        }
        return true;
    }
}

※使用這個 class,配合上面的 FilterType.CUSTOM,可以排除或包括 @Component

2018年8月25日 星期六

Quartz 2.x 整合 SpringBoot 2.x


buildscript {
    ext {
        springBootVersion = '2.0.3.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
    
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
    
repositories {
    mavenCentral()
}
    
ext {
    springVersion = '4.3.18.RELEASE'
    springBootVersion = '2.0.3.RELEASE'
    quartzVersion = '2.2.1'
}
    
dependencies {
    compile 'org.springframework.boot:spring-boot-starter'
    testCompile 'org.springframework.boot:spring-boot-starter-test'
    compile 'org.springframework.boot:spring-boot-starter-web'
    compile group: 'org.springframework', name: 'spring-context-support', version: springVersion
    compile group: 'org.springframework', name: 'spring-tx', version: springVersion
    compile group: 'org.quartz-scheduler', name: 'quartz', version: quartzVersion
    compile group: 'org.quartz-scheduler', name: 'quartz-jobs', version: quartzVersion
}




@SpringBootApplication
@ComponentScan("annotation")
public class Library {
    public static void main(String[] args) {
        SpringApplication.run(Library.class, args);
    }
}

※@ComponentScan 的程式碼可看我的另一篇,最下面的 annotation

2018年8月22日 星期三

Quartz 2.x 整合 Spring 3/4

※整合 Spring

官網連結

※build.gradle

ext {
    springVersion = '4.3.18.RELEASE'
    quartzVersion = '2.2.1'
}
    
dependencies {
    compile("org.springframework:spring-context:$springVersion")
    compile group: 'org.springframework', name: 'spring-context-support', version: springVersion
    compile group: 'org.springframework', name: 'spring-tx', version: springVersion
    compile group: 'org.quartz-scheduler', name: 'quartz', version: quartzVersion
    compile group: 'org.quartz-scheduler', name: 'quartz-jobs', version: quartzVersion
}

※一定要有 quartz 的 jar 包,否則編譯時居然是報找不到 spring jar 包的編譯錯誤,很奇怪


官網有說明,提供了 JobDetailFactoryBean 和 MethodInvokingJobDetailFactoryBean
而每一個又分成使用 SimpleTriggerFactoryBean 和 SchedulerFactoryBean


※XML設定


※使用 JobDetailFactoryBean

public class MyJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
        System.out.println("xxxxxxxxxxxxxxxxxxxxx");
        // ctx.getJobDetail().getJobDataMap().get("aaa");
    }
}




※quartz.xml 之 SimpleTriggerFactoryBean

<bean id="jobDetail"
    class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
    <property name="jobClass" value="job.MyJob" />
    <!-- <property name="durability" value="true" /> -->
    <property name="jobDataAsMap">
        <map>
            <entry key="aaa" value="111"/>
        </map>
    </property>
</bean>
    
<bean id="simpleTrigger"
    class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
    <property name="jobDetail" ref="jobDetail" />
    <property name="startDelay" value="10" />
    <property name="repeatInterval" value="2000" />
</bean>
    
<bean
    class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="simpleTrigger" />
        </list>
    </property>
</bean>

※durability 預設為 false,Spring 3.x 要為 true 才可以,否則會出「Jobs added with no trigger must be durable」的錯
原文是 Specify the job's durability, i.e. whether it should remain stored in the job store even if no triggers point to it anymore.
大概是說不管有沒有 trigger 指向它,是否應該保留在 job store 裡,反正 3.x 不是 true 就會報這個錯
關於 jobStore,可看官網第九篇,有三種


※quartz.xml 之 SchedulerFactoryBean

<bean id="jobDetail"
    class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
    <property name="jobClass" value="job.MyJob" />
</bean>
    
<bean id="cronTrigger"
    class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="jobDetail" ref="jobDetail" />
    <property name="cronExpression" value="0/2 0 0 * * ?" />
</bean>
    
<bean
    class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="cronTrigger" />
        </list>
    </property>
</bean>





※MethodInvokingJobDetailFactoryBean

package job;
    
public class MyJob {
    public void getXxx() {
        System.out.println("xxxxxxxxxxxxxxxxxxxxx");
    }
}

※使用 JobDetailFactoryBean 要繼承;使用 MethodInvokingJobDetailFactoryBean 不用


※quartz.xml 之 SimpleTriggerFactoryBean

<bean id="myJob" class="job.MyJob" />
    
<bean id="jobDetail"
    class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="myJob" />
    <property name="targetMethod" value="getXxx" />
    <!-- <property name="concurrent" value="false"/> -->
</bean>
    
<bean id="simpleTrigger"
    class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
    <property name="jobDetail" ref="jobDetail" />
    <property name="startDelay" value="10" />
    <property name="repeatInterval" value="2000" />
</bean>
    
<bean
    class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="simpleTrigger" />
        </list>
    </property>
</bean>

※concurrent 預設為 true,如果改成 false,和 @DisallowConcurrentExecution 是一樣的意思,可在 MyJob 加這個註解,和 concurrent 不寫或寫 true 比較看看,程式碼可看這篇


※quartz.xml 之 SchedulerFactoryBean

<bean id="myJob" class="job.MyJob" />
    
<bean id="jobDetail"
    class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="myJob" />
    <property name="targetMethod" value="getXxx" />
    <!-- <property name="concurrent" value="false" /> -->
</bean>
    
<bean id="cronTrigger"
    class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="jobDetail" ref="jobDetail" />
    <property name="cronExpression" value="0/2 0 0 * * ?" />
</bean>
    
<bean
    class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="cronTrigger" />
        </list>
    </property>
</bean>




※測試類

new ClassPathXmlApplicationContext("quartz.xml");





※整合 Spring Web

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/quartz.xml</param-value>
</context-param>
    
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

※gradle 增加 compile("org.springframework:spring-web:$springVersion"),伺服器一啟動就開始跑了



※整合 SpringWebMVC

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/quartz.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
    
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

※gradle 增加 compile("org.springframework:spring-webmvc:$springVersion"),伺服器一啟動就開始跑了




※annotation 設定



@Component
public class MyAnnotationJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
        System.out.println("xxxxxxxxxxxxxxxxxxxxx");
    }
    
    // public void getXxx() {
    //     System.out.println("xxxxxxxxxxxxxxxxxxxxx");
    // }
}




@Configuration
@ComponentScan("annotation")
public class QuartzConfiguration {
    @Autowired
    private MyAnnotationJob myJob;
    
    // @Bean
    // public JobDetailFactoryBean jobDetail() {
    // JobDetailFactoryBean job = new JobDetailFactoryBean();
    // job.setJobClass(MyAnnotationJob.class);
    // job.setDurability(true);
    // return job;
    // }
    
    @Bean
    public MethodInvokingJobDetailFactoryBean jobDetail() {
        MethodInvokingJobDetailFactoryBean job = new MethodInvokingJobDetailFactoryBean();
        job.setTargetObject(myJob);
        job.setTargetMethod("getXxx");
        return job;
    }
    
    @Bean
    public SimpleTriggerFactoryBean simpleTrigger() {
        SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
        trigger.setJobDetail(jobDetail().getObject());
        trigger.setStartDelay(10);
        trigger.setRepeatInterval(2000);
        return trigger;
    }
    
    // @Bean
    // public CronTriggerFactoryBean cronTriggerBean() {
    // CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
    // cronTriggerFactoryBean.setJobDetail(jobDetail().getObject());
    // cronTriggerFactoryBean.setCronExpression("*/2 * * * * ?");
    // return cronTriggerFactoryBean;
    // }
    
    @Bean
    public SchedulerFactoryBean schedulerFactory() {
        SchedulerFactoryBean schedule = new SchedulerFactoryBean();
        schedule.setTriggers(simpleTrigger().getObject());
        return schedule;
    }
}




※spring 測試

new AnnotationConfigApplicationContext(annotation.QuartzConfiguration.class);



※SpringWeb的 web.xml 設定

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>annotation.QuartzConfiguration</param-value>
</context-param>
    
<context-param>
    <param-name>contextClass</param-name>
    <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
    
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>




※SpringWebMVC的 web.xml 設定

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>annotation.QuartzConfiguration</param-value>
    </init-param>
    
    <init-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
    
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>



2018年8月20日 星期一

Hello Quartz (Quartz 2.x 一)

※文檔

官網教學連結
官網設定教學連結

W3Cschool Quartz 官網

好心網友翻譯的連結:
第一課 使用 Quartz
第二課 Quartz API,Jobs 和 Triggers 介紹
第三課 關於更多的 Jobs和 JobDetails
第四課 關於更多的 Triggers
第五課 SimpleTriggers
第六課 CronTriggers
第七課 TriggerListeners & JobListeners
第八課 SchedulerListeners
第九課 JobStores
第十課 Configuration, Resource Usage和SchedulerFactory
第十一課 進階(企業)功能
第十二課 Quartz 其他功能


※Hello Quartz

import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
    
public class TestQuartz {
    public static void main(String[] args) {
        JobDetail job = JobBuilder.newJob(MyJob.class)
            .withIdentity("myJob1", "group1")
            .build();
    
        Trigger trigger = TriggerBuilder
            .newTrigger()
            .withIdentity("myTrigger1", "group2")
            .startNow()
            .withSchedule(SimpleScheduleBuilder
                .simpleSchedule()
                .withIntervalInSeconds(3)
                .repeatForever())
            .build();
    
        System.out.println(job.getKey()); // group1.myJob1
        System.out.println(trigger.getKey());// group2.myTrigger1
    
        try {
            SchedulerFactory schedFact = new StdSchedulerFactory();
            Scheduler sched = schedFact.getScheduler();
            System.out.println(trigger.getJobKey()); // null
            sched.scheduleJob(job, trigger);
            System.out.println(trigger.getJobKey()); // 註冊之後才能取得 group1.myJob1
            sched.start();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}

※注意官網有靜態 import,如下:
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
之後的代碼,我也會用這種方式

※假設 SchedulerFactory 和 Scheduler 寫在最上面,然後馬上就 start(),最後在呼叫 JobDetail、
Trigger並註冊 scheduleJob,也是可以運行的


public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("xxxxxxxxxxxxx");
    }
}

※Job 就是時間到後要執行的方法



※JobDataMap

JobDetail job = newJob(MyJob.class)
    .withIdentity("myJob", "group1")
    .usingJobData("aaa", "111")
    .usingJobData("bbb", 222)
    .build();
    
Trigger trigger = newTrigger()
    .withIdentity("myTrigger1", "group2")
    .startNow()
    .withSchedule(simpleSchedule()
    .withIntervalInSeconds(3)
    .repeatForever())
    .build();
    
try {
    SchedulerFactory schedFact = new StdSchedulerFactory();
    Scheduler sched = schedFact.getScheduler();
    sched.scheduleJob(job, trigger);
    sched.start();
} catch (SchedulerException e) {
    e.printStackTrace();
}

※使用 JobDataMap 可以塞值



public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobKey key = context.getJobDetail().getKey();
    
        // JobDataMap dataMap = context.getMergedJobDataMap();
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        String a = dataMap.getString("aaa");
        int b = dataMap.getInt("bbb");
    
        System.out.println("key=" + key); // group1.myJob
        System.out.println("a=" + a); // 111
        System.out.println("b=" + b);// 222
    }
}

※取值時,可以使用 getMergedJobDataMap() 或 getJobDetail().getJobDataMap(),但還是有區別的,看下面的 @PersistJobDataAfterExecution



※@DisallowConcurrentExecution

JobDetail job = newJob(MyJob.class).withIdentity("myJob", "group1").build();
Trigger trigger = newTrigger()
    .withIdentity("myTrigger1", "group2")
    .startNow()
    .withSchedule(simpleSchedule()
        .withIntervalInSeconds(3)
        .repeatForever())
    .build();
    
try {
    SchedulerFactory schedFact = new StdSchedulerFactory();
    Scheduler sched = schedFact.getScheduler();
    sched.scheduleJob(job, trigger);
    sched.start();
} catch (SchedulerException e) {
    e.printStackTrace();
}




@DisallowConcurrentExecution
public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        try {
            System.out.println("前=" + new Date());
            Thread.sleep(5000);
            System.out.println("後=" + new Date());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

※我設定每3秒執行一次,但 Job 裡會睡 5 秒,所以我還沒加這個註解前會是如下的結果:
前=Tue Aug 21 14:17:44 SGT 2018
前=Tue Aug 21 14:17:47 SGT 2018
後=Tue Aug 21 14:17:49 SGT 2018
前=Tue Aug 21 14:17:50 SGT 2018
後=Tue Aug 21 14:17:52 SGT 2018
前=Tue Aug 21 14:17:53 SGT 2018
後=Tue Aug 21 14:17:55 SGT 2018
前=Tue Aug 21 14:17:56 SGT 2018
後=Tue Aug 21 14:17:58 SGT 2018
前=Tue Aug 21 14:17:59 SGT 2018
後=Tue Aug 21 14:18:01 SGT 2018
前=Tue Aug 21 14:18:02 SGT 2018
後=Tue Aug 21 14:18:04 SGT 2018

※44~49 才是一組,中間會有下一次的執行,但如果加上這個註解,結果如下:

前=Tue Aug 21 14:19:12 SGT 2018
後=Tue Aug 21 14:19:17 SGT 2018
前=Tue Aug 21 14:19:17 SGT 2018
後=Tue Aug 21 14:19:22 SGT 2018
前=Tue Aug 21 14:19:22 SGT 2018
後=Tue Aug 21 14:19:27 SGT 2018
前=Tue Aug 21 14:19:27 SGT 2018
後=Tue Aug 21 14:19:32 SGT 2018
前=Tue Aug 21 14:19:32 SGT 2018
後=Tue Aug 21 14:19:37 SGT 2018
前=Tue Aug 21 14:19:37 SGT 2018

※此時中間沒有插入下一次的執行,是以執行完為主的



※@PersistJobDataAfterExecution

JobDetail job = newJob(MyJob.class)
    .withIdentity("myJob", "group1")
    .usingJobData("aaa", "111")
    .usingJobData("bbb", 222)
    .build();
    
Trigger trigger = newTrigger()
    .withIdentity("myTrigger1", "group2")
    .startNow()
    .withSchedule(simpleSchedule()
        .withIntervalInSeconds(3)
        .repeatForever())
    .build();
    
try {
    SchedulerFactory schedFact = new StdSchedulerFactory();
    Scheduler sched = schedFact.getScheduler();
    sched.scheduleJob(job, trigger);
    sched.start();
} catch (SchedulerException e) {
    e.printStackTrace();
}




@PersistJobDataAfterExecution
public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // JobDataMap dataMap = context.getMergedJobDataMap(); // 不會更新的
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        System.out.println("a=" + dataMap.getString("aaa")); // 111|333
        System.out.println("b=" + dataMap.getInt("bbb"));// 222|444
    
        dataMap.put("aaa", "333");
        dataMap.put("bbb", 444);
    }
}

※註解的意思是執行後可保存資料,如範例的 333 和 444,有加上這個註解以及使用的是 getJobDetail().getJobDataMap() 才可以,使用 getMergedJobDataMap() 不會

※第一次執行後是 111 和 222,之後就都會是 333 和 444 了

※官網有說明加上這個註解後,要考慮是不是也要加上 @DisallowConcurrentExecution

2018年8月14日 星期二

JUnit 4 整合 SpringBoot 2.x

※build.gradle

compile 'org.springframework.boot:spring-boot-starter:2.0.3.RELEASE'
testCompile 'org.springframework.boot:spring-boot-starter-test:2.0.3.RELEASE'




※啟動類

@SpringBootApplication
@ComponentScan("ooo.xxx")
public class Xxx {
    public static void main(String[] args) {
        SpringApplication.run(Xxx.class, args);
    }
}




※測試類

@RunWith(SpringRunner.class)
@SpringBootTest(classes = { Xxx.class })
public class LibraryTest {
    @Autowired
    Animal animal;
    
    @Autowired
    Bird bird;
    
    @Test
    public void testXxx() {
        System.out.println(animal);
        System.out.println(bird);
    }
}

※Animal、Bird 類別在 ooo.xxx 的 package,如果和 LibraryTest 不同 package,那就要在啟動類加 @ComponentScan

2018年8月12日 星期日

Task (Gradle 4.x 六)

※三種宣告

task 'xxx' { // 「'」可省略
    println 'xxxxx'
}
    
tasks.create('zzz') {
    println 'zzzzz'
}
    
tasks.create(name: 'ooo') {
    println 'ooooo'
}
    
task findTask {
    // println tasks.findByPath('xxx')?.name
    try {
        println tasks.getByPath('xxx').name // 有Exception時,? 無效
    } catch (UnknownTaskException e) {
        println '我錯了'
    }
}

※findByPath 找不到回傳 null; getByPath 找不到拋例外


※task 群組

task setInfo1(group: 'aaa', description: 'I am 1') {
    println tasks.findByPath('setInfo1')?.group // group 即可抓到,但 this.group 是抓這個專案的
    println tasks.findByPath('setInfo1')?.description
}
    
task setInfo2 {
    setGroup('aaa')
    setDescription('I am 2')
    println tasks.findByPath('setInfo2')?.group
    println tasks.findByPath('setInfo2')?.description
}

※gradle tasks 會看到,參數可看 Task


※task 的執行順序

task taskOrder1() {
    println 'taskOrder1'
    
    doLast {
        println 'a'
    }
    
    doFirst {
        println '1'
    }
    
    doLast {
        println 'b'
    }
    
    doFirst {
        println '2'
    }
}
    
    
task taskOrder2 {
    println 'taskOrder2'
    
    doLast {
        println 'c'
    }
    
    doFirst {
        println '3'
    }
    
    doLast {
        println 'd'
    }
    
    doFirst {
        println '4'
    }
}

※doFirst、doLast 都是執行階段才會執行,所以下 gradle clean 或 gradle task 名稱只會執行設定階段,並不會只有執行指定的 task,所有 task 都會執行
要 run 執行階段,要下 gradle task 名稱,但要有 doFirst 或 doLast,且只會執行指定的 task

※task taskOrder1(dependsOn: 'taskOrder2') // 只針對執行階段

※taskOrder1.dependsOn('taskOrder2'),也可用這種方式,寫在括號裡是被依賴的,被依賴的會先執行

※也可以 mustRunAfter 方法


※動態依賴

task taskOrder1 {
    dependsOn tasks.findAll {
        it.name.startsWith('xxx')
    }
}

※xxx 開頭的 task 才會被依賴


※task 的其他寫法

task taskOrder1 {
    doFirst {
        println '1'
    }
    
    doFirst {
        println '2'
    }
}
    
taskOrder1.doFirst {
    println '3'
}




※doLast 等同 <<

task taskOrder2 << {
    println 'taskOrder2'
}
    
task taskOrder2 {
    println 'taskOrder2'
    
    doLast {
        println '3'
    }
    
    doFirst {
        println '2'
    }
} << {
    println '4'
}

※最後的 << 只能一次

2018年8月11日 星期六

Executors

※基本的執行緒池

// ExecutorService es = Executors.newFixedThreadPool(3);
// ExecutorService es = Executors.newCachedThreadPool();
ExecutorService es = Executors.newSingleThreadExecutor();
    
for (int i = 1; i <= 7; i++) {
    es.execute(() -> {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int j = 1; j <= 5; j++) {
            System.out.println(Thread.currentThread().getName() + "->" + j);
        }
    });
}
es.shutdown();

※newFixedThreadPool:固定大小的執行緒
newCachedThreadPool:無限大小的執行緒
newSingleThreadExecutor:單一的執行緒

※shutdown():不增加新的執行緒,當前執行緒不會關閉
shutdownNow():不增加新的執行緒,當前執行緒也會關閉


※排程

ScheduledExecutorService ses = Executors.newScheduledThreadPool(3);
ses.schedule(() -> {
    System.out.println("yeah");
}, 2L, TimeUnit.SECONDS);
ses.shutdown();
    
ScheduledExecutorService sess = Executors.newScheduledThreadPool(3);
sess.scheduleAtFixedRate(() -> {
    System.out.println("xxx");
}, 2L, 1L, TimeUnit.SECONDS);
try {
    Thread.sleep(5000L);
    sess.shutdown();
} catch (InterruptedException e) {
    e.printStackTrace();
}
    
ScheduledExecutorService seas = Executors.newSingleThreadScheduledExecutor();
seas.scheduleAtFixedRate(() -> {
    System.out.println("xxx");
}, 2L, 1L, TimeUnit.SECONDS);
try {
    Thread.sleep(5000L);
    seas.shutdown();
} catch (InterruptedException e) {
    e.printStackTrace();
}

※newScheduledThreadPool、newSingleThreadScheduledExecutor 回傳 ScheduledExecutorService,裡面有執行一次的 schedule() 和重覆執行的 scheduleAtFixedRate()

※重覆執行要注意太早 shutdown() 就不執行了



※1.8 的 newWorkStealingPool

Executors.newWorkStealingPool();

※底層第一行就是 new ForkJoinPool,ForkJoinPool 是 1.7 增加的類別,但這個執行緒池是 1.8 才新增的,可參考這裡


※ThreadPoolExecutor

Executors 底層使用的就是這個,共有7個參數
new ThreadPoolExecutor(5,
20,
10,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());

表示一開始就有 5 個執行緒可以用,最多20個
假設有30個 request 進來,5個先執行,然後25個會放在第5個參數的 queue 裡
再來15個執行緒去 queue 抓來執行,最後10個看第7個參數的放棄策略
執行完的15個執行緒,過10毫秒 (第3和4的參數) 就關閉
第六個參數是將 Runnable 包裝成 Thread

以下是第 5~7 可用的參數
BlockingQueue
ArrayBlockingQueue
DelayedWorkQueue 基於 heap 的資料結構,按照時間順序將每個任務進行排序
BlockingDeque
LinkedBlockingDeque 預設是 Integer.MAX_VALUE,所以有 OOM 的風險
SynchronousQueue 一生產就消費,如沒有消費者就阻塞,所以沒有容量大小
DelayQueue 只能消費已過期的
TransferQueue
LinkedTransferQueue 很像 SynchronousQueue,但有容量,消費者沒取到就會阻塞
LinkedBlockingQueue 預設是 Integer.MAX_VALUE,所以有 OOM 的風險
PriorityBlockingQueue 按照自定義的優先級消費
ThreadFactory
DaemonThreadFactory 創建背景執行的執行緒,之後出來的執行緒都是背景執行緒
DefaultThreadFactory 預設的執行緒工廠
PrivilegedThreadFactory 創建與當前執行緒具有相同權限的新執行緒
RejectedExecutionHandler
DiscardOldestPolicy 丟棄最老的任務,就是在 queue 裡面的
AbortPolicy 新來的任務直接丟棄並報異常
CallerRunsPolicy 同步調用,start() 調用後執行 run(),但這個策略是直接調用 run()
DiscardPolicy 新來的任務直接丟棄但不報異常 

ThreadLocal


public class Test {
    // private Map<Thread, Double> map = new HashMap<>();
    private ThreadLocal<Double> threadLocal = new ThreadLocal<>();
    
    public static void main(String[] args) {
        new Test().test();
    }
    
    private void test() {
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                Double v = Math.random();
                System.out.println(Thread.currentThread().getName() + " 的 值 是 =" + v);
                // map.put(Thread.currentThread(), v);
                threadLocal.set(v);
    
                new innerAClass().ia();
                new innerBClass().ib();
            }).start();
    
        }
    }
    
    class innerAClass {
        public void ia() {
            // System.out.println(Thread.currentThread().getName() + "(a)取出的值是=" +
            // map.get(Thread.currentThread()));
            System.out.println(Thread.currentThread().getName() + "(a)取出的值是=" + threadLocal.get());
        }
    }
    
    class innerBClass {
        public void ib() {
            // System.out.println(Thread.currentThread().getName() + "(b)取出的值是=" +
            // map.get(Thread.currentThread()));
            System.out.println(Thread.currentThread().getName() + "(b)取出的值是=" + threadLocal.get());
        }
    }
}



Callable、Future、CompletionService

※Callable、Future

ExecutorService es = Executors.newSingleThreadExecutor();
Future<String> future = es.submit(() -> "yeah!");
    
System.out.println("等一兮呢");
try {
    System.out.println(future.get());
    es.shutdown();
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

※submit 裡的參數就是 Callable

※Callable 和 Runnable 類似,但有回傳值

※Future 可以用 get() 取得 Runnable 的回傳值


※get 參數

ExecutorService es = Executors.newSingleThreadExecutor();
Future<String> future = es.submit(() -> {
    Thread.sleep(1000L);
    return "yeah!";
});
    
System.out.println("等一兮呢");
try {
    System.out.println(future.get(500L, TimeUnit.MILLISECONDS));
} catch (InterruptedException | ExecutionException | TimeoutException e) {
    e.printStackTrace();
} finally {            
    es.shutdown();
}

※如果超過 get 設定的時間,就會出現 java.util.concurrent.TimeoutException



※CompletionService

ExecutorService 有多個 Callable 時,每個 Callable 都會回傳一個Future,使用 get 取得結果時,不一定會是第一個 Future,使用 CompletionServce 可以解決這個問題

ExecutorService es = Executors.newFixedThreadPool(3);
CompletionService<String> ecs = new ExecutorCompletionService<>(es);
    
for (int i = 0; i < 3; i++) {
    int temp = i;
    ecs.submit(() -> {
        Thread.sleep(new Random().nextInt(10000));
        return "嘻嘻嘻" + temp;
    });
}
    
for (int i = 0; i < 3; i++) {
    Future<String> future;
    try {
        future = ecs.take();
        System.out.println(future.get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
    
    if(i == 2) {
        es.shutdown();
    }
}

※一組執行緒,誰先執行完誰就先執行

Project 的依賴 (Gradle 4.x 五)

官網依賴連結
官網 repository 連結

buildscript { ScriptHandler sh ->
    sh.repositories { RepositoryHandler rh ->
        rh.jcenter()
        rh.mavenLocal()
        rh.mavenCentral()
    
        rh.maven { MavenArtifactRepository mar ->
            mar.url "http://repo.mycompany.com/maven2"
            mar.credentials { PasswordCredentials pc ->
                pc.username "user"
                pc.password "password"
            }
        }
    }
    
    sh.dependencies {
        classpath group: 'junit', name: 'junit', version: '4.12'
    }
}

※這是還不熟練的寫法,可以將閉包參數去掉,因為已經有預設 import 了



buildscript {
    repositories {
        jcenter()
        mavenLocal()
        mavenCentral()
    
        maven {
            url "http://repo.mycompany.com/maven2"
            credentials {
                username "user"
                password "password"
            }
        }
    }
    
    dependencies {
        classpath project(':subGradle1') // 依賴其他專案
        classpath fileTree(includes: ['*.jar', '*.war'], dir: 'libs')
    }
}

※在 buildscript 同層,也有 repositories 和 dependencies
寫在 buildscript 裡是針對寫 gradle 時用的 jar 包; 寫在 buildscript 同層是針專案要用的 jar 包

Could not find method xxx() for arguments:不寫 classpath 會出的錯
Cannot resolve external dependency xxx because no repositories are defined:在buildscript 裡沒有repositories 出現的錯

※依賴裡,舊版的
https://docs.gradle.org/4.10.3/userguide/building_java_projects.html
https://docs.gradle.org/5.6.4/userguide/building_java_projects.html
https://docs.gradle.org/current/userguide/java_library_plugin.html

4~6 版
compileOnly
implementation (取代 compile)
runtimeOnly (取代 runtime)
testCompileOnly
testImplementation
testRuntimeOnly

只有第 6 版有
api



※依賴衝突


maven 的依賴是最短路徑優先,路徑一樣就是先寫的優先
gradle 則是最新的版本優先
以 hibernate 3.6.3 為例,有以下三種方法

configurations.all {
    resolutionStrategy {
        failOnVersionConflict() // 不要自動處理版本衝突
    }
}



configurations.all {
    resolutionStrategy {
        force 'org.slf4j:slf4j-api:1.7.24' // 強制使用某一個版本
    }
}



dependencies {
    implementation (group: 'org.hibernate', name: 'hibernate-core', version: '3.6.3.Final') {
        exclude group:"org.slf4j" , module:"slf4j-api" // 排除依賴後,還要自己在下載
        // transitive=false // 所有依賴都不要,debug 用
    }
}

2018年8月10日 星期五

檔案/目錄操作 (Gradle 4.x 四)

官網連結

GradleTest 是父專案,SubGradle1 是子專案, Project 裡的方法有很多都是寫在父或子專案效果會不同的

※父專案

task getAbsoluteOfRoot {
    println getRootDir().absolutePath // D:\GradleTest
    println getProjectDir().absolutePath // D:\GradleTest
    println getBuildDir().absolutePath // D:\GradleTest\build
}
    
getFileContent('xxx.txt')
def getFileContent(String filePath) {
    // println file(filePath).text
    files(filePath).each {
        println it.text
    }
}



※子專案

task getAbsoluteOfSub {
    println getRootDir().absolutePath // D:\GradleTest
    println getProjectDir().absolutePath // D:\GradleTest\SubGradle1
    println getBuildDir().absolutePath // D:\GradleTest\SubGradle1\build
}



※複製檔案

task copyTaskDir(type: Copy) {
    from 'D:/testCopy'
    into 'D:/result/xxx'
    exclude { it.file.name.endsWith('apk') }
    delete 'D:/result/xxx/abc.txt'
}
    
copy {
    from file('xxx.txt')
    // println getSubprojects().getAt(0).getBuildDir()
    into getSubprojects().getAt(0).getBuildDir()
    rename { 'ooo.txt' }
}

※以上兩種方式都可以,但要注意 delete 會先執行,再做複製的動作


※複製目錄

copy {
    from files('testCopy/')
    println getSubprojects().getAt(0).getBuildDir()
    into getSubprojects().getAt(0).getBuildDir()
    exclude { it.file.name.endsWith('apk') }
}

task copyFile (type: Copy) {
    from files('testCopy/')
    into getSubprojects().getAt(0).getBuildDir()
    exclude { it.file.name.endsWith('apk') }
}




※fileTree

// fileTree('')
// fileTree.visit {}
    
fileTree('D:/testCopy') {
    it.visit {
        print it.directory ? "目錄" : "|--檔案"
        println "為:$it.name"
        // copy {}
    }
}

※註解的兩行就等於它下面的寫法,所以不用寫的這麼麻煩

2018年8月9日 星期四

Project 屬性 (Gradle 4.x 三)


※使用 def、ext

def springVersion = '5.0.8.RELEASE'
    
ext {
    mybatisVersion = '3.4.6'
}
    
dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    compile group: 'org.springframework', name: 'spring-context', version: springVersion
    compile group: 'org.mybatis', name: 'mybatis', version: mybatisVersion
}

※如果 def 和 ext 都寫一樣的 key,會以 ext 為主


※ext 進階使用

ext {
    xxx = [
        mybatisVersion : '3.4.6',
        springVersion : '5.0.8.RELEASE'
    ]
}
    
dependencies {
    compile group: 'org.springframework', name: 'spring-context', version: rootProject.ext.xxx.springVersion
    compile group: 'org.mybatis', name: 'mybatis', version: rootProject.ext.xxx.mybatisVersion
}

※可以定義 Map,Map 裡還能再寫 Map



※使用 gradle.properties


官網連結

這個檔案預設沒有,可以新增在 GRADLE_USER_HOME 或者專案目錄下,Project 有一個寫死的變數,就是這一個,所以名字不能改,如果兩個地方都有這個檔案且 key 相同,會以 GRADLE_USER_HOME 為主


預設的 GRADLE_USER_HOME 在 %username%\.gradle,但可以臨時改變這個目錄,如 gradle -Dgradle.user.home=D:\ooo,就是將目錄變成 D 槽的 ooo 資料夾,將檔案新增在此即可

屬性流程:

初始化由專案的 gradle.properties -> GRADLE_USER_HOME 的 gradle.properties -> build.gradle 的 def -> build.gradle 的 ext,後者蓋前者
所以這 4 個地方如果有 key 一樣,最後會被 ext 裡的 key 覆蓋

※gradle.properties

isDebug=false
xxx='x2'




※settings.gradle

// if (isDebug.toBoolean()) {
//    include 'SubGradle2'
// }
 
if (hasProperty('isDebug') ? isDebug.toBoolean() : false) {
    include 'SubGradle2'
}

※hasProperty 判斷有沒有這個 key,只要有,不管內容是什麼,就算是空的也一樣,都會回傳 true

※因為怕沒有這個 key 會報錯,所以用了 hasProperty

※我試過寫了版本號當字串,如 spring 的版本,然後用在 dependencies
四個地方都設定,但只有 def 和 ext 有用,而且也沒報錯,不知道為什麼


倉庫使用私服

repositories {
    maven {
        credentials {
            username "$user"
            password "$passwd"
        }
        url '私服網址'
    }
}

※user 和 passwd 變數就可以寫在 gradle.properties 裡,當然在這裡寫死也是可以

Project 常用方法 (Gradle 4.x 二)

有幾個命令也可以撈出一些資訊
gradle projects
gradle tasks
gradle tasks --all 不加 all,other 就看不到
官網連結


String getName();
Object getGroup();
Object getVersion();
    
/* 主要是以下幾個方法
Project getRootProject();
Project getParent();
Project getProject();
Set<Project> getAllprojects();
Set<Project> getSubprojects();
Map<String, Project> getChildProjects();
Project findProject(String var1);
Project project(String var1, Closure var2);
Map<Project, Set<Task>> getAllTasks(boolean var1);
*/
    
    
task gradleInfo {
    println gradle.gradleVersion
    println gradle.gradleHomeDir
    println gradle.gradleUserHomeDir
}
    
task getCoordinate {
    println '--- 自己的 group-name-version ---'
    print group
    println "-$name-$version"
    
    subprojects.eachWithIndex { Project entry, int i ->
        i++
        println "--- 第 $i 個兒子的 group-name-version ---"
        println "$entry.group-$entry.name-$entry.version"
        println()
    }
}
    
project('SubGradle2') {
    println it.name
}
    
// 設定子專案
project 'SubGradle1', {
    println it.name
    group 'x.x.xxx'
    version '9.9.999'
    // apply from:'../xxx.txt'
    
    this.subprojects.eachWithIndex { Project entry, int i ->
    i++
    println "--- 第 $i 個兒子的 group-name-version ---"
    println "$entry.group-$entry.name-$entry.version"
    println()
    }
}

※getCoordinate 不是子專案的 group,$group 這個居然是 null,不知道為什麼

※apply from 的路徑是以 project('xxx') 裡面為準的,內容寫對 project('xxx') 的設定

2018年8月8日 星期三

build 生命週期和監聽方法 (Gradle 4.x 一)

官網連結

.初始化階段:
Gradle 支持單項目和多項目構建
在初始化階段,Gradle 確定哪些項目將參與構建,並為每個項目創建一個 Project 實例

.設定階段:
在此階段,配置專案物件
將執行構建的所有專案的構建腳本
Gradle 1.4 引入了一種稱為按需配置的孵化選擇功能。在此模式下,Gradle 僅配置相關專案

.執行階段:
Gradle 確定要在執行期間創建和設定任務的子集,然後子集傳遞給 gradle 命令和當前目錄。最後 Gradle 執行每個選定的任務


其中設定的部分還有四種狀態,Internal、Incubating、Public、Deprecated,最後兩個大家都知道,就不說明了
官網連結

※Internal:
內部功能不是為公共使用而設計的,僅供Gradle本身使用
可以在任何時候以任何方式改變而不另行通知
所以,儘量避免使用此類功能
內部功能可能演變為公共功能

※Incubating:
孵化狀態下的功能可能會在將來的 Gradle 版本中發生變化,直到它不再孵化為止
孵化的所有方法/屬性/類都使用 @Incubating

gradle 預設 import 的套件非常多,看官網連結


this.gradle.addProjectEvaluationListener(new ProjectEvaluationListener() {
    @Override
    void beforeEvaluate(Project project) {
        println '無'
    }
    
    @Override
    void afterEvaluate(Project project, ProjectState projectState) {
        println '設定後,執行前1'
    }
})
    
this.gradle.addListener(new BuildListener() {
    @Override
    void buildStarted(Gradle gradle) {
        println '無'
    }
    
    @Override
    void settingsEvaluated(Settings settings) {
        println '無'
    }
    
    @Override
    void projectsLoaded(Gradle gradle) {
        println '無'
    }
    
    @Override
    void projectsEvaluated(Gradle gradle) {
        println '設定後,執行前4-1'
    }
    
    @Override
    void buildFinished(BuildResult buildResult) {
        println '生命週期完成1-2'
    }
})
    
this.gradle.addBuildListener(new BuildListener() {
    @Override
    void buildStarted(Gradle gradle) {
        println '無'
    }
    
    @Override
    void settingsEvaluated(Settings settings) {
        println '無'
    }
    
    @Override
    void projectsLoaded(Gradle gradle) {
        println '無'
    }
    
    @Override
    void projectsEvaluated(Gradle gradle) {
        println '設定後,執行前4-2'
    }
    
    @Override
    void buildFinished(BuildResult buildResult) {
        println '生命週期完成1-1'
    }
})
    
this.beforeEvaluate {
    println '無'
}
    
this.gradle.beforeProject {
    println '無'
}
    
this.afterEvaluate {
    println '設定後,執行前3'
}
    
this.gradle.afterProject {
    println '設定後,執行前2'
}
    
this.gradle.buildFinished {
    println '生命週期完成2'
}

※在 build.gradle 同層,有一個檔案叫 settings.gradle,再裡面寫 println,會發現它是最早印出來的,因為初始化就在這裡

2018年8月2日 星期四

JSON、XML、IO (groovy 2.x 六)

官網 JSON 教學官網 XML 教學官網 IO 教學


※JSON

package script
    
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
    
def json = JsonOutput.toJson([1, 'a', 3.2, [a: 1]]) // 轉成 JSON 字串
println json
// println JsonOutput.prettyPrint(json) // 印出格式化後的 JSON 字串
List<Object> a = new JsonSlurper().parse(json.getBytes()) // 將 JSON 字串轉成物件
println a
    
class Animal {
    Integer id
    String name
}
    
def animalList = [ new Animal(id: 1, name: 'tiger'),
                   new Animal(id: 2, name: 'turtle') ]
def animalJson = JsonOutput.toJson(animalList)
println animalJson
// println JsonOutput.prettyPrint(animalJson)
List<Animal> la = new JsonSlurper().parse(animalJson.getBytes())
println la





※XML

final def xml = '''<html>
    <head>
        <title>Test</title>
    </head>
    <body>
        <h1>YEAH</h1>
        <ul>
            <li class=\'aaa\'>AA</li>
            <li class=\'bbb\'>BB</li>
            <li class=\'ccc\'>CC</li>
        </ul>
        <ul>
            <li class=\'add\'>AD</li>
            <li class=\'aee\'>AE</li>
            <li class=\'aff\'>AF</li>
        </ul>
    </body>    
</html>
'''





def rtn = new XmlSlurper().parseText(xml) // 根只能一個,也就是最外層的 html 只能有一個
println rtn.name() // html
println rtn.body[0].h1[0].text() // YEAH
println rtn.body.h1.text() // YEAH
println rtn.body.ul.li[1].text() // B
println rtn.body.ul.li[1]["@class"] // bbb
println rtn.body.ul.li[1].'@class' // bbb
println rtn.body.ul.li[1].@class // bbb
println rtn.body.ul.li.text() // AABBCCADAEAF
println rtn.body.ul.li.@class // aaabbbcccaddaeeaff
    
def list = []
rtn.body.ul.each { ul ->
    ul.li.each {
        list << it
    }
    println()
}
println list // [AA, BB, CC, AD, AE, AF]

※取屬性有三種寫法



※depthFirst、children()

def filter = rtn.body.ul.depthFirst().findAll {
    it.@class in ['aaa', 'add']
}/*.collect { //如果想輸出非 text,可再加 collect
    it.@class
}*/
println "filter= $filter" [AA, AD]
    
def filter0 = rtn.body.ul.children().findAll {
    println it
}
println filter0
    
    
def filter1 = rtn.body.ul.'*'.find {
    println it.@class // aaa
    println it.name() // li
    it
}
println "filter1= $filter1" // AA
    
def filter2 = rtn.body.ul.'**'.find {
    println it.@class //
    println it.name() // ul
    it
}
println "filter2= $filter2" // AABBCC
    
def filter3 = rtn.body.ul.'*'.findAll {
    println it.@class // aaa\r\nli\r\nbbb\r\nli...
    println it.name() // li
    it
}
println "filter3= $filter3" // AABBCCADAEAF
    
def filter4 = rtn.body.ul.'**'.findAll {
    println it.@class //
    println it.name() // ul\r\naaa\r\nli\r\nbbb...
    it
}
println "filter4= $filter4" // [AABBCC, AA, BB, CC, ADAEAF, AD, AE, AF]

※depthFirst() 可用 '**' 代替;children() 可用 '*' 代替



※產生 XML

import groovy.xml.MarkupBuilder
    
//<aaa display="true">
//  <bbb xxx="x" ppp="1">123</bbb>
//  <bbb xxx="o" ppp="2">
//      456
//      <ddd>sub</ddd>
//  </bbb>
//  <ccc xxx="x" ppp="3">789</ccc>
//  <ccc xxx="o" ppp="4">012</ccc>
//</aaa>
    
def sw = new StringWriter()
def xml = new MarkupBuilder(sw)
xml.aaa(display: 'true') {
    bbb(xxx: 'x', ppp: '1', value: 123)
    bbb(xxx: 'o', ppp: '2', value: 456) {
        ddd(value: 'sub')
    }
    ccc(xxx: 'x', ppp: '3', value: 789)
    ccc(xxx: 'o', ppp: '4', value: '012')
}
println sw




※使用bean,產生XML

class Aaa {
    String display
    List<Bbb> b
    List<Ccc> c
}
    
class Bbb {
    String xxx
    String ppp
    String value
    Ddd d
}
    
class Ccc {
    String xxx
    String ppp
    String value
}
    
class Ddd {
    String value
}
    
def d = new Ddd(value: 'sub')
def b1 = new Bbb(xxx: 'x', ppp: 1, value: 123)
def b2 = new Bbb(xxx: 'o', ppp: 2, value: 456, d: d)
def c1 = new Bbb(xxx: 'x', ppp: 3, value: 789)
def c2 = new Bbb(xxx: 'o', ppp: 4, value: '012')
def aaa = new Aaa(display: true, b: [b1, b2], c: [c1, c2])
    
def sw = new StringWriter()
def xml = new MarkupBuilder(sw)
    
xml.aaa(display: aaa.display) {
    for (def bc : aaa) {
        /*
        for (def bd : bc.b) {
            bbb(xxx: bd.xxx, ppp: bd.ppp, value: bd.value) {
                if (bd.d != null) {
                    ddd(value: bd.d.value)
                }
            }
        }
        */
    
        aaa.b.each { ab ->
            bbb(xxx: ab.xxx, ppp: ab.ppp, value: ab.value) {
                ab.d.each {
                    ddd(value: it.value)
                }
            }
        }
    
        bc.c.each {
            ccc(xxx: it.xxx, ppp: it.ppp, value: it.value)
        }
    }
}
println sw

※註解是我一開始用閉包時,ddd 這一層包不進去 bbb,所以只好使用 for 迴圈的方式,後來發現 aaa.b 不用 it 才有辦法做到



※IO

假設已有一個檔案 xxx.txt,內容是:
123
abc
456
def

def file = new File('xxx.txt')
println file.getText()
println file.readLines() // [123, abc, 456, def]
println file.readLines('UTF-8') // [123, abc, 456, def]
    
file.each {
    println it
}
    
file.eachLine { content, num ->
    println "$num $content"
}
    
println file.withReader {
    def buffer = new char[2]
    it.read(buffer)
    buffer
}
    
def out = new File('ooo.txt')
out.withWriter {
    String enter = System.getProperty('line.separator')
    it.append('abc').append(enter).append('123').append(enter)
    it.append('def').append(enter).append('456')
}
    
def copyFile(File src, File dest) {
    if (!src.exists()) {
        src.createNewFile()
    }
    
    src.withReader { r ->
        def lines = r.readLines()
        dest.withWriter { w ->
            lines.eachWithIndex { String content, int i ->
                w.append("$i $content" + System.getProperty('line.separator'))
            }
        }
    }
    println 'copy success'
}
copyFile(new File('ooo.txt'), new File('copy.txt'))



2018年8月1日 星期三

呼叫方法的流程 (groovy 2.x 五)

官網連結
當你呼叫一個方法,你沒寫過這個方法,而且也不是 jar 檔裡的方法時,java 會出錯;但 groovy 不一定會出錯,是有一定的流程的

官網的圖,一開始就分有沒有實作 GroovyInterceptable,有實作就呼叫 invokeMethod,但如果沒有 invokeMethod,官網就沒畫了

※我參考官網的圖,有一個部分我並不十分了解,以下說明
Method exists in MetaClass or class,就這個不是很懂
Property exists in MetaClass or class 且還要是閉包才會呼叫,因為 property 是使用在屬性的,而閉包算是比較特殊的

以上這兩個我試的結果,感覺是兩個合起來,所以我認為是「閉包方法或 class」,這兩個好像沒有誰一定會先執行,我有等 30 分鐘再執行,有可能是另外一個執行

所以以下我就以這兩個合併的前提寫了以下的代碼,看圖除了 exception 外,有 5 個結果,但 invokeMethod 重複了,這要看有沒有實作 GroovyInterceptable 而定,所以實際上有 4 個結果


※有實作 GroovyInterceptable

class MyGroovy implements GroovyInterceptable {
    //  @Override
    //  Object invokeMethod(String methodName, Object args) {
    //      "1.方法名:${methodName},參數:${args}"
    //  }
    
    //  def abc(String p1, Object p2) {
    //        "2-1.參數一:${p1},參數二:${p2}"
    //  }
    
    def methodMissing(String methodName, Object args) {
        "3.方法名:${methodName},參數:${args}"
    }
    
    static void main(String[] args) {
    //  MyGroovy.metaClass.abc = { p1, p2 ->
    //      "2-2.參數一:${p1},參數二:${p2}"
    //  }
        MyGroovy m = new MyGroovy()
        println m.abc('xyz', 123)
    }
}

※全部註解打開,會先執行1,1如果註解,就會執行2,依此類推

※有2-1、2-2 這種的,不一定是 2-1 先執行,也有可能 2-2 先執行


※沒實作 GroovyInterceptable

class MyGroovy2 {
    //  def abc(String p1, Object p2) {
    //      "1-1.參數一:${p1},參數二:${p2}"
    //  }
    
    //  def methodMissing(String methodName, Object args) {
    //      "2.方法名:${methodName},參數:${args}"
    //  }
    
    @Override
    Object invokeMethod(String methodName, Object args) {
        "3.方法名:${methodName},參數:${args}"
    }
    
    static void main(String[] args) {
    //  MyGroovy2.metaClass.abc = { p1, p2 ->
    //      "1-2.參數一:${p1},參數二:${p2}"
    //  }
        
        MyGroovy2 m2 = new MyGroovy2()
        println m2.abc('xyz', 123)
    }
}




以上主要是有個類別是 final 類,如 String,沒辦法繼承它,也沒辦法改它,所以可以使用這個功能來新增

※測試

class Animal {
    static void main(String... aaa) {
        // ExpandoMetaClass.enableGlobally() // 據說 1.8.1 版就不需要也能成功了
        // new Animal().nonStaticMethod() // 還沒動態加載,所以找不到方法
    
        Animal.metaClass.constructor = { String s ->  // = 可改成 <<
            println "String constructor"
        } << { Integer i -> // 使用 << 可以增加 overloading 方法,= 不可以
            println "Integer constructor"
        }
    
        Animal.metaClass.static.staticMethod = { // = 可改成 <<
            println 'static method'
        }
    
        Animal.metaClass.nonStaticMethod = { // = 可改成 <<
            println 'nonStatic method'
        }
    
        Animal a = new Animal(8)
        Animal.staticMethod() // 不能使用 call()
        new Animal().nonStaticMethod() // 不能使用 call()
    }
}

※在新增方法後,在別的地方使用,還是沒有新增的方法,所以有了 ExpandoMetaClass.enableGlobally(),在新增方法前呼叫即可,但我沒試出來,一直都可以,找了網路上的文章,聽說是 1.8.1 不需要的樣子