springmvc入门学习

来源:互联网 发布:山东联通宽带网络测速 编辑:程序博客网 时间:2024/06/08 01:08

2017.05.24:

spring控制器

Controller控制器,是MVC中的部分C,为什么是部分呢?因为此处的控制器主要负责功能处理部分:
1、收集、验证请求参数并绑定到命令对象;
2、将命令对象交给业务对象,由业务对象处理并返回模型数据;
3、返回ModelAndView(Model部分是业务对象返回的模型数据,视图部分为逻辑视图名)。
还记得DispatcherServlet吗?主要负责整体的控制流程的调度部分:
1、负责将请求委托给控制器进行处理;
2、根据控制器返回的逻辑视图名选择具体的视图进行渲染(并把模型数据传入)。
因此MVC中完整的C(包含控制逻辑+功能处理)由(DispatcherServlet + Controller)组成。
因此此处的控制器是Web MVC中部分,也可以称为页面控制器、动作、处理器。
spring Web MVC支持多种类型的控制器,比如实现Controller接口,从Spring2.5开始支持注解方式的控制器(如@Controller、@RequestMapping、@RequestParam、@ModelAttribute等),我们也可以自己实现相应的控制器(只需要定义相应的HandlerMapping和HandlerAdapter即可)。

三种常用控制器:

首先创建一个实体(model)

