Saturday, July 23, 2016

Spring Boot Uygulamalarının RHEL 7'de systemd Servisi Olarak Eklenmesi

Spring Boot, Spring tabanlı web uygulaması geliştirmeyi kolaylaştıran özellikleri ile son dönemde tercih edilen kurumsal uygulama geliştirme yaklaşımıdır.
Java Tools and Technologies Landscape 2016
Spring boot uygulamalarının dağıtımını jar ya da war olarak yapabilirsiniz. jar olarak yayına çıktığınızda herhangi bir uygulama sunucusuna ihtiyaç duymadan, sadece Java Sanal Makinasını kullanarak çalıştırabilirsiniz. jar olarak çıkmak için maven projesinin pom.xml dosyasında packaging tanımını jar olarak vermeniz gerekir:
<packaging>jar</packaging>
Bu jar dosyasının çalıştırılabilir bir jar dosyası olması için pom.xml'de spring-boot maven eklentisinde basit bir tanımlama yapmanız gerekir:
<build>
   <plugins>
      . . . .
      <plugin>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-maven-plugin</artifactId>
         <configuration>
            <executable>true</executable>
         </configuration>
      </plugin>
      . . .
   </plugins>
</build>
 Ardından aşağıdaki maven komutunu projenin kök dizininde çalıştırmalısınız:
[guru@server1 hr-boot]$ mvn clean install spring-boot:repackage
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building hr-boot-rest-jpa 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ hr ---
[INFO] Deleting /home/guru/hr-boot/target
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hr ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 1 resource
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hr ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 12 source files to /home/guru/hr-boot/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hr ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/guru/hr-boot/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ hr ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-surefire-plugin:2.18.1:test (default-test) @ hr ---
[INFO] No tests to run.
[INFO] 
[INFO] --- maven-jar-plugin:2.5:jar (default-jar) @ hr ---
[INFO] Building jar: /home/guru/hr-boot/target/hr-0.0.1-SNAPSHOT.jar
[INFO] 
[INFO] --- spring-boot-maven-plugin:1.3.6.RELEASE:repackage (default) @ hr ---
[INFO] 
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ hr ---
[INFO] Installing /home/guru/hr-boot/target/hr-0.0.1-SNAPSHOT.jar to /home/guru/.m2/repository/com/example/hr/0.0.1-SNAPSHOT/hr-0.0.1-SNAPSHOT.jar
[INFO] Installing /home/guru/hr-boot/pom.xml to /home/guru/.m2/repository/com/example/hr/0.0.1-SNAPSHOT/hr-0.0.1-SNAPSHOT.pom
[INFO] 
[INFO] --- spring-boot-maven-plugin:1.3.6.RELEASE:repackage (default-cli) @ hr ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.497 s
[INFO] Finished at: 2016-07-23T09:09:38+03:00
[INFO] Final Memory: 30M/174M
[INFO] ------------------------------------------------------------------------ 
target dizininin yeniden oluşturulduğunu ve içeriğine göz attığınızda ise jar dosyasını göreceksiniz:
[guru@server1 hr-boot]$ cd target
[guru@server1 target]$ ls -lh
total 27M
drwxrwxr-x 3 guru guru  72 Jul 23 09:26 classes
drwxrwxr-x 3 guru guru  24 Jul 23 09:26 generated-sources
-rw-rw-r-- 1 guru guru 27M Jul 23 09:26 hr-0.0.1-SNAPSHOT.jar
-rw-rw-r-- 1 guru guru 14K Jul 23 09:26 hr-0.0.1-SNAPSHOT.jar.original
drwxrwxr-x 2 guru guru  27 Jul 23 09:26 maven-archiver
drwxrwxr-x 3 guru guru  34 Jul 23 09:26 maven-status
war olarak çıkmak için ise maven projesinin pom.xml dosyasında packaging tanımını war olarak vermeniz gerekir:
<packaging>war</packaging>
Çalıştırdığımız komutta bir değişiklik yapmıyoruz:
[guru@server1 hr-boot]$ mvn clean install spring-boot:repackage
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building hr-boot-rest-jpa 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ hr ---
[INFO] Deleting /home/guru/hr-boot/target
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hr ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 1 resource
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hr ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 12 source files to /home/guru/hr-boot/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hr ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/guru/hr-boot/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ hr ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-surefire-plugin:2.18.1:test (default-test) @ hr ---
[INFO] No tests to run.
[INFO] 
[INFO] --- maven-war-plugin:2.5:war (default-war) @ hr ---
[INFO] Packaging webapp
[INFO] Assembling webapp [hr] in [/home/guru/hr-boot/target/hr-0.0.1-SNAPSHOT]
[INFO] Processing war project
[INFO] Webapp assembled in [151 msecs]
[INFO] Building war: /home/guru/hr-boot/target/hr-0.0.1-SNAPSHOT.war
[INFO] 
[INFO] --- spring-boot-maven-plugin:1.3.6.RELEASE:repackage (default) @ hr ---
[INFO] 
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ hr ---
[INFO] Installing /home/guru/hr-boot/target/hr-0.0.1-SNAPSHOT.war to /home/guru/.m2/repository/com/example/hr/0.0.1-SNAPSHOT/hr-0.0.1-SNAPSHOT.war
[INFO] Installing /home/guru/hr-boot/pom.xml to /home/guru/.m2/repository/com/example/hr/0.0.1-SNAPSHOT/hr-0.0.1-SNAPSHOT.pom
[INFO] 
[INFO] --- spring-boot-maven-plugin:1.3.6.RELEASE:repackage (default-cli) @ hr ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6.809 s
[INFO] Finished at: 2016-07-23T09:14:35+03:00
[INFO] Final Memory: 30M/175M
[INFO] ------------------------------------------------------------------------
target dizininin yeniden oluşturulduğunu ve içeriğine göz attığınızda ise war dosyasını göreceksiniz:
[guru@server1 target]$ ls -lh
total 50M
drwxrwxr-x 3 guru guru  72 Jul 23 09:14 classes
drwxrwxr-x 3 guru guru  24 Jul 23 09:14 generated-sources
drwxrwxr-x 4 guru guru  35 Jul 23 09:14 hr-0.0.1-SNAPSHOT
-rw-rw-r-- 1 guru guru 27M Jul 23 09:14 hr-0.0.1-SNAPSHOT.war
-rw-rw-r-- 1 guru guru 24M Jul 23 09:14 hr-0.0.1-SNAPSHOT.war.original
drwxrwxr-x 2 guru guru  27 Jul 23 09:14 maven-archiver
drwxrwxr-x 3 guru guru  34 Jul 23 09:14 maven-status
target dizinindeki  hr-0.0.1-SNAPSHOT.jar dosyasını /home/guru dizinine hr-app adıyla kopyalalım:
[guru@erver1 hr-boot]$ cd target/
[guru@server1 target]$ ls
classes  generated-sources  hr-0.0.1-SNAPSHOT.jar  hr-0.0.1-SNAPSHOT.jar.original  maven-archiver  maven-status
[guru@server1 target]$ cp hr-0.0.1-SNAPSHOT.jar /home/guru/hr-app
[guru@server1 target]$ ls -lh /home/guru/hr-app
-rwxrwxr-x 1 guru guru 27M Jul 23 16:34 /home/guru/hr-app
Bu dosyaya yakından baktığınızda aslında başlığında bir betiğin yer aldığını göreceksiniz:
[guru@server1 ~]$ head -240 hr-app
#!/bin/bash
#
#    .   ____          _            __ _ _
#   /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
#  ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
#   \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
#    '  |____| .__|_| |_|_| |_\__, | / / / /
#   =========|_|==============|___/=/_/_/_/
#   :: Spring Boot Startup Script ::
#

### BEGIN INIT INFO
# Provides:          hr
# Required-Start:    $remote_fs $syslog $network
# Required-Stop:     $remote_fs $syslog $network
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: hr-boot-rest-jpa
# Description:       HR Spring Boot Project
# chkconfig:         2345 99 01
### END INIT INFO

[[ -n "$DEBUG" ]] && set -x

# Initialize variables that cannot be provided by a .conf file
WORKING_DIR="$(pwd)"
# shellcheck disable=SC2153
[[ -n "$JARFILE" ]] && jarfile="$JARFILE"
[[ -n "$APP_NAME" ]] && identity="$APP_NAME"

# Follow symlinks to find the real jar and detect init.d script
cd "$(dirname "$0")" || exit 1
[[ -z "$jarfile" ]] && jarfile=$(pwd)/$(basename "$0")
while [[ -L "$jarfile" ]]; do
  [[ "$jarfile" =~ init\.d ]] && init_script=$(basename "$jarfile")
  jarfile=$(readlink "$jarfile")
  cd "$(dirname "$jarfile")" || exit 1
  jarfile=$(pwd)/$(basename "$jarfile")
done
jarfolder="$(dirname "$jarfile")"
cd "$WORKING_DIR" || exit 1
. . .
RHEL 7'de servislerin yönetimi için systemd hizmetini kullanıyoruz. Servisin tanımı ve diğer servislerle bağımlılığını /usr/lib/systemd/system dizininde service uzantılı bir dosyada tanımlıyoruz. Örneğimizde bu dosyaya hr-boot.service adını vereceğiz:
[Unit]
Description=hr-boot service
Wants=mysqld.service
After=network.target mysqld.service

