Когато Runtime.exec () няма

Като част от езика Java, java.langпакетът имплицитно се импортира във всяка Java програма. Капаните на този пакет често изплуват на повърхността, засягайки повечето програмисти. Този месец ще обсъдя капаните, които се крият в Runtime.exec()метода.

Pitfall 4: Когато Runtime.exec () няма

Класът се java.lang.Runtimeотличава със статичен метод getRuntime(), който извлича текущата Java Runtime Environment. Това е единственият начин да се получи препратка към Runtimeобекта. С тази препратка можете да стартирате външни програми, като извикате метода на Runtimeкласа exec(). Разработчиците често извикват този метод, за да стартират браузър за показване на страница за помощ в HTML.

Има четири претоварени версии на exec()командата:

  • public Process exec(String command);
  • public Process exec(String [] cmdArray);
  • public Process exec(String command, String [] envp);
  • public Process exec(String [] cmdArray, String [] envp);

За всеки от тези методи команда - и вероятно набор от аргументи - се предава на специфично за операционната система извикване на функция. Това впоследствие създава специфичен за операционната система процес (работеща програма) с препратка към Processклас, върнат в Java VM. Най- Processкласа е абстрактен клас, тъй като специфичен подклас на Processсъществува за всяка операционна система.

Можете да предадете три възможни входни параметъра в тези методи:

  1. Единичен низ, който представлява както програмата за изпълнение, така и всички аргументи към тази програма
  2. Масив от низове, които отделят програмата от нейните аргументи
  3. Масив от променливи на средата

Предайте във формата променливи на средата name=value. Ако използвате версията на exec()с един низ както за програмата, така и за нейните аргументи, обърнете внимание, че низът се анализира, използвайки празно пространство като разделител чрез StringTokenizerкласа.

Препъване в IllegalThreadStateException

Първата клопка, свързана с Runtime.exec()е IllegalThreadStateException. Преобладаващият първи тест на API е да се кодират най-очевидните му методи. Например, за да изпълним процес, който е външен за Java VM, използваме exec()метода. За да видим стойността, която връща външният процес, използваме exitValue()метода за Processкласа. В първия ни пример ще се опитаме да изпълним Java компилатора ( javac.exe):

Листинг 4.1 BadExecJavac.java

импортиране на java.util. *; импортиране на java.io. *; публичен клас BadExecJavac {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Процес proc = rt.exec ("javac"); int exitVal = proc.exitValue (); System.out.println ("Process exitValue:" + exitVal); } catch (Throwable t) {t.printStackTrace (); }}}

Поредица от BadExecJavacпроизвежда:

E: \ classes \ com \ javaworld \ jpitfalls \ article2> java BadExecJavac java.lang.IllegalThreadStateException: процесът не е излязъл при java.lang.Win32Process.exitValue (Native Method) в BadExecJavac.main (BadExejac. 

Ако външен процес все още не е завършил, exitValue()методът ще изведе IllegalThreadStateException; затова тази програма се провали. Докато в документацията се посочва този факт, защо този метод не може да изчака, докато може да даде валиден отговор?

По-задълбочен поглед върху методите, налични в Processкласа, разкрива waitFor()метод, който прави точно това. Всъщност waitFor()връща и изходната стойност, което означава, че не бихте използвали exitValue()и waitFor()във връзка помежду си, а по-скоро бихте избрали едното или другото. Единственото възможно време, което бихте използвали exitValue()вместо, waitFor()би било, когато не искате програмата ви да блокира чакането във външен процес, който може никога да не завърши. Вместо да използвам waitFor()метода, бих предпочел да предаде булев параметър, извикан waitForв exitValue()метода, за да определи дали текущата нишка трябва да изчака или не. Булево би било по-полезно, защотоexitValue()е по-подходящо име за този метод и не е необходимо два метода да изпълняват една и съща функция при различни условия. Такава проста дискриминация на условията е областта на входния параметър.

Следователно, за да избегнете този капан, хванете IllegalThreadStateExceptionили изчакайте процесът да завърши.

Сега, нека решим проблема в Листинг 4.1 и изчакаме процесът да завърши. В листинг 4.2 програмата отново се опитва да изпълни javac.exeи след това изчаква външният процес да завърши:

Листинг 4.2 BadExecJavac2.java

импортиране на java.util. *; импортиране на java.io. *; публичен клас BadExecJavac2 {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Процес proc = rt.exec ("javac"); int exitVal = proc.waitFor (); System.out.println ("Process exitValue:" + exitVal); } catch (Throwable t) {t.printStackTrace (); }}}

За съжаление, поредица от BadExecJavac2продукти не дава резултат. Програмата виси и никога не завършва. Защо javacпроцесът никога не завършва?

Защо виси Runtime.exec ()

Документацията на JDK за Javadoc дава отговор на този въпрос:

Тъй като някои естествени платформи осигуряват само ограничен размер на буфера за стандартни входни и изходни потоци, невъзможността за незабавно записване на входния поток или четене на изходния поток на подпроцеса може да доведе до блокиране на подпроцеса и дори блокиране.

Дали това е просто случай на програмисти, които не четат документацията, както се подразбира в често цитирания съвет: прочетете финото ръководство (RTFM)? Отговорът е частично да. В този случай четенето на Javadoc ще ви отведе наполовина; тя обяснява, че трябва да обработвате потоците към вашия външен процес, но не ви казва как.

