https://blog.csdn.net/javazejian/article/details/54561302#自动装配与注解注入
https://blog.csdn.net/javazejian/article/details/56267036
Spring IOC 也是一个java对象,在某些特定的时间被创建后,可以进行对其他对象的控制,包括初始化、创建、销毁等。
我们可以把IoC模式看做是工厂模式的升华,可以把IoC看作是一个大工厂,只不过这个大工厂里要生成的对象都是在配置文件(XML)中给出定义的,
然后利用Java的反射技术,根据XML中给出的类名生成相应的对象。从某种程度上来说,IoC相当于把在工厂方法里通过硬编码创建对象的代码,
改变为由XML文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性,更是达到最低的耦合度,
因此我们要明白所谓为的IOC就将对象的创建权,交由Spring完成,从此解放手动创建对象的过程,同时让类与类间的关系到达最低耦合度。
public class BookServiceImpl implements BookService
{
//读取配置文件的工具类
PropertiesUtil propertiesUtil=new PropertiesUtil("conf/className.properties");
private BookDao bookDao;
public void DaymicObject() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//获取完全限定名称
String className=propertiesUtil.get("bookDao.name");
//通过反射
Class c=Class.forName(className);
//动态生成实例对象
bookDao= (BookDao) c.newInstance();
}
}
Spring 容器装配Bean(XML配置方式和注解配置方式)
//默认查找classpath路径下的文件
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring/spring-ioc.xml");
//多文件,也可传递数组
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring/spring-ioc.xml","spring/spring-ioc2.xml",.....);
//默认为项目工作路径 即项目的根目录
FileSystemXmlApplicationContext applicationContext=
new FileSystemXmlApplicationContext("/src/main/resources/spring/spring-ioc.xml");
//也可以读取classpath下的文件
FileSystemXmlApplicationContext applicationContext=new FileSystemXmlApplicationContext("classpath:spring/spring-ioc.xml");
//使用前缀file 表示的是文件的绝对路径
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("file:D:/app.spring.xml");
//多文件与ClassPathXmlApplicationContext相同
下面将采用注解的方式来达到上述xml的配置的效果:
package com.zejian.spring.springIoc.conf;
import com.zejian.spring.springIoc.dao.AccountDao;
import com.zejian.spring.springIoc.dao.impl.AccountDaoImpl;
import com.zejian.spring.springIoc.service.AccountService;
import com.zejian.spring.springIoc.service.impl.AccountServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Created by zejian on 2017/1/15.
* Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
*/
@Configuration
public class BeanConfiguration {
@Bean
public AccountDao accountDao(){
return new AccountDaoImpl();
}
@Bean
public AccountService accountService(){
AccountServiceImpl bean=new AccountServiceImpl();
//注入dao
bean.setAccountDao(accountDao());
return bean;
}
}
这种基于java的注解配置方式是在spring3.0中引入的,此时使用到注解,因此必须确保spring-context包已引入。
org.springframework
spring-context
${spring.version}
那怎么使用呢?事实上跟xml很相似,不过这是使用到AnnotationConfigApplicationContext来加载java的配置文件,运行结果跟xml一样。
@Test
public void testByConfigurationAnnotation() throws Exception {
AnnotationConfigApplicationContext config=new AnnotationConfigApplicationContext(BeanConfiguration.class);
//名称必须BeanConfiguration中工程方法名称一致
AccountService accountService= (AccountService) config.getBean("accountService");
accountService.doSomething();
}
生成对象(生成后放在bean容器中管理)
装配对象(从bean容器中获取对象后设置给)
■生成对象(生成后放在bean容器中管理)
<context:component-scan/>
@Service@Component@Repository
还可给@Component、@Service和@Repository输入一个String值的名称,如果没有提供名称,那么默认情况下就是一个简单的类名(第一个字符小写)变成Bean名称。
@Component与@Service的含义并无差异,只不过@Service更能让我们明白该类为业务类罢了。
至于@Repository在表示数据访问层含义的同时还能够启用与Spring数据访问相关链的其他功能
Spring的框架中提供了与@Component注解等效的三个注解:
@Repository 用于对DAO实现类进行标注,
@Service 用于对Service实现类进行标注,
@Controller 用于对Controller实现类进行标注(web层控制器)
■装配对象(从bean容器中获取对象后设置给)
<context:annotation-config/>
(@Autowired&@Resource&@Value)
请注意,当spring的xml配置文件出了<context:component-scan/> 后,
<context:annotation-config/>就可以退休了,
因为<context:component-scan/>已包含了<context:annotation-config/>的功能了
基于xml的自动装配
Spring的自动装配有三种模式:byTpye(根据类型),byName(根据名称)、constructor(根据构造函数)。
基于注解的自动装配(@Autowired&@Resource&@Value)
基于@Autowired注解的自动装配
@Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。
通过 @Autowired的使用标注到成员变量时不需要有set方法,请注意@Autowired 默认按类型匹配的
在@Autowired中还传递了一个required=false的属性,
false指明当userDao实例存在就注入不存就忽略,如果为true,就必须注入,若userDao实例不存在,就抛出异常。
由于默认情况下@Autowired是按类型匹配的(byType),如果需要按名称(byName)匹配的话,
可以使用@Qualifier注解与@Autowired结合
public class UserServiceImpl implements UserService {
//标注成员变量
@Autowired
@Qualifier("userDao1")
private UserDao userDao;
}
//@Autowired标注成员变量
@Autowired
@Qualifier("userDao")
private UserDao userDao;
//上述代码等价于@Resource
@Resource(name=“userDao”)
private UserDao userDao;//用于成员变量
//也可以用于set方法标注
@Resource(name=“userDao”)
public void setUserDao(UserDao userDao) {
this.userDao= userDao;
}
关于分层管理开发一般按如下方式(这样的好处是脉络清晰,方便管理):
spring-web.xml文件:web层相关bean声明
spring-service.xml文件:service层相关bean声明
spring-dao.xml文件:dao层相关bean声明
spring-tx.xml文件:事务相关bean和规则声明
spring-security.xml文件:安全相关声明
spring-application.xml 文件:汇聚文件或总bean声明。
Bean的作用域
Singleton作用域
所谓Bean的作用域是指spring容器创建Bean后的生存周期即由创建到销毁的整个过程。
之前我们所创建的所有Bean其作用域都是Singleton,这是Spring默认的,
在这样的作用域下,每一个Bean的实例只会被创建一次,而且Spring容器在整个应用程序生存期中都可以使用该实例。
因此之前的代码中spring容器创建Bean后,通过代码获取的bean,无论多少次,都是同一个Bean的实例。
我们可使用<bean>标签的scope属性来指定一个Bean的作用域,如下:
prototype作用域
除了Singleton外还有另外一种比较常用的作用域,prototype,它代表每次获取Bean实例时都会新创建一个实例对象,类似new操作符。我们来简单测试一下
通过注解来声明作用域
@Scope("prototype")
public class AccountDaoImpl {
//......
}
当一个Bean被设置为prototype 后Spring就不会对一个bean的整个生命周期负责,
容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,
随后就对该prototype实例不闻不问了。因此我们需要慎用它,
一般情况下,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。
所谓有状态就是该bean有保存信息的能力,不能共享,否则会造成线程安全问题,
而无状态则不保存信息,是线程安全的,可以共享,spring中大部分bean都是Singleton,整个生命周期过程只会存在一个。
@Scope(value = "singleton")>@Scope(value = "session" )>@Scope(value = "request")>@Scope(value = "prototype")
显然singletonBean永远只有一个实例,而PrototypeBean则每次被获取都会创建新的实例,
对应RequestBean,在同一次Http请求过程中是同一个实例,当请求结束,RequestBean也随着销毁,
在新的Http请求则会生成新的RequestBean实例,对于SessionBean,由于是在同一个浏览器中访问属于同一次会话,
因此SessionBean实例都是同一个实例对象。现在使用另外一个浏览器启动访问,观察SessionBean是否变化。
由于Spring容器只会在创建bean实例时帮助我们注入该实例bean所依赖的其他bean实例,而且只会注入一次,
这并不是request、session作用域所希望看到的,毕竟它们都需要在不同的场景下注入新的实例对象而不是唯一不变的实例对象。
为了解决这种困境,必须放弃直接在xml中注入bean实例,改用java代码方式(实现ApplicationContextAware接口)或者注解的方式(@Autowired)注入。
Bean的延长加载
" default-lazy-init="true"
lazy-init="false"
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
传统的OOP程序经常表现出一些不自然的现象,核心业务中总掺杂着一些不相关联的特殊业务,如日志记录,权限验证,事务控制,性能检测,错误信息检测等等,
这些特殊业务可以说和核心业务没有根本上的关联而且核心业务也不关心它们.
如果说OOP的出现是把编码问题进行模块化,那么AOP就是把涉及到众多模块的某一类问题进行统一管理。
/**
* Created by zejian on 2017/2/15.
* Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
*/
public class HelloWord {
public void sayHello(){
System.out.println("hello world !");
}
public static void main(String args[]){
HelloWord helloWord =new HelloWord();
helloWord.sayHello();
}
}
编写AspectJ类,注意关键字为aspect(MyAspectJDemo.aj,其中aj为AspectJ的后缀),含义与class相同,即定义一个AspectJ的类
/**
* Created by zejian on 2017/2/15.
* Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
* 切面类
*/
public aspect MyAspectJDemo {
/**
* 定义切点,日志记录切点
*/
pointcut recordLog():call(* HelloWord.sayHello(..));
/**
* 定义切点,权限验证(实际开发中日志和权限一般会放在不同的切面中,这里仅为方便演示)
*/
pointcut authCheck():call(* HelloWord.sayHello(..));
/**
* 定义前置通知!
*/
before():authCheck(){
System.out.println("sayHello方法执行前验证权限");
}
/**
* 定义后置通知
*/
after():recordLog(){
System.out.println("sayHello方法执行后记录日志");
}
}
我们发现,明明只运行了helloworld的ain函数,却在sayHello函数运行前后分别进行了权限验证和日志记录,事实上这就是AspectJ的功劳了。
AspectJ是一个java实现的AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),
让java代码具有AspectJ的AOP功能(当然需要特殊的编译器),可以这样说AspectJ是目前实现AOP框架中最成熟,
功能最丰富的语言,更幸运的是,AspectJ与java程序完全兼容,几乎是无缝关联
pointcut 函数名 : 匹配表达式
pointcut recordLog():call(* HelloWord.sayHello(..));
recordLog()是函数名称,自定义的,* 表示任意返回值,接着就是需要拦截的目标函数,sayHello(..)的..,表示任意参数类型。
关于定义通知的语法:首先通知有5种类型分别如下:
before 目标方法执行前执行,前置通知
after 目标方法执行后执行,后置通知
after returning 目标方法返回时执行 ,后置返回通知
after throwing 目标方法抛出异常时执行 异常通知
around 在目标函数执行中执行,可控制目标函数是否执行,环绕通知
[返回值类型] 通知函数名称(参数) [returning/throwing 表达式]:连接点函数(切点函数){
函数体
}
/**
* 定义前置通知
*
* before(参数):连接点函数{
* 函数体
* }
*/
before():authCheck(){
System.out.println("sayHello方法执行前验证权限");
}
/**
* 定义后置通知
* after(参数):连接点函数{
* 函数体
* }
*/
after():recordLog(){
System.out.println("sayHello方法执行后记录日志");
}
/**
* 定义后置通知带返回值
* after(参数)returning(返回值类型):连接点函数{
* 函数体
* }
*/
after()returning(int x): get(){
System.out.println("返回值为:"+x);
}
/**
* 异常通知
* after(参数) throwing(返回值类型):连接点函数{
* 函数体
* }
*/
after() throwing(Exception e):sayHello2(){
System.out.println("抛出异常:"+e.toString());
}
/**
* 环绕通知 可通过proceed()控制目标函数是否执行
* Object around(参数):连接点函数{
* 函数体
* Object result=proceed();//执行目标函数
* return result;
* }
*/
Object around():aroundAdvice(){
System.out.println("sayAround 执行前执行");
Object result=proceed();//执行目标函数
System.out.println("sayAround 执行后执行");
return result;
}
■AspectJ的织入方式及其原理概要
一般分为动态织入和静态织入,动态织入的方式是在运行时动态将要增强的代码织入到目标类中,
这样往往是通过动态代理技术完成的,如Java JDK的动态代理(Proxy,底层通过反射实现)或者CGLIB的动态代理(底层通过继承实现),
Spring AOP采用的就是基于运行时增强的代理技术。
ApectJ采用的就是静态织入的方式。ApectJ主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,
在java目标类编译时织入,即先编译aspect类再编译目标类。
使用Spring AOP 的aspectJ功能时,需要使用以下代码启动aspect的注解功能支持:
通配符
在定义匹配表达式时,通配符几乎随处可见,如*、.. 、+ ,它们的含义如下:
.. :匹配方法定义中的任意数量的参数,此外还匹配类定义中的任意数量包
+ :匹配给定类的任意子类
* :匹配任意数量的字符
//任意返回值,任意名称,任意参数的公共方法
execution(public * *(..))
//匹配com.zejian.dao包及其子包中所有类中的所有方法
within(com.zejian.dao..*)
//匹配实现了DaoUser接口的所有子类的方法
within(com.zejian.dao.DaoUser+)
//匹配以set开头,参数为int类型,任意返回值的方法
execution(* set*(int))
为了方便类型(如接口、类名、包名)过滤方法,Spring AOP 提供了within关键字。其语法格式如下:
within()
//匹配com.zejian.dao包及其子包中所有类中的所有方法
@Pointcut("within(com.zejian.dao..*)")
//匹配UserDaoImpl类中所有方法
@Pointcut("within(com.zejian.dao.UserDaoImpl)")
//匹配UserDaoImpl类及其子类中所有方法
@Pointcut("within(com.zejian.dao.UserDaoImpl+)")
//匹配所有实现UserDao接口的类的所有方法
@Pointcut("within(com.zejian.dao.UserDao+)")
方法签名表达式
如果想根据方法签名进行过滤,关键字execution可以帮到我们,语法表达式如下
//scope :方法作用域,如public,private,protect
//returnt-type:方法返回值类型
//fully-qualified-class-name:方法所在类的完全限定名称
//parameters 方法参数
execution( .*(parameters))
//匹配UserDaoImpl类中的所有方法
@Pointcut("execution(* com.zejian.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl类中的所有公共的方法
@Pointcut("execution(public * com.zejian.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl类中的所有公共方法并且返回值为int类型
@Pointcut("execution(public int com.zejian.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl类中第一个参数为int类型的所有公共的方法
@Pointcut("execution(public * com.zejian.dao.UserDaoImpl.*(int , ..))")
其他指示符
bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法;
//匹配名称中带有后缀Service的Bean。
@Pointcut("bean(*Service)")
private void myPointcut1(){}
this :用于匹配当前AOP代理对象类型的执行方法;请注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配
//匹配了任意实现了UserDao接口的代理对象的方法进行过滤
@Pointcut("this(com.zejian.spring.springAop.dao.UserDao)")
private void myPointcut2(){}
target :用于匹配当前目标对象类型的执行方法;
//匹配了任意实现了UserDao接口的目标对象的方法进行过滤
@Pointcut("target(com.zejian.spring.springAop.dao.UserDao)")
private void myPointcut3(){}
@annotation(com.zejian.spring.MarkerMethodAnnotation) : 根据所应用的注解进行方法过滤
//匹配使用了MarkerAnnotation注解的方法(注意是方法)
@Pointcut("@annotation(com.zejian.spring.annotation.MarkerAnnotation)")
private void myPointcut5(){}