[Service]
Type=simple
ExecStart=/home/guru/hr-app
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target
Spring boot uygulamamız MySQL sunucusunu kullanıyor. Servisimizin çalışabilmesi için öncelikle MySQL sunucusunun başlatılması gerektiğini Wants ile tanımlıyoruz.
Şimdi önce servisi etkinleştireceğiz ve sonra servisi başlatabiliriz:
[guru@server1 hr-boot]$ sudo systemctl enable hr-boot
Created symlink from /etc/systemd/system/multi-user.target.wants/hr-boot.service to /usr/lib/systemd/system/hr-boot.service.
[guru@server1 hr-boot]$ sudo systemctl start hr-boot
[guru@server1 hr-boot]$ sudo systemctl status hr-boot
 hr-boot.service - hr-boot
   Loaded: loaded (/usr/lib/systemd/system/hr-boot.service; enabled; vendor preset: disabled)
   Active: active (running) since Sat 2016-07-23 15:05:55 EEST; 1h 39min ago
 Main PID: 24437 (hr-app)
   CGroup: /system.slice/hr-boot.service
           ├─24437 /bin/bash /home/guru/hr-app
           └─24456 /usr/bin/java -jar /home/guru/hr-app

Jul 23 15:06:10 server1.example.com hr-app[24437]: 2016-07-23 15:06:10.939  INFO 24456 --- [           main] o.s.j.e.a.AnnotationMB...tartup
Jul 23 15:06:10 server1.example.com hr-app[24437]: 2016-07-23 15:06:10.959  INFO 24456 --- [           main] o.s.c.support.DefaultL...483647
Jul 23 15:06:10 server1.example.com hr-app[24437]: 2016-07-23 15:06:10.961  INFO 24456 --- [           main] o.s.m.s.b.SimpleBroker...ing...
Jul 23 15:06:10 server1.example.com hr-app[24437]: 2016-07-23 15:06:10.963  INFO 24456 --- [           main] o.s.m.s.b.SimpleBroker...ns]]]]
Jul 23 15:06:10 server1.example.com hr-app[24437]: 2016-07-23 15:06:10.965  INFO 24456 --- [           main] o.s.m.s.b.SimpleBroker...arted.
Jul 23 15:06:11 server1.example.com hr-app[24437]: 2016-07-23 15:06:11.115  INFO 24456 --- [           main] s.b.c.e.t.TomcatEmbedd...(http)
Jul 23 15:06:11 server1.example.com hr-app[24437]: 2016-07-23 15:06:11.124  INFO 24456 --- [           main] com.example.hr.HrBootA...5.437)
Jul 23 15:07:09 server1.example.com hr-app[24437]: 2016-07-23 15:07:09.445  INFO 24456 --- [MessageBroker-1] o.s.w.s.c.WebSocketMes...ECTED(
Jul 23 16:23:51 server1.example.com hr-app[24437]: 2016-07-23 16:23:51.487  INFO 24456 --- [MessageBroker-1] o.s.w.s.c.WebSocketMes...ECTED(
Jul 23 16:45:19 server1.example.com systemd[1]: Started hr-boot.
Hint: Some lines were ellipsized, use -l to show in full.
[guru@server1 hr-boot]$ sudo systemctl stop hr-boot
[guru@server1 hr-boot]$ sudo systemctl status hr-boot
 hr-boot.service - hr-boot
   Loaded: loaded (/usr/lib/systemd/system/hr-boot.service; enabled; vendor preset: disabled)
   Active: inactive (dead) since Sat 2016-07-23 16:45:52 EEST; 7s ago
 Main PID: 24437 (code=killed, signal=TERM)

Jul 23 16:45:51 server1.example.com hr-app[24437]: 2016-07-23 16:45:51.589  INFO 24456 --- [       Thread-3] o.s.m.s.b.SimpleBroker...ing...
Jul 23 16:45:51 server1.example.com hr-app[24437]: 2016-07-23 16:45:51.589  INFO 24456 --- [       Thread-3] o.s.m.s.b.SimpleBroker...ns]]]]
Jul 23 16:45:51 server1.example.com hr-app[24437]: 2016-07-23 16:45:51.590  INFO 24456 --- [       Thread-3] o.s.m.s.b.SimpleBroker...opped.
Jul 23 16:45:51 server1.example.com hr-app[24437]: 2016-07-23 16:45:51.616  INFO 24456 --- [       Thread-3] o.s.j.e.a.AnnotationMB...utdown
Jul 23 16:45:51 server1.example.com hr-app[24437]: 2016-07-23 16:45:51.621  INFO 24456 --- [       Thread-3] o.s.s.concurrent.Threa...cutor'
Jul 23 16:45:51 server1.example.com hr-app[24437]: 2016-07-23 16:45:51.625  INFO 24456 --- [       Thread-3] o.s.s.c.ThreadPoolTask...duler'
Jul 23 16:45:51 server1.example.com hr-app[24437]: 2016-07-23 16:45:51.626  INFO 24456 --- [       Thread-3] o.s.s.concurrent.Threa...cutor'
Jul 23 16:45:51 server1.example.com hr-app[24437]: 2016-07-23 16:45:51.626  INFO 24456 --- [       Thread-3] o.s.s.concurrent.Threa...cutor'
Jul 23 16:45:51 server1.example.com hr-app[24437]: 2016-07-23 16:45:51.627  INFO 24456 --- [       Thread-3] j.LocalContainerEntity...fault'
Jul 23 16:45:52 server1.example.com systemd[1]: Stopped hr-boot.
Hint: Some lines were ellipsized, use -l to show in full.
Eğer hr-boot servisini başlattığımızda mysqld servisi kapalı ise systemd önce mysqld servisini başlatacaktır:
[guru@server1 ~]$ sudo systemctl status mysqld
● mariadb.service - MariaDB database server
   Loaded: loaded (/usr/lib/systemd/system/mariadb.service; enabled; vendor preset: disabled)
  Drop-In: /etc/systemd/system/mariadb.service.d
           └─migrated-from-my.cnf-settings.conf
   Active: inactive (dead) since Sat 2016-07-23 16:53:30 EEST; 1min 25s ago
 Main PID: 12912 (code=exited, status=0/SUCCESS)
   Status: "MariaDB server is down"

Jul 23 11:10:16 server1.example.com mysqld[12912]: Version: '10.1.11-MariaDB'  socket: '/var/lib/mysql/mysql.sock'  port: 3306  Mar...Server
Jul 23 11:10:16 server1.example.com systemd[1]: Started MariaDB database server.
Jul 23 16:53:28 server1.example.com systemd[1]: Stopping MariaDB database server...
Jul 23 16:53:28 server1.example.com mysqld[12912]: 2016-07-23 16:53:28 140506222205696 [Note] /usr/sbin/mysqld: Normal shutdown
Jul 23 16:53:28 server1.example.com mysqld[12912]: 2016-07-23 16:53:28 140506222205696 [Note] Event Scheduler: Purging the queue. 0 events
Jul 23 16:53:28 server1.example.com mysqld[12912]: 2016-07-23 16:53:28 140505547527936 [Note] InnoDB: FTS optimize thread exiting.
Jul 23 16:53:28 server1.example.com mysqld[12912]: 2016-07-23 16:53:28 140506222205696 [Note] InnoDB: Starting shutdown...
Jul 23 16:53:30 server1.example.com mysqld[12912]: 2016-07-23 16:53:30 140506222205696 [Note] InnoDB: Shutdown completed; log seque...602918
Jul 23 16:53:30 server1.example.com mysqld[12912]: 2016-07-23 16:53:30 140506222205696 [Note] /usr/sbin/mysqld: Shutdown complete
Jul 23 16:53:30 server1.example.com systemd[1]: Stopped MariaDB database server.
Hint: Some lines were ellipsized, use -l to show in full.
[guru@server1 ~]$ sudo systemctl status hr-boot
● hr-boot.service - hr-boot
   Loaded: loaded (/usr/lib/systemd/system/hr-boot.service; enabled; vendor preset: disabled)
   Active: inactive (dead) since Sat 2016-07-23 16:45:52 EEST; 9min ago
 Main PID: 24437 (code=killed, signal=TERM)

Jul 23 16:45:51 server1.example.com hr-app[24437]: 2016-07-23 16:45:51.589  INFO 24456 --- [       Thread-3] o.s.m.s.b.SimpleBroker...ing...
Jul 23 16:45:51 server1.example.com hr-app[24437]: 2016-07-23 16:45:51.589  INFO 24456 --- [       Thread-3] o.s.m.s.b.SimpleBroker...ns]]]]
Jul 23 16:45:51 server1.example.com hr-app[24437]: 2016-07-23 16:45:51.590  INFO 24456 --- [       Thread-3] o.s.m.s.b.SimpleBroker...opped.
Jul 23 16:45:51 server1.example.com hr-app[24437]: 2016-07-23 16:45:51.616  INFO 24456 --- [       Thread-3] o.s.j.e.a.AnnotationMB...utdown
Jul 23 16:45:51 server1.example.com hr-app[24437]: 2016-07-23 16:45:51.621  INFO 24456 --- [       Thread-3] o.s.s.concurrent.Threa...cutor'
Jul 23 16:45:51 server1.example.com hr-app[24437]: 2016-07-23 16:45:51.625  INFO 24456 --- [       Thread-3] o.s.s.c.ThreadPoolTask...duler'
Jul 23 16:45:51 server1.example.com hr-app[24437]: 2016-07-23 16:45:51.626  INFO 24456 --- [       Thread-3] o.s.s.concurrent.Threa...cutor'
Jul 23 16:45:51 server1.example.com hr-app[24437]: 2016-07-23 16:45:51.626  INFO 24456 --- [       Thread-3] o.s.s.concurrent.Threa...cutor'
Jul 23 16:45:51 server1.example.com hr-app[24437]: 2016-07-23 16:45:51.627  INFO 24456 --- [       Thread-3] j.LocalContainerEntity...fault'
Jul 23 16:45:52 server1.example.com systemd[1]: Stopped hr-boot.
Hint: Some lines were ellipsized, use -l to show in full.
[guru@server1 ~]$ sudo systemctl start hr-boot
[guru@server1 ~]$ sudo systemctl status mysqld
 mariadb.service - MariaDB database server
   Loaded: loaded (/usr/lib/systemd/system/mariadb.service; enabled; vendor preset: disabled)
  Drop-In: /etc/systemd/system/mariadb.service.d
           └─migrated-from-my.cnf-settings.conf
   Active: active (running) since Sat 2016-07-23 16:55:14 EEST; 3s ago
 Main PID: 27975 (mysqld)
   Status: "Taking your SQL requests now..."
   CGroup: /system.slice/mariadb.service
           └─27975 /usr/sbin/mysqld

