Sun Microsystems запустила Java в 1995 году с конкретным обещанием: «Write Once, Run Anywhere» - написать один раз, запустить где угодно. Для веб-разработки это означало Java-апплеты - скомпилированные Java-программы, работающие прямо в браузере через плагин JVM. Идея звучала убедительно: полноценная графика, анимация, звук, работа с сетью и обработка ввода - всё внутри тега <applet> на обычной HTML-странице.
В 1999 году, если вам нужна была интерактивная графика - реальный график в реальном времени, перетаскиваемая диаграмма, анимированная визуализация - Java-апплеты были единственным серьёзным вариантом. JavaScript был ограничен валидацией форм и простыми манипуляциями DOM. Flash существовал, но был проприетарным и дорогим. Java был открытым, стандартизированным и поддерживался Sun.
Мы писали апплеты. Вот как это выглядело.
Архитектура: JDK 1.1 и AWT
Java-апплеты использовали Abstract Window Toolkit (AWT) для графики и интерфейса. Модель - событийная: браузер вызывал методы жизненного цикла апплета (init(), start(), stop(), destroy()), а разработчик переопределял paint() для отрисовки на экране.
// StockTickerApplet.java - JDK 1.1, 1999 год
// Бегущая строка с котировками - классический use case для апплетов
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
public class StockTickerApplet extends Applet implements Runnable {
// Данные передаются из HTML через теги <param>
private String tickerText = "MSFT 89.50 (+1.25) AAPL 28.75 (-0.50) SUNW 42.00 (+2.00)";
private int scrollSpeed = 2; // пикселей за кадр
private Color bgColor = Color.black;
private Color textColor = Color.green; // зелёный на чёрном - стиль 1999
private Thread animationThread;
private int xPosition;
private int textWidth;
private Font tickerFont;
@Override
public void init() {
String paramText = getParameter("text");
String paramSpeed = getParameter("speed");
if (paramText != null) tickerText = paramText;
if (paramSpeed != null) scrollSpeed = Integer.parseInt(paramSpeed);
tickerFont = new Font("Monospaced", Font.BOLD, 14);
setBackground(bgColor);
FontMetrics fm = getFontMetrics(tickerFont);
textWidth = fm.stringWidth(tickerText);
xPosition = getSize().width;
}
@Override
public void start() {
if (animationThread == null || !animationThread.isAlive()) {
animationThread = new Thread(this);
animationThread.start();
}
}
@Override
public void stop() {
animationThread = null; // остановить цикл при уходе со страницы
}
@Override
public void run() {
while (Thread.currentThread() == animationThread) {
xPosition -= scrollSpeed;
if (xPosition < -textWidth) {
xPosition = getSize().width;
}
repaint();
try {
Thread.sleep(40); // ~25 кадров в секунду
} catch (InterruptedException e) {
break;
}
}
}
@Override
public void paint(Graphics g) {
g.setFont(tickerFont);
g.setColor(textColor);
g.drawString(tickerText, xPosition, getSize().height / 2 + 5);
}
// Двойная буферизация - обязательна в AWT 1999 года
// Без неё каждый repaint() вызывал видимое мерцание
private Image offscreenImage;
private Graphics offscreenGraphics;
@Override
public void update(Graphics g) {
Dimension d = getSize();
if (offscreenImage == null) {
offscreenImage = createImage(d.width, d.height);
offscreenGraphics = offscreenImage.getGraphics();
}
offscreenGraphics.setColor(bgColor);
offscreenGraphics.fillRect(0, 0, d.width, d.height);
paint(offscreenGraphics);
g.drawImage(offscreenImage, 0, 0, this);
}
}
Компиляция и деплой:
# Компиляция JDK 1.1
javac StockTickerApplet.java
# Результат: StockTickerApplet.class
# Упаковка в JAR для уменьшения числа HTTP-запросов
jar cvf ticker.jar StockTickerApplet.class
Вставка в HTML:
<!-- Тег <applet> - стандарт HTML 4.0 -->
<applet code="StockTickerApplet.class"
archive="ticker.jar"
width="500"
height="30"
alt="Для просмотра требуется Java">
<param name="text" value="MSFT 89.50 (+1.25) AAPL 28.75 (-0.50)">
<param name="speed" value="3">
<!-- Фолбэк для браузеров без плагина -->
<p>Ваш браузер не поддерживает Java-апплеты.</p>
</applet>
Интерактивный пример: диаграмма с подсветкой
// BarChartApplet.java - интерактивная столбчатая диаграмма
// Столбцы подсвечиваются при наведении мыши
// Такое было невозможно в JavaScript 1999 года
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
public class BarChartApplet extends Applet implements MouseMotionListener {
private String[] labels = {"Янв", "Фев", "Мар", "Апр", "Май", "Июн"};
private int[] values = {42, 67, 55, 89, 73, 91};
private int hoveredBar = -1;
@Override
public void init() {
addMouseMotionListener(this);
setBackground(Color.white);
}
@Override
public void paint(Graphics g) {
int w = getSize().width;
int h = getSize().height;
int margin = 30;
int barW = (w - 2 * margin) / values.length - 4;
int maxVal = 0;
for (int v : values) if (v > maxVal) maxVal = v;
// Оси
g.setColor(Color.black);
g.drawLine(margin, h - margin, w - margin, h - margin);
g.drawLine(margin, margin, margin, h - margin);
// Столбцы
for (int i = 0; i < values.length; i++) {
int barH = (int)((double)values[i] / maxVal * (h - 2 * margin));
int x = margin + i * (barW + 4) + 4;
int y = h - margin - barH;
g.setColor(i == hoveredBar ? Color.orange : new Color(51, 102, 204));
g.fillRect(x, y, barW, barH);
g.setColor(Color.black);
g.drawRect(x, y, barW, barH);
g.setFont(new Font("SansSerif", Font.PLAIN, 10));
g.drawString(labels[i], x, h - 15);
g.drawString(String.valueOf(values[i]), x, y - 2);
}
}
@Override
public void mouseMoved(MouseEvent e) {
int w = getSize().width;
int margin = 30;
int barW = (w - 2 * margin) / values.length - 4;
int newHovered = -1;
for (int i = 0; i < values.length; i++) {
int x = margin + i * (barW + 4) + 4;
if (e.getX() >= x && e.getX() <= x + barW) {
newHovered = i;
break;
}
}
if (newHovered != hoveredBar) {
hoveredBar = newHovered;
repaint();
}
}
@Override public void mouseDragged(MouseEvent e) {}
}
В 1999 году это производило сильное впечатление. Диаграмма, реагирующая на движение мыши - на JavaScript это было принципиально невозможно. Максимум доступный в JS: swap картинок через onmouseover. Java давал настоящую программируемую графику.
Почему апплеты умерли
Время загрузки. Плагин JVM должен был инициализироваться перед первым апплетом. На модеме 56 кбит/с пользователи смотрели на серый прямоугольник 10-30 секунд. Многие уходили, не дождавшись.
Фрагментация версий. IE поставлялся с Microsoft JVM. Netscape использовал плагин Sun JVM. Java 1.0, 1.1 и 1.2 имели несовместимые API. Апплет, работавший идеально в IE/Windows, ломался в Netscape/Mac или наоборот. Обещание «Write Once, Run Anywhere» не выполнялось на практике.
// Код, выявлявший различия между JVM:
String javaVersion = System.getProperty("java.version");
// Разветвление по версии - что полностью разрушало идею WORA
// Microsoft JVM не включал некоторые классы RMI
// Реализация Thread.sleep() в Netscape JVM давала баги в отдельных версиях
// Поведение java.awt.Color различалось между JDK 1.0 и 1.1
Flash был зрелищнее. Macromedia Flash 4 (1999) и Flash 5 (2000) предлагали векторную анимацию, потоковое аудио и программирование на ActionScript с несравнимо лучшим инструментарием для дизайнеров. Flash загружался быстрее, не требовал JVM, позволял создавать интерактивный контент без Java-разработчиков. К 2001 году Flash выиграл нишу «интерактивный веб».
Что оставили апплеты в наследство
Java-апплеты, несмотря на провал как массовой веб-технологии, доказали: браузер может быть платформой для полноценных приложений, а не только для отображения документов. Эта идея никуда не ушла: Flash, Silverlight, HTML5 Canvas, WebGL, WebAssembly - все они преследовали ту же цель.
Архитектурные паттерны разработки апплетов - методы жизненного цикла (init/start/stop/destroy), слушатели событий, двойная буферизация - прослеживаются в каждом UI-фреймворке, который последовал за ними. Жизненный цикл Activity в Android прямо унаследован от жизненного цикла апплета. Компонентная модель React и Web Components следует тому же паттерну init/render/destroy.
Провал был поучительным: «Write Once, Run Anywhere» требует не только переносимости языка, но и стандартизации runtime-окружения. JVM не был идентичен у разных поставщиков браузеров. Урок этого провала привёл к решению строить веб-платформу вокруг одного согласованного runtime - JavaScript-движка.