Тук играе друга променлива, както е видно от големия брой въпроси и заблуди на програмиста относно този API в дискусионните групи: макар че Runtime.exec()и API на процеса изглеждат изключително прости, тази простота е заблуждаваща, защото простото или очевидно използване на API е склонен към грешка. Урокът тук за дизайнера на API е да запази прости API за прости операции. Операциите, склонни към сложности и специфични за платформата зависимости, трябва да отразяват домейна точно. Възможно е една абстракция да бъде пренесена твърде далеч. В JConfigбиблиотеката дава пример за по-пълен API за дръжка на файловете и процесите операции (виж по-долу ресурси за повече информация).

Сега, нека следваме документацията за JDK и да обработваме изхода на javacпроцеса. Когато стартирате javacбез никакви аргументи, той създава набор от инструкции за използване, които описват как да стартирате програмата и значението на всички налични опции на програмата. Знаейки, че това е към stderrпотока, можете лесно да напишете програма за изчерпване на този поток, преди да изчакате процеса да излезе. Листинг 4.3 изпълнява тази задача. Въпреки че този подход ще работи, това не е добро общо решение. По този начин програмата на Листинг 4.3 е ​​наречена MediocreExecJavac; тя предоставя само посредствено решение. По-добро решение би изпразнило както стандартния поток за грешки, така и стандартния изходен поток. И най-доброто решение би изпразнило тези потоци едновременно (това ще покажа по-късно).

Листинг 4.3 MediocreExecJavac.java

импортиране на java.util. *; импортиране на java.io. *; публичен клас MediocreExecJavac {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Процес proc = rt.exec ("javac"); InputStream stderr = proc.getErrorStream (); InputStreamReader isr = нов InputStreamReader (stderr); BufferedReader br = нов BufferedReader (isr); Линия на низа = нула; System.out.println (""); while ((line = br.readLine ())! = null) System.out.println (line); System.out.println (""); int exitVal = proc.waitFor (); System.out.println ("Process exitValue:" + exitVal); } catch (Throwable t) {t.printStackTrace (); }}}

Поредица от MediocreExecJavacгенерира:

E: \ classes \ com \ javaworld \ jpitfalls \ article2> java MediocreExecJavac Употреба: javac, където включва: -g Генериране на цялата информация за отстраняване на грешки -g: няма Генериране на информация за отстраняване на грешки -g: {lines, vars, source} Генериране само на информация за отстраняване на грешки -O Оптимизиране; може да попречи на отстраняването на грешки или да увеличи файловете на класа -nowarn Не генерира предупреждения -verbose Изходни съобщения за това, което прави компилаторът-оттегляне Изходни местоположения на изход, където се използват остарели API-та -classpath Посочете къде да намерите файловете на потребителския клас -sourcepath Посочете къде да намерите входните файлове на източника -bootclasspath Замяна на местоположението на файловете с клас на bootstrap -extdirs Замяна на местоположението на инсталираните разширения -d Посочете къде да поставите генерираните файлове на класа -encoding Посочете кодиране на символи, използвано от изходни файлове -cilj Генериране на файлове на клас за конкретна версия на VM Процес изход Стойност: 2

So, MediocreExecJavac works and produces an exit value of 2. Normally, an exit value of 0 indicates success; any nonzero value indicates an error. The meaning of these exit values depends on the particular operating system. A Win32 error with a value of 2 is a "file not found" error. That makes sense, since javac expects us to follow the program with the source code file to compile.

Thus, to circumvent the second pitfall -- hanging forever in Runtime.exec() -- if the program you launch produces output or expects input, ensure that you process the input and output streams.

Assuming a command is an executable program

Under the Windows operating system, many new programmers stumble upon Runtime.exec() when trying to use it for nonexecutable commands like dir and copy. Subsequently, they run into Runtime.exec()'s third pitfall. Listing 4.4 demonstrates exactly that:

Listing 4.4 BadExecWinDir.java

import java.util.*; import java.io.*; public class BadExecWinDir { public static void main(String args[]) { try { Runtime rt = Runtime.getRuntime(); Process proc = rt.exec("dir"); InputStream stdin = proc.getInputStream(); InputStreamReader isr = new InputStreamReader(stdin); BufferedReader br = new BufferedReader(isr); String line = null; System.out.println(""); while ( (line = br.readLine()) != null) System.out.println(line); System.out.println(""); int exitVal = proc.waitFor(); System.out.println("Process exitValue: " + exitVal); } catch (Throwable t) { t.printStackTrace(); } } } 

A run of BadExecWinDir produces:

E:\classes\com\javaworld\jpitfalls\article2>java BadExecWinDir java.io.IOException: CreateProcess: dir error=2 at java.lang.Win32Process.create(Native Method) at java.lang.Win32Process.(Unknown Source) at java.lang.Runtime.execInternal(Native Method) at java.lang.Runtime.exec(Unknown Source) at java.lang.Runtime.exec(Unknown Source) at java.lang.Runtime.exec(Unknown Source) at java.lang.Runtime.exec(Unknown Source) at BadExecWinDir.main(BadExecWinDir.java:12) 

Както беше посочено по-рано, стойността на грешката 2означава "файл не е намерен", което в този случай означава, че имената на изпълнимия файл dir.exeне може да бъде намерен. Това е така, защото командата на директорията е част от интерпретатора на команди на Windows, а не отделен изпълним файл. За да стартирате интерпретатора на команди на Windows, изпълнете или command.comили cmd.exe, в зависимост от операционната система Windows, която използвате. Листинг 4.5 стартира копие на интерпретатора на команди на Windows и след това изпълнява предоставената от потребителя команда (напр. dir).

Листинг 4.5 GoodWindowsExec.java