Jul 23 16:55:14 server1.example.com mysqld[27975]: 2016-07-23 16:55:14 140191628089472 [Note] InnoDB: Highest supported file format...acuda.
Jul 23 16:55:14 server1.example.com mysqld[27975]: 2016-07-23 16:55:14 140191628089472 [Note] InnoDB: 128 rollback segment(s) are active.
Jul 23 16:55:14 server1.example.com mysqld[27975]: 2016-07-23 16:55:14 140191628089472 [Note] InnoDB: Waiting for purge to start
Jul 23 16:55:14 server1.example.com mysqld[27975]: 2016-07-23 16:55:14 140191628089472 [Note] InnoDB:  Percona XtraDB (http://www.p...602918
Jul 23 16:55:14 server1.example.com mysqld[27975]: 2016-07-23 16:55:14 140191628089472 [Note] Plugin 'FEEDBACK' is disabled.
Jul 23 16:55:14 server1.example.com mysqld[27975]: 2016-07-23 16:55:14 140190882457344 [Note] InnoDB: Dumping buffer pool(s) not yet started
Jul 23 16:55:14 server1.example.com mysqld[27975]: 2016-07-23 16:55:14 140191628089472 [Note] Server socket created on IP: '::'.
Jul 23 16:55:14 server1.example.com mysqld[27975]: 2016-07-23 16:55:14 140191628089472 [Note] /usr/sbin/mysqld: ready for connections.
Jul 23 16:55:14 server1.example.com mysqld[27975]: Version: '10.1.11-MariaDB'  socket: '/var/lib/mysql/mysql.sock'  port: 3306  Mar...Server
Jul 23 16:55:14 server1.example.com systemd[1]: Started MariaDB database server.
Hint: Some lines were ellipsized, use -l to show in full.
[guru@server1 ~]$ sudo systemctl status hr-boot
 hr-boot.service - hr-boot
   Loaded: loaded (/usr/lib/systemd/system/hr-boot.service; enabled; vendor preset: disabled)
   Active: active (running) since Sat 2016-07-23 16:55:13 EEST; 12s ago
 Main PID: 27976 (hr-app)
   CGroup: /system.slice/hr-boot.service
           ├─27976 /bin/bash /home/guru/hr-app
           └─27999 /usr/bin/java -jar /home/guru/hr-app

Jul 23 16:55:25 server1.example.com hr-app[27976]: 2016-07-23 16:55:25.899  INFO 27999 --- [           main] o.s.w.s.handler.Simple...ndler]
Jul 23 16:55:25 server1.example.com hr-app[27976]: 2016-07-23 16:55:25.970  INFO 27999 --- [           main] .m.m.a.ExceptionHandle...andler
Jul 23 16:55:26 server1.example.com hr-app[27976]: 2016-07-23 16:55:26.039  INFO 27999 --- [           main] o.s.w.s.handler.Simple...ndler]
Jul 23 16:55:26 server1.example.com hr-app[27976]: 2016-07-23 16:55:26.456  INFO 27999 --- [           main] o.s.j.e.a.AnnotationMB...tartup
Jul 23 16:55:26 server1.example.com hr-app[27976]: 2016-07-23 16:55:26.472  INFO 27999 --- [           main] o.s.c.support.DefaultL...483647
Jul 23 16:55:26 server1.example.com hr-app[27976]: 2016-07-23 16:55:26.473  INFO 27999 --- [           main] o.s.m.s.b.SimpleBroker...ing...
Jul 23 16:55:26 server1.example.com hr-app[27976]: 2016-07-23 16:55:26.473  INFO 27999 --- [           main] o.s.m.s.b.SimpleBroker...ns]]]]
Jul 23 16:55:26 server1.example.com hr-app[27976]: 2016-07-23 16:55:26.474  INFO 27999 --- [           main] o.s.m.s.b.SimpleBroker...arted.
Jul 23 16:55:26 server1.example.com hr-app[27976]: 2016-07-23 16:55:26.579  INFO 27999 --- [           main] s.b.c.e.t.TomcatEmbedd...(http)
Jul 23 16:55:26 server1.example.com hr-app[27976]: 2016-07-23 16:55:26.589  INFO 27999 --- [           main] com.example.hr.HrBootA...2.792)
Hint: Some lines were ellipsized, use -l to show in full.
[guru@server1 ~]$ 
Servisimizin konfigürasyonu için çalıştırılabilir dosya ile aynı isimli ancak uzantısı .conf olan bir dosya oluşturuyoruz. Tipik olarak Java Sanal Makinasının ayarlarını yapmak için kullanırız:
[guru@server1 ~]$ ls -lh hr-app.conf 
-r-------- 1 guru guru 80 Jul 23 17:04 hr-app.conf
[guru@server1 ~]$ cat hr-app.conf 
JAVA_OPTS="-Xms2048m -Xmx2048m -Xcomp -XX:+UseG1GC -XX:+UseStringDeduplication"
jcmd komutunu kullanarak, servis olarak çalıştırdığımız Java prosesinin, Java Sanal Makinası parametrelerinin hr-app.conf dosyasında JAVA_OPTS ile tanımladığımız değerlere sahip olduğunu doğrulayabiliriz:
[guru@server1 ~]$ sudo jcmd 
33366 sun.tools.jcmd.JCmd
29067 /home/guru/hr-app
[guru@server1 ~]$ sudo jcmd  29067 VM.command_line
29067:
VM Arguments:
jvm_args: -Dsun.misc.URLClassPath.disableJarChecking=true -Xms2048m -Xmx2048m -Xcomp -XX:+UseG1GC -XX:+UseStringDeduplication 
java_command: /home/guru/hr-app
java_class_path (initial): /home/guru/hr-app
Launcher Type: SUN_STANDARD
[guru@server1 ~]$ 

Tuesday, July 19, 2016

Spring Çatısında Bileşen Yapılandırma

Yazılım geliştirmek zor iş. Bu zorluğun temel nedeni, yazılımın soyut bir varlık olmasıdır. İnsanlar genel olarak soyut varlıklar ile çalışma konusunda pek başarılı değildir. Bugün ilkokulda anlatılmaya başlanan küme teorisi 18. yüzyılda geliştirilmiştir (Georg Cantor (1845-1918)). Saymayı insanlık çok geç keşfetmiştir. Saymak son derece soyut bir kavramdır. Ama biz öğrenmeyi beş duyu organımızı kullanarak gerçekleştiriyoruz. Bebeklikten itibaren duyarak, görerek, dokunarak öğreniyoruz.

Yazılıma dokunabilir miyiz? Yazılımın bir kokusu ya da bir rengi var mıdır? Yazılımı yere sert bir zemine düşürdüğümüzde nasıl ses çıkarır?
Kaliteli bir nesneye dayalı tasarım için genel geçer bir yöntem bulunmayabilir. Nesneye dayalı yaklaşım tek başına kalite bir yazılım geliştirmeyi garanti etmiyor. İzlenmesi gereken bazı ilkeler var. Bu ilkeler anlaşılır ve doğru bir şekilde uygulanabilirse, potansiyel olarak ortaya kaliteli bir çözüm çıkabilir. Sadece bu prensipleri bilmek yetmez elbet! Daha pek çok bilgiye muhtacız! Örneğin, bu prensipleri yoğun olarak kullanan "tasarım kalıplarını" da bilseniz çok iyi olur. Her zaman karşımıza özgün problemler çıkmıyor. Çoğu zaman problemler tekrar ediyor: bazı "dertlerimiz" ortak ya da "hüzünlerimiz" evrensel. Bu tekrar eden problemlerin sınanmış, uygulandıklarında kazanımlarını ve yitimlerini bildiğimiz çözümleri var. Yazılımda kalıplar işte bu çözümlerin bir katalog haline getirilmesinden başka bir şey değil! Bir kalıbı çalışırken, ilke olarak o kalıbın tanımlandığı bağlama bakmanız gerekir: tasarım problemi, paralel programlama problemi, gerçek-zaman problemi, ağ programlama problemi, büyük veri problemi gibi. Problemin çözümünü doğru katalogda aramalısınız! Kalıpların yazılımda kullanımı Dört Kafadarın (Gang of Four) "Design Patterns: Elements of Reusable Object-Oriented Software" kitabı ile gerçekleşmiştir. Bu dört kafadar sırayla Erich Gamma, Richard Helm, Ralph Johnson ve John Vlissides'den oluşuyor. John Vlissides dışındaki yazarlar hayattalar. Kitabın basım yılı 1994. Yıllar önce John Vlissides'in girişimiyle kitabın ikinci baskısı için bir araya gelmiş olsalar da, kısa bir süre sonra John Vlissides'in ölümü ile bu girişim amacına ulaşamamıştır. Yirmi yılı aşkın bir süre olmasına rağmen içindeki bilgiler hala geçerliliğini koruyor ve kitap satmaya devam ediyor. Bilişim sektöründe bir kitabın ömrü ortalama beş yıl olduğunu düşünürsek, kitabın içindeki bilgilerin değerini daha kolay kavrayabiliriz. Kitapta 23 adet tasarım kalıbı bulunuyor. Yazarlar bu kalıpları geliştirmiş değiller, sadece bilinen bu teknikleri bir sistematik içinde formal olarak tanımlamışlardır. Yazarların bu kalıpları daha kolay irdeleyebilmek için 3 farklı kategoriye ayırdıklarını görüyoruz: 
  • Yaratımsal (=Creational) Kalıplar
  • Yapısal (=Structural) Kalıplar
  • Davranışsal (=Behavioral) Kalıplar
Daha sonra Erich Gamma'nın da itiraf edeceği gibi bu sınıflandırma gerçekten çok kötü bir sınıflandırmadır. Eğer ikinci baskıyı yapabilselerdi kitapta değiştirecekleri ilk başlık bu sınıflandırma olacaktı. Bu yüzden verdiğim tasarım kalıbı eğitimlerinde bu sınıflandırmayı kullanmam. Kullandığım sınıflandırma için bu bağlantıdaki eğitimin içeriğine bakabilirsiniz.
Bu yazının amacı aslında tasarım kalıplarını çalışmak değil. Bu 23 kalıbın neredeyse tamamında kullanımını gördüğümüz bir prensibi inceleyerek devam etmek istiyorum: Yüksek Uyum Düşük Bağlantı (High Cohesion Low Coupling).
Uyum ile bir sınıfın metodları arasındaki uyum anlatılmak istenir. Bunun yüksek olmasını isteriz. Eğer sınıfın metodlarının ortak bir hedefi varsa ya da ortak bir problemin çözümünü amaçlıyorlarsa sınıfın metodları arasındaki uyum yüksektir. Sınıfın kafası karışık olmamalıdır. Sınıfın tek bir sorumluluğu olmalıdır ya da tek bir konuda uzman olmalıdır ve sınıfın metodları da bu uzmanlık ile ilgili olmalıdır. Sınıf her problemi çözmeye çalışmamalıdır! Evde televizyon, su tesisatı, bulaşık makinası ya da klima bozulduğunda nasıl biz tamir etmiyorsak, sınıfın metodları da her problem ile ilgilenmemelidir. 
Değişimi yönetmek için sınıfın metodları arasında yüksek uyum olmasını istiyoruz:
Şekil-1 Düşük uyumlu bir sınıf
Şekil-1'de her işini kendi görmeye çalışan bir sınıf verilmiş. Bu sınıfın birden fazla sorumluluğu, birden fazla uzmanlığı bulunuyor. Bir sınıfı değiştirmek için tek bir nedenimizin olması gerekir. Bir sınıfla ilgili değişiklikler tek bir eksende olmalıdır. Daha iyi bir çözüm Şekil-2'de verilmiştir. Burada her bir sınıfın tek bir sorumluluğu, tek bir uzmanlığı bulunuyor.
Şekil-2 Yüksek uyumlu sınıflar
Sınıf metodları arasındaki uyumu arttırmaya çalışınca bir problem belirmeye başlıyor. Sınıfların hizmet verme ve hizmet alma rolleri bulunur. Bir sınıf metodları üzerinden hizmet verir, bir problemi çözer, çözüme katkıda bulunur. Benzer şekilde bir sınıfın metodu uzmanlığı dışında bir iş yapması gerektiğinde, bu işi uzmanına ihale (=delegate) eder. Böylelikle bu iki sınıf arasında bir bağımlılık oluştururuz. İki sınıf arasındaki bağımlılığın düşük olmasını arzu ederiz. İki sınıf arasında oluşturabileceğimiz en güçlü bağımlılık kalıtım (=inheritance) ile gerçekleşir. Temel sınıfta yapacağımız değişiklik kalıtım yolu ile bu sınıftan türetilen hangi seviyeden olursa olsun tüm sınıflara yansıyacaktır. Kalıtım yolu ile oluşturulan bağımlılıklar yazılımı kırılgan yapar, değişime karşı bir kırılganlıktır bu. Yazılımı biraz esnetmek istediğinizde kırılacaktır. Bu nedenle kalıtımı ancak problem uzayındaki sınıflar (=domain classes) arasında düşünürüz. Çözüm sınıflarında (=solution classes) kalıtım kullanılmaz. Genel olarak bir sınıf diğer bir sınıfı tanımında görüyorsa, örneğin Java programlama dili için import ifadelerinde adı geçiyorsa, bu iki sınıf arasında bir bağ vardır. Bu bağı azaltmanın bir yolu var mı? Dört kafadarın kitabında yer alan 23 çözümde de gördüğümüz bir yaklaşım var. Hizmet alan ve hizmet veren iki sınıf biri birlerini doğrudan görmesinler. Aralarına bir sözleşme koyalım. Hizmet veren sınıf bu sözleşmeye göre hizmet versin, bu sözleşmeyi tanısın . Benzer şekilde hizmet alan sınıfta bu sözleşmeyi bilsin, bu sözleşmeye göre hizmet alsın. Sözleşme soyut sınıf (=abstract class) olarak gerçeklenebileceği gibi Java gibi programlama dillerinde gördüğümüz arayüz (=interface) olarak da gerçeklenebilir. Sözleşmeye bağlı olmayı sorun etmeyiz. Böylelikle hizmet alan ve veren sınıflar biri birilerini doğrudan görmezler. Soyut sınıfı ya da arayüzü görürler. Bu şekilde sınıflar arasında düşük bir bağ kurmuş olduk. Bu gevşek bağ sayesinde, yazılımı isterlerdeki değişimlere uydurmak üzere esnetmek daha kolay olacaktır. 
Yukarıdaki nedenlerden dolayı Şekil-3'de verilen tasarım düşük kaliteli bir çözüm sunar: Tek sorumluluk ilkesi ihlal edildiği için sınıf içi metodlar arası uyum düşük, sınıflar arasında ise kalıtımdan dolayı sıkı bağ kurulmuş.
Şekil-3 Düşük kaliteli bir çözüm: Değişimi yönetmek ve test etmek zor, çözüm tekrar kullanılabilir değil.
Şekil-4'de ise nispeten daha iyi bir çözüm var. Kalıtım yerine içerme (=composition) ilişkisi tercih edilmiş. Ancak içerme ilişkisinde bağımlılık bir somut sınıf üzerinden kurulmuş. Eğer bu eksende ileride bir değişiklik öngörülüyor ise bu eksendeki değişimi yönetebilmek için tasarıma bir esneklik kazandırılmalıdır.
Şekil-4 Daha kaliteli bir çözüm: Test etmek kolay, tekrar kullanılabilir.
Bu esnekliği ise hizmet alan ve veren sınıflar arasına bir arayüz atarak sağlıyoruz. İçerme ilişkisinde şimdi somut bir sınıf değil soyut bir arayüz yer alıyor (Şekil-5):
Şekil-5 Değişimi yönetebileceğimiz kaliteli bir çözüm
Ancak şimdi başka bir problem belirmeye başladı. Çalışma zamanında hizmet alacak sınıfa hangi somut sınıftan hizmet alacağını söylememiz gerekiyor. Bunu kim söyleyecek? Hizmet alan ya da hizmet veren sınıflara bu sorumluluğu vermememiz gerekir. Aksi halde yine aynı tuzağa düşeriz ve sınıflar arasında sıkı bir bağ kurmuş oluruz. Bu sorumluluğu üçüncü bir sınıfa vermemiz gerekir. Çözüm için soyut fabrika kalıbından yararlanabiliriz. Gerçek dünyada da bir ürünü üretmek zorsa, ürün karmaşık ise fabrika kuruyoruz: araba fabrikası, uçak fabrikası gibi. Gerçekten de araba ve uçak üretmek zor, çünkü arabayı oluşturan bileşenler arasında karmaşık bir bağ var. Parçaların belirli bir sırada yerleştirilmesi gerekiyor. Uçakta durum daha da karmaşık bir hal alıyor. O yüzden fabrikalar var. Gerçek bir projede çok sayıda bu türden yazılmış fabrika sınıfları yer alır. Keşke tek bir fabrika olsaydı ve tüm ürünleri üretebilseydi. Spring çatısı bunu başarıyor. Tüm karmaşık ve ileride arayüzler (=interface) üzerinden bağımlılık kurduğumuz somut sınıfların  değişmesi gerektiğinde, bu değişikliği kodlamadan ele almamıza olanak sağlıyor. Şimdi bu fabrikanın içini gezeceğiz ve nasıl üretim yaptığına bakacağız. Bu fabrikanın ürettiklerini bileşen olarak adlandıracağız. Özel olarak Spring bileşenlerine Spring Bean adını veriyoruz. Şimdi fabrikanın nasıl üretim yaptığını anlamak için kullanacağımız alan modelini (=domain model) tanıyalım
Employee.java:
package com.example.hr.domain;

public class Employee {
    private String identityNo;
    private String firstName;
    private String lastName;
    private int birthYear;
    private double salary;

    public Employee() {
    }

    public Employee(String identityNo, String firstName, String lastName, int birthYear, double salary) {
        this.identityNo = identityNo;
        this.firstName = firstName;
        this.lastName = lastName;
        this.birthYear = birthYear;
        this.salary = salary;
    }

    public String getIdentityNo() {
        return identityNo;
    }

    public void setIdentityNo(String identityNo) {
        this.identityNo = identityNo;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public int getBirthYear() {
        return birthYear;
    }

    public void setBirthYear(int birthYear) {
        this.birthYear = birthYear;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Employee employee = (Employee) o;

        return identityNo.equals(employee.identityNo);

    }

    @Override
    public int hashCode() {
        return identityNo.hashCode();
    }

    @Override
    public String toString() {
        return "Employee{" +
                "identityNo='" + identityNo + '\'' +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", birthYear=" + birthYear +
                ", salary=" + salary +
                '}';
    }
}
Employee sınıfı bir firmada çalışanları modelliyor. Sınıfın identityNo özelliği çalışanların tek türlü ayırt edilmesini sağlayan tekil bir alan ve TC Kimlik Numarası değerini taşıyor. Employee sınıfından nesnelerin aynı zamanda kalıcı olmasını isteriz. Genellikle veritabanında saklarız ancak bu örnekte bellekte saklamayı tercih edeceğiz. Nesnelerin nerede kalıcı olacağı tercihimiz değişkenlik gösterebilir. Bu nedenle kalıcılık tercihimizi bir arayüzün arkasında gizleyeceğiz:
EmployeeRepository.java:
package com.example.hr.repository;

import com.example.hr.domain.Employee;

import java.util.Collection;
import java.util.Optional;

public interface EmployeeRepository {
    Optional<Employee> find(String identityNo);
    Optional<Employee> delete(String identityNo);
    Optional<Employee> add(Employee employee);
    Optional<Employee> update(Employee employee);
    Collection<Employee> findAll();
    Collection<Employee> findTop10OrderBySalary();
    Collection<Employee> findTop10OrderByAge();
}
Kalıcılık için belleği kullandığımızda çözümü InMemoryEmployeeRepository sınıfında oluşturacağız:
package com.example.hr.repository.impl;

import com.example.hr.domain.Employee;
import com.example.hr.repository.EmployeeRepository;
import com.example.hr.service.IdentityService;

import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

public class InMemoryEmployeeRepository implements EmployeeRepository {
    private Map<String,Employee> employees;
    private IdentityService identityService;

    public void setEmployees(Map<String, Employee> employees) {
        this.employees = employees;
    }

    public void setIdentityService(IdentityService identityService) {
        this.identityService = identityService;
    }

    @Override
    public Optional<Employee> find(String identityNo) {
        Employee employee;
        return (((employee = employees.get(identityNo)) != null) || employees.containsKey(identityNo))
                ? Optional.of(employee)
                : Optional.empty();
    }

    @Override
    public Optional<Employee> delete(String identityNo) {
        Employee employee;
        employee= employees.remove(identityNo);
        return employee!=null
                ? Optional.of(employee)
                : Optional.empty();
    }

    @Override
    public Optional<Employee> add(Employee employee) {
        if (employee.getIdentityNo()==null)
            employee.setIdentityNo(identityService.createIdentity());
        String identityNo = employee.getIdentityNo();
        Employee existing;
        return ((existing= employees.putIfAbsent(identityNo,employee)) == null )
            ? Optional.of(employee)
            : Optional.empty();
    }

    @Override
    public Optional<Employee> update(Employee employee) {
        String identityNo = employee.getIdentityNo();
        Employee existing;
        return ((existing= employees.replace(identityNo,employee)) != null )
                ? Optional.of(employee)
                : Optional.empty();
    }

    @Override
    public Collection<Employee> findAll() {
        return employees.values();
    }

    @Override
    public Collection<Employee> findTop10OrderBySalary() {
        return employees.values().stream()
                .sorted((e1,e2) -> Double.compare(e2.getSalary(),e1.getSalary()))
                .limit(10)
                .collect(Collectors.toList());
    }

    @Override
    public Collection<Employee> findTop10OrderByAge() {
        return employees.values().stream()
                .sorted((e1,e2) -> Integer.compare(e1.getBirthYear(),e2.getBirthYear()))
                .limit(10)
                .collect(Collectors.toList());    }
}
Ancak InMemoryEmployeeRepository sınıfının bir bağımlılığı bulunuyor. Tekil TC Kimlik Numarası üretme probleminin çözümünü  InMemoryEmployeeRepository sınıfına vermek istemedik. Her sınıfın tek bir uzmanlığı olsun isteriz. Bu nedenle bu uzmanlığı başka bir sınıfa vereceğiz:
package com.example.hr.service.impl;

import com.example.hr.service.IdentityService;

import java.util.Collection;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

public class SimpleIdentityService implements IdentityService {
    private Set<String> identities= new HashSet<>();
    private Random random= new Random();

    @Override
    public String createIdentity() {
        String candidateIdentity;
        do{
            candidateIdentity= getIdentity();
        }while(identities.contains(candidateIdentity));
        return candidateIdentity;
    }

    @Override
    public Collection<String> createIdentity(int size) {
        Collection<String> uniqueIdentities= new HashSet<>();
        while (uniqueIdentities.size()<size)
              uniqueIdentities.add(createIdentity());
        return uniqueIdentities;
    }

    @Override
    public String registerIdentity(String identity) {
        if (identities.contains(identity))
            return createIdentity();
        identities.add(identity);
        return identity;
    }

    private String getIdentity() {
        int digits[]=new int[11];
        digits[0] = getRandomDigit(1,9);
        for(int i = 1; i < 9; i++) {
            digits[i] = getRandomDigit(0,9);
        }
        digits[9] = ((digits[0] + digits[2] + digits[4] + digits[6] + digits[8]) * 7 -
                (digits[1] + digits[3] + digits[5] + digits[7])) % 10;
        digits[10] = (digits[0] + digits[1] + digits[2] + digits[3] + digits[4] + digits[5] +
                digits[6] + digits[7] + digits[8] + digits[9]) % 10;
        StringBuilder identity= new StringBuilder();
        for (int digit: digits)
           identity.append(digit);
        return identity.toString();
    }

    private int getRandomDigit(int min, int max) {
        return random.nextInt(max-min+1)+min;
    }

}
Bu durumda InMemoryEmployeeRepository ile SimpleIdentityService sınıfı arasında hizmet alma-verme şeklinde bir bağımlılık oluştu. Bu bağımlılığı gevşek tutabilmek ve değişikliği yönetebilmek için bağımlılığı bir arayüz üzerinden tanımlıyoruz:
package com.example.hr.service;

import java.util.Collection;

public interface IdentityService {
    String createIdentity();
    Collection<String> createIdentity(int size);
    String registerIdentity(String identity);
}
Fabrikanın üretim yapabilmesi için fabrika ayarlarını ve bileşenleri tanımlamamız gerekiyor. Bunun için değişik yaklaşımlar bulunuyor:

1. XML dokümanında bileşenleri yapılandırmak


<?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="empRepo"
          class="com.example.hr.repository.impl.InMemoryEmployeeRepository">
        <property name="identityService" ref="identitySrvc"/>
        <property name="employees">
            <map>
                <entry key="55724320514" value-ref="jack"/>
                <entry key="16522830254" value-ref="kate"/>
            </map>
        </property>
    </bean>
    <bean id="identitySrvc"
          class="com.example.hr.service.impl.SimpleIdentityService">
    </bean>
    <bean id="55724320514" name="jack"          class="com.example.hr.domain.Employee">
        <property name="identityNo" value="55724320514" />
        <property name="firstName" value="Jack" />
        <property name="lastName" value="Shephard" />
        <property name="birthYear" value="1971" />
        <property name="salary" value="125000" />
    </bean>
    <bean id="16522830254" name="kate"          class="com.example.hr.domain.Employee">
        <property name="identityNo" value="16522830254" />
        <property name="firstName" value="Kate" />
        <property name="lastName" value="Austen" />
        <property name="birthYear" value="1980" />
        <property name="salary" value="75000" />
    </bean>
</beans>
Bu konfigürasyonu test etmek için basit bir Spring konsol uygulaması kodlayacağız:
package com.example.hr.app;

import com.example.hr.domain.Employee;
import com.example.hr.repository.EmployeeRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Application {
    static final Logger logger = LoggerFactory.getLogger(Application.class);
    public static void main(String[] args) {
        ConfigurableApplicationContext context=
                new ClassPathXmlApplicationContext("spring-config.xml");
        EmployeeRepository employeeRepository=
                context.getBean(EmployeeRepository.class);
        employeeRepository.add(new Employee(null,"James","Sawyer",1972,50000));
        employeeRepository.findAll()
                          .forEach( employee -> logger.info(employee.toString()) );
        context.close();
    }
}
Uygulamayı çalıştırdığımızda aşağıdaki ekran görüntüsü ile karşılaştık:
C:\opt64\java\jdk1.8.0_77\bin\java -Didea.launcher.port=7532 -Didea.launcher.bin.path=C:\opt\idea-2016.1\bin -Dfile.encoding=UTF-8 -classpath C:\opt64\java\jdk1.8.0_77\jre\lib\charsets.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\deploy.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\ext\access-bridge-64.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\ext\cldrdata.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\ext\dnsns.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\ext\jaccess.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\ext\jfxrt.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\ext\localedata.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\ext\nashorn.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\ext\sunec.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\ext\sunjce_provider.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\ext\sunmscapi.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\ext\sunpkcs11.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\ext\zipfs.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\javaws.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\jce.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\jfr.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\jfxswt.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\jsse.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\management-agent.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\plugin.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\resources.jar;C:\opt64\java\jdk1.8.0_77\jre\lib\rt.jar;C:\var\workspace\sources-binkurt-blogspot\stufy-spring-component\target\classes;C:\Users\bkurt\.m2\repository\org\springframework\spring-context\4.2.5.RELEASE\spring-context-4.2.5.RELEASE.jar;C:\Users\bkurt\.m2\repository\org\springframework\spring-aop\4.2.5.RELEASE\spring-aop-4.2.5.RELEASE.jar;C:\Users\bkurt\.m2\repository\aopalliance\aopalliance\1.0\aopalliance-1.0.jar;C:\Users\bkurt\.m2\repository\org\springframework\spring-beans\4.2.5.RELEASE\spring-beans-4.2.5.RELEASE.jar;C:\Users\bkurt\.m2\repository\org\springframework\spring-core\4.2.5.RELEASE\spring-core-4.2.5.RELEASE.jar;C:\Users\bkurt\.m2\repository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar;C:\Users\bkurt\.m2\repository\org\springframework\spring-expression\4.2.5.RELEASE\spring-expression-4.2.5.RELEASE.jar;C:\Users\bkurt\.m2\repository\ch\qos\logback\logback-classic\1.1.7\logback-classic-1.1.7.jar;C:\Users\bkurt\.m2\repository\ch\qos\logback\logback-core\1.1.7\logback-core-1.1.7.jar;C:\Users\bkurt\.m2\repository\org\slf4j\slf4j-api\1.7.20\slf4j-api-1.7.20.jar;C:\opt\idea-2016.1\lib\idea_rt.jar com.intellij.rt.execution.application.AppMain com.example.hr.app.Application
Jul 19, 2016 1:39:12 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@515f550a: startup date [Tue Jul 19 13:39:12 EEST 2016]; root of context hierarchy
Jul 19, 2016 1:39:12 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring-config.xml]
13:39:12.824 [main] INFO com.example.hr.app.Application - Employee{identityNo='55724320514', firstName='Jack', lastName='Shephard', birthYear=1971, salary=125000.0}
Jul 19, 2016 1:39:12 PM org.springframework.context.support.ClassPathXmlApplicationContext doClose
13:39:12.828 [main] INFO com.example.hr.app.Application - Employee{identityNo='16522830254', firstName='Kate', lastName='Austen', birthYear=1980, salary=75000.0}
INFO: Closing org.springframework.context.support.ClassPathXmlApplicationContext@515f550a: startup date [Tue Jul 19 13:39:12 EEST 2016]; root of context hierarchy
13:39:12.828 [main] INFO com.example.hr.app.Application - Employee{identityNo='84371896950', firstName='James', lastName='Sawyer', birthYear=1972, salary=50000.0}

Process finished with exit code 0
Bu konfigürasyonda InMemoryEmployeeRepository ile SimpleIdentityService sınıfları arasındaki bağımlılığı elle çözdük:
<bean id="empRepo"
          class="com.example.hr.repository.impl.InMemoryEmployeeRepository">
        <property name="identityService" ref="identitySrvc"/>
    . . .
</bean>
Bağımlığın çözülmesi işlemine bağlama (=wiring) adını veriyoruz. Bu bağımlılığı çözme sorumluluğunu Spring çatısına verebiliriz:
<bean id="empRepo"
          autowire="byType"
          class="com.example.hr.repository.impl.InMemoryEmployeeRepository">
</bean>
Bu işleme ise otomatik bağlama (=autowiring) adını veriyoruz. Otomatik bağlama ada göre, tipe göre ya da kurucuya göre gerçekleştirilebilir. Burada iki bağımlılığı çözdük: identityService ve employees. employee bir Map türünde öznitelik olduğu için spring-config.xml knofigürasyon dosyasında Employee tipinde bileşenler aradı ve iki tane buldu: jack ve kate kimlikli bileşenler. Bu bileşenleri Map veri yapısına id anahtarı ile ekledi. Herhangi bir bileşen iki farklı şekilde yaratılabilir:
  • Varsayılan kurucu ile önce yaratılır daha sonra set metodları ile ilklendirilir:
<bean id="55724320514" name="jack"
      class="com.example.hr.domain.Employee">
  <property name="identityNo" value="55724320514" />
  <property name="firstName" value="Jack" />
  <property name="lastName" value="Shephard" />
  <property name="birthYear" value="1971" />
  <property name="salary" value="125000" />
</bean>
Burada önce "new Employee()" ile varsayılan kurucu fonksiyon çalışır ve nesne yaratılır, ardından "property name" ile verilen her bir öznitelik için setter metodu çalışır: setIdentityNo("55724320514"), setFirstName("Jack"), setLastName("Shephard"), setBirthYear(1971) ve setSalary(125000). Employee sınıfı kodlanırken Java Beans isimlendirme kuralına uyulmalıdır. Neyse ki kullanılan geliştirme araçlarında otomatik kod ürettirildiğinde bu kurala göre kod üretirler. Buna göre firstName isimli öz nitelik için getter metodu getFirstName() ve setter motodu ise setFirstName() olmalıdır. Özniteliğin ilk karakterinin büyüdüğüne dikkat ediniz. boolean tipinde alanlar için get öneki yerine is öneki gelebilir.
  • Parametre alan kurucu fonksiyon ile ilklendirilererek yaratılır. 
<bean id="84371896950" name="james"      class="com.example.hr.domain.Employee">
  <constructor-arg name="identityNo" value="84371896950" />
  <constructor-arg name="firstName" value="James" />
  <constructor-arg name="lastName" value="Sawyer" />
  <constructor-arg name="birthYear" value="1972" />
  <constructor-arg name="salary" value="50000" />
</bean>
Burada nesne "new Employee("84371896950","James","Sawyer",1972,50000)" ile parametreli kurucu fonksiyonu ile yaratılır ve ilklendirilmiş olur.
Her iki yöntemin de kendine göre kazanımı ve yitimi bulunmaktadır. setter metodlarını kullanarak ilklendirmenin kazanımı sınıfın kalabalık özniteliğinin olması durumunda okumayı kolaylaştırmasıdır. Sınıf geliştikçe eklenen özniteliklerin kurucu fonksiyon fonksiyon kalabalığı yaratır. Bir öznitelik için setter çağrısı unutulursa, yürütme zamanında muhtemel bir NullPointerException alınacaktır. Kurucu fonksiyonları uygulama programcısını tüm öznitelikler için değer vermeye ve ilklendirmeye zorlar. Ancak sınıflar arasında çevresel bir bağımlılık varsa kurucu fonsiyonlar üzerinden ilklendiremezsiniz. Yukarıda verilen XML konfigürasyonu ile yapılan tanımın aynısını p-schema ve c-schema ile de gerçekleştirebiliriz:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" >
    <bean id="empRepo"
          autowire="byType"
          class="com.example.hr.repository.impl.InMemoryEmployeeRepository">
    </bean>
    <bean id="identitySrvc"
          class="com.example.hr.service.impl.SimpleIdentityService">
    </bean>
    <bean id="55724320514" name="jack"          class="com.example.hr.domain.Employee"
          p:identityNo="55724320514"
          p:firstName="Jack"
          p:lastName="Shephard"
          p:birthYear="1971"
          p:salary="125000" />
    <bean id="16522830254" name="kate"          class="com.example.hr.domain.Employee"
          c:identityNo="16522830254"
          c:firstName="Kate"
          c:lastName="Austen"
          c:birthYear="1980"
          c:salary="75000"
          />
</beans>
p-schema ve c-schema ile öznitelik ve kurucu üzerinden yapılan XML tabanlı yapılandırma için daha yalın bir gösterimden daha fazla bir kazanımı bulunmamaktadır.
Spring bileşenlerinin ne zaman yaratılacağı ve ne kadar süre yaşayacaklarını erim (=scope) tanımları belirler. Spring çatısında var sayılan erim "singleton"'dır. Fabrikayı açar açmaz
ConfigurableApplicationContext context=
                new ClassPathXmlApplicationContext("spring-config.xml");
XML konfigürasyon dosyası içinde tanımladığımız tüm bileşenler üretilir. Biz fabrikadan bileşeni istediğimizde 
EmployeeRepository employeeRepository=
                context.getBean(EmployeeRepository.class);
bileşen çoktan üretilmiştir. singleton eriminde bileşenler açılışta üretildikten sonra uygulamanın sonuna kadar var olurlar. Fabrika, her EmployeeRepository tipinden bir bileşen istediğimizde, bize başta bir kere ürettiği bileşeni dönecektir. Eğer bu singleton nesnesinin üretimi işlemci ya da bellek kullanımı açısından maliyetliyse ve üstelik her senaryoda ihtiyaç duyulmuyorsa, yaratılmasını ilk getBean() çağrısına kadar geciktirebilir:
<bean id="empRepo"
          autowire="byType"
          lazy-init="true"
          class="com.example.hr.repository.impl.InMemoryEmployeeRepository">
</bean>
Spring çatısındaki dört erim tanımı bulunmaktadır:
  • singleton
  • prototype
  • request
  • session
prototype eriminde, bileşenden her istediğimizde yeni bir kopyası üretilir. request ve session erimleri web uygulaması geliştirirken kullanabileceğimiz erimlerdir. erimi değiştirmek için bean tanımında scope özniteliğini kullanıyoruz:
<bean id="identitySrvc"
      scope="prototype"
      class="com.example.hr.service.impl.SimpleIdentityService">
</bean>
Erim prototype iken lazy-init tanımının bir işlevi bulunmaz.

2. Minimum bir XML dokümanı ve not düşerek bileşenleri yapılandırmak

Spring bileşenlerinin yapılandırılması için kullanılabilecek ikinci yöntem ise minimum bir XML konfigürasyon dokümanı ve daha çok Java notlarını kullanmaktır. XML ile yaptığımız tüm tanımlamaları Java notlarını kullanarak gerçekleştirebiliriz. Önce InMemoryEmployeeRepository ve SimpleIdentityService sınıflarının birer bileşen olduklarını göstermek üzere sınıflara not düşüyoruz:
SimpleIdentityService.java:
package com.example.hr.service.impl;