package cn.cfs.springmvc.domain;public class User {    //主键    private Integer id;    //用户名    private String username;    //密码    private String password;    /**     * 获取 主键     *     * @return 主键     */    public Integer getId() {        return id;    }    /**     * 设置 主键     *     * @param id 主键     */    public void setId(Integer id) {        this.id = id;    }    /**     * 获取 用户名     *     * @return 用户名     */    public String getUsername() {        return username;    }    /**     * 设置 用户名     *     * @param username 用户名     */    public void setUsername(String username) {        this.username = username;    }    /**     * 获取 密码     *     * @return 密码     */    public String getPassword() {        return password;    }    /**     * 设置 密码     *     * @param password 密码     */    public void setPassword(String password) {        this.password = password;    }    @Override    public String toString() {        return "User [id=" + id + ", username=" + username + ", password="                + password + "]";    }        }

(一)CommandController命令控制器

package cn.cfs.springmvc.controller;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.springframework.validation.BindException;import org.springframework.web.servlet.ModelAndView;import org.springframework.web.servlet.mvc.AbstractCommandController;import cn.cfs.springmvc.domain.User;public class UserCommandController extends AbstractCommandController {    //绑定数据    public UserCommandController() {        this.setCommandClass(User.class);        this.setCommandName("user");    }    @Override    protected ModelAndView handle(HttpServletRequest request,            HttpServletResponse response, Object command, BindException errors)            throws Exception {        User user =(User)command;        System.out.println(user.toString());        return new ModelAndView("index");    }}

action-servlet.xml配置文件中的代码:

<!-- 采用className的方式去访问 --><bean id="userCommandController"  class="cn.cfs.springmvc.controller.UserCommandController"></bean>

前台通过访问url:http://192.168.1.253:8082/springmvc_01/usercommandcontroller.action?id=99&username=aaa&password=ssss 来测试

控制台打印语句:User [id=99, username=aaa, password=ssss]

(二)SimpleFormController简单表单控制器

package cn.cfs.springmvc.controller;import org.springframework.web.servlet.mvc.SimpleFormController;import cn.cfs.springmvc.domain.User;public class UserFormController extends SimpleFormController {    //在构造方法里去绑定    public UserFormController() {        this.setCommandClass(User.class);        this.setCommandName("user");    }        @Override    protected void doSubmitAction(Object command) throws Exception {        User user=(User)command;        System.out.println(user.toString());    }}

action-servlet.xml配置文件中的代码:

<!-- 采用className的方式去访问 -->    <bean id="userFormController" class="cn.cfs.springmvc.controller.UserFormController">        <property name="formView" value="login" />        <property name="successView" value="index" />    </bean>

formView 是通过get方法访问这个请求处理后跳转的页面,successView是通过post方法访问这个请求处理后跳转的页面。举个例子,例如添加用户,肯定先要跳转到添加用户的页面上,然后在表单中填写一些用户信息,点击保存提交按钮跳转到控制器中,处理完自己的业务代码再跳到指定页面,这种简单表单方式就是为了解决这种需求。

访问路径:http://192.168.1.253:8082/springmvc_01/userformcontroller.action 跳转到添加页面的地址,因为是直接在浏览器敲的所以是get方式访问的,会跳转到login.jsp页面上

login.jsp代码:

<%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>登录</title></head><body>    <form action="${pageContext.request.contextPath}/userformcontroller.action" method="post">        <table>            <tr>                <td>用户名:</td>                <td><input type="text" name="username" /></td>            </tr>            <tr>                <td>密码:</td>                <td><input type="password" name="password" /></td>            </tr>            <tr>                <td colspan="2"><input type="submit" value="保存提交" /></td>            </tr>        </table>    </form></body></html>

根据上面login.jsp中代码所示,输入完用户名和密码后,点击保存提交按钮,通过form中post的方式将表单内容提交到userformcontroller.action这个请求上,回通过控制器找到doSubmitAction这个方法,在这个方法中处理自己业务逻辑,最后跳转到index.jsp页面上,最后总结一下,这种方式是通过get和post提交方式来区分跳转的页面的,只有post提交方式才会跳转到带有处理自己业务逻辑的后台方法中。

控制台打印语句:User [id=null, username=aaaa, password=123456]

(三)WizardFormController向导控制器

package cn.cfs.springmvc.controller;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.springframework.validation.BindException;import org.springframework.web.servlet.ModelAndView;import org.springframework.web.servlet.mvc.AbstractWizardFormController;import cn.cfs.springmvc.domain.User;public class UserWizardController extends AbstractWizardFormController {    //绑定数据    public UserWizardController() {        this.setCommandClass(User.class);        this.setCommandName("user");    }        /**     * 完成提交的函数     */    @Override    protected ModelAndView processFinish(HttpServletRequest request,            HttpServletResponse response, Object command, BindException errors)            throws Exception {        User user=(User)command;        System.out.println(user.toString());        return new ModelAndView("index");    }       /**     * 取消方法,配置点击取消后要跳转的页面     */    @Override    protected ModelAndView processCancel(HttpServletRequest request,            HttpServletResponse response, Object command, BindException errors)            throws Exception {        return new ModelAndView("index");    }   }

action-servlet.xml配置文件中的代码:

<!-- 采用className的方式去访问 -->    <bean id="userwizardcontroller" class="cn.cfs.springmvc.controller.UserWizardController">        <property name="pages">            <list>                <value>/wizard/1</value>                <value>/wizard/2</value>                <value>/wizard/3</value>            </list>        </property>    </bean>

list下的value为跳转的页面,对应的上下文中会创建一个叫wizard的文件夹,以及对应的3个页面文件

访问路径:http://192.168.1.253:8082/springmvc_01/userwizardcontroller.action 会按照list中value的先后顺序去加载页面


wizard/1.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>向导表单控制器</title></head><body>    <form action="${pageContext.request.contextPath}/userwizardcontroller.action" method="post">        <table>            <tr>                <td>                    第一个页面,id                    <input type="text" name="id" value="${user.id}" />                                    </td>            </tr>            <tr>                <td>                    <input type="submit" name="_target1" value="下一步" />                    <input type="submit" name="_cancel" value="取消" />                </td>            </tr>        </table>    </form></body></html>
wizard/2.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>向导表单控制器</title></head><body>    <form action="${pageContext.request.contextPath}/userwizardcontroller.action" method="post">        <table>            <tr>                <td>                    第二个页面,用户名                    <input type="text" name="username"  value="${user.username}"/>                                    </td>            </tr>            <tr>                <td>                    <input type="submit" name="_target0" value="上一步" />                    <input type="submit" name="_cancel" value="取消" />                    <input type="submit" name="_target2" value="下一步" />                </td>            </tr>        </table>    </form></body></html>

wizard/3.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>向导表单控制器</title></head><body>    <form action="${pageContext.request.contextPath}/userwizardcontroller.action" method="post">        <table>            <tr>                <td>                    第三个页面,密码                    <input type="text" name="password"  value="${user.password}" />                                    </td>            </tr>            <tr>                <td>                    <input type="submit" name="_target1" value="上一步" />                    <input type="submit" name="_finish" value="完成" />                    <input type="submit" name="_cancel" value="取消" />                </td>            </tr>        </table>    </form></body></html>

上面的jsp文件中主要是通过 提交按钮的 name名称去控制上下页,取消,完成操作的,详细请看下面的表格(1-1);

向导表单控制器属性一览表(1-1)属性具体的的含义_targetNN是可变化的,指具体的页码,从0开始,例如当前为第二页,上一页则为_target0,下一页则为_target1_cancel取消,点击后请求发送到后台的取消方法中,根据具体的配置跳转到指定的取消页面_finish完成,点击后请求发送到后台的完成方法中,会自动将向导页的表单数据做拼接,根据具体的配置跳转到指定的完成请求处理后的页面

上述为当前springmvc比较常用的控制器,当然还有很多,例如默认的AbstractController这是最简单控制器,很多参数都需要通过原始的方法去获取,后续还待完善。


数据的交互(获取页面数据与将数据返回到页面)

一、从页面接收参数

Spring MVC接收请求提交的参数值的几种方法

  • 使用HttpServletRequest获取

    @RequestMapping("/login.do")  public String login(HttpServletRequest request) {    String name = request.getParameter("name")    String pass = request.getParameter("pass")  }
  • 使用@RequestParam注解,和表单的name属性保持一致

      @RequestMapping("/login.do")    public String login(HttpServletRequest request,    @RequestParam("pass") String name,    @RequestParam("pass") String password) { // 表单属性是pass,用变量password接收       syso(name);       syso(password)    }
  • 自动注入Bean属性

    <form action="login.do">    用户名:<input name="name"/>    密码:<input name="pass"/>    <input type="submit" value="登陆">  </form>
      //封装的User类    public class User{      private String name;      private String pass;    }
    @RequestMapping("/login.do")  public String login(User user) {    syso(user.getName());    syso(user.getPass());  }

二、向页面传值

  1. 使用Map、Model和ModelMap
    Java代码:

     @RequestMapping("/test") public String test(Map<String, Object> map, Model model, ModelMap modelMap){     map.put("names", Arrays.asList("caoyc","zhh","cjx"));     model.addAttribute("time", new Date());     modelMap.addAttribute("city", "ChengDu");     modelMap.put("gender", "male");     return "hello"; }

    JSP页面:

     1time:${requestScope.time}<br/> 2、names:${requestScope.names }<br/> 3、city:${requestScope.city }<br/> 4、gender:${requestScope.gender }
  2. 使用ModelAndView ModelAndView(String viewName, Map model)
     @RequestMapping("/test") public ModelAndView test(){     ModelAndView mav=new ModelAndView("hello");     mav.addObject("time", new Date());     mav.getModel().put("name", "caoyc");     return mav; }
  3. Session存储:可以利用HttpServletReequest的getSession()方法

     @RequestMapping("/login.do")   public String login(String name,String pwd                               ModelMap model,HttpServletRequest request){        User user = serService.login(name,pwd);        HttpSession session = request.getSession();        session.setAttribute("user",user);        model.addAttribute("user",user);        return "success";   }
  4. Spring MVC 默认采用的是转发来定位视图,如果要使用重定向,可以如下操作

1. 使用RedirectView2. 使用redirect:前缀
public ModelAndView login() {      RedirectView view = new RedirectView("regirst.do");      return new ModelAndView(view);  }

或者用如下方法,工作中常用的方法:

  public String login(){      //TODO      return "redirect:regirst.do";  }

2017.05.26

spring DAO

Spring提供的DAO(数据访问对象)支持主要的目的是便于以标准的方式使用不同的数据访问技术, 如JDBC,Hibernate或者JDO等。它不仅可以让你方便地在这些持久化技术间切换, 而且让你在编码的时候不用考虑处理各种技术中特定的异常。
为了便于以一种一致的方式使用各种数据访问技术,如JDBC、JDO和Hibernate, Spring提供了一套抽象DAO类供你扩展。这些抽象类提供了一些方法,通过它们你可以 获得与你当前使用的数据访问技术相关的数据源和其他配置信息。

Dao支持类:
JdbcDaoSupport - JDBC数据访问对象的基类。 需要一个DataSource,同时为子类提供 JdbcTemplate。
HibernateDaoSupport - Hibernate数据访问对象的基类。 需要一个SessionFactory,同时为子类提供 HibernateTemplate。也可以选择直接通过 提供一个HibernateTemplate来初始化, 这样就可以重用后者的设置,例如SessionFactory, flush模式,异常翻译器(exception translator)等等。
JdoDaoSupport - JDO数据访问对象的基类。 需要设置一个PersistenceManagerFactory, 同时为子类提供JdoTemplate。 
JpaDaoSupport - JPA数据访问对象的基类。 需要一个EntityManagerFactory,同时 为子类提供JpaTemplate。 

(1)Sping对JdbcDaoSupport的支持

数据库建表:

drop table if exists user;  /*==============================================================*/ /* Table: user                         */ /*==============================================================*/ create table user(   id          bigint AUTO_INCREMENT not null,   name         varchar(24),   age         int,   primary key (id) );

public class User {   private Integer id;   private String name;   private Integer age;    public Integer getId() {     return id;   }    public void setId(Integer id) {     this.id = id;   }    public String getName() {     return name;   }    public void setName(String name) {     this.name = name;   }    public Integer getAge() {     return age;   }    public void setAge(Integer age) {     this.age = age;   } }


public interface IUserDAO {   public void insert(User user);    public User find(Integer id); }    import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException;  /** * Created by IntelliJ IDEA.<br> * <b>Note</b>: 基类DAO,提供了数据源注入 */public class BaseDAO {   private DataSource dataSource;    public DataSource getDataSource() {     return dataSource;   }    public void setDataSource(DataSource dataSource) {     this.dataSource = dataSource;   }    public Connection getConnection() {     Connection conn = null;     try {       conn = dataSource.getConnection();     } catch (SQLException e) {       e.printStackTrace();     }     return conn;   } }  

public class UserDAO extends BaseDAO implements IUserDAO {    public JdbcTemplate getJdbcTemplate(){     return new JdbcTemplate(getDataSource());   }   public void insert(User user) {     String name = user.getName();     int age = user.getAge().intValue();  //    jdbcTemplate.update("INSERT INTO user (name,age) " //        + "VALUES('" + name + "'," + age + ")");      String sql = "insert into user(name,age) values(?,?)";     getJdbcTemplate().update(sql,new Object[]{name,age});   }    public User find(Integer id) {     List rows = getJdbcTemplate().queryForList(         "SELECT * FROM user WHERE id=" + id.intValue());      Iterator it = rows.iterator();     if (it.hasNext()) {       Map userMap = (Map) it.next();       Integer i = new Integer(userMap.get("id").toString());       String name = userMap.get("name").toString();       Integer age = new Integer(userMap.get("age").toString());        User user = new User();        user.setId(i);       user.setName(name);       user.setAge(age);        return user;     }     return null;   } }


<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"     "http://www.springframework.org/dtd/spring-beans.dtd">  <beans>   <bean id="dataSource"     class="org.apache.commons.dbcp.BasicDataSource" singleton="true">     <property name="driverClassName">       <value>com.mysql.jdbc.Driver</value>     </property>     <property name="url">       <value>jdbc:mysql://localhost:3306/springdb</value>     </property>     <property name="username">       <value>root</value>     </property>     <property name="password">       <value>leizhimin</value>     </property>   </bean>    <bean id="baseDAO" class="com.lavasoft.springnote.ch05_jdbc03_temp.BaseDAO" abstract="true">    <property name="dataSource">       <ref bean="dataSource"/>     </property>   </bean>    <bean id="userDAO"     class="com.lavasoft.springnote.ch05_jdbc03_temp.UserDAO" parent="baseDAO">   </bean>  </beans>

import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext;  public class SpringDAODemo {   public static void main(String[] args) {     ApplicationContext context = new FileSystemXmlApplicationContext("D:\\_spring\\src\\com\\lavasoft\\springnote\\ch05_jdbc03_temp\\bean-jdbc-temp.xml");     User user = new User();     user.setName("hahhahah");     user.setAge(new Integer(22));     IUserDAO userDAO = (IUserDAO) context.getBean("userDAO");     userDAO.insert(user);     user = userDAO.find(new Integer(1));     System.out.println("name: " + user.getName());   } }

(2)Spring DAO之Hibernate

HibernateDaoSupport - Hibernate数据访问对象的基类。 需要一个SessionFactory,同时为子类提供 HibernateTemplate。也可以选择直接通过 提供一个HibernateTemplate来初始化, 这样就可以重用后者的设置,例如SessionFactory, flush模式,异常翻译器(exception translator)等等。

drop table if exists user;  /*==============================================================*/ /* Table: user                         */ /*==============================================================*/ create table user(   id          bigint AUTO_INCREMENT not null,   name         varchar(24),   age         int,   primary key (id) ); 


public class User {   private Integer id;   private String name;   private Integer age;    public Integer getId() {     return id;   }    public void setId(Integer id) {     this.id = id;   }    public String getName() {     return name;   }    public void setName(String name) {     this.name = name;   }    public Integer getAge() {     return age;   }    public void setAge(Integer age) {     this.age = age;   } } 

<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping   PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"   "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  <hibernate-mapping>    <class name="com.lavasoft.springnote.ch06_hbm_02deTx.User"      table="user">      <id name="id" column="id">       <generator class="native"/>     </id>      <property name="name" column="name"/>      <property name="age" column="age"/>    </class>  </hibernate-mapping>

public interface IUserDAO {   public void insert(User user);   public User find(Integer id); }   import org.hibernate.SessionFactory; import org.springframework.orm.hibernate3.HibernateTemplate;  /** * Created by IntelliJ IDEA.<br> * <b>User</b>: leizhimin<br> * <b>Date</b>: 2008-4-23 15:15:55<br> * <b>Note</b>: DAO实现 */public class UserDAO implements IUserDAO {   private HibernateTemplate hibernateTemplate;    public void setSessionFactory(SessionFactory sessionFactory) {     this.hibernateTemplate =new HibernateTemplate(sessionFactory);   }    public void insert(User user) {     hibernateTemplate.save(user);     System.out.println("所保存的User对象的ID:"+user.getId());   }    public User find(Integer id) {     User user =(User) hibernateTemplate.get(User.class, id);     return user;   } }

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>   <bean id="dataSource"     class="org.springframework.jdbc.datasource.DriverManagerDataSource">     <property name="driverClassName">       <value>com.mysql.jdbc.Driver</value>     </property>     <property name="url">       <value>jdbc:mysql://localhost:3306/springdb</value>     </property>     <property name="username">       <value>root</value>     </property>     <property name="password">       <value>leizhimin</value>     </property>   </bean>    <bean id="sessionFactory"     class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"     destroy-method="close">     <property name="dataSource">       <ref bean="dataSource"/>     </property>     <property name="mappingResources">       <list>         <value>com/lavasoft/springnote/ch06_hbm_02proTx/User.hbm.xml</value>       </list>     </property>     <property name="hibernateProperties">       <props>         <prop key="hibernate.dialect">           org.hibernate.dialect.MySQLDialect         </prop>       </props>     </property>   </bean>     <bean id="userDAO" class="com.lavasoft.springnote.ch06_hbm_02proTx.UserDAO">     <property name="sessionFactory">       <ref bean="sessionFactory"/>     </property>   </bean> </beans>

import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; public class SpringHibernateDemo {   public static void main(String[] args) {     ApplicationContext context =new FileSystemXmlApplicationContext("D:\\_spring\\src\\com\\lavasoft\\springnote\\ch06_hbm_02proTx\\bean-hbm_tx.xml");      // 建立DAO物件     IUserDAO userDAO = (IUserDAO) context.getBean("userDAO");      User user = new User();     user.setName("caterpillar");     user.setAge(new Integer(30));      userDAO.insert(user);      user = userDAO.find(new Integer(1));      System.out.println("name: " + user.getName());   } }


2017.05.27

AOP

AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

使用"横切"技术,AOP把软件系统分为两个部分:核心关注点横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

AOP核心概念

1、横切关注点

对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点

2、切面(aspect)

类是对物体特征的抽象,切面就是对横切关注点的抽象

3、连接点(joinpoint)

被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器

4、切入点(pointcut)

对连接点进行拦截的定义

5、通知(advice)

所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类

6、目标对象

代理的目标对象

7、织入(weave)

将切面应用到目标对象并导致代理对象创建的过程

8、引入(introduction)

在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段

Spring对AOP的支持

Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:

1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了

2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB

AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:

1、定义普通业务组件

2、定义切入点,一个切入点可能横切多个业务组件

3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作

所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。

下面给出一个Spring AOP的.xml文件模板,名字叫做aop.xml,之后的内容都在aop.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:aop="http://www.springframework.org/schema/aop"    xmlns:tx="http://www.springframework.org/schema/tx"    xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">            </beans>
复制代码

基于Spring的AOP简单实现

注意一下,在讲解之前,说明一点:使用Spring AOP,要成功运行起代码,只用Spring提供给开发者的jar包是不够的,请额外上网下载两个jar包:

1、aopalliance.jar

2、aspectjweaver.jar

开始讲解用Spring AOP的XML实现方式,先定义一个接口:

public interface HelloWorld{    void printHelloWorld();    void doPrint();}

定义两个接口实现类:

复制代码
public class HelloWorldImpl1 implements HelloWorld{    public void printHelloWorld()    {        System.out.println("Enter HelloWorldImpl1.printHelloWorld()");    }        public void doPrint()    {        System.out.println("Enter HelloWorldImpl1.doPrint()");        return ;    }}
复制代码
复制代码
public class HelloWorldImpl2 implements HelloWorld{    public void printHelloWorld()    {        System.out.println("Enter HelloWorldImpl2.printHelloWorld()");    }        public void doPrint()    {        System.out.println("Enter HelloWorldImpl2.doPrint()");        return ;    }}
复制代码

横切关注点,这里是打印时间:

复制代码
public class TimeHandler{    public void printTime()    {        System.out.println("CurrentTime = " + System.currentTimeMillis());    }}
复制代码

有这三个类就可以实现一个简单的Spring AOP了,看一下aop.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:aop="http://www.springframework.org/schema/aop"    xmlns:tx="http://www.springframework.org/schema/tx"    xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">                <bean id="helloWorldImpl1" class="com.xrq.aop.HelloWorldImpl1" />        <bean id="helloWorldImpl2" class="com.xrq.aop.HelloWorldImpl2" />        <bean id="timeHandler" class="com.xrq.aop.TimeHandler" />                <aop:config>            <aop:aspect id="time" ref="timeHandler">                <aop:pointcut id="addAllMethod" expression="execution(* com.xrq.aop.HelloWorld.*(..))" />                <aop:before method="printTime" pointcut-ref="addAllMethod" />                <aop:after method="printTime" pointcut-ref="addAllMethod" />            </aop:aspect>        </aop:config></beans>
复制代码

写一个main函数调用一下:

复制代码
public static void main(String[] args){    ApplicationContext ctx =             new ClassPathXmlApplicationContext("aop.xml");            HelloWorld hw1 = (HelloWorld)ctx.getBean("helloWorldImpl1");    HelloWorld hw2 = (HelloWorld)ctx.getBean("helloWorldImpl2");    hw1.printHelloWorld();    System.out.println();    hw1.doPrint();        System.out.println();    hw2.printHelloWorld();    System.out.println();    hw2.doPrint();}
复制代码

运行结果为:

复制代码
CurrentTime = 1446129611993Enter HelloWorldImpl1.printHelloWorld()CurrentTime = 1446129611993CurrentTime = 1446129611994Enter HelloWorldImpl1.doPrint()CurrentTime = 1446129611994CurrentTime = 1446129611994Enter HelloWorldImpl2.printHelloWorld()CurrentTime = 1446129611994CurrentTime = 1446129611994Enter HelloWorldImpl2.doPrint()CurrentTime = 1446129611994
复制代码

看到给HelloWorld接口的两个实现类的所有方法都加上了代理,代理内容就是打印时间

基于Spring的AOP使用其他细节

1、增加一个横切关注点,打印日志,Java类为:

复制代码
public class LogHandler{    public void LogBefore()    {        System.out.println("Log before method");    }        public void LogAfter()    {        System.out.println("Log after method");    }}
复制代码
复制代码
<?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:aop="http://www.springframework.org/schema/aop"    xmlns:tx="http://www.springframework.org/schema/tx"    xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">                <bean id="helloWorldImpl1" class="com.xrq.aop.HelloWorldImpl1" />        <bean id="helloWorldImpl2" class="com.xrq.aop.HelloWorldImpl2" />        <bean id="timeHandler" class="com.xrq.aop.TimeHandler" />        <bean id="logHandler" class="com.xrq.aop.LogHandler" />                <aop:config>            <aop:aspect id="time" ref="timeHandler" order="1">                <aop:pointcut id="addTime" expression="execution(* com.xrq.aop.HelloWorld.*(..))" />                <aop:before method="printTime" pointcut-ref="addTime" />                <aop:after method="printTime" pointcut-ref="addTime" />            </aop:aspect>            <aop:aspect id="log" ref="logHandler" order="2">                <aop:pointcut id="printLog" expression="execution(* com.xrq.aop.HelloWorld.*(..))" />                <aop:before method="LogBefore" pointcut-ref="printLog" />                <aop:after method="LogAfter" pointcut-ref="printLog" />            </aop:aspect>        </aop:config></beans>
复制代码

测试类不变,打印结果为:

复制代码
CurrentTime = 1446130273734Log before methodEnter HelloWorldImpl1.printHelloWorld()Log after methodCurrentTime = 1446130273735CurrentTime = 1446130273736Log before methodEnter HelloWorldImpl1.doPrint()Log after methodCurrentTime = 1446130273736CurrentTime = 1446130273736Log before methodEnter HelloWorldImpl2.printHelloWorld()Log after methodCurrentTime = 1446130273736CurrentTime = 1446130273737Log before methodEnter HelloWorldImpl2.doPrint()Log after methodCurrentTime = 1446130273737
复制代码

要想让logHandler在timeHandler前使用有两个办法:

(1)aspect里面有一个order属性,order属性的数字就是横切关注点的顺序

(2)把logHandler定义在timeHandler前面,Spring默认以aspect的定义顺序作为织入顺序

2、我只想织入接口中的某些方法

修改一下pointcut的expression就好了:

复制代码
<?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:aop="http://www.springframework.org/schema/aop"    xmlns:tx="http://www.springframework.org/schema/tx"    xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">                <bean id="helloWorldImpl1" class="com.xrq.aop.HelloWorldImpl1" />        <bean id="helloWorldImpl2" class="com.xrq.aop.HelloWorldImpl2" />        <bean id="timeHandler" class="com.xrq.aop.TimeHandler" />        <bean id="logHandler" class="com.xrq.aop.LogHandler" />                <aop:config>            <aop:aspect id="time" ref="timeHandler" order="1">                <aop:pointcut id="addTime" expression="execution(* com.xrq.aop.HelloWorld.print*(..))" />                <aop:before method="printTime" pointcut-ref="addTime" />                <aop:after method="printTime" pointcut-ref="addTime" />            </aop:aspect>            <aop:aspect id="log" ref="logHandler" order="2">                <aop:pointcut id="printLog" expression="execution(* com.xrq.aop.HelloWorld.do*(..))" />                <aop:before method="LogBefore" pointcut-ref="printLog" />                <aop:after method="LogAfter" pointcut-ref="printLog" />            </aop:aspect>        </aop:config></beans>
复制代码

表示timeHandler只会织入HelloWorld接口print开头的方法,logHandler只会织入HelloWorld接口do开头的方法

3、强制使用CGLIB生成代理

前面说过Spring使用动态代理或是CGLIB生成代理是有规则的,高版本的Spring会自动选择是使用动态代理还是CGLIB生成代理内容,当然我们也可以强制使用CGLIB生成代理,那就是<aop:config>里面有一个"proxy-target-class"属性,这个属性值如果被设置为true,那么基于类的代理将起作用,如果proxy-target-class被设置为false或者这个属性被省略,那么基于接口的代理将起作用


2017.06.02

aop 代码实现:

最基本的代理模式  实现代码:

公共接口代码:

复制代码
1 public interface IHello {2     /**3      * 业务方法4      * @param str5      */6     void sayHello(String str);7 }
复制代码

  目标类代码:

复制代码
1 public class Hello implements IHello{2     @Override3     public void sayHello(String str) {4         System.out.println("hello "+str);5     }6     7 }
复制代码

  代理类代码,我们给它添加日志记录功能,在方法开始前后执行特定的方法,是不是和AOP特别像呢?

复制代码
public class ProxyHello implements IHello{        private IHello hello;        public ProxyHello(IHello hello) {        super();        this.hello = hello;    }    @Override    public void sayHello(String str) {        Logger.start();//添加特定的方法        hello.sayHello(str);        Logger.end();    }}
复制代码

  日志类代码:

复制代码
1 public class Logger {2     public static void start(){3         System.out.println(new Date()+ " say hello start...");4     }5     6     public static void end(){7         System.out.println(new Date()+ " say hello end");8     }9 }
复制代码

  测试代码:

复制代码
1 public class Test {2     public static void main(String[] args) {3         IHello hello = new ProxyHello(new Hello());//如果我们需要日志功能,则使用代理类4         //IHello hello = new Hello();//如果我们不需要日志功能则使用目标类5         hello.sayHello("明天");    6     }7 }
复制代码

 java.lang.reflect.InvocationHandler 实现 spring aop jdk代理:

jdk代理实现主要是实现InvocationHandler,并且将目标对象注入到代理对象中,利用反射机制来执行目标对象的方法。

  接口实现与静态代理相同,代理类代码:

复制代码
 1 public class DynaProxyHello implements InvocationHandler{ 2      3     private Object target;//目标对象 4     /** 5      * 通过反射来实例化目标对象 6      * @param object 7      * @return 8      */ 9     public Object bind(Object object){10         this.target = object;11         return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this);12     }13     14     @Override15     public Object invoke(Object proxy, Method method, Object[] args)16             throws Throwable {17         Object result = null;18         Logger.start();//添加额外的方法19         //通过反射机制来运行目标对象的方法20         result = method.invoke(this.target, args);21         Logger.end();22         return result;23     }24     25 }
复制代码

  测试类代码:

复制代码
1 public class DynaTest {2     public static void main(String[] args) {3         IHello hello = (IHello) new DynaProxyHello().bind(new Hello());//如果我们需要日志功能,则使用代理类4         //IHello hello = new Hello();//如果我们不需要日志功能则使用目标类5         hello.sayHello("明天");6     }7 }
复制代码

  看完上面的代码可能和Spring AOP相比有一个问题,日志类只能在方法前后打印,但是AOP应该是可以在满足条件就可以执行,所有是否可以将DynaPoxyHello对象和日志操作对象(Logger)解耦呢?

  看下面代码实现,将将DynaPoxyHello对象和日志操作对象(Logger)解耦:

  我们要在被代理对象的方法前面或者后面去加上日志操作代码(或者是其它操作的代码),那么,我们可以抽象出一个接口,这个接口里就只有两个方法:一个是在被代理对象要执行方法之前执行的方法,我们取名为start,第二个方法就是在被代理对象执行方法之后执行的方法,我们取名为end。

 Logger的接口:

1 public interface ILogger {2     void start(Method method);3     void end(Method method);4 }

  Logger的接口实现:

复制代码
 1 public class DLogger implements ILogger{ 2     @Override 3     public void start(Method method) { 4         System.out.println(new Date()+ method.getName() + " say hello start..."); 5     } 6     @Override 7     public void end(Method method) { 8         System.out.println(new Date()+ method.getName() + " say hello end"); 9     }10 }
复制代码

  动态代理类:

复制代码
 1 public class DynaProxyHello1 implements InvocationHandler{ 2     //调用对象 3     private Object proxy; 4     //目标对象 5     private Object target; 6      7     public Object bind(Object target,Object proxy){ 8         this.target=target; 9         this.proxy=proxy;10         return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this);11     }12     13     14     @Override15     public Object invoke(Object proxy, Method method, Object[] args)16             throws Throwable {17         Object result = null;18         //反射得到操作者的实例19         Class clazz = this.proxy.getClass();20         //反射得到操作者的Start方法21         Method start = clazz.getDeclaredMethod("start", new Class[]{Method.class});22         //反射执行start方法23         start.invoke(this.proxy, new Object[]{this.proxy.getClass()});24         //执行要处理对象的原本方法25         method.invoke(this.target, args);26         //反射得到操作者的end方法27         Method end = clazz.getDeclaredMethod("end", new Class[]{Method.class});28         //反射执行end方法29         end.invoke(this.proxy, new Object[]{method});30         return result;31     }32     33 }
复制代码

  测试代码:

复制代码
1 public class DynaTest1 {2     public static void main(String[] args) {3         IHello hello = (IHello) new DynaProxyHello1().bind(new Hello(),new DLogger());//如果我们需要日志功能,则使用代理类4         //IHello hello = new Hello();//如果我们不需要日志功能则使用目标类5         hello.sayHello("明天");6     }7 }
复制代码

  通过上面例子,可以发现通过动态代理和发射技术,已经基本实现了AOP的功能,如果我们只需要在方法执行前打印日志,则可以不实现end()方法,这样就可以控制打印的时机了。如果我们想让指定的方法打印日志,我们只需要在invoke()方法中加一个对method名字的判断,method的名字可以写在xml文件中,这样我们就可以实现以配置文件进行解耦了,这样我们就实现了一个简单的spring aop框架。







简单代码:

package com.jeedev.demo.view;import javax.servlet.http.HttpServletRequest;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.stereotype.Controller;import org.springframework.ui.ModelMap;import org.springframework.web.bind.annotation.RequestMapping;/** * 多视图解析器支持示例 * @author huligong * */@Controller@RequestMapping(value = "/demo")public class JeeDevViewResolverTestController {    private static Log logger = LogFactory.getLog(JeeDevViewResolverTestController.class);        @RequestMapping(value = "/test1")    public String test1(HttpServletRequest request, ModelMap map) {        logger.info("使用JSP视图解析器");        map.put("name", "hello world");        return "jspTest.jsp";    }        @RequestMapping(value = "/test2")    public String test2(HttpServletRequest request, ModelMap map) {        logger.info("使用Freemarker视图解析器");        map.put("name", "hello world");        return "hello.ftl";    }        @RequestMapping(value = "/test3")    public String test3(HttpServletRequest request, ModelMap map) {        logger.info("使用Velocity视图解析器");        map.put("name", "hello world");        return "/html/demo.htm";    }}

Controller入参是HttpServletRequest和Modelmap,在请求过来的时候,组件启动spring 容器,同时将请求对象和xml中配置的所有bean对象生成的map传给controller。

注解@requestmap就是获取请求的列表和具体的数值判断。

















原创粉丝点击