David Steinsland – informatikkstudent og webutvikler

Fremhevede innlegg

Utvidelse av registerklassen i Java

| Ingen kommentarer »

I februar skrev jeg et innlegg om hvordan du kunne sette opp et register i Java, som inneholdt for eksempel datastrukturen din, og som sørget for at dataen ble lagret ved programslutt. I dag tenkte jeg å utvide denne klassen litt, med generiske metoder.

import java.util.Map;
import java.util.HashMap;

public class Registry implements Serializable
{
    private static final long serialVersionUID = 7422025143560909873L;
    private static Map registry = new HashMap<>();

    public static void addObject (String k, Object v)
    {
        this.registry.put (k, v);
    }

    public static  T getObject (String key)
	{
		return (T) this.registry.get(key);
	}
}

Her har jeg gjort følgende endringer:

  • Gjort klassen statisk, slik at du ikke behøver å opprette en instans av klassen før du benytter den.
  • Innført generisk metode for å hente frem et objekt, slik at du slipper å kaste om objektet når du henter det frem.

I praksis, så vil klassen fungere slik:

public class Main
{
	public static void main (String[] args)
	{
		Registry.addObject ( "node", new Node("someName"));

		// begge disse er like:
		Node n = Registry.getObject ("node");
		Node n2 = Registry.getObject ("node");
	}
}

Vise hvor lang tid nettsiden laster

| Ingen kommentarer »

I PHP 5.4 har det kommet en ny SERVER-variabel som heter REQUEST_TIME_FLOAT og gir oss tiden da nettsiden begynner å laste (i millisekunder). Denne muliggjør at vi slipper å definere en konstant helt i begynnelsen av koden vår, som inneholder verdien av microtime(true). Nå kan vi i stedet for gjøre hele prosessen slik:

register_shutdown_function (function ()
{
	$s = $_SERVER['REQUEST_TIME_FLOAT'];
	$e = microtime (true);

	echo "\n\nPage Rendered: ", sprintf ("%0.5f", ($e - $s)), " seconds";
});

Koden kan ligge hvor som helst i koden din (men legg den helst litt langt oppe …). Poenget med å dra nytte av register_shutdown_function() er at metoden blir kalt selv om nettsiden avslutter uventet, for eksempel via exit og die.

Uavhengige cronjobber i PHP

| Ingen kommentarer »

PHP tips og triks

Cron er et ganske nyttig verktøy som gjør deg istand til å utføre spesielle oppgaver på et gitt tidspunkt, men er uheldigvis kun tilgjengelig på UNIX-systemer. I Windows har man noe som heter planlagte oppgaver, som også går an å anvende.
Men hva er dette godt for, når mange webhotell-tilbydere ikke engang har funksjonen(e) aktivert?

Det eneste alternativ som står igjen, er å lage en cron-emulator med PHP (eller lignende). Vi ønsker altså å lage et system som selv kan utføre oppgaver på gitte tidspunkt.

Ved et par Google-søk er det mulig å finne utallige slike eksempler, men personlig har jeg alltid følt de har manglet noe. Jeg ønsker det skal være enkelt og lett forståelig, samt enkelt å integrere i andre systemer.

I stedet for å bruke mye tid på å finne «det perfekte scriptet», så satte jeg av en liten time til å utvikle noe slikt selv.

Jeg endte opp med noe jeg selv mener er ganske enkelt å forstår seg på, samtidig som det er ganske enkelt å utvide også.

Hvordan koden fungerer

Totalt inneholder applikasjonen tre klasser:

  • Scheduler — inneholder de ulike handlingene, og bestemmer hvilke som skal kjøres
  • SchedulerEvent — egen klasse for hver handling. Her lagres informasjon om hvilken kommando som skal kjøres, og hvor mange sekunder det går mellom hver gang
  • SchedulerInterval — en bitteliten klasse som bestemmer intervallet en handling skal kjøres i.

Scheduler tar i bruk Singleton-mønsteret, slik at du kan få tak i klasseinstansen hvor som helst, uten å miste data som objektet lagrer.

Jeg har benyttet meg av et par ukjente triks for å oppnå ønsket resultat:

  • For å sikre meg om at koden skal utføres, uansett hva brukeren velger å gjøre, har jeg tatt i bruk ignore_user_abort()
  • For at data er lagret til neste sidevisning, har jeg tatt i bruk register_shutdown_function. Der utføres det en kode som lagrer de ulike handlingene i en fil, som blir hentet opp ved neste sidevisning
  • Til slutt lar jeg SchedulerEvent-klassen implementere Serializable. På den måten kan jeg lagre hele objektet i eksempelvis en tekstfil, og så gjenopprette det igjen

Opprette handlinger

Koden under legger til en handling, som bestemmer at filen cron_scripts/my_file.php skal kjøres hver time. En gyldig URL gis.

$scheduler = Scheduler::getInstance ();

$event = new SchedulerEvent ("http://example.com/cron_scripts/my_file.php",
    new SchedulerInterval (SchedulerInterval::HOURLY));
$scheduler->addEvent ($event);

Kjøre handlinger

Koden for å kjøre handlinger er nødt til å ligge i en fil som blir kjørt på hver sidevisning, eksempelvis index.php.
Dette gjør vi for å være sikker på å utføre handlingene med best mulig presisjon.

// Sørger for at koden under blir utført uansett.
ignore_user_abort(true);

include_once 'class.scheduler.php';
include_once 'class.schedulerevent.php';
include_once 'class.schedulerinterval.php';