import com.example.hr.service.IdentityService;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

@Service
@Scope("singleton")
public class SimpleIdentityService implements IdentityService {
    private Set<String> identities= new HashSet<>();
    private Random random= new Random();

    @Override
    public String createIdentity() {
        String candidateIdentity;
        do{
            candidateIdentity= getIdentity();
        }while(identities.contains(candidateIdentity));
        return candidateIdentity;
    }

    @Override
    public Collection<String> createIdentity(int size) {
        Collection<String> uniqueIdentities= new HashSet<>();
        while (uniqueIdentities.size()<size)
              uniqueIdentities.add(createIdentity());
        return uniqueIdentities;
    }

    @Override
    public String registerIdentity(String identity) {
        if (identity==null || identities.contains(identity))
            return createIdentity();
        identities.add(identity);
        return identity;
    }

    private String getIdentity() {
        int digits[]=new int[11];
        digits[0] = getRandomDigit(1,9);
        for(int i = 1; i < 9; i++) {
            digits[i] = getRandomDigit(0,9);
        }
        digits[9] = ((digits[0] + digits[2] + digits[4] + digits[6] + digits[8]) * 7 -
                (digits[1] + digits[3] + digits[5] + digits[7])) % 10;
        digits[10] = (digits[0] + digits[1] + digits[2] + digits[3] + digits[4] + digits[5] +
                digits[6] + digits[7] + digits[8] + digits[9]) % 10;
        StringBuilder identity= new StringBuilder();
        for (int digit: digits)
           identity.append(digit);
        return identity.toString();
    }

    private int getRandomDigit(int min, int max) {
        return random.nextInt(max-min+1)+min;
    }

}
InMemoryEmployeeRepository.java:
package com.example.hr.repository.impl;

import com.example.hr.domain.Employee;
import com.example.hr.repository.EmployeeRepository;
import com.example.hr.service.IdentityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

@Repository
@Scope("singleton")
public class InMemoryEmployeeRepository implements EmployeeRepository {
    @Resource
    private Map<String,Employee> employees;
    @Autowired
    private IdentityService identityService;

    public void setEmployees(Map<String, Employee> employees) {
        this.employees = employees;
    }

    public void setIdentityService(IdentityService identityService) {
        this.identityService = identityService;
    }

    @Override
    public Optional<Employee> find(String identityNo) {
        Employee employee;
        return (((employee = employees.get(identityNo)) != null) || employees.containsKey(identityNo))
                ? Optional.of(employee)
                : Optional.empty();
    }

    @Override
    public Optional<Employee> delete(String identityNo) {
        Employee employee;
        employee= employees.remove(identityNo);
        return employee!=null
                ? Optional.of(employee)
                : Optional.empty();
    }

    @Override
    public Optional<Employee> add(Employee employee) {
        String identityNo = employee.getIdentityNo();
        identityNo= identityService.registerIdentity(identityNo);
        employee.setIdentityNo(identityNo);
        Employee existing;
        return ((existing= employees.putIfAbsent(identityNo,employee)) == null )
            ? Optional.of(employee)
            : Optional.empty();
    }

    @Override
    public Optional<Employee> update(Employee employee) {
        String identityNo = employee.getIdentityNo();
        Employee existing;
        return ((existing= employees.replace(identityNo,employee)) != null )
                ? Optional.of(employee)
                : Optional.empty();
    }

    @Override
    public Collection<Employee> findAll() {
        return employees.values();
    }

    @Override
    public Collection<Employee> findTop10OrderBySalary() {
        return employees.values().stream()
                .sorted((e1,e2) -> Double.compare(e2.getSalary(),e1.getSalary()))
                .limit(10)
                .collect(Collectors.toList());
    }

    @Override
    public Collection<Employee> findTop10OrderByAge() {
        return employees.values().stream()
                .sorted((e1,e2) -> Integer.compare(e1.getBirthYear(),e2.getBirthYear()))
                .limit(10)
                .collect(Collectors.toList());    }
}
Spring çatısında bir sınıfı bileşen olarak işaretlemek için üç not bulunmaktadır:
  • @Component
  • @Service
  • @Repository
Java EE 6 ile gelen Context and Dependency Injection (CD) şartnamesinde ise bir sınıfı bileşen olarak tanımlamak için tek bir not bulunur: @Named. Buna karşılık olarak Spring çatısında yer alan üç notun aslında biri birinden farkı yoktur. Her bir not bileşenin hangi katmanın bir bileşeni olduğunu göstermek dışında özel bir anlamı bulunmaz. @Component ile damgalanan bileşen sunum katmanının bir bileşenidir. @Service ile damgalanan bileşen iş mantığı katmanının ve son olarak @Repository ile damgalanan bileşen tümleştirme katmanının bir bileşeni olduğunu betimler. Hepsini sadece @Component notunu düşerek de bileşen olarak tanımlayabilirdik. Spring MVC ile buna iki tane daha not eklenmiştir:
  • @Controller
  • @RestController
Bu arada Spring çatısı @Named damgası ile damgalanan CDI bileşenlerini de bileşen olarak algılar. InMemoryEmployeeRepository sınıfını @Repository ve SimpleIdentityService sınıfını ise @Service damgası ile damgalamayı tercih ettik. Ancak sınıflara bu notları düşmek Spring fabrikası tarafından bileşen olarak algılanması ve ele alınması için yeterli değildir. Aynı zaman konfigürasyon dosyasında bir tanımlama yapmak ve bileşenlerin hangi paket altında taranacağını belirtmek gerekir. İşte minimum konfigürasyon sadece bunu içerir.
spring-config.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"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config/>
    <context:component-scan base-package="com.example.hr.repository.impl"/>
    <context:component-scan base-package="com.example.hr.service.impl"/>
    <bean id="55724320514" name="jack"
          class="com.example.hr.domain.Employee"
          p:identityNo="55724320514"
          p:firstName="Jack"
          p:lastName="Shephard"
          p:birthYear="1971"
          p:salary="125000"/>
    <bean id="16522830254" name="kate"          class="com.example.hr.domain.Employee"
          c:identityNo="16522830254"
          c:firstName="Kate"
          c:lastName="Austen"
          c:birthYear="1980"
          c:salary="75000"
    />
</beans>
Minimum konfigürasyonumuz bu iki tanımı içeriyor: <context:annotation-config /> ve <context:component-scan />.<context:annotation-config /> tanımıyla kabaca, notları kullanarak  yapılandırma yapacağımızı söylüyoruz. <context:component-scan /> ile bu damgalanan bileşenlerinin hangi paket altında olacağını belirtiyoruz. Burada base-package ile verile paket altında öz yinelemeli olarak bileşenler taranacaktır. Java dilindeki import davranışından farklı bir davranış. 
Erimi tanımlamak için ise @Scope notunu kullanıyoruz. CDI'da her farklı erim için ayrı bir not kullanılırken Spring çatısında tüm erimler için aynı notu kullanıyoruz:
  • @Scope("singleton")
  • @Scope("prototype")
  • @Scope("request")
  • @Scope("session")
Bağımlılıkları otomatik olarak çözmek için @Autowired notunu kullanıyoruz. @Autowired notunu doğrudan özniteliği damgalayarak kullanabileceğiniz gibi setter metodunu damgalayarak da kullanabilirsiniz:
@Repository
@Scope("singleton")
public class InMemoryEmployeeRepository implements EmployeeRepository {
    @Resource
    private Map<String,Employee> employees;
    @Autowired
    private IdentityService identityService;
ya da
@Repository
@Scope("singleton")
public class InMemoryEmployeeRepository implements EmployeeRepository {
    private Map<String,Employee> employees;
    private IdentityService identityService;

    @Resource
    public void setEmployees(Map<String, Employee> employees) {
        this.employees = employees;
    }

    @Autowired
    public void setIdentityService(IdentityService identityService) {
        this.identityService = identityService;
    }
Otomatik olarak bağlanmasını istediğiniz öznitelik için @Autowired yerine @Resource damgasını da kullanabilirsiniz. @Resource damgası Java SE 6+ içinden çıkar ve JSR-250 ile tanımlanmış Common Annotations şartnamesinde yeralır. @Resource notunu daha çok JDBC Connection Pool, JMS Queue/Topic, SMTP Server gibi yönetilen kaynakları bağlamak için kullanıyoruz. Ayrıca Spring, CDI damgaları ile uyumludur. Bu nedenle @Inject damgası kullanılan bileşenler için otomatik bağlama çalışacaktır.
Elle XML yapılandırma dosyasında kodladığımız çalışanları başka bir dosyaya almayı ve daha sonra bu dosyayı kök yapılandırma dosyasında bağlamayı tercih ederiz:
spring-config.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config/>
    <context:component-scan base-package="com.example.hr.repository.impl"/>
    <context:component-scan base-package="com.example.hr.service.impl"/>
    <import resource="employees.xml" />
</beans>
employees.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       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="jack"
          class="com.example.hr.domain.Employee"
          p:identityNo="55724320514"
          p:firstName="Jack"
          p:lastName="Shephard"
          p:birthYear="1971"
          p:salary="125000"/>
    <bean id="kate"
          class="com.example.hr.domain.Employee"
          c:identityNo="16522830254"
          c:firstName="Kate"
          c:lastName="Austen"
          c:birthYear="1980"
          c:salary="75000"
    />
</beans>
Singleton erimine sahip bileşenler bağlam oluşturulurken yaratılır. Ancak bunu bağlamdan bileşeni ilk kez isteyene kadar geciktirebileceğimizi XML tabanlı yapılandırmada görmüştük. Aynı etkiyi @Lazy damgası ile sağlayabiliriz:
@Repository
@Scope("singleton")
@Lazy
public class InMemoryEmployeeRepository implements EmployeeRepository {
 . . . . 
}

3. Salt Java kullanarak bileşenleri yapılandırmak

Spring 3 ile birlikte bileşenleri sadece Java kullanarak, tek satır XML yazmadan yapılandırabiliyoruz. Bunun için @Configuration notunu düştüğümüz Java sınıfında XML dokümanında tanımladıklarımızın bire bir aynısını tanımlayabiliyoruz:
package com.example.hr.config;

import com.example.hr.domain.Employee;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = {
        "com.example.hr.service.impl",
        "com.example.hr.repository.impl"
})
public class AppConfig {
    @Bean(name= {"55724320514","jack"})
    Employee jack(){
        return new Employee("55724320514", "Jack","Shephard",1971,125000);
    }
    @Bean(name= {"16522830254","kate"})
    Employee kate(){
        return new Employee("16522830254", "Kate","Austen",1980,75000);
    }
}
Uygulamada neredeyse bir değişiklik olmayacak. Sadece fabrikayı yarattığımız sınıfı değiştiriyoruz:
package com.example.hr.app;

import com.example.hr.config.AppConfig;
import com.example.hr.domain.Employee;
import com.example.hr.repository.EmployeeRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Application {
    static final Logger logger = LoggerFactory.getLogger(Application.class);
    public static void main(String[] args) {
        ConfigurableApplicationContext context=
                new AnnotationConfigApplicationContext(AppConfig.class);
        EmployeeRepository employeeRepository=
                context.getBean(EmployeeRepository.class);
        employeeRepository.add(new Employee(null,"James","Sawyer",1972,50000));
        employeeRepository.findAll()
                          .forEach( employee -> logger.info(employee.toString()) );
        context.close();
    }
}
Konfigürasyon sınıfındaki çalışanları @Bean notunu düştüğümüz metodlar içinde yaratıyoruz. Bu tanımları başka bir konfigürasyon sınıfına almak isteyebiliriz:
package com.example.hr.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@ComponentScan(basePackages = {
        "com.example.hr.service.impl",
        "com.example.hr.repository.impl"
})
@Import(EmployeesConfig.class)
public class AppConfig {
}
EmployeesConfig.java:
package com.example.hr.config;

import com.example.hr.domain.Employee;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class EmployeesConfig {
    @Bean(name= {"55724320514","jack"})
    Employee jack(){
        return new Employee("55724320514", "Jack","Shephard",1971,125000);
    }
    @Bean(name= {"16522830254","kate"})
    Employee kate(){
        return new Employee("16522830254", "Kate","Austen",1980,75000);
    }
}
Java notlarının sorunların biri değişiklik yaptığımızda derlemek zorunda olmamızdır. Halbuki konfigürasyonu XML dosyasında yaptığımızda her değişiklikte yeniden derlemek zorunda kalmayız. Bu nedenle sıkça değişecek tanımlamaları XML dokümanında, nadiren değişecek tanımlamalarıysa Java sınıfında yapmayı tercih ederiz:
package com.example.hr.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

@Configuration
@ComponentScan(basePackages = {
        "com.example.hr.service.impl",
        "com.example.hr.repository.impl"
})
@ImportResource("classpath:employees.xml")
public class AppConfig {
}

4. Yapılandırmada SPEL (SPring Expression Language) İfadelerinin ve properties Dosyasının Kullanımı

SPEL kullanılarak yürütme zamanında bileşenleri sorgulamak ve erişmek mümkündür. SPEL ifadelerini #{ } içine yazıyoruz. Buraya Java'da geçerli herhangi bir ifadeyi yerleştirebilirsiniz. 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" >
    <context:property-placeholder location="classpath:kate.properties"/>
    <bean id="empRepo"
          autowire="byType"
          class="com.example.hr.repository.impl.InMemoryEmployeeRepository">
    </bean>
    <bean id="identitySrvc"
          class="com.example.hr.service.impl.SimpleIdentityService">
    </bean>
    <bean id="55724320514" name="jack"
          class="com.example.hr.domain.Employee"
          p:identityNo="55724320514"
          p:firstName="Jack"
          p:lastName="Shephard"
          p:birthYear="1971"
          p:salary="#{ kate.getSalary() + 10000.0 }" />
    <bean id="16522830254" name="kate"
          class="com.example.hr.domain.Employee"
          c:identityNo="${katea.identityNo}"
          c:firstName="${katea.firstName}"
          c:lastName="${katea.lastName}"
          c:birthYear="${katea.birthYear}"
          c:salary="${katea.salary}"
          />
    <bean id="70210491790" name="james"
          class="com.example.hr.domain.Employee"
          c:identityNo="70210491790"          c:firstName="James"
          c:lastName="Sawyer"
          c:birthYear="1972"
          c:salary="#{systemProperties['james.salary']}"
          />
</beans>
Örneğimizde jack bileşeninin maaşını kate bileşeninin maaşının 10000.0 fazlası olarak tanımlamak üzere #{ kate.getSalary() + 10000.0 } ifadesini kullandık. Diğer bir kullanımda, james bileşeninin maaşını komut satırından sağlanan sistem değişkeninden almak üzere #{ systemProperties['james.salary'] } yazdık. Bunun için uygulamayı çalıştırırken Java Sanal Makinasına james.salary parametresini geçmek gerekir: -Djames.salary=50000. Veritabanı erişim bilgileri gibi parametrik değerleri, bir properties dosyasına alıp, bileşenleri yapılandırıken, değerleri bu dosyadan çekebiliriz:
kate.properties:
katea.identityNo=16522830254
katea.firstName=Kate
katea.lastName=Austen
katea.birthYear=1980
katea.salary=75001
kate.properties dosyasının adını ve yerini spring-config.xml Spring yapılandırma dosyasında tanımlıyoruz:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" >
    <context:property-placeholder location="classpath:kate.properties"/>
      . . . . . .
</beans>
properties dosyası anahtar ve değer satırlarından oluşur. Anahtar ve değer ikililerini aralarına eşittir sembolü (=) yerleştirek, her biri bir satıra gelecek şekilde diziyoruz. Değeri bağlarken ise erişmek istediğimiz anahtarı ${ } arasına koyarak tanımlıyoruz
<bean id="16522830254" name="kate"
      class="com.example.hr.domain.Employee"
      c:identityNo="${katea.identityNo}"
      c:firstName="${katea.firstName}"
      c:lastName="${katea.lastName}"
      c:birthYear="${katea.birthYear}"
      c:salary="${katea.salary}"
      />
SPEL ifadelerini salt Java kullanılarak tanımlanan yapılandırmada da kullanabiliriz:
package com.example.hr.config;

import com.example.hr.domain.Employee;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;

@Configuration
@PropertySource("classpath:kate.properties")
public class EmployeesConfig {
    @Value("${katea.salary}")
    private Double katesSalary;
    @Value("#{ kate.getSalary() + 10000}")
    private Double jackSalary;
    @Bean(name= {"55724320514","jack"})
    Employee jack(){
        return new Employee("55724320514", "Jack","Shephard",1971,jackSalary);
    }
    @Bean(name= {"16522830254","kate"})
    Employee kate(){
        return new Employee("16522830254", "Kate","Austen",1980,katesSalary);
    }
    @Bean(name= {"70210491790","james"})
    Employee james(){
        return new Employee("70210491790", "James","Sawyer",1972,50000);
    }
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

5. Bileşen Yapılandırmada @Profile Kullanımı

Spring 3.1 ile gelen yeniliklerden biri, değişik ortamlar için farklı ayarlar kullanmamıza olanak sağlayan profil tanımlamasıdır. Yazılım bir dizi etkinlik sonucu ortaya çıkar. Yazılım yaşam döngüsü olarak adlandırılan bu süreçte, yazılımın farklı ortamlarda çalıştırılır: geliştirme ortamı, test ortamı, üretim ortamı. Bu farklı ortamların ayarlarını her bir ortam için geliştirdiğimiz profillerde tanımlıyoruz. Profilleri Spring çatısının XML tabanlı yapılandırma dosyasında tanımlayabileceğimiz gibi @Profile damgasını kullanarak Java tabanlı olarak da tanımlayabiliriz. Bu konunun detaylarını bu yazıdan okuyabilirsiniz.

6. Spring Boot Uygulamalarının Yapılandırılması

Spring Boot uygulamalarının yapılandırılmasından application.properties dosyasından yararlanıyoruz. Bu dosyayı config paketine ya da classpath'e koyarak tanıtabilirsiniz. Aşağıda tipik bir application.properties dosyası yer alıyor:
server.port=8001
server.context-path=/hr
server.servlet-path=/resources

spring.datasource.url=jdbc:mysql://localhost:3306/hr
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
Spring boot uygulamasında tüm yapılandırma parametrelerinin bir varsayılan değeri bulunur. application.properties dosyasına değiştirmek istediğimiz parametrelerin değerini yazıyoruz. Farklı profillerde application.properties dosyalası tanımlamak için dosyayı, dev profili için application-dev.properties, test profili için application-test.properties, prod profili için application-prod.properties şeklinde isimlendiriyoruz. Spring boot uygulamasını çalıştırırken herhangi bir profile tanımlanmamışsa application-default.properties dosyasını kullanacaktır. Aktif profili ise -Dspring.profiles.active sistem değişkeni ile kontrol ediyoruz:
java -jar hr-app.jar -Dspring.profiles.active=dev
Bu durumda Spring boot uygulamamız application-dev.properties dosyasını kullanacaktır.