// sørger for at objektene blir lagret til neste sidevisning.
register_shutdown_function ('Scheduler::shutdown');

$scheduler = Scheduler::getInstance ();

foreach ($scheduler->getEvents() as $event)
{
	// utfører handlinger som ennå ikke har blitt utført,
    // eller som skal utføres basert på tiden gitt av metoden
	if ($event->getNextRunTime() <= new DateTime() )
    {
        $event->run ();
    }
}

Når en handling skal utføres, blir det sendt et separat POST-request til den valgte filen. Dette er for å unngå at handlingen ikke skal trekke ned hastigheten på siden.

Last ned

Last ned PHP Scheduler (49 nedlastninger)   (krever PHP >= 5.2.2)

Lagre og hente frem objekter i Java (cache)

| Ingen kommentarer »

Javaprat

Noe ganske essensielt med et program er egenskapen å huske innstillinger til neste oppstart. Men hvordan skal vi få til dette? Skal vi bruke en database eller XML? Eller finnes det andre metoder?

I Java har vi en interface som heter «Serializable». Klasse som implementerer den, har muligheten til å bli lagret på disk til senere bruk ved bruk av ObjectOutputStream og ObjectInputStream.

Helt enkelt, kan vi gjøre slik:

MyObject myObj = new MyObject (); // MyObject implementerer Serializable

try
{
    FileOutputStream fos = new FileOutputStream ("myObject.ser");
    ObjectOutputStream oos = new ObjectOutputStream(fos);

    oos.writeObject(myObj);

    oos.flush();
    oos.close();
}
catch (Exception e)
{
    e.printStackTrace();
}

Objektet vårt er nå lagret i filen myObject.ser, og vi kan hente det frem igjen slik:

try
{
    FileInputStream fis = new FileInputStream("myObject.ser");
    ObjectInputStream ois = new ObjectInputStream(fis);

    // myObj er nå av samme instans som i eksempelet ovenfor
    MyObject myObj = (MyObject) ois.readObject();

    ois.close();
}
catch (Exception e)
{
    e.printStackTrace();
}

Som du kanskje ser, så er det ikke så veldig komplisert. Men hva dersom applikasjonen inneholder mange flere objekter? Å lagre alle objektene manuelt er både tidkrevende og unødvendig.

Vi kan nemlig anvende oss av programmeringsparadigmet Registry. Essensielt er det en klasse som inneholder alle andre objekter som brukes i en applikasjon. Registeret anvender også gjerne Singleton-mønsteret slik at objektene kan nås overalt.

Når vi har et register trenger vi bare lagre register-objektet. Alle objekter som måtte være i registeret vil da også bli lagret.

Et eksempel på et slik register:

class Registry implements Serializable
{
	private static final long serialVersionUID = 7422025143560909873L;
	private static final Registry instance = new Registry();

	private HashMap registry = new HashMap();

	private Registry ()
	{ }

	public static Registry getInstance ()
	{
		return instance;
	}

	public void addObject (String k, Object v)
	{
		this.registry.put (k, v);
	}

	public Object getObject (String k)
	{
		return this.registry.get (k);
	}
}

Du har kanskje lagt merke til datafeltet serialVersionUID, og lurer på hva det er?

serialVersionUID er en universell versjonsindikator for en klasse som støtter serial-handling. Deserialiseringa bruker dette ID-nummeret for å sikre at klassen er i samsvar med det serialiserte objektet.

I praksis betyr dette at Java bruker serienummeret til å sjekke om det objektet du henter frem fra filen er i samsvar med det objektet du la inn. Det høres kanskje litt diffus ut fortsatt, men ta følgende scenario:

- Du oppretter et objekt av typen MyObject (som i kode #1), og lagrer dette til en fil
- I mellomtiden endrer du på klassestrukturen til MyObject, og legger til flere datafelt
- Når du henter frem objektet igjen, etter klasseendringene, hva tror du skjer?

Java kjenner nemlig ikke igjen klassene, og tror de er forskjellige. Du vil derfor få en feilmelding om at det er en ugyldig klasse. Dersom du hadde implementert serialVersionUID, hadde Java forstått at objektene likevel var like.

Verdien er unik per klasse, og blir generert av Java selv. For å finne ut hvilket nummer du skal legge inn i hver klasse, må du ta ibruk serialver-programmet (følger med Java-installasjonen).

Du åpner opp kommandolinjen og endrer mappe til hvor klassefilene dine ligger. Deretter utfører du kommandoen:

serialver Registry

Bytt ut Registry med hva klassenavnet er i ditt tilfelle.

Et eksempel på bruk

class Program
{
    public static void main (String[] args)
    {
        Registry reg = Registry.getInstance();

        reg.addObject ("myObj", new MyObject ("Something"));
        reg.addObject ("foo", new MyObject ("bar"));

        // lagre objektene ved avslutning
        try
        {
            FileOutputStream fos = new FileOutputStream ("registry.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);

            oos.writeObject(reg);

            oos.flush();
            oos.close();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }

        // ... så kan vi hente dem frem igjen
        try
        {
            FileInputStream fis = new FileInputStream("registry.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);

            reg = (Registry) ois.readObject();
            ois.close();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }

        // do something..
        MyObject myObj = (MyObject) reg.getObjet ("myObj");
    }
}

Oppsummering

  • Alle klasser som skal lagres implementere Serializable, og bør inneholdet  serialVersionUID
  • Et felles register med alle objekter vil spare deg for tid og ressurser, for eksempel ved lagring